Linux下的Object文件加载器

admin 2023年6月25日09:05:21评论5 views字数 5221阅读17分24秒阅读模式

前言

在Windows下已经有了很多针对Coff文件的加载器,如CoffLoader和CS的BOF功能,但是linux上面相关功能还是欠缺的,因此本本文章介绍一下相关技术,并提供了实现代码

项目地址(求个star):https://github.com/Sndav/coffee

目标设定

void println(char *buf);
void debugln(char *buf);
void hello_world();


int test_func_call(unsigned char *buf){
println(buf);
return 0;
}

int main(){
char *buf = "Hello World!";
test_func_call(buf);
debugln(buf);
hello_world();
return 1;
}

期望可以加载上述文件所生成的test.o文件

ELF文件中结构

我们可以用objdump -d test.o -M intel看一下这个load函数的汇编

Disassembly of section .text:

0000000000000000 <test_func_call>:
0: f3 0f 1e fa endbr64
4: 55 push rbp
5: 48 89 e5 mov rbp,rsp
8: 48 83 ec 10 sub rsp,0x10
c: 48 89 7d f8 mov QWORD PTR [rbp-0x8],rdi
10: 48 8b 45 f8 mov rax,QWORD PTR [rbp-0x8]
14: 48 89 c7 mov rdi,rax
17: e8 00 00 00 00 call 1c <test_func_call+0x1c>
1c: b8 00 00 00 00 mov eax,0x0
21: c9 leave
22: c3 ret

0000000000000023 <main>:
23: f3 0f 1e fa endbr64
27: 55 push rbp
28: 48 89 e5 mov rbp,rsp
2b: 48 83 ec 10 sub rsp,0x10
2f: 48 8d 05 00 00 00 00 lea rax,[rip+0x0] # 36 <main+0x13>
36: 48 89 45 f8 mov QWORD PTR [rbp-0x8],rax
3a: 48 8b 45 f8 mov rax,QWORD PTR [rbp-0x8]
3e: 48 89 c7 mov rdi,rax
41: e8 [00 00 00 00] call 46 <main+0x23> # 可以看到这里的操作数全是0
46: 48 8b 45 f8 mov rax,QWORD PTR [rbp-0x8]
4a: 48 89 c7 mov rdi,rax
4d: e8 [00 00 00 00] call 52 <main+0x2f>
52: b8 00 00 00 00 mov eax,0x0
57: e8 00 00 00 00 call 5c <main+0x39>
5c: b8 01 00 00 00 mov eax,0x1
61: c9 leave
62: c3 ret

.rela节

我们可以发现,上述反编译代码的call指令的操作数全部是0,为了能正确找到call的正确位置,所以链接器会修改这个偏移值。怎么修改呢,需要根据rela节中的数据进行修改。rela节的表项结构体如下

typedef struct {
Elf32_Addr r_offset;
Elf32_Word r_info;
Elf32_Sword r_addend;
} Elf32_Rela;

我们可以使用readelf -r test.o获取函数的重定向节,一般来说.text的rela节的名字是.rela.text

Relocation section '.rela.text' at offset 0x268 contains 5 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000000018 000500000004 R_X86_64_PLT32 0000000000000000 println - 4
000000000032 000300000002 R_X86_64_PC32 0000000000000000 .rodata - 4
000000000042 000400000004 R_X86_64_PLT32 0000000000000000 test_func_call - 4
00000000004e 000700000004 R_X86_64_PLT32 0000000000000000 debugln - 4
000000000058 000800000004 R_X86_64_PLT32 0000000000000000 hello_world - 4

Relocation section '.rela.eh_frame' at offset 0x2e0 contains 2 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000000020 000200000002 R_X86_64_PC32 0000000000000000 .text + 0
000000000040 000200000002 R_X86_64_PC32 0000000000000000 .text + 23

我们可以看到其中的一行,这里面有6个值,但是结构体只有3个,这个原因我们下面会解释

000000000018  000500000004 R_X86_64_PLT32    0000000000000000 println - 4
  • r_offset: 000000000018: 代表着这个重定向位置相对section首地址的偏移,这里就是用括号框起来的这个位置,

17: e8 [00 00 00 00]        call   1c <test_func_call+0x1c>
  • r_info: 000500000004:对于32位ELF文件可进一步细分为 24 位符号表索引和 8 位类型字段,64位ELF文件可以32位的的符号表索引和32位的类型字段

    • sym = r_info >> 32 = 5

    • type = r_info & 0xFFFFFFFF = 4

  • R_X86_64_PLT32: 这个不是一个字段,type就是来决定R_X86_64_PLT32

    • 这个类型可以在Intel的官网中找到 https://www.intel.com/content/dam/develop/external/us/en/documents/mpx-linux64-abi.pdf


    • Linux下的Object文件加载器

    • 我们可以看到4代表的就是R_X86_64_PLT32

  • 0000000000000000 println:这两个是从符号表中关联过来的,后面会详细介绍

  • r_addend: -4:这是一个很重要的字段,在重定位中有着很重要的作用,在后续的重定位指针章节中会详细介绍

.symtab节

typedef struct {
Elf64_Word st_name;
unsigned char st_info;
unsigned char st_other;
Elf64_Half st_shndx;
Elf64_Addr st_value;
Elf64_Xword st_size;
} Elf64_Sym;

我们可以通过readelf -s test.o读取符号节

Symbol table '.symtab' contains 9 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS test.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1 .text
3: 0000000000000000 0 SECTION LOCAL DEFAULT 5 .rodata
4: 0000000000000000 35 FUNC GLOBAL DEFAULT 1 test_func_call
5: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND println
6: 0000000000000023 64 FUNC GLOBAL DEFAULT 1 main
7: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND debugln
8: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND hello_world
  • st_name: 这里的起始是一个索引,对应strtab中的字符串。

    • 可以通过在strtab中读取这个值

  • st_shndx: 代表着该符号所在的节的序号,还有几个特殊的值

    • SHN_UNDEF表示这个符号并未在当前文件定义,这个文件没有这个符号的位置

    • SHN_ABS表示这个符号是一个绝对地址,不需要重定位

    • SHN_COMMON表示这个符号是用来定义对齐字节的。

  • st_value: 代表着该符号,相对于符号所在节起始地址的偏移

    • 若改符号st_shndx=SHN_COMMON,那么st_value代表着对齐字节数

  • 其他字段暂时用不到

重定位偏移

我们可以看到上面关于重定位类型的图,这里的S,A,L,P,G,GOT分别代表

  • A 代表用于计算可重定位字段值的被加数。

  • B 代表在执行期间加载到内存中的共享对象的基地址。一般来说,共享对象的基虚拟地址为0,但执行地址会有所不同。

  • G 代表重定位项符号在全局偏移表(GOT)中的偏移量,在执行期间该符号将位于此处。

  • GOT 代表全局偏移表的地址。

  • L 代表符号的过程链接表(PLT)条目的位置(段偏移或地址)。

  • P 代表正在重定位的存储单元的位置(段偏移或地址)(使用 r_offset 计算)。

  • S 代表重定位项中索引所在符号的值。

  • Z 代表重定位项中索引所在符号的大小。

到这里,就需要研究一下这些重定向模式了,但是值得一提的是,在这个加载器中很多类型都是相同的,比如说R_X86_64_PLT32R_X86_64_PC32,因为我们根本没有PLT表

为了重定位,我们需要将重定位的类型分成两种情况,

  • 程序内重定向:指的是一个.o程序当中某个函数对另一个函数的调用,对程序内字符串的引用等,比如说在这个例子中main函数中对test_func_call的调用

  • 程序外重定向:指的是程序调用外部函数,需要重定向程序外的函数的真实地址,比如说在这个例子中main中对hello_world,debugln的调用

程序内重定向

程序内重定向的逻辑其实和标准的链接步骤相同,按照基本的规则进行链接即可。我们这里来看一下本例当中main函数对test_func_call的调用。

在重定位表中,这个对应如下表项

000000000042  000400000004 R_X86_64_PLT32    0000000000000000 test_func_call - 4

这里我们可以看到我们修改的偏移是42字节,类型是R_X86_64_PLT32,代表着修改的长度是32位,4字节,addend = -4。对应的汇编如下

0000000000000023 <main>:
23: f3 0f 1e fa endbr64
27: 55 push rbp
28: 48 89 e5 mov rbp,rsp
2b: 48 83 ec 10 sub rsp,0x10
2f: 48 8d 05 00 00 00 00 lea rax,[rip+0x0] # 36 <main+0x13>
36: 48 89 45 f8 mov QWORD PTR [rbp-0x8],rax
3a: 48 8b 45 f8 mov rax,QWORD PTR [rbp-0x8]
3e: 48 89 c7 mov rdi,rax
>41: e8 [00 00 00 00] call 46 <main+0x23>
46: 48 8b 45 f8 mov rax,QWORD PTR [rbp-0x8]
4a: 48 89 c7 mov rdi,rax
4d: e8 00 00 00 00 call 52 <main+0x2f>
52: b8 00 00 00 00 mov eax,0x0
57: e8 00 00 00 00 call 5c <main+0x39>
5c: b8 01 00 00 00 mov eax,0x1
61: c9 leave
62: c3 ret

根据手册这个类型的计算规则是L + A - P,但是由于我们这里没有PLT表,但是根据基本原理我们这里要这么计算

sym_real_address + r_addend - patch_real_address
  • sym_real_address: 符号的真实内存地址

  • patch_real_address: 在内存中修改的需要修改的起始地址

  • r_addend这是需要加上的,用来解决偏移问题的

同理,其他的模式也可以通过具体分析去写出来

程序外重定向

程序外重定向就更加简单了,根本不需要考虑重定向类型。由于call指令的操作数记录的是相对rip的偏移,而真实的rip相对修改的地址有4/8个字节的偏差(根据操作数长度判定)。所以直接计算这个偏移即可

sym_real_address  - patch_real_address - 4/8

来源:https://xz.aliyun.com/  感谢【sndav

原文始发于微信公众号(衡阳信安):Linux下的Object文件加载器

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年6月25日09:05:21
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Linux下的Object文件加载器http://cn-sec.com/archives/1830902.html

发表评论

匿名网友 填写信息