PWN二进制安全修仙秘籍【第二章#二进制文件篇03】静态链接

各位观众姥爷是否还记得在这一章的第一节,我们介绍过一个概念叫链接,静态链接就是链接的一种,不记得的滚回去温习❗❗PWN二进制安全修仙秘籍【第二章#二进制文件篇01】从源代码到可执行文件https://blog.****.net/weixin_53801131/article/details/142418934

 那么这一节主要讲静态链接的过程

1. 示例代码编写

以下内容完全抄袭以下文章,可以移步至下述文章进行阅读????

静态链接与动态链接https://blog.****.net/weixin_50749380/article/details/134931262

首先我们先创建一个目录叫做 test test 目录作为根目录,根目录下创建四个源文件,分别是 add.csub.cdiv.ctest.h

目录图长这样????

 这四个源文件内容分别如下:

test.h头文件定义三种运算,注意头文件只进行定义????

// test.h
#ifndef __TEST_H_
#define __TEST_H_
int add(int a,int b);
int sub(int a,int b);
int div(int a,int b);
#endif

 add.c文件用于计算变量a和b的和????

// add.c
#include "test.h"
int add(int a,int b) {
    return a + b;
}

 sub.c文件用于计算a和b的差????

// sub.c
#include "test.h"
int sub(int a,int b) {
    return a - b;
}

 div.c文件用于计算a和b的商????

// div.c
#include "test.h"
int div(int a,int b) {
    return a / b;
}

注意区别include后面接的是方括号还是双引号:

include" "是先从程序源文件所在目录查找,若未找到,则去系统默认目录查找。

include<>直接去系统默认目录查找。

2. 编译示例代码

2.1 编译目标文件

将所有源文件都编译成目标文件:

gcc -c *.c

*.c表示所有以.c结尾的文件,也即所有的源文件。执行完该命令,会发现 test 目录中多了三个目标文件,分别是 add.osub.o 和 div.o

 2.2 打包成静态库文件

把所有目标文件打包成静态库文件:

ar rcs libtest.a *.o

*.o表示所有以.o结尾的文件,也即所有的目标文件。执行完该命令,发现 test 目录中多了一个静态库文件 libtest.a,大功告成。

 我们也可以通过以下命令查看静态链接库内的文件:

ar -t libtest.a

 

 2.3 规范化目录及文件

在比较规范的项目目录中;lib 文件夹一般用来存放库文件include 文件夹一般用来存放头文件src 文件夹一般用来存放源文件bin 文件夹一般用来存放可执行文件

|-- include
|   -- test.h
|-- lib
|  -- libtest.a
|-- src
|  -- main.c

main.c文件可以像下面这样使用 libtest.a 中的函数,main.c文件内容如下????

#include <stdio.h>
#include "test.h"  //必须引入头文件
int main() {
    int m, n;
    printf("Input two numbers: ");
    scanf("%d %d", &m, &n);
    printf("%d+%d=%d\n", m, n, add(m, n));
    printf("%d-%d=%d\n", m, n, sub(m, n));
    printf("%d÷%d=%d\n", m, n, div(m, n));
    return 0;
}

在编译 main.c 的时候,我们需要使用-I(大写的字母i)选项指明头文件的包含路径;使用-L选项指明静态库的包含路径;使用-l(小写字母L)选项指明静态库的名字。所以,main.c 的完整编译命令为:

gcc src/main.c -I include/ -L lib/ -l test -o math

注意,使用-l选项指明静态库的名字时,既不需要lib前缀,也不需要.a后缀,只能写 testGCC 会自动加上前缀和后缀。

 

 运行编译生成后的文件

 发现除号好像错了,不管了,接着往下看

3. 静态链接的过程

静态链接做的最重要的两件事

符号解析

重定位

先说符号解析是什么

符号解析就是将每个符号(符号就是函数、全局变量、静态变量这些能变的)的引用与其定义进行关联。 

重定位又是什么咧?别急,我知道你很急,但你先别急

重定位就是将符号的定义与一个内存地址进行关联,然后修改这些符号的引用,让它指向这个内存地址。 

 3.1 分析静态链接过程的示例代码

下面再利用以下代码分析静态链接的过程

先是一个main.c文件,具体内容如下

//main.c
extern int shared;
extern void func(int *a, int *b);
int main() {
    int a=100;
    func(&a, &shared);
    return 0;
}

然后是func.c文件,具体内容如下

//func.c
int shared = 1;
int tmp = 0;
void func(int *a, int *b) {
    tmp = *a;
    *a = *b;
    *b= tmp;
}

使用以下命令对上述两个文件进行链接,形成一个可执行文件,并保留编译过程的中间文件。

gcc -static -fno-static-protector main.c func.c -save-temps --verbose -o func.ELF

下面这些都是我生成的文件

 3.2 对示例代码的地址进行分析

上面这么多中间文件,我们这里只看中间产物main.o和静态链接可执行文件func.ELF

使用objdump命令查看文件各个节的详细信息

这里我们对比来看,着重看.text、.data和.bss节

> objdump -h main.o

main.o:     file format pe-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000040  0000000000000000  0000000000000000  0000012c  2**4
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000000  0000000000000000  0000000000000000  00000000  2**4
                  ALLOC, LOAD, DATA
  2 .bss          00000000  0000000000000000  0000000000000000  00000000  2**4
                  ALLOC
> objdump -h func.ELF

func.ELF:     file format pei-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00001d18  0000000000401000  0000000000401000  00000400  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE, DATA
  1 .data         000000e0  0000000000403000  0000000000403000  00002200  2**4
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000980  0000000000407000  0000000000407000  00000000  2**5
                  ALLOC

解释一下,VMA(Virtual Memory Address)是虚拟地址

LMA(Local Memory Address)是加载地址

尚未进行链接的过程文件main.o的VMA都是0,而在链接完成后的func.ELF中,相似节被合并,且完成了虚拟地址的分配。

3.3 对中间文件main.o进行分析

使用objdump查看main.o的反汇编代码,参数“-mi386:intel”表示以intel格式输出

> objdump -d -M intel --section=.text main.o

main.o:     file format pe-x86-64

Disassembly of section .text:

0000000000000000 <main>:
   0:   55                      push   rbp
   1:   48 89 e5                mov    rbp,rsp
   4:   48 83 ec 30             sub    rsp,0x30
   8:   e8 00 00 00 00          call   d <main+0xd>
   d:   c7 45 fc 64 00 00 00    mov    DWORD PTR [rbp-0x4],0x64
  14:   48 8d 45 fc             lea    rax,[rbp-0x4]
  18:   48 8b 15 00 00 00 00    mov    rdx,QWORD PTR [rip+0x0]        # 1f <main+0x1f>
  1f:   48 89 c1                mov    rcx,rax
  22:   e8 00 00 00 00          call   27 <main+0x27>
  27:   b8 00 00 00 00          mov    eax,0x0
  2c:   48 83 c4 30             add    rsp,0x30
  30:   5d                      pop    rbp
  31:   c3                      ret

在地址0x22处,可以看到main函数的地址从0开始,对func函数的调用在偏移0x27处。

0xe8是call指令的操作码,后四个字节是被调用函数相对于调用指令的下一条指令的偏移量;此时符号还没用重定位,相对偏移为0x00000000,通过计算call指令调用的地址为0x20+(-0)=0x20,要注意这只是一个临时地址。

3.4 对静态链接可执行文件func.ELF进行分析

同理,进行分析

>objdump -d -M intel --section=.text func.ELF | grep -A 16 "<main>"
0000000000401550 <main>:
  401550:       55                      push   rbp
  401551:       48 89 e5                mov    rbp,rsp
  401554:       48 83 ec 30             sub    rsp,0x30
  401558:       e8 23 01 00 00          call   401680 <__main>
  40155d:       c7 45 fc 64 00 00 00    mov    DWORD PTR [rbp-0x4],0x64
  401564:       48 8d 45 fc             lea    rax,[rbp-0x4]
  401568:       48 8b 15 21 2f 00 00    mov    rdx,QWORD PTR [rip+0x2f21]        # 404490 <.refptr.shared>
  40156f:       48 89 c1                mov    rcx,rax
  401572:       e8 19 00 00 00          call   401590 <func>
  401577:       b8 00 00 00 00          mov    eax,0x0
  40157c:       48 83 c4 30             add    rsp,0x30
  401580:       5d                      pop    rbp


➜  hehao objdump -d -M intel --section=.text func.ELF | grep -A 16 "<func>"
0000000000401590 <func>:
  401590:       55                      push   rbp
  401591:       48 89 e5                mov    rbp,rsp
  401594:       48 89 4d 10             mov    QWORD PTR [rbp+0x10],rcx
  401598:       48 89 55 18             mov    QWORD PTR [rbp+0x18],rdx
  40159c:       48 8b 45 10             mov    rax,QWORD PTR [rbp+0x10]

在地址0x401572处,调用func函数的call指令,其下一条mov指令位置0x401577,因此相对于mov指令偏移量为0x19的地址为0x401577+0x19=0x0x401590

3.5 分析重定位表

重定位表的作用是什么?

它是可重定位文件中最重要的内容,用于告诉链接器如何修改节的内容。每个重定位表对应一个需要被重定位的节,如下所示

> objdump -r main.o

main.o:     file format pe-x86-64

RELOCATION RECORDS FOR [.text]:
OFFSET           TYPE              VALUE
0000000000000009 R_X86_64_PC32     __main
000000000000001b R_X86_64_PC32     .refptr.shared
0000000000000023 R_X86_64_PC32     func

有三个偏移量,说明.text存在三个重定位入口。类型为R_X86_64_PC32说明为相对寻址(CPU将指令中编码的32位值加上PC下一条指令地址的值得到有效地址),还有一种是R_X86_64_32用于绝对寻址(直接使用在指令中编码的32位值作为有效地址)

上一篇:Ruby脚本:自动化网页图像下载的实践案例


下一篇:RTOS系统移植