1

我想通过修改elf文件的.text二进制文件来挂钩一个函数,我的意思是用'bl yyyy'替换'bl xxxx'之类的指令,'yyyy'指向elf文件中的填充区域。跳转后,我保存寄存器并调用 dlopen&dlsym 来获取另一个库的新函数的地址,调用它,然后恢复寄存器并跳转回'xxxx'。

这不是很难,我几乎成功了,除了一个问题:我不能在我的钩子函数中使用 64 位 var。int 类型没有问题,但是当我 printf int64_t var 时,它总是显示一个错误的数字。

1 这是源代码:
test_ori

// test_ori.c
#include <stdio.h>
#include <dlfcn.h>

// I will hook sub and jump to myfn
void sub() {
  printf("sub called...\n");
}

// The purpose of sub2 is just for let me know the addr of dlopen&dlsym
void (*func)();
void sub2() {
  void *p = dlopen("/system/lib/libyyy.so", RTLD_NOW);
  func = (void (*)())dlsym(p,"myfn2");
  func();
}

int main(){
  sub();
  sub2();
  return 0;
}

libyyy.so

// yyy.c
#include <stdio.h>

void myfn() {
  int x = 1;
  uint32_t y = 2;
  uint64_t z = 3;
  printf("x=%d, y=%u, z=%llu\n", x, y, z);
}

void myfn2() {}


2 使用 objump 找到 dlopen&dlsym 的地址

// dlopen is 0x8440, dlsym is 0x844c
84a8:       f7ff efca       blx     8440 <dlopen@plt>
...
84b2:       f7ff efcc       blx     844c <dlsym@plt>

// sub is 0x84d4
84e0:       003c            movs    r4, r7
84e2:       0000            movs    r0, r0
84e4:       b510            push    {r4, lr}
84e6:       f7ff fff5       bl      84d4 <puts@plt+0x7c>
84ea:       f7ff ffd9       bl      84a0 <puts@plt+0x48>


3 查找填充区域并修改elf文件

// I use the offset 0x550(it's padding area) as my jump destination, the addr is 0x8550
// by the way, I also modify the segment's size field(0x580->0x600) so my new code can be loaded
ori  ->  84e6:       f7ff fff5       bl      84d4
new  ->  84e6:       f000 f833       bl      8550


4 从 0x8550 开始的钩子进程,由 asm:

1.  push {r0-r7}        //  save registers
2.  push {lr}           //  save lr
3.  mov  r1, #0         //  param2 of dlopen(RTLD_NOW)
4.  mov  r0, pc
5.  add  r0, #xx        //  param1 of dlopen(addr of "libyyy.so")
6.  blx  xxxx           //  call dlopen
7.  mov  r1, pc         
8.  add  r1, #xx        //  param2 of dlsym(addr of "myfn")
9.  blx  xxxx           //  call dlsym
10. blx  r0             //  call myfn
11. pop  {r3}           //  
12. mov  lr, r3         //  restore lr
13. pop  {r0-r7}        //  restore registers
14. b    xxxx           //  jump back


5 修改elf文件:将代码写入padding区

// I convert the asm above to machine code and write it(and strings "libyyy.so" & "myfn") to file  
// then I check it in gdb:  
(gdb) x/20i 0x8550
   0x8550:      push    {r0, r1, r2, r3, r4, r5, r6, r7}
   0x8552:      push    {lr}
   0x8554:      movs    r1, #0
   0x8556:      mov     r0, pc
   0x8558:      adds    r0, #24
   0x855a:      blx     0x8440
   0x855e:      mov     r1, pc
   0x8560:      adds    r1, #26
   0x8562:      blx     0x844c
   0x8566:      blx     r0
   0x8568:      pop     {r3}
   0x856a:      mov     lr, r3
   0x856c:      pop     {r0, r1, r2, r3, r4, r5, r6, r7}
   0x856e:      b.w     0x84d4

6 结果

# ./test_new
x=1, y=2, z=12884901888
sub called...


如您所见,x 和 y 是正常的,但 z(uint64_t) 是错误的。它应该是 3,但我在这里得到12884901888(0x300000000)。似乎 z 的高/低寄存器不正确,但你能告诉我为什么以及如何解决它吗?感谢您的关注!

4

1 回答 1

2

这个问题的答案在ARM 过程调用标准中。

 printf("x=%d, y=%u, z=%llu\n", x, y, z);

这有四个论据。格式字符串,32 位和x第三y z。由于前三个参数位于奇数地址,编译器z填充一个空格以便指令可以工作;如果堆栈未对齐,这些 64 位加载将不起作用。ldrdstrd

目前尚不清楚您是否正在为您编译的C代码显示汇编程序,或者您是否正在尝试修改生成的代码。很可能您不能确保堆栈是八字节对齐的,因为var args必须放在堆栈上。

Ps:关于ARM 8字节对齐有一个很好的堆栈溢出问题,但我现在找不到它。随时编辑我的问题或发表评论。

于 2013-08-01T19:22:05.483 回答