Linux dlopen 注入 和 hook分析

http://blog.51cto.com/haidragon/2135226

https://github.com/gaffe23/linux-inject

 

目的:将动态库so注入到目标程序中

核心原理:1、获取目标程序函数(__libc_dlopen_mode、malloc、free)的地址;2、获取一段可执行的内存地址;3、将汇编注入代码写入这段内存地址;4、通过int3断点来查看函数调用返回值和恢复写入内存数据。

1、通过读取(/proc/%d/maps)获取libc的基地址,通过dlopen和dlsym获取当前程序中需要获取的函数(__libc_dlopen_mode、malloc、free)的地址,函数如下:

long getFunctionAddress(char* funcName)

{

void* self = dlopen("libc.so.6", RTLD_LAZY);

void* funcAddr = dlsym(self, funcName);

return (long)funcAddr;

}

因为函数(__libc_dlopen_mode、malloc、free)都在libc库中,所以用获取的地址-基地址=偏移地址;

同样读取目标程序的(/proc/%d/maps)获取libc的基地址,用此基地址+偏移地址=函数地址;

所以:偏移地址 = (当前函数地址)-(当前基地址);

目标函数地址 = (目标库基地址) + (获取的偏移地址)

2、读取目标程序(/proc/%d/maps)一段可执行(属性为x)内存地址,此内存地址需要计算最佳位置:

// find a good address to copy code to

long addr = freespaceaddr(target) + sizeof(long);

 

// now that we have an address to copy code to, set the target's rip to

// it. we have to advance by 2 bytes here because rip gets incremented

// by the size of the current instruction, and the instruction at the

// start of the function to inject always happens to be 2 bytes long.

regs.rip = addr + 2;

,计算注入函数的大小,注入函数的最后一个返回值写为int3用于恢复现场等,将需要写入内存大小的数据保存,写入注入的汇编函数(malloc分配注入so路径大小内存->使用dlopen打开注入的so->free分配内存)

3、写入汇编代码后,运行,在第一个int3中断,向malloc返回值寄存器rax赋值注入so的路径,运行,第二个int3中断,判断寄存器rax返回的dlopen函数打开的so地址是否为空,运行,第三个int3中断,判断free是否成功,运行,中断后恢复写入的内存数据,并恢复原始寄存器值,之后停止附加,退出.

 

注:arm通过raise替换int3 

// call raise(SIGTRAP) to get back control of the target.

asm(

// pop off the stack to get the address of raise()

"pop {r1} \n"

// specify SIGTRAP as the first argument

"mov r0, #5 \n"

// call raise()

"blx r1"

);

https://github.com/crmulliner/adbi

https://blog.csdn.net/roland_sun/article/details/34109569

 

adbi分为so注入程序和hook程序:

adbi计算偏移值有两种方法:

1、和linux-inject一样采用(1)偏移地址 = (当前函数地址)-(当前基地址);

目标函数地址 = (目标库基地址) + (获取的偏移地址);

(2)通过读取目标so库中的符号表来获取偏移值

2、注入方式和linux-inject不一样,adbi通过将汇编注入代码写入栈中:

// write code to stack

codeaddr = regs.ARM_sp - sizeof(sc);

通过获得mprotect函数地址来改写整个栈的读写保护,最后执行栈中的代码来调用dlopen打开so路径,之后恢复寄存器,如下:

unsigned int sc[] = {

0xe59f0040, //        ldr     r0, [pc, #64]   ; 48 <.text+0x48>

0xe3a01000, //        mov     r1, #0  ; 0x0

0xe1a0e00f, //        mov     lr, pc

0xe59ff038, //        ldr     pc, [pc, #56]   ; 4c <.text+0x4c>

0xe59fd02c, //        ldr     sp, [pc, #44]   ; 44 <.text+0x44>

0xe59f0010, //        ldr     r0, [pc, #16]   ; 30 <.text+0x30>

0xe59f1010, //        ldr     r1, [pc, #16]   ; 34 <.text+0x34>

0xe59f2010, //        ldr     r2, [pc, #16]   ; 38 <.text+0x38>

0xe59f3010, //        ldr     r3, [pc, #16]   ; 3c <.text+0x3c>

0xe59fe010, //        ldr     lr, [pc, #16]   ; 40 <.text+0x40>

0xe59ff010, //        ldr     pc, [pc, #16]   ; 44 <.text+0x44>

0xe1a00000, //        nop                     r0

0xe1a00000, //        nop                     r1

0xe1a00000, //        nop                     r2

0xe1a00000, //        nop                     r3

0xe1a00000, //        nop                     lr

0xe1a00000, //        nop                     pc

0xe1a00000, //        nop                     sp

0xe1a00000, //        nop                     addr of libname

0xe1a00000, //        nop                     dlopenaddr

};

sc[11] = regs.ARM_r0;

sc[12] = regs.ARM_r1;

sc[13] = regs.ARM_r2;

sc[14] = regs.ARM_r3;

sc[15] = regs.ARM_lr;

sc[16] = regs.ARM_pc;

sc[17] = regs.ARM_sp;

sc[19] = dlopenaddr;

libaddr = regs.ARM_sp - n*4 - sizeof(sc);

sc[18] = libaddr;

将此段数据压入栈后,调用mprotect来改写读写保护:

regs.ARM_sp = regs.ARM_sp - n*4 - sizeof(sc);

 

// call mprotect() to make stack executable

regs.ARM_r0 = stack_start; // want to make stack executable

//printf("r0 %x\n", regs.ARM_r0);

regs.ARM_r1 = stack_end - stack_start; // stack size

//printf("mprotect(%x, %d, ALL)\n", regs.ARM_r0, regs.ARM_r1);

regs.ARM_r2 = PROT_READ|PROT_WRITE|PROT_EXEC; // protections

 

// normal mode, first call mprotect

if (nomprotect == 0) {

if (debug)

printf("calling mprotect\n");

regs.ARM_lr = codeaddr; // points to loading and fixing code

regs.ARM_pc = mprotectaddr; // execute mprotect()

}

// no need to execute mprotect on old Android versions

else {

regs.ARM_pc = codeaddr; // just execute the 'shellcode'

}

对比:adbi注入方式和linux-inject优缺点:

adbi优点:adbi注入在栈中恢复现场,不需要保存上下文寄存器,将代码写入栈中后,只需要继续运行代码

adbi缺点:代码冗余,很多bug,获取符号表时没判断重定位等

linux-inject优点:直接读取可执行段,来执行程序,通过int3来赋值,判断成功等

linux-inject缺点:注入时需要中途与可执行程序交互。会暂停程序执行等

 

 

 

 

https://blog.csdn.net/roland_sun/article/details/36049307

adbi的hook分析:

arm指令和Thumb区别等:

1、BX利用Rn寄存器中目的地址值的最后一位来判断跳转后的状态。当最后一位为0时,表示转移到ARM状态;当最后一位为1时,表示转移到Thumb状态

2、无论是ARM还是Thumb,其指令在存储器中都是边界对齐的。(2字节或者4字节对齐,最低位不起作用!)因此,在执行跳转过程中,PC寄存器中的最低位被舍弃,不起作用。在BX指令的执行过程中,最低位正好被用作状态判断的标志,不会造成存储器访问不对齐的错误

**重点分析链接**

https://blog.csdn.net/xiaoi123/article/details/80365732  猫神分析的,在hook中的那个thumb指令分析有点少(爱破解群里那位)

Hook 流程大概是:

读取elf取函数符号地址->(保存函数代码)修改函数地址处的代码跳转到我们自己的处理函数->自己处理函数->恢复原函数地址与代码->调用原函数

Hook.c的重点还是arm与thumb指令的处理了

arm处理很简单:

LDR pc, [pc, #0],由于pc寄存器读出的值实际上是当前指令地址加8,所以这里是把jump[2]的值加载进pc寄存器中,而jump[2]处保存的是hook函数的地址

thumb指令处理就需要注意了:

首先thumb指令是两字节,pc寄存器指向的是顺数第二条指令的取指,需要明白arm的三级流水线,取指-译指-执行,所以thumb的pc指向的是当前指令加4,arm是pc指向的当前指令加8,thumb这里的处理是:

h->jumpt[1] = 0xb4;

h->jumpt[0] = 0x60; // push {r5,r6}

h->jumpt[3] = 0xa5;

h->jumpt[2] = 0x03; // add r5, pc, #12

h->jumpt[5] = 0x68;

h->jumpt[4] = 0x2d; // ldr r5, [r5]

h->jumpt[7] = 0xb0;

h->jumpt[6] = 0x02; // add sp,sp,#8

h->jumpt[9] = 0xb4;

h->jumpt[8] = 0x20; // push {r5}

h->jumpt[11] = 0xb0;

h->jumpt[10] = 0x81; // sub sp,sp,#4

h->jumpt[13] = 0xbd;

h->jumpt[12] = 0x20; // pop {r5, pc}

h->jumpt[15] = 0x46;

h->jumpt[14] = 0xaf; // mov pc, r5 ;

 

但这里也有个假设,就是被hook函数的起始地址必须是4字节对齐的,哪怕被hook函数使用Thumb指令集写的

所以这里通过将自己函数地址压栈后pop弹到pc从而跳转到我们自己的处理函数

 

以上基本原理就是这些,我将通过修改此hook来hook实验下android虚拟机davlik中libvm.so中的解释函数dvmInvokeMethod,来获取method方法,从来获取android函数调用流程与参数arg等。

你可能感兴趣的:(linux,adbi,linux,hook,注入)