是否可以(启发式)在剥离的 ELF 中识别主函数的开始地址?

逆向工程 拆卸 x86 小精灵 重新组装
2021-06-21 00:40:16

所以基本上我正在处理一些跳闸的动态链接ELF 二进制文件(32 位 Linux x86),objdump用于反汇编它们,修改并尝试重新组装它们。

在未剥离的二进制中,我们可以根据符号表得到 main 函数的起始地址,但是,在剥离后的二进制中,我们只是不知道 main 函数在哪里。

当然我可以调整整个文本部分,从ELF的原始入口点开始。

但问题是:

  1. 有一些控制转移序言本ELF(如/收尾_start; __do_global_dtors_aux; __libc_csu_fini; __i686.get_pc_thunk.bx; __do_global_ctors_aux)插入.dtors.ctors部分,我也必须拆卸此部分,其装置。

  2. 我担心如果我从重新组装的 ELF 的入口点开始,那么我可能会双重初始化一些东西,因为在我重新组装的 asm 代码中,我有_start; __do_global_dtors_aux; __libc_csu_fini而链接器也会在新的 ELF 中附加这些函数。

所以我想用某种方式来识别main剥离 ELF 中函数(启发式)......

现在我在这个问题上没有一些策略,谁能给我一些帮助?

4个回答

关键在于确定有问题的图像是否使用了“标准”C 运行时库(glibc、musl、uclibc)。如果是,那么您可以获取入口点地址并将该地址处的代码与您从这些库中收集的启动例程进行匹配,并确定 main() 位置,因为您知道哪个call是将控制转移到main().

然后,图像可能不会链接到任何众所周知的 C 运行时,例如,如果它是直接调用内核系统调用的代码段,或者它是否设法鞭打自己的 CRT 库。

另一个好处是,如果程序根本不是用 C 编写的,而是使用其他一些花哨的语言,但这似乎超出了问题的范围,因为main()我猜与那些无关。

我开始在 « Reversing ELF 64-bit LSB executable, x86-64 ,gdb » 中回答这个问题,但这仅针对 AMD64。

确实,关键是定位__libc_start_main函数,并将它的第一个参数作为指向main函数入口的指针此功能的完整描述如下(来自其手册页):

__libc_start_main

名称

__libc_start_main - 初始化程序

概要

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

描述

__libc_start_main()函数应执行任何必要的执行环境初始化,使用适当的参数调用主函数,并处理从main(). 如果main()函数返回,则返回值应传递给exit()函数。

注意:虽然本规范旨在独立于实现,但进程和库初始化可能包括:

  • 如果有效用户 ID 与真实用户 ID 不同,则执行任何必要的安全检查。
  • 初始化线程子系统。
  • 注册rtld_fini以在此动态共享对象退出(或卸载)时释放资源。
  • 注册fini处理程序以在程序退出时运行。
  • 调用初始化函数(*init)()
  • main()使用适当的参数调用
  • exit()使用来自 的返回值进行调用main()

此列表仅为示例。

__libc_start_main()不在源标准中;它只是在二进制标准中。

也可以看看

在部分进程初始化每个ISO / IEC 23360的体系结构专用部件。

因此, the__libc_start_main不仅为您提供了main()程序的地址,还为您提供了对int argc,char** argv和 的访问权限char** envp

然后重点是从汇编代码中提取这些参数中的每一个,这些参数可能因您使用的 ABI(应用程序二进制接口)而异(函数参数可能被压入堆栈或特定寄存器中)。

objdump -f exe_name

exe_name:     file format elf32-little
architecture: UNKNOWN!, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x00306990

上面提到的起始地址是可执行文件的主要入口点。您还可以使用 gdb 验证这一点

(gdb) break *0x00306990

通常,起始地址映射到符号_start,所以你也可以这样做

(gdb) break _start

如果我没记错的话,_start调用__libc_start_main又调用__libc_csu_init

所以我在想,在从 反汇编的汇编代码中objdump,我们应该总能找到:

push addr
call __libc_start_main

从启发式的角度来看,我们可以认为addr上面代码中的 是main函数的起始地址

我对吗?有什么例外吗?