上一次分析了Cydia Substrate so hook 框架的实现,实际使用中,发现这样的框架并不能满足我的一些需求,比如我要知道一个函数内部某处代码的运行时的寄存器值,用原始的框架就无法做到。
想实现的功能是只要指定一个地址,就可以打印该处代码执行时的寄存器环境、HOOK的地址以及线程的TID,同时支持多个地址的添加。
先实现一个通用的消息打印函数
void printAllReg(int Reg_R0,int Reg_R1,int Reg_R2,int Reg_R3,int Stack_SP,int Stack_SP2)
{
int *pSP2 = &Stack_SP2;
int Reg_R12 = *(pSP2+1);
int Reg_R11 = *(pSP2+2);
int Reg_R10 = *(pSP2+3);
int Reg_R9 = *(pSP2+4);
int Reg_R8 = *(pSP2+5);
int Reg_R7 = *(pSP2+13);
int Reg_R6 = *(pSP2+12);
int Reg_R5 = *(pSP2+11);
int Reg_R4 = *(pSP2+10);
char *pFormat="Hookaddr:0x%08x tid=%x R0:0x%08x R1:0x%08x R2:0x%08x R3:0x%08x \nR4:0x%08xR5:0x%08x R6:0x%08x R7:0x%08x R8:0x%08x R9:0x%08x R10:0x%08x R11:0x%08xR12:0x%08x ";
LOGD(pFormat,(Stack_SP2-1-(int)pSSELibBase),gettid(),Reg_R0,Reg_R1,Reg_R2,Reg_R3,Reg_R4,Reg_R5,Reg_R6,Reg_R7,Reg_R8,Reg_R9,Reg_R10,Reg_R11,Reg_R12);
}
里面具体的参数意义后面再讲。
printAllReg要由一个中转函数来调用,这个函数必须满足如下要求:
1.调用前后不能改变R0-R12,LR寄存器的值
2.调用完打印函数后,跳到old函数
3.把监控地址入栈,这个要求比较关键,不然打印出来不知道是哪处代码在打印,也就没有意义了。
根本满足这三点要求,C语言函数肯定是不行的,只有用arm汇编来实现了
void HookAddr()
{
__asm__(
"PUSH {R0-R7} \t\n"//保存可能会改变的寄存器
"mov r4,r8 \t\n"
"PUSH {R4} \t\n"
"mov r4,r9 \t\n"
"PUSH {R4} \t\n"
"mov r4,r10 \t\n"
"PUSH {R4} \t\n"
"mov r4,r11 \t\n"
"PUSH {R4} \t\n"
"mov r4,r12 \t\n"
"PUSH {R4} \t\n"//将r8-r12入栈,直接push {R8}这样的指令是不支持的
"ldr r7,=0x1a2b3001\t\n"//0x1a2b3001是什么意思?
"PUSH {R7} \t\n"
"PUSH {LR} \t\n"//将返回地址入栈,printAllReg执行完后,lr寄存器会变掉
"ldr r7, =printAllReg \t\n"//将两个地址入栈后,调用打印函数
"BLX r7 \t\n"
"POP {R7} \t\n"
"mov lr,r7 \t\n"//恢复lr
"POP {R7} \t\n"
"POP {R4} \t\n"
"mov r12,r4 \t\n"//r12经过rlx后,会变掉,这里专门恢复一下
"POP {R0-R3} \t\n" //平衡堆栈
"POP {R0-R7} \t\n" //恢复r0-r7
"MOV R0,R0 \t\n" //让下面的bx pc指令4字节对齐
"BX PC \t\n"//转到arm模式,以进行跳转
"MOV R0,R0 \t\n"//占位
"MOV R0,R0 \t\n"//占位,这里后面会被patch掉
"MOV R0,R0 \t\n"//占位,这里后面会被patch掉
"MOV R0,R0 \t\n"//占位,这里后面会被patch掉
"BX LR \t\n"
);
}
这段函数经过编译后,用
IDA
反汇编的结果如下:
红框里的指令都是我们需要patch的指令
0x1A2B3001为这值占用了四个字节,这个值是要入栈给printAllReg的,我们在这里写入HOOK的地址,就能被printAllReg函数所用了。
再回头来看下printAllReg里int Reg_R12 =*(pSP2+1);这样指令的作用,从中转函数我们知道,调用printAllReg时,堆栈里值是这样排列的LR,HOOK Addr,R12,R11,R10,R9,R8,R7,R6,R5,R4;Stack_SP2指向的就是HookAddr,向后递推,就可以把R12-R4的地址读出来了,当然也可以改下printAllReg的调用界面,把这些值全放参数里传过来。
第一个红框,patch成跳转指令,0x16e0放把下四个字节内容加载到PC的指令,0x16e4放跳转地址
使用如下函数进行patch
void setAddrHook(void* rawFunc)
{
int memSize = 0x60;
char *memBase = mmap(0,memSize,PROT_READ| PROT_WRITE | PROT_EXEC,MAP_ANONYMOUS | MAP_PRIVATE,0,0);
if(memBase)
{
void*pHookFunc;
pHookFunc= (char*)HookAddr;
if(((int)pHookFunc)%4)
{
pHookFunc = (char*)HookAddr -1;
}
memcpy(memBase,pHookFunc,memSize);
MSHookFunction(rawFunc, (void*)(memBase+1),(void**)(memBase+0x38));
*(int*)(memBase+0x40)=(int)rawFunc;
*(int*)(memBase+0x34)=0xe51ff004;
int* pFunc = (int*)((char*)rawFunc -1);
}
}
经过这个函数patch后的中转函数,如下图所示
每个hook都会分配一段代码内存用于保存这样的中转函数,以后需要监控一个指定函数时,只需要调用setAddrHook,输入地址就可以了。限于Cydia Substrate框架的限制,输入的hook地址指向的前面12个字节最好不要被跳转所分割。
也可以按此方法对HOOK函数的过滤函数进行通用化改造,实现输入函数地址即可打印函数调用信息的界面
(创建了一个Android逆向分析群,欢迎有兴趣的同学加入,群号码:376745720)