自修改代码,堆中的复制/跳转失败

时间:2012-04-11 17:16:32

标签: c assembly gdb objdump self-modifying

首先,我对这篇文章的篇幅感到抱歉,但我想清楚地解释这个问题。

我尝试用C 编写一种小的自我修改程序,但是我遇到了一些麻烦,我不确切知道为什么。

平台是: Ubuntu / Linux 2.6.32-40 x86_64,prog是基于x86 arch,gcc(Ubuntu 4.4.3-4ubuntu5.1)4.4.3,GNU ld(GNU Binutils)对于Ubuntu)2.20.1-system.20100303

该程序的目的是创建一个读/写/执行内存块(使用 memalign(3) mprotect(2)),在这块内存中复制一个名为p()的小函数(在.text段中定义),然后通过指针执行复制的函数。 p()功能只会使用printf(puts)显示一条消息。

为了获取p()代码的起始和结束地址(复制它),我使用函数本身的地址和dummy()函数的地址在{之后创建} {1}}中的{1}}。

p()

块内存创建和复制已成功完成,但当块中的代码运行时发生段错误。通过使用.text,很明显我们输入了块的代码(复制函数的主体),但是对 printf的调用失败。当反汇编int p() { ... } <- address where the copy starts int dummy() { ... } <- address where the copy stops 函数和块中的代码时,我发现'call'中的地址使用不一样。

我不知道为什么地址不正确,当代码被复制时会显示它,并且当我反汇编gdb函数时它与objdump(或gdb)给我的相同。

使用p()创建二进制文件,以避免p()-static的重定位过程出现潜在问题。在got/plt上运行代码似乎不是问题,因为执行复制函数的开头(在ld.so下检查)。

程序的简化src:

heap

gdb反汇编的<... skip include/checks ...> #define newline() putchar('\n') /* - function copied in the chunk */ int p() { printf("hello world\n"); return 0; } /* - dummy function to get last address of p() */ int dummy() { return 0; } int main() { char *s, *code, *chunk; unsigned int pagesz, sz; int (*ptr)(void); pagesz = sysconf(_SC_PAGE_SIZE); chunk = (char*)memalign(pagesz, 4 * pagesz); mprotect(chunk, 4 * pagesz, PROT_WRITE|PROT_EXEC|PROT_READ); /* - get size, display addr */ sz = (char *)dummy - (char *)p; printf("Copy between : %p - %p\n", (char *)p, (char *)dummy); printf("Copy in chunk : %p - %p\n", chunk, chunk + sz, sz); /* - copy code (1 byte in supp) */ printf("Copied code : "); for(s = (char *)p, code = chunk; \ s <= (char *)dummy; s++, code++) { *code = *s; /* - write in console -- to check if code of p() after disas * it with objdump(1) is the same, RESULT : ok */ printf("%02x ", *(unsigned char *)code); } newline(); /* - run once orginal function */ ptr = p; ptr(); /* - run copied function (in chunk) */ ptr = (int (*)(void))chunk; ptr(); newline(); free(chunk); return 0; } 函数:

p()

当程序在gdb(1)下运行时,复制的代码与上面提供的objdump(1)相同(十六进制值):

objdump(1)

如果我们查看主要内容,我们接下来进入大块:

080483c3 <p>:
 80482c0:       55                      push   %ebp
 80482c1:       89 e5                   mov    %esp,%ebp
 80482c3:       83 ec 18                sub    $0x18,%esp
 80482c6:       c7 04 24 a8 d9 0a 08    movl   $0x80ad9a8,(%esp)
 80482cd:       e8 7e 0c 00 00          call   8048f50 <_IO_puts>
 80482d2:       b8 00 00 00 00          mov    $0x0,%eax
 80482d7:       c9                      leave  
 80482d8:       c3                      ret    

080483dc <dummy>:
 ....

但是当p()和chunk被反汇编时,我们在内存块中有# gcc -m32 -o selfmodif_light selfmodif_light.c -static -g -O0 # gdb -q ./selfmodif_light Reading symbols from /path/.../selfmodif_light...done. (gdb) list 55 50 /* - run once orginal function */ 51 ptr = p; 52 ptr(); 53 54 /* - run copied function (in chunk) */ 55 ptr = (int (*)(void))chunk; <<< The problem is here >>> 56 ptr(); 57 58 newline(); 59 free(chunk); (gdb) br 56 Breakpoint 1 at 0x8048413: file tmp.c, line 56. (gdb) run Starting program: /path/.../selfmodif_light Copy between : 0x80482c0 - 0x80482d9 Copy in chunk : 0x80d2000 - 0x80d2019 Copied code : 55 89 e5 83 ec 18 c7 04 24 a8 d9 0a 08 e8 7e 0c 00 00 b8 00 00 00 00 c9 c3 55 hello world Breakpoint 1, main () at tmp.c:56 56 ptr(); 而不是p()函数中的(gdb) disas main Dump of assembler code for function main: 0x080482e3 <+0>: push %ebp ... <skip> ... => 0x08048413 <+304>: mov 0x18(%esp),%eax 0x08048417 <+308>: call *%eax ... <skip> ... 0x08048437 <+340>: ret End of assembler dump. ?由于这个原因显示的地址不一样。

call 0x80d2c90

检查内存时,代码似乎相同。此时我不明白发生了什么,问题是什么? gdb的解释失败了,代码副本还是什么?

call 0x8048f50 <puts>

如果设置了断点,则继续在内存块中继续执行:

(gdb) disas p
Dump of assembler code for function p:
   0x080482c0 <+0>:     push   %ebp
   0x080482c1 <+1>:     mov    %esp,%ebp
   0x080482c3 <+3>:     sub    $0x18,%esp
   0x080482c6 <+6>:     movl   $0x80ad9a8,(%esp)
   0x080482cd <+13>:    call   0x8048f50 <puts> <<= it is not the same address
   0x080482d2 <+18>:    mov    $0x0,%eax
   0x080482d7 <+23>:    leave  
   0x080482d8 <+24>:    ret
End of assembler dump.
(gdb) disas 0x80d2000,0x80d2019
Dump of assembler code from 0x80d2000 to 0x80d2019:
   0x080d2000:  push   %ebp
   0x080d2001:  mov    %esp,%ebp
   0x080d2003:  sub    $0x18,%esp
   0x080d2006:  movl   $0x80ad9a8,(%esp)
   0x080d200d:  call   0x80d2c90             <<= than here (but it should be ??)
   0x080d2012:  mov    $0x0,%eax
   0x080d2017:  leave  
   0x080d2018:  ret
End of assembler dump.

所以在这一点上,程序运行如此,发生了段错误或$ eip被更改,程序结束时没有错误。

(gdb) x/25bx p // code of p in .text
0x80482c0 <p>:  0x55    0x89    0xe5    0x83    0xec    0x18    0xc7    0x04
0x80482c8 <p+8>:    0x24    0xa8    0xd9    0x0a    0x08    0xe8    0x7e    0x0c
0x80482d0 <p+16>:   0x00    0x00    0xb8    0x00    0x00    0x00    0x00    0xc9
0x80482d8 <p+24>:   0xc3

(gdb) x/25bx 0x80d2000 // code of copy in the chunk
0x80d2000:  0x55    0x89    0xe5    0x83    0xec    0x18    0xc7    0x04
0x80d2008:  0x24    0xa8    0xd9    0x0a    0x08    0xe8    0x7e    0x0c
0x80d2010:  0x00    0x00    0xb8    0x00    0x00    0x00    0x00    0xc9
0x80d2018:  0xc3

我不明白发生了什么,失败了什么。代码的副本似乎没问题,跳进内存块也是如此,为什么地址(调用的)不好?

感谢您的回答和时间

1 个答案:

答案 0 :(得分:7)

80482cd:       e8 7e 0c 00 00          call   8048f50

那是relative CALL(到+ 0xC7E)。将该指令移动到其他EIP时,需要修改偏移量。