LINUX下可执行文件逆向分析基础

一、概述

在如下示例程序print_banner中,调用了glibc动态库中的函数printf,在编译和链接阶段,链接器无法知道进程运行起来之后printf函数的加载地址,所以示例中call printf的地址只有在进程运行起来以后才能确定。

 080483cc :
 80483cc:    push %ebp
 80483cd:    mov  %esp, %ebp
 80483cf:    sub  $0x8, %esp
 80483d2:    sub  $0xc, %esp
 80483d5:    push $0x80484a8  
 80483da:    call ****
 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 :
      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 
     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雏形图,供参考。
    LINUX下可执行文件逆向分析基础_第1张图片

你可能感兴趣的:(逆向工程)