一、开始
Upx是一款开源免费的跨平台的压缩壳,因此作为学习目的还是很不错的,具体upx的介绍可以查看其官网(链接见参考),通过学习upx更好的了解elf文件的加壳思路和方式,另外本文也只是对学习过程做一个记录,如有疏漏或错误之处,请多指教,学习交流。
二、分析点
主要分析32位linux下可执行文件的加壳方式,目的是分析清楚upx的加壳流程和加壳思路
三、源码分析
1.首先可以clone源码自行编译,或者下载已经编译好的可执行文件
2.upx编译
编译需要lzma-sdk,下载上一步的代码已经包含,接下来需要安装ucl库,下载链接如下,http://cpgs.fdcservers.net/ubuntu/ubuntu/pool/universe/u/ucl/
解压后执行./configure,生成makefile文件,然后执行make进行编译,最后执行make install进行安装即可,再将ucl-1.0.3下的include/ucl文件夹复制到upx/src中即可,最后再将upx/stub/scripts/check_whitespace.sh文件中的代码注释掉
接下来在upx源码目录下使用make all 命令进行upx编译,最后生成src/upx.out如下所示。
3.测试使用,观察文件执行前后的变化,首先准备一个简单的demo程序,编译生成elf可执行程序。
#include<stdio.h> #include<time.h> void getNowTime() { time_t now; time(&now); printf("now time is %d\n", now); } int main() { getNowTime(); return 0; } |
再使用upx -3 demo选择快速方式加壳,提示错误如下
由于压缩的demo文件太小,小于40kb,不能被压缩,采用静态方式编译即可。
使用upx -1 demo 成功压缩
使用readelf –hlS demo分别查看对应的文件信息
压缩前文件信息如下所示:
ELF Header: Magic: 7f 45 4c 46 01 01 01 03 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - GNU ABI Version: 0 Type: EXEC (Executable file) Machine: Intel 80386 Version: 0x1 Entry point address: 0x8048736 Start of program headers: 52 (bytes into file) Start of section headers: 727032 (bytes into file) Flags: 0x0 Size of this header: 52 (bytes) Size of program headers: 32 (bytes) Number of program headers: 6 Size of section headers: 40 (bytes) Number of section headers: 33 Section header string table index: 30
Section Headers: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .note.ABI-tag NOTE 080480f4 0000f4 000020 00 A 0 0 4 [ 2] .note.gnu.build-i NOTE 08048114 000114 000024 00 A 0 0 4 [ 3] .rel.plt REL 08048138 000138 000070 08 AI 0 24 4 [ 4] .init PROGBITS 080481a8 0001a8 000023 00 AX 0 0 4 [ 5] .plt PROGBITS 080481d0 0001d0 0000e0 00 AX 0 0 16 [ 6] .text PROGBITS 080482b0 0002b0 072a4c 00 AX 0 0 16 [ 7] __libc_freeres_fn PROGBITS 080bad00 072d00 000abd 00 AX 0 0 16 [ 8] __libc_thread_fre PROGBITS 080bb7c0 0737c0 0000a5 00 AX 0 0 16 [ 9] .fini PROGBITS 080bb868 073868 000014 00 AX 0 0 4 [10] .rodata PROGBITS 080bb880 073880 01a90c 00 A 0 0 32 [11] __libc_subfreeres PROGBITS 080d618c 08e18c 000028 00 A 0 0 4 [12] .stapsdt.base PROGBITS 080d61b4 08e1b4 000001 00 A 0 0 1 [13] __libc_atexit PROGBITS 080d61b8 08e1b8 000004 00 A 0 0 4 [14] __libc_thread_sub PROGBITS 080d61bc 08e1bc 000004 00 A 0 0 4 [15] .eh_frame PROGBITS 080d61c0 08e1c0 012ac4 00 A 0 0 4 [16] .gcc_except_table PROGBITS 080e8c84 0a0c84 0000a1 00 A 0 0 1 [17] .tdata PROGBITS 080e9f5c 0a0f5c 000010 00 WAT 0 0 4 [18] .tbss NOBITS 080e9f6c 0a0f6c 000018 00 WAT 0 0 4 [19] .init_array INIT_ARRAY 080e9f6c 0a0f6c 000008 00 WA 0 0 4 [20] .fini_array FINI_ARRAY 080e9f74 0a0f74 000008 00 WA 0 0 4 [21] .jcr PROGBITS 080e9f7c 0a0f7c 000004 00 WA 0 0 4 [22] .data.rel.ro PROGBITS 080e9f80 0a0f80 000070 00 WA 0 0 32 [23] .got PROGBITS 080e9ff0 0a0ff0 000008 04 WA 0 0 4 [24] .got.plt PROGBITS 080ea000 0a1000 000044 04 WA 0 0 4 [25] .data PROGBITS 080ea060 0a1060 000f20 00 WA 0 0 32 [26] .bss NOBITS 080eaf80 0a1f80 000e0c 00 WA 0 0 32 [27] __libc_freeres_pt NOBITS 080ebd8c 0a1f80 000018 00 WA 0 0 4 [28] .comment PROGBITS 00000000 0a1f80 000034 01 MS 0 0 1 [29] .note.stapsdt NOTE 00000000 0a1fb4 000b54 00 0 0 4 [30] .shstrtab STRTAB 00000000 0b168f 000168 00 0 0 1 [31] .symtab SYMTAB 00000000 0a2b08 007ea0 10 32 849 4 [32] .strtab STRTAB 00000000 0aa9a8 006ce7 00 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings) I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific)
Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align LOAD 0x000000 0x08048000 0x08048000 0xa0d25 0xa0d25 R E 0x1000 LOAD 0x0a0f5c 0x080e9f5c 0x080e9f5c 0x01024 0x01e48 RW 0x1000 NOTE 0x0000f4 0x080480f4 0x080480f4 0x00044 0x00044 R 0x4 TLS 0x0a0f5c 0x080e9f5c 0x080e9f5c 0x00010 0x00028 R 0x4 GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x10 GNU_RELRO 0x0a0f5c 0x080e9f5c 0x080e9f5c 0x000a4 0x000a4 R 0x1
Section to Segment mapping: Segment Sections... 00 .note.ABI-tag .note.gnu.build-id .rel.plt .init .plt .text __libc_freeres_fn __libc_thread_freeres_fn .fini .rodata __libc_subfreeres .stapsdt.base __libc_atexit __libc_thread_subfreeres .eh_frame .gcc_except_table 01 .tdata .init_array .fini_array .jcr .data.rel.ro .got .got.plt .data .bss __libc_freeres_ptrs 02 .note.ABI-tag .note.gnu.build-id 03 .tdata .tbss 04 05 .tdata .init_array .fini_array .jcr .data.rel.ro .got |
压缩后文件信息如下所示:
ELF Header: Magic: 7f 45 4c 46 01 01 01 03 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - GNU ABI Version: 0 Type: EXEC (Executable file) Machine: Intel 80386 Version: 0x1 Entry point address: 0xc4fd20 Start of program headers: 52 (bytes into file) Start of section headers: 0 (bytes into file) Flags: 0x0 Size of this header: 52 (bytes) Size of program headers: 32 (bytes) Number of program headers: 3 Size of section headers: 40 (bytes) Number of section headers: 0 Section header string table index: 0
There are no sections in this file.
Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align LOAD 0x000000 0x00c01000 0x00c01000 0x4f529 0x4f529 R E 0x1000 LOAD 0x000da4 0x080ebda4 0x080ebda4 0x00000 0x00000 RW 0x1000 GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x10 |
通过对压缩前后文件信息的对比发现,upx压缩之后会对头部信息Start of section headers,Number of section headers,Section header string table index进行擦除,对section段进行擦除,程序头的NOTE,TLS,GNU_RELRO进行擦除,LOAD段的基地址被修改。头部的相关信息Entry point address,Number of program headers被修改。
四、流程
首先从main函数出发,跟踪函数调用流程,定位调用的压缩函数。
在main函数中开始调用do_files()函数,继续跟进,
其中do_files()会调用do_one_file(),继续跟进
当命令参数为压缩命令时调用pm.pack(),继续跟进,
其中通过getPacker()函数确定加壳文件类型,再调用doPack(),
该函数为加壳函数,在Packer类中定义为纯虚函数,因此需要定位其具体的实现类,主要关注elf文件的加壳,如下所示
因此接下来需要关注该加壳函数在PackUnix类中的实现。继续跟进,如下所示,
具体的实现过程为对应的如下虚函数pack1(),pack2(),pack3(),pack4()
同样定位实现类,对应的继承关系如下所示,
此处主要针对32位elf可执行文件进行分析,实现类为PackLinuxElf32和PackLinuxElf32x86,PackLinuxElf32对函数pack1~4()进行了重写,针对不同的架构PackLinuxElf32x86对pack1()进行了重写。
PackLinuxElf32x86::Pack1函数如下。
调用generateElfHdr()函数生成elf头
其中将elf Header中的e_shoff,e_shnum,e_shstrndx置0。接下来分析PackLinuxElf32::pack2()追加压缩数据
其中主要是对文件大小做判断,小于0x40的不进行压缩,抛出NotCompressible异常。对数据进行压缩调用packExtent()函数。
接下来分析PackLinuxElf32::pack3()追加loader
加载压缩PT_LOADs,压缩调试信息,调用packExtent()。
对PT_LOAD进行修改,设置各个字段的值。
计算新的偏移的值。修改DT_INIT,修改inputfile中的e_shoff,e_shnum,e_shstrndx为0。
接下来分析PackLinuxElf32::pack4()添加壳的头部
将shstrtab等信息写入输出文件
如果是非共享库则打开文件校验section头部和程序头部,复制旧的section头部,最后更新.e_shstrndx。
调用父类的pack4(),写入packHeader和overlay_offset。
五、总结
通过对源码的分析了解到upx对一个32位的可执行文件进行了哪些修改和压缩,在遇到类似upx的加壳时能通过相关特征进行判断,也可以对upx进行定制来对抗脱壳。
其中pack()函数为加壳函数,每一步分别调用pack1()进行头部的处理,对头部的section信息进行擦出,pack2()对数据段进行压缩,pack3()修改LOAD段,pack4()则添加最后的壳的头部,并修改壳的section段。
六、参考