关于逆向工程:LINUX下可执行文件逆向分析基础

10次阅读

共计 1854 个字符,预计需要花费 5 分钟才能阅读完成。

一、概述

在如下示例程序 print_banner 中,调用了 glibc 动静库中的函数 printf,在编译和链接阶段,链接器无奈晓得过程运行起来之后 printf 函数的加载地址,所以示例中 call printf 的地址只有在过程运行起来当前能力确定。

 080483cc <print_banner>:
 80483cc:    push %ebp
 80483cd:    mov  %esp, %ebp
 80483cf:    sub  $0x8, %esp
 80483d2:    sub  $0xc, %esp
 80483d5:    push $0x80484a8  
 80483da:    call **<printf 函数的地址 >**
 80483df:    add $0x10, %esp
 80483e2:    nop
 80483e3:    leave
 80483e4:    ret

那么过程运行起来之后,glibc 动静库也装载了,printf 函数地址也确定了,上述 call 指令中的地址是如何取得的呢。call 地址次要是在运行时 / 链接时进行重定位。运行时重定位和链接时重定位相干常识见背景常识模块。

背景常识

1、古代操作系统不容许批改代码段,只能批改数据段

2、编译阶段和运行阶段汇编的变动

参考文档:https://blog.csdn.net/linyt/a…
编译阶段 是将.c 源码翻译成汇编指令的中间件.o
查看应用 objdump -d test.o,能够看到 printf 的地址临时应用 fc ff ff ff 代替,这个地址在链接 / 运行时会进行修改。

00000000 <print_banner>:
      0:  55                   push %ebp
      1:  89 e5                mov %esp, %ebp
      3:  83 ec 08             sub   $0x8, %esp
      6:  c7 04 24 00 00 00 00 movl  $0x0, (%esp)
      d:  e8 fc ff ff ff      call  e <print_banner+0xe>
     12:  c9                   leave
     13:  c3                   ret

链接阶段 是将一个或多个两头文件 (.o) 通过链接器链接成一个可执行文件,链接次要实现

各个两头文件之间的同名 section 合并
对代码段,数据段以及各符号进行地址调配
链接时重定位修改

3、运行时重定位和链接时重定位

  • 链接时重定位:如果函数在其余.o 文件中定义,则链接时 printf 地址即可确定,间接重定位
  • 运行时重定位:如果函数在动静库内(链接阶段是能够晓得 printf 在哪定义的,只是如果定义在动静库内不晓得它的地址而已),则会在运行时进行重定位。运行时重定位是无奈批改代码段的,只能将 printf 重定位到数据段。那在编译阶段就已生成好的 call 指令,怎么感知这个已重定位好的数据段内容呢。
    答案是:链接器生成一段额定的小代码片段,通过这段代码支获取 printf 函数地址,并实现对它的调用。伪代码如下。
    链接阶段发现 printf 定义在动静库时,链接器生成一段小代码 print_stub,而后 printf_stub 地址取代原来的 printf。因而转化为链接阶段对 printf_stub 做链接重定位,而运行时才对 printf 做运行时重定位。

    .text
    ...
    
    // 调用 printf 的 call 指令
    call printf_stub
    ...
    
    printf_stub:
      mov rax, [printf 函数的贮存地址] // 获取 printf 重定位之后的地址
      jmp rax // 跳过去执行 printf 函数
    
    .data
    ...
    printf 函数的贮存地址:这里贮存 printf 函数重定位后的地址
    
  • PLT 和 GOT 表
    动静链接次要有 2 个因素

    须要寄存内部函数的数据段
    获取数据段寄存函数地址的一小段额定代码

    如果可执行文件中调用多个动静库函数,那每个函数都须要这两样货色,这样每样货色就造成一个表,每个函数应用中的一项。
    总不能每次都叫这个表那个表,于是得正名。寄存函数地址的数据表,称为重局偏移表(GOT, Global Offset Table),而那个额定代码段表,称为程序链接表(PLT,Procedure Link Table)。它们两姐妹各司其职,联结出手演出这一出运行时重定位好戏。
    那么 PLT 和 GOT 长得什么样子呢?后面已有一些阐明,上面以一个例子和简略的示意图来阐明 PLT/GOT 是如何运行的。
    假如最开始的示例代码 test.c 减少一个 write_file 函数,在该函数外面调用 glibc 的 write 实现写文件操作。依据后面探讨的 PLT 和 GOT 原理,test 在运行过程中,调用方(如 print_banner 和 write_file)是如何通过 PLT 和 GOT 穿针引线之后,最终调用到 glibc 的 printf 和 write 函数的?
    上面是 PLT 和 GOT 雏形图,供参考。

正文完
 0