编辑
2025-01-22
记录知识
0
请注意,本文编写于 156 天前,最后修改于 59 天前,其中某些信息可能已经过时。

目录

一、ELF的结构
二、关注的节
2.1 .text
2.2 .data
2.3 rodata
2.4 .bss
三、如何保护
3.1 制造篡改
3.2 开展保护

为了让系统的二进制文件避免被破解,我们先需要针对elf文件进行了解,本文以防破解为目的,来简单了解一下elf的组成,如果需要详细了解的,建议阅读《elf.pdf》和《elf-64-gen.pdf》,文件点击即可阅读。理解此文章需要一点elf知识,建议可以先搜索引擎简单了解一下。

一、ELF的结构

这里以网上的图片为例,ELF结构如下:

image.png 可以发现,这里列举了必要的ELF内容,这里解释如下:

ELF Header: ELF的头结构 .text: 程序的代码段 .rodata: 程序的只读数据区 .data: 程序的已初始化的数据区 .bss: 程序的未初始化数据(初始化为0算未初始化) .symtab: 链接符号表 .strtab: 字符串表 Section Header:节的头表

而实际上,我以一个helloworld.c的so编译一个libhelloworld.so为例,查看一下elf的结构:

抛开elf头和节头,剩下的段如下:

# readelf -S libhelloworld.so 节头: [号] 名称 类型 地址 偏移量 大小 全体大小 旗标 链接 信息 对齐 [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .note.gnu.build-i NOTE 00000000000001c8 000001c8 0000000000000024 0000000000000000 A 0 0 4 [ 2] .gnu.hash GNU_HASH 00000000000001f0 000001f0 0000000000000024 0000000000000000 A 3 0 8 [ 3] .dynsym DYNSYM 0000000000000218 00000218 00000000000000d8 0000000000000018 A 4 3 8 [ 4] .dynstr STRTAB 00000000000002f0 000002f0 000000000000007b 0000000000000000 A 0 0 1 [ 5] .gnu.version VERSYM 000000000000036c 0000036c 0000000000000012 0000000000000002 A 3 0 2 [ 6] .gnu.version_r VERNEED 0000000000000380 00000380 0000000000000020 0000000000000000 A 4 1 8 [ 7] .rela.dyn RELA 00000000000003a0 000003a0 00000000000000a8 0000000000000018 A 3 0 8 [ 8] .rela.plt RELA 0000000000000448 00000448 0000000000000048 0000000000000018 AI 3 19 8 [ 9] .init PROGBITS 0000000000000490 00000490 0000000000000014 0000000000000000 AX 0 0 4 [10] .plt PROGBITS 00000000000004b0 000004b0 0000000000000050 0000000000000010 AX 0 0 16 [11] .text PROGBITS 0000000000000500 00000500 00000000000000fc 0000000000000000 AX 0 0 8 [12] .fini PROGBITS 00000000000005fc 000005fc 0000000000000010 0000000000000000 AX 0 0 4 [13] .eh_frame_hdr PROGBITS 000000000000060c 0000060c 0000000000000034 0000000000000000 A 0 0 4 [14] .eh_frame PROGBITS 0000000000000640 00000640 000000000000009c 0000000000000000 A 0 0 8 [15] .init_array INIT_ARRAY 0000000000010df0 00000df0 0000000000000008 0000000000000008 WA 0 0 8 [16] .fini_array FINI_ARRAY 0000000000010df8 00000df8 0000000000000008 0000000000000008 WA 0 0 8 [17] .dynamic DYNAMIC 0000000000010e00 00000e00 00000000000001c0 0000000000000010 WA 4 0 8 [18] .got PROGBITS 0000000000010fc0 00000fc0 0000000000000028 0000000000000008 WA 0 0 8 [19] .got.plt PROGBITS 0000000000010fe8 00000fe8 0000000000000030 0000000000000008 WA 0 0 8 [20] .data PROGBITS 0000000000011018 00001018 0000000000000016 0000000000000000 WA 0 0 8 [21] .bss NOBITS 0000000000011030 0000102e 0000000000000008 0000000000000000 WA 0 0 4 [22] .comment PROGBITS 0000000000000000 0000102e 000000000000002a 0000000000000001 MS 0 0 1 [23] .symtab SYMTAB 0000000000000000 00001058 0000000000000690 0000000000000018 24 64 8 [24] .strtab STRTAB 0000000000000000 000016e8 0000000000000236 0000000000000000 0 0 1 [25] .shstrtab STRTAB 0000000000000000 0000191e 00000000000000e5 0000000000000000 0 0 1

其他的节的解释,可以查看官方文档。这里不做解释了。

二、关注的节

我们带着问题来找答案:目前需要对一个elf文件进行保护,那我们保护什么呢?答案应该如下:

  1. 代码的实现
  2. 程序的数据

我们不需要关心的是

  1. 无需加载到内存中的节
  2. 参与动态链接的节
  3. 参与异常处理的节
  4. 特殊或用于标识的节

这里我们可以知道,下面节是我们无需关心的:

.rela.dyn .rela.plt .plt .got .got.plt .eh_frame_hdr .eh_frame .shstrtab .*gnu*

需要关心的是:

.init .init_array .fini .fini_array .text .rodata .data .bss

但是这里我们注意的是32位和64位的.init* 和.fini* 目前没有由程序参与,故可以排除掉,则我们关心如下节即可:

.text .rodata .data .bss

2.1 .text

我们知道.text是二进制的代码段,我们可以通过命令查看如下,以libhelloworld.so为例

# objdump -d -j .text libhelloworld.so libhelloworld.so: 文件格式 elf64-littleaarch64 Disassembly of section .text: 0000000000000500 <hello_world@@Base-0xd4>: 500: 90000080 adrp x0, 10000 <hello_world@@Base+0xfa2c> 504: f947ec00 ldr x0, [x0, #4056] 508: b4000040 cbz x0, 510 <puts@plt+0x20> 50c: 17fffff5 b 4e0 <__gmon_start__@plt> 510: d65f03c0 ret 514: d503201f nop 518: b0000080 adrp x0, 11000 <hello_world@@Base+0x10a2c> 51c: 9100c000 add x0, x0, #0x30 520: b0000081 adrp x1, 11000 <hello_world@@Base+0x10a2c> 524: 9100c021 add x1, x1, #0x30 528: eb00003f cmp x1, x0 52c: 540000c0 b.eq 544 <puts@plt+0x54> // b.none 530: 90000081 adrp x1, 10000 <hello_world@@Base+0xfa2c> 534: f947e421 ldr x1, [x1, #4040] 538: b4000061 cbz x1, 544 <puts@plt+0x54> 53c: aa0103f0 mov x16, x1 540: d61f0200 br x16 544: d65f03c0 ret 548: b0000080 adrp x0, 11000 <hello_world@@Base+0x10a2c> 54c: 9100c000 add x0, x0, #0x30 550: b0000081 adrp x1, 11000 <hello_world@@Base+0x10a2c> 554: 9100c021 add x1, x1, #0x30 558: cb000021 sub x1, x1, x0 55c: d37ffc22 lsr x2, x1, #63 560: 8b810c41 add x1, x2, x1, asr #3 564: eb8107ff cmp xzr, x1, asr #1 568: 9341fc21 asr x1, x1, #1 56c: 540000c0 b.eq 584 <puts@plt+0x94> // b.none 570: 90000082 adrp x2, 10000 <hello_world@@Base+0xfa2c> 574: f947f042 ldr x2, [x2, #4064] 578: b4000062 cbz x2, 584 <puts@plt+0x94> 57c: aa0203f0 mov x16, x2 580: d61f0200 br x16 584: d65f03c0 ret 588: a9be7bfd stp x29, x30, [sp, #-32]! 58c: 910003fd mov x29, sp 590: f9000bf3 str x19, [sp, #16] 594: b0000093 adrp x19, 11000 <hello_world@@Base+0x10a2c> 598: 3940c260 ldrb w0, [x19, #48] 59c: 35000140 cbnz w0, 5c4 <puts@plt+0xd4> 5a0: 90000080 adrp x0, 10000 <hello_world@@Base+0xfa2c> 5a4: f947e800 ldr x0, [x0, #4048] 5a8: b4000080 cbz x0, 5b8 <puts@plt+0xc8> 5ac: b0000080 adrp x0, 11000 <hello_world@@Base+0x10a2c> 5b0: f9400c00 ldr x0, [x0, #24] 5b4: 97ffffc7 bl 4d0 <__cxa_finalize@plt> 5b8: 97ffffd8 bl 518 <puts@plt+0x28> 5bc: 52800020 mov w0, #0x1 // #1 5c0: 3900c260 strb w0, [x19, #48] 5c4: f9400bf3 ldr x19, [sp, #16] 5c8: a8c27bfd ldp x29, x30, [sp], #32 5cc: d65f03c0 ret 5d0: 17ffffde b 548 <puts@plt+0x58> 00000000000005d4 <hello_world@@Base>: 5d4: a9be7bfd stp x29, x30, [sp, #-32]! 5d8: 910003fd mov x29, sp 5dc: 52800020 mov w0, #0x1 // #1 5e0: b9001fe0 str w0, [sp, #28] 5e4: b0000080 adrp x0, 11000 <hello_world@@Base+0x10a2c> 5e8: 91008000 add x0, x0, #0x20 5ec: 97ffffc1 bl 4f0 <puts@plt> 5f0: d503201f nop 5f4: a8c27bfd ldp x29, x30, [sp], #32 5f8: d65f03c0 ret

2.2 .data

我们知道.data是二进制的已初始化数据段,我们可以通过命令查看如下

# readelf -p .data libhelloworld.so String dump of section '.data': [ 8] Hello, World!

2.3 rodata

我们知道.rodata是二进制的只读数据段,我们需要添加一个变量让其存放在rodata,如下:

const char k[]="Hello, Kylin!";

此时我们查看如下

# readelf -p .rodata libhelloworld.so String dump of section '.rodata': [ 0] Hello, Kylin!

2.4 .bss

我们知道.bss是二进制的未初始化数据段,我们需要添加一个变量让其存放在.bss,如下:

char bss[64] = "";

此时我们查看如下

# objdump -d -j .bss libhelloworld.so libhelloworld.so: 文件格式 elf64-littleaarch64 Disassembly of section .bss: 0000000000011038 <completed.9189>: ... 0000000000011040 <bss>: ...

三、如何保护

我们抓住了elf的重点,就是

可加载文件的在运行时加载的text/data/rodata/bss段 如果我们下载hexedit工具,我们只需要对libhelloworld.so进行修改,就能左右代码的执行,数据的信息等等。为了简单易懂,我以修改rodata为例示例修改elf文件。

3.1 制造篡改

假设程序如下:

#include <stdio.h> void hello_world() { const char k[]="Hello, Kylin!"; printf("%s\n", k); } # LD_LIBRARY_PATH=./ ./test-helloworld Hello, Kylin!

已知rodata的内容是"Hello, Kylin!",我需要通过hexedit修改为"Hello, World!"如下:

hexedit libhelloworld.so

我们知道Hello, World!和Hello, Kylin!的二进制如下

# echo "Hello, World!" | xxd -g 1 00000000: 48 65 6c 6c 6f 2c 20 57 6f 72 6c 64 21 0a Hello, World!. # echo "Hello, Kylin!" | xxd -g 1 00000000: 48 65 6c 6c 6f 2c 20 4b 79 6c 69 6e 21 0a Hello, Kylin!.

这里我们打算把Kylin更换成World,我们使用hexedit编辑即可

hexedit libhelloworld.so

这里我们将4b 79 6c 69 6e修改成57 6f 72 6c 64即可。此时我们再运行程序

# LD_LIBRARY_PATH=./ ./test-helloworld Hello, World!

3.2 开展保护

为了针对elf程序的修改行为,我们计划是新增一个名字为.encrypt的加密节,里面存放上述关键节的hash加密。关于hash的选择后面文章会提,这里提供添加节的方法如下:

objcopy --add-section .encrypt=sha256_text.bin libhelloworld.so libhelloworld_en.so

此时,这个新的so会多出现一个节,如下:

readelf -S libhelloworld_en.so [23] .encrypt PROGBITS 0000000000000000 00001058 0000000000000100 0000000000000000 0 0 1

至此,我们根据elf的基本组成原理,为了安全性考量,新增了一个由程序自定义行为(PROGBITS)的节.encrypt来实现对核心文件防篡改的基本需求