当相关函数位于二进制文件本身并使用标准调用约定时,这很简单。例子:
void make_noise() { printf("Quack!\n"); }
int fn1() { puts("fn1"); make_noise(); return 1; }
int fn2() { puts("fn2"); make_noise(); return 2; }
int main() { puts("main"); return fn1() + fn2() - 3; }
gcc -w t.c -o a.out && ./a.out
输出(预期):
main
fn1
Quack!
fn2
Quack!
现在让我们消除噪音:
gdb -q --write ./a.out
(gdb) disas/r make_noise
Dump of assembler code for function make_noise:
0x000000000040052d <+0>: 55 push %rbp
0x000000000040052e <+1>: 48 89 e5 mov %rsp,%rbp
0x0000000000400531 <+4>: bf 34 06 40 00 mov $0x400634,%edi
0x0000000000400536 <+9>: e8 d5 fe ff ff callq 0x400410 <puts@plt>
0x000000000040053b <+14>: 5d pop %rbp
0x000000000040053c <+15>: c3 retq
End of assembler dump.
这告诉我们一些事情:
- 我们想要删除的函数从地址开始
0x40052d
- 操作码为
retq
指令是0xC3
.
我们来打补丁retq
作为第一条指令make_noise
,看看会发生什么:
(gdb) set *(char*)0x40052d = 0xc3
(gdb) disas make_noise
Dump of assembler code for function make_noise:
0x000000000040052d <+0>: retq
0x000000000040052e <+1>: mov %rsp,%rbp
0x0000000000400531 <+4>: mov $0x400634,%edi
0x0000000000400536 <+9>: callq 0x400410 <puts@plt>
0x000000000040053b <+14>: pop %rbp
0x000000000040053c <+15>: retq
End of assembler dump.
有效!
(gdb) q
Segmentation fault (core dumped) ## This is a long-standing GDB bug
现在让我们运行修补后的二进制文件:
$ ./a.out
main
fn1
fn2
瞧!无噪音。
如果该函数位于不同的二进制文件中,LD_PRELOAD
Florian Weimer 提到的技术通常比二进制修补更容易。