如何破解elf文件来调用其他函数()而不是main?

时间:2013-09-05 10:28:57

标签: c

我有一个普通的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?

2 个答案:

答案 0 :(得分: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
$

我们需要使用main找出functionreadelf的地址:

$ 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 = 080483d8替换function = 080483c4的地址,我使用了十六进制编辑器,不要忘记按照尊重的顺序翻转字节。它将成为:

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

现在测试一下:

$ ./prog1
Function
$ 

参考:How main() is executed on Linux


这是一种快速而肮脏的方式。如果您只是想在调用main之前调用某些内容,则可以使用GCC属性function使__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
$ 

参考:Declaring Attributes of Functions

答案 1 :(得分:4)

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

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