1

我有一个普通的 C 程序。我已经制作了它的可执行文件。如果我读取一个 elf 文件,它会告诉我入口点是入口点地址:0x80482e0。跟踪入口点后,我看到最终调用如下。

080482b0 <__gmon_start__@plt-0x10>:
 80482b0:   ff 35 50 96 04 08       pushl  0x8049650
 80482b6:   ff 25 54 96 04 08       jmp    *0x8049654
 80482bc:   00 00                   add    %al,(%eax)

如何破解 0x8049654 的值来调用其他函数而不是 main?相信主函数地址会存放在地址——0x8049654?我对么?我想要做的是,我不想调用 main(),而是想破解它来调用其他函数?可能吗?

主函数地址是否应该包含在 *0x8049654 中?

4

2 回答 2

9

main不调用__gmon_start__

(gdb) disassemble main 
Dump of assembler code for function main:
0x080483d8 <main+0>:    push   %ebp                       // main() address
0x080483d9 <main+1>:    mov    %esp,%ebp
0x080483db <main+3>:    and    $0xfffffff0,%esp
0x080483de <main+6>:    sub    $0x10,%esp
0x080483e1 <main+9>:    movl   $0x80484c9,(%esp)
0x080483e8 <main+16>:   call   0x80482f8 <puts@plt>
0x080483ed <main+21>:   mov    $0x0,%eax
0x080483f2 <main+26>:   leave  
0x080483f3 <main+27>:   ret    
End of assembler dump.
(gdb) disassemble __gmon_start__
Dump of assembler code for function __gmon_start__@plt:
0x080482d8 <__gmon_start__@plt+0>:  jmp    *0x80495c8
0x080482de <__gmon_start__@plt+6>:  push   $0x0
0x080482e3 <__gmon_start__@plt+11>: jmp    0x80482c8
End of assembler dump.
(gdb) # no call to main

它是从函数传递的_start

(gdb) disassemble _start 
Dump of assembler code for function _start:
0x08048310 <_start+0>:  xor    %ebp,%ebp
0x08048312 <_start+2>:  pop    %esi
0x08048313 <_start+3>:  mov    %esp,%ecx
0x08048315 <_start+5>:  and    $0xfffffff0,%esp
0x08048318 <_start+8>:  push   %eax
0x08048319 <_start+9>:  push   %esp
0x0804831a <_start+10>: push   %edx
0x0804831b <_start+11>: push   $0x8048400
0x08048320 <_start+16>: push   $0x8048410
0x08048325 <_start+21>: push   %ecx
0x08048326 <_start+22>: push   %esi
0x08048327 <_start+23>: push   $0x80483d8
0x0804832c <_start+28>: call   0x80482e8 <__libc_start_main@plt>
0x08048331 <_start+33>: hlt    
0x08048332 <_start+34>: nop
...

您可以阅读 ELF 标头,您将找到_start存储在的地址e_entry

   e_entry     This member gives the virtual address to which the system
               first transfers control, thus starting the process.  If
               the file has no associated entry point, this member holds
               zero.

这里有一个简单的程序来获取地址:

#include <stdio.h>
#include <elf.h>

int main(int argc, char **argv) {
  FILE *file;
  Elf32_Ehdr hdr;

  if( argc < 2 ) {
    printf("uage: %s [FILE]\n", argv[0]);
    return -1;
  }

  if( (file = fopen(argv[1], "r")) == NULL ) {
    perror("Error");
    return -1;
  }

  fread(&hdr, sizeof(Elf32_Ehdr), 1, file);
  fclose(file);

  if( (hdr.e_ident[EI_MAG0] != ELFMAG0) ||
    (hdr.e_ident[EI_MAG1] != ELFMAG1) ||
    (hdr.e_ident[EI_MAG2] != ELFMAG2) ||
    (hdr.e_ident[EI_MAG3] != ELFMAG3) ) {
    printf("Error: Error: Not a valid ELF file.\n");
    return -1;
  }

  printf("Entry: 0x%.8x\n", hdr.e_entry);

  return 0;
}

所以如果你想重定向main到其他功能,你需要修补这部分:

0x08048327 <_start+23>: push   $0x80483d8

并将其替换为您的功能。这里我有一个简单的程序:

#include <stdio.h>

void function(void) {
  puts("Function");
}

int main(int argc, char **argv) {

  puts("Main");

  return 0;
}

将打印:

$ ./prog1
Main
$

我们需要找出 和 的地址mainfunction使用readelf

$ readelf -s prog1

Symbol table '.dynsym' contains 5 entries:
...

Symbol table '.symtab' contains 66 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
...
    61: 080483c4    20 FUNC    GLOBAL DEFAULT   14 function
...
    64: 080483d8    28 FUNC    GLOBAL DEFAULT   14 main
...
$ 

现在修补它push $0x80483d8并将地址替换为main = 080483d8function = 080483c4我使用了十六进制编辑器,不要忘记按顺序翻转字节。它会变成:

0x08048327 <_start+23>: push   $0x80483c4

现在测试它:

$ ./prog1
Function
$ 

参考:Linux上main()是如何执行的


这是一种快速而肮脏的方式。如果您只想在调用之前调用某些内容,main则可以function使用 GCC 属性创建一个构造函数,__attribute__((constructor))如下所示:

#include <stdio.h>

__attribute__((constructor)) void function(void) {
  puts("Function");
}

int main(int argc, char **argv) {

  puts("Main");

  return 0;
}

现在它将在 main 之前调用:

$ gcc -Wall prog.c -o prog
$ ./prog
Function
Main
$ 

参考:声明函数的属性

于 2013-09-05T13:20:44.293 回答
3

Elf 文件中描述的入口点不是您的main()函数。main()就C语言而言是第一,但操作系统有其他需求(取决于操作系统和编译器)。例如,对于 GCC,您的初始入口点可能来自 ; 中的汇编代码crt0.o。此代码处理所需的任何基本初始化,然后调用main().

虽然可以执行二进制编辑,但这肯定不是微不足道的,并且假设您有代码的源代码,那么这样做会有什么好处是非常值得怀疑的。

于 2013-09-05T10:47:19.223 回答