【问题标题】:How to find the main function's entry point of elf executable file without any symbolic information?如何在没有任何符号信息的情况下找到elf可执行文件的main函数入口点?
【发布时间】:2012-04-10 17:58:07
【问题描述】:

我在Ubuntu-Linux 11.10平台上开发了一个小cpp程序。 现在我想对其进行逆向工程。我是初学者。 我使用这样的工具:GDB 7.0、hte 编辑器、hexeditor

我第一次让它变得非常简单。在符号信息的帮助下,我创建了 main 函数的地址并制作了我需要的一切。 然后我条纹(--strip-all)可执行elf文件,我遇到了一些问题。 我知道main 函数在这个程序中从 0x8960 开始。 但是我不知道没有这些知识我应该如何找到这一点。 我尝试使用 gdb 逐步调试我的程序,但它进入了__libc_start_main 然后进入ld-linux.so.3(因此,它会找到并加载程序所需的共享库)。我调试了大约 10 分钟。当然,可能在 20 分钟内我就可以到达 main 函数的入口点,但是,似乎必须存在更简单的方法。

我应该怎么做才能找到没有任何符号信息的main 函数的入口点? 您能否在 gdb 的帮助下从 elf 文件的逆向工程中向我推荐一些好书/网站/other_sources? 任何帮助将不胜感激。

【问题讨论】:

  • (ld-linux.so 不是内核,它是动态链接器,在用户空间。)
  • Mat,好吧,我不介意,但问题是一样的。
  • 我认为 x86 上的 glibc 在调用 _start 上的 __libc_start_main 之前会推送 main() 的地址。至少不久前没有。
  • 是的,你说的很对!我昨天已经以这种方式找到了。 :)

标签: linux reverse elf


【解决方案1】:

在剥离的 Linux ELF 二进制文件中定位 main() 很简单。不需要符号信息。

__libc_start_main 的原型是

int __libc_start_main(int (*main) (int, char**, char**), 
                      int argc, 
                      char *__unbounded *__unbounded ubp_av, 
                      void (*init) (void), 
                      void (*fini) (void), 
                      void (*rtld_fini) (void), 
                      void (*__unbounded stack_end));

main()的运行时内存地址是第一个参数int (*main) (int, char**, char**)对应的参数。这意味着在调用__libc_start_main 之前保存在运行时堆栈上的最后一个内存地址是main() 的内存地址,因为参数在函数定义中按其对应参数的相反顺序被推入运行时堆栈。

可以通过 4 个步骤在gdb 中输入main()

  1. 找到程序入口点
  2. 查找__libc_start_main 的调用位置
  3. 在调用_libc_start_main之前将断点设置为最后保存在堆栈中的地址
  4. 让程序执行continue,直到遇到main() 的断点

32 位和 64 位 ELF 二进制文件的过程相同。

在一个名为“test_32”的剥离 32 位 ELF 二进制文件示例中输入 main()

$ gdb -q -nh test_32
Reading symbols from test_32...(no debugging symbols found)...done.
(gdb) info file                                  #step 1
Symbols from "/home/c/test_32".
Local exec file:
    `/home/c/test_32', file type elf32-i386.
    Entry point: 0x8048310
    < output snipped >
(gdb) break *0x8048310
Breakpoint 1 at 0x8048310
(gdb) run
Starting program: /home/c/test_32 

Breakpoint 1, 0x08048310 in ?? ()
(gdb) x/13i $eip                                 #step 2
=> 0x8048310:   xor    %ebp,%ebp
   0x8048312:   pop    %esi
   0x8048313:   mov    %esp,%ecx
   0x8048315:   and    $0xfffffff0,%esp
   0x8048318:   push   %eax
   0x8048319:   push   %esp
   0x804831a:   push   %edx
   0x804831b:   push   $0x80484a0
   0x8048320:   push   $0x8048440
   0x8048325:   push   %ecx
   0x8048326:   push   %esi
   0x8048327:   push   $0x804840b                # address of main()
   0x804832c:   call   0x80482f0 <__libc_start_main@plt>
(gdb) break *0x804840b                           # step 3
Breakpoint 2 at 0x804840b
(gdb) continue                                   # step 4 
Continuing.

Breakpoint 2, 0x0804840b in ?? ()                # now in main()
(gdb) x/x $esp+4
0xffffd110: 0x00000001                           # argc = 1
(gdb) x/s **(char ***) ($esp+8)
0xffffd35c: "/home/c/test_32"                    # argv[0]
(gdb)

在一个名为“test_64”的剥离 64 位 ELF 二进制文件示例中输入 main()

$ gdb -q -nh test_64
Reading symbols from test_64...(no debugging symbols found)...done.
(gdb) info file                                  # step 1
Symbols from "/home/c/test_64".
Local exec file:
    `/home/c/test_64', file type elf64-x86-64.
    Entry point: 0x400430
    < output snipped >
(gdb) break *0x400430
Breakpoint 1 at 0x400430
(gdb) run 
Starting program: /home/c/test_64 

Breakpoint 1, 0x0000000000400430 in ?? ()
(gdb) x/11i $rip                                 # step 2
=> 0x400430:    xor    %ebp,%ebp
   0x400432:    mov    %rdx,%r9
   0x400435:    pop    %rsi
   0x400436:    mov    %rsp,%rdx
   0x400439:    and    $0xfffffffffffffff0,%rsp
   0x40043d:    push   %rax
   0x40043e:    push   %rsp
   0x40043f:    mov    $0x4005c0,%r8
   0x400446:    mov    $0x400550,%rcx
   0x40044d:    mov    $0x400526,%rdi            # address of main()
   0x400454:    callq  0x400410 <__libc_start_main@plt>
(gdb) break *0x400526                            # step 3
Breakpoint 2 at 0x400526
(gdb) continue                                   # step 4
Continuing.

Breakpoint 2, 0x0000000000400526 in ?? ()        # now in main()
(gdb) print $rdi                                    
$3 = 1                                           # argc = 1
(gdb) x/s **(char ***) ($rsp+16)
0x7fffffffe35c: "/home/c/test_64"                # argv[0]
(gdb) 

程序初始化的详细处理以及调用main()之前发生的事情以及如何到达main()可以在Patrick Horgan的教程"Linux x86 Program Start Up or - How the heck do we get to main()?"中找到

【讨论】:

    【解决方案2】:

    如果您有一个非常精简的版本,或者甚至是打包的二进制文件,就像使用 UPX 一样,您可以使用 gdb 以如下艰难的方式对其进行:

    $ readelf -h echo | grep Entry
    Entry point address:               0x103120
    

    然后你可以在 GDB 中打破它:

    $ gdb mybinary
    (gdb) break * 0x103120
    Breakpoint 1 at 0x103120gdb) 
    (gdb) r
    Starting program: mybinary 
    Breakpoint 1, 0x0000000000103120 in ?? ()
    

    然后,你可以看到进入说明:

    (gdb) x/10i 0x0000000000103120
    => 0x103120:    bl      0x103394
      0x103124: dcbtst  0,r5
      0x103128: mflr    r13
      0x10312c: cmplwi  r7,2
      0x103130: bne     0x103214
      0x103134: stw     r5,0(r6)
      0x103138: add     r4,r4,r3
      0x10313c: lis     r0,-32768
      0x103140: lis     r9,-32768
      0x103144: addi    r3,r3,-1
    

    希望对你有帮助

    【讨论】:

    • 我认为这是唯一正确的答案。所有其他答案都是 C 特定的(甚至特定于编译器/libc 实现),但这可靠地回答了实际问题(Linux ELF 可执行文件的入口点)。
    • 设置断点不适用于交叉编译的二进制文件,但在这种情况下您可以使用disassemble 0x103120
    【解决方案3】:

    据我所知,一旦程序被剥离,就没有直接的方法可以找到符号 main 本来会引用的函数。

    符号main的值对于程序启动不是必需的:在ELF格式中,程序的启动由ELF可执行头的e_entry字段指定。该字段通常指向 C 库的初始化代码,而不是直接指向 main

    虽然 C 库的初始化代码在设置 C 运行时环境后确实调用了 main(),但此调用是在链接时完全解析的普通函数调用。

    在某些情况下,特定于实现的启发式方法(即 C 运行时内部的特定知识)可用于确定 main 在剥离的可执行文件中的位置。但是,我不知道这样做的便携方式。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-08-21
      • 1970-01-01
      • 2020-11-02
      • 2017-11-18
      • 1970-01-01
      • 1970-01-01
      • 2013-03-31
      • 2023-03-04
      相关资源
      最近更新 更多