改造 Cydia Substrate 框架用于函数内代码的HOOK

   上一次分析了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-R12LR寄存器的值

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反汇编的结果如下:

wKioL1SUC5yQO82gAAU0E6OUgcE201.jpg

红框里的指令都是我们需要patch的指令

   0x1A2B3001为这值占用了四个字节,这个值是要入栈给printAllReg的,我们在这里写入HOOK的地址,就能被printAllReg函数所用了。

  再回头来看下printAllRegint 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后的中转函数,如下图所示

   wKiom1SUCw3hUeCRAAVYvyw5eZM888.jpg

    每个hook都会分配一段代码内存用于保存这样的中转函数,以后需要监控一个指定函数时,只需要调用setAddrHook,输入地址就可以了。限于Cydia Substrate框架的限制,输入的hook地址指向的前面12个字节最好不要被跳转所分割。

    也可以按此方法对HOOK函数的过滤函数进行通用化改造,实现输入函数地址即可打印函数调用信息的界面

 


你可能感兴趣的:(hook,cydia,Substrate,函数内)