解析rootadb在ELF二进制文件中定位方法调用的原理
拆解rootadb工具修补adbd的核心逻辑
让我一步步给你掰明白这段代码到底是怎么找到并修改adbd里的setuid/setgid调用的,还有那些变量到底存了啥——
首先先把核心代码贴出来:
u32 *sgid = (u32*)&setgid; int fd = open( "/sbin/adbd", O_RDWR ); fstat( fd, &st ); buf = memalign( 32, st.st_size ); read( fd, buf, st.st_size ); lseek64( fd, 0, SEEK_SET ); for( start = buf, end = start + st.st_size - 0x20; start < end; start++ ) if( !memcmp( &start[1], &sgid[1], sizeof( u32 ) * 2 ) ) memcpy( &start[1], patch, sizeof( patch ) );
一、先搞懂sgid变量到底存了什么
你看到的u32 *sgid = (u32*)&setgid;这行,核心逻辑是拿当前进程里libc库中setgid函数的机器指令当“特征模板”:
&setgid取的是C标准库中setgid函数的入口地址,把它转成u32*指针,是因为Android常用的ARM架构里,每条机器指令都是4字节(刚好对应一个u32)。- 所以
sgid[0]就是setgid函数的第一条指令,sgid[1]、sgid[2]就是接下来的第二条、第三条指令。比如ARM上setgid的实现通常是先把参数放到指定寄存器,再触发软中断(SWI指令)调用内核系统调用,这些指令的二进制值就存在sgid数组里。
二、代码的整体工作流程
- 读取adbd二进制到内存:打开
/sbin/adbd(读写模式),获取文件大小后,把整个文件读到内存缓冲区buf里——直接修改磁盘文件容易出问题,先在内存里改完再写回磁盘更安全。 - 遍历缓冲区找匹配的指令:循环从
buf开头遍历到接近结尾的位置(留0x20字节是为了防止越界访问),每次检查从start[1]开始的8字节(两个u32,也就是两条ARM指令),是不是和sgid[1]开始的8字节完全一致。- 为什么跳过
start[0]和sgid[0]?因为函数开头的第一条指令通常是函数序言(比如push {r4, lr}这类保存寄存器的操作),不同函数的序言可能略有差异,但真正触发系统调用的核心指令(比如SWI)是统一的,所以跳过第一个指令,匹配后面的核心指令序列。
- 为什么跳过
- 替换成NOP指令:一旦找到匹配的位置,就用
patch数据覆盖原来的指令——patch里肯定是NOP(空操作)指令的二进制值,ARM上NOP通常是0xE1A00000,替换之后adbd执行到这里就不会调用setgid/setuid放弃root权限了。
三、为什么这种匹配方式能生效?
因为adbd也是用同一个libc库编译出来的,它调用setgid和setuid的指令序列,和当前进程里libc中setgid函数的指令序列是完全一致的——所以用当前进程的setgid指令当模板,就能精准定位adbd里的系统调用位置。
内容的提问来源于stack exchange,提问作者cweiske
相关产品推荐
相关产品推荐

