事例
// mymain.c
#include <stdio.h>
#include <time.h>
#include <unistd.h>
void mytest2()
{
printf("[%s:%05d] %s enter!\n", __FILE__, __LINE__, __FUNCTION__);
}
void mytest()
{
printf("[%s:%05d] %s enter!\n", __FILE__, __LINE__, __FUNCTION__);
}
int main(int argc, const char *argv[])
{
while (1) {
sleep(1);
mytest();
}
return 0;
}
编译
gcc -m32 -g mymain.c
背景
- 在上述事例中,main调用mytest函数,mytest和mytest2为可执行文件中的函数
- 利用mytest2替换main函数中mytest的调用
原理
- mytest和mytest2均属于可执行文件中的函数,则一旦编译器连接成功后,它们之间的相对位置是固定的
- 利用相对位置固定的原理,则它们的调用原理是通过相对地址进行调用的,即:main调用mytest函数,则从main函数跳转到mytest函数,CPU利用它们之间的相对位置进行寻址
main调用mytest原理解析
Dump of assembler code for function main:
0x0804847f <+0>: 8d 4c 24 04 lea 0x4(%esp),%ecx
0x08048483 <+4>: 83 e4 f0 and $0xfffffff0,%esp
0x08048486 <+7>: ff 71 fc push -0x4(%ecx)
0x08048489 <+10>: 55 push %ebp
0x0804848a <+11>: 89 e5 mov %esp,%ebp
0x0804848c <+13>: 51 push %ecx
0x0804848d <+14>: 83 ec 04 sub $0x4,%esp
0x08048490 <+17>: 83 ec 0c sub $0xc,%esp
0x08048493 <+20>: 6a 01 push $0x1
0x08048495 <+22>: e8 76 fe ff ff call 0x8048310 <sleep@plt>
0x0804849a <+27>: 83 c4 10 add $0x10,%esp
0x0804849d <+30>: e8 bb ff ff ff call 0x804845d <mytest>
0x080484a2 <+35>: eb ec jmp 0x8048490 <main+17>
End of assembler dump.
- 从上图可知调用mytest对应的内存地址为:0x0804849d,而该地址处对应的e8为CPU指令码,后面0xffffffbb(此处为小端字节序)为操作数。
- 该操作数为call指令的下一条指令地址0x080484a2相对于mytest函数地址的相对偏移量(该偏移量用补码表示)
- 0xffffffbb的由来:对0x080484a2 - 0x804845d的值求补码即可得到操作数(偏移量0xffffffbb)
- 因此,想要使用mytest2替换mytest函数,则只修改其操作数就可达到要求。
操作过程
(gdb) print mytest2
$2 = {void ()} 0x804843b <mytest2>
(gdb) print /x 0x080484a2 - 0x804843b //获得相对位置
$3 = 0x67
(gdb) print /x ~0x67 + 1 //获得补码
$6 = 0xffffff99
(gdb) set *(0x0804849d + 1) = 0xffffff99
(gdb) continue
Dump of assembler code for function main:
0x0804847f <+0>: 8d 4c 24 04 lea 0x4(%esp),%ecx
0x08048483 <+4>: 83 e4 f0 and $0xfffffff0,%esp
0x08048486 <+7>: ff 71 fc push -0x4(%ecx)
0x08048489 <+10>: 55 push %ebp
0x0804848a <+11>: 89 e5 mov %esp,%ebp
0x0804848c <+13>: 51 push %ecx
0x0804848d <+14>: 83 ec 04 sub $0x4,%esp
0x08048490 <+17>: 83 ec 0c sub $0xc,%esp
0x08048493 <+20>: 6a 01 push $0x1
0x08048495 <+22>: e8 76 fe ff ff call 0x8048310 <sleep@plt>
0x0804849a <+27>: 83 c4 10 add $0x10,%esp
0x0804849d <+30>: e8 99 ff ff ff call 0x804843b <mytest2>
0x080484a2 <+35>: eb ec jmp 0x8048490 <main+17>
End of assembler dump.
- 从上图可知,已经将0x0804849d地址出对mytest的调用修改为mytest2的调用
结果展示
修改前
[mymain.c:00012] mytest enter!
[mymain.c:00012] mytest enter!
[mymain.c:00012] mytest enter!
[mymain.c:00012] mytest enter!
[mymain.c:00012] mytest enter!
修改后
[mymain.c:00007] mytest2 enter!
[mymain.c:00007] mytest2 enter!
[mymain.c:00007] mytest2 enter!
[mymain.c:00007] mytest2 enter!
[mymain.c:00007] mytest2 enter!