ELF文件追寻记(一)

记得前段时间写的文章《Linux动态共享库与版本控制初记》留下了一大堆问题,最起码的ELF文件是什么都没有弄清楚,接下来我准备搞几篇文章弄弄这个东西,也算满足我自己的一个小心愿。

先来看一个非常简单的C程序吧(m.c)

#include <stdio.h>

int main(int argc,char** argv)
{
     int a = 5;
     return 0;
}
因为简单,所以才能最直观的弄清楚原理。

使用gcc命令

gcc -o m m.c
生成可执行程序m

再使用file命令

file m
观察这个文件的基本信息,得到

m: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.6.9, dynamically linked (uses shared libs), for GNU/Linux 2.6.9, not stripped
由此我们可以看到,这个文件是ELF类型的,符合32位小端模式的,可执行文件。它还没有没strip过。

再使用readelf命令提取这个ELF的文件头信息

readelf m -h
得到

ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2‘s complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x8048280
  Start of program headers:          52 (bytes into file)
  Start of section headers:          1852 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         7
  Size of section headers:           40 (bytes)
  Number of section headers:         28
  Section header string table index: 25
从上面的信息可以看到进入点地址是0x8048280。这个地址是哪个函数呢?

我们必须使用更多命令来获得信息

使用命令objdump来获得更多信息

objdump -d ./m
得到(部分)

08048280 <_start>:
 8048280:       31 ed                   xor    %ebp,%ebp
 8048282:       5e                      pop    %esi
 8048283:       89 e1                   mov    %esp,%ecx
 8048285:       83 e4 f0                and    $0xfffffff0,%esp
 8048288:       50                      push   %eax
 8048289:       54                      push   %esp
 804828a:       52                      push   %edx
 804828b:       68 80 83 04 08          push   $0x8048380
 8048290:       68 90 83 04 08          push   $0x8048390
 8048295:       51                      push   %ecx
 8048296:       56                      push   %esi
 8048297:       68 54 83 04 08          push   $0x8048354
 804829c:       e8 c7 ff ff ff          call   8048268 <__libc_start_main@plt>
 80482a1:       f4                      hlt    
 80482a2:       90                      nop    
 80482a3:       90                      nop 
看到call命令后的<_libc_start_main@plt>猜到,<>这个符号包裹的应该是个函数,那么,0x8048280出的函数就是<_start>。

即进入点是函数start,这个函数做了什么事呢?

看汇编代码注意到,它调用了函数_libc_start_main  (@,按照C的标准是不应该出现在函数名中的,函数名应该是字母,数字,下划线的集合,所以函数名应该是

_libc_start_main),这个函数的地址应该是0x8048268,恩

在调用这个函数之前,执行了很多push,看看有哪些:0x8048380 0x8048390 0x8048354

再次浏览objdump的信息,发现

08048354 <main>:
 8048354:       8d 4c 24 04             lea    0x4(%esp),%ecx
 8048358:       83 e4 f0                and    $0xfffffff0,%esp
 804835b:       ff 71 fc                pushl  0xfffffffc(%ecx)
 804835e:       55                      push   %ebp
 804835f:       89 e5                   mov    %esp,%ebp
 8048361:       51                      push   %ecx
 8048362:       83 ec 10                sub    $0x10,%esp
 8048365:       c7 45 f8 05 00 00 00    movl   $0x5,0xfffffff8(%ebp)
 804836c:       b8 00 00 00 00          mov    $0x0,%eax
 8048371:       83 c4 10                add    $0x10,%esp
 8048374:       59                      pop    %ecx
 8048375:       5d                      pop    %ebp
 8048376:       8d 61 fc                lea    0xfffffffc(%ecx),%esp
 8048379:       c3                      ret    
 804837a:       90                      nop    
 804837b:       90                      nop    
 804837c:       90                      nop    
 804837d:       90                      nop    
 804837e:       90                      nop    
 804837f:       90                      nop   
是main()函数。

这些汇编语言看的挺熟悉的,对m.c执行命令gcc 

gcc -S m.c
生成m.s文件,浏览内容

main:
   leal  4(%esp), %ecx
   andl  $-16, %esp
   pushl -4(%ecx)
   pushl %ebp
   movl  %esp, %ebp
   pushl %ecx
   subl  $16, %esp
   movl  $5, -8(%ebp)
   movl  $0, %eax
   addl  $16, %esp
   popl  %ecx
   popl  %ebp
   leal  -4(%ecx), %esp
   ret 
一一对应啊。我知道m.s里面的汇编是AT&T汇编,那么上面的那个是什么汇编呢,亟待解决。

另外两个地址0x8048380 0x8048390处对应的函数分别是__libc_csu_fini()和__libc_csu_init(),在此不深究。

到此,发现了函数这样调用的:

_start()调用_libc_start_main(),而_libc_start_main()的入参参数中一个是main()函数。

使用ltrace命令跟踪./m程序

ltrace ./m
得到

__libc_start_main(0x8048354, 1, 0xbf9478f4, 0x8048390, 0x8048380 <unfinished ...>
+++ exited (status 0) +++
由此再次确认,main()函数是_libc_start_main()函数的第一个参数,第4,5个参数分别是__libc_csu_init()和__libc_csu_fini(),那第2,3个参数是什么呢?

先看看_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)
			);
可以看到第2个参数就是main函数的的第1个参数,即“main”

可以做个实验

ltrace ./m 1
得到

__libc_start_main(0x8048354, 2, 0xbf97ec54, 0x8048390, 0x8048380 <unfinished ...>
+++ exited (status 0) +++
你看,数值变为2了,因为我们输入了两个参数:main , 1

_libc_start_main()的第3个参数地址很奇怪,下次再搞。第4,5个参数名也和__libc_csu_init()和__libc_csu_fini()一一对应。


任重而道远,下次继续,我就不相信搞不懂。


感谢一下文章


北极以北 之 main函数之前 














ELF文件追寻记(一)

上一篇:JBoss 系列八十九: JBoss 7/WildFly 中如何阻止一个模块中被默认加载


下一篇:Photoshop使用图层蒙版制作黑白风格的抽象艺术字教程