我想通过修改elf文件的.text二进制来挂钩一个函数,我的意思是用'bl yyyy'替换'bl xxxx'之类的指令,'yyyy'指向elf文件中的填充区域。跳转后,我保存寄存器并调用dlopen& dlsym来获取另一个lib的新函数的addr,调用它,然后恢复寄存器并跳回'xxxx'。
这不是很难,除了一个问题我几乎成功了:我不能在钩子函数中使用64位var。 int类型没有问题,但是当我printf int64_t var时,它总是显示错误的数字。
1这是src代码:
的 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&amp; 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查找填充区域并修改精灵文件
// 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文件:将代码写入填充区域
// 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的高/低寄存器不正确,但你能告诉我为什么以及如何解决它?
谢谢你的关注!
答案 0 :(得分:2)
这个问题的答案在ARM Procedure call standard。
printf("x=%d, y=%u, z=%llu\n", x, y, z);
这有四个论点。 格式字符串,32位x
和y
以及3 rd z
值。由于前三个参数将z
置于奇数地址,因此编译器会填充一个空格,以便ldrd
和strd
指令可以工作;如果堆栈未对齐,则这些64位加载不起作用。
目前尚不清楚您是否为您编译的 C 代码显示汇编程序,或者您是否正在尝试修改生成的代码。很可能你不能确保the stack is eight byte aligned作为 var args 必须放在堆栈上。
Ps:关于ARM 8字节对齐有一个很好的堆栈溢出问题,但我现在找不到它。随意编辑我的问题或发表评论。