该文章主要针对android平台下的so文件使用upx加壳做的分析,加壳命令为:upx.out --android-shlib test.so
简单的elf文件结构这里就不重复说明了,如果还不是很清楚可以先了解下elf文件结构。图1为upx加壳后的so文件格式,下面分别多各部分做说明。
elf header:这部分保持跟原so文件相同,不过里面部分数据需要修改,比如section headers的偏移等。
program headers:该部分会对第一个LOAD段的size大小做修改(因为使用了upx壳后体积变小了),其他数据保持不变。
sections:这里存放着原so中的节,这些节包括:"DT_GNU_HASH","DT_HASH", "DT_STRTAB","DT_SYMTAB","DT_REL","DT_RELA","DT_JMPREL","DT_VERDEF","DT_VERSYM","DT_VERNEEDED",因为在so中跟重定位相关的节不能够被加壳,并且在文件中的偏移位置需要保持不变,所以一般情况下是在.text节之后开始做压缩处理。
section headers:upx压缩壳会将section headers表上移,放到上一部分的sections后面,并且不做任何压缩处理,原因是在高版本的android系统中会对section headers做校验,需要读取section headers中的数据,因此需要以明文的形式存放在so中。
.shstrtab:这个节用于存放各个section的名字。
l_info:为一个结构体,其格式如下,其中magic为’7fUPX’,各个产商会对此做修改。
{
LE32 l_checksum;
LE32 l_magic;
LE16 l_size;
unsigned char l_version;
unsigned char l_format;
}
p_info:为一个结构体,其格式如下,
{
unsigned p_progid;
unsigned p_filesize;
unsigned p_blocksize;
}
compressed data:被压缩的数据,即实现对代码段做加密。
b_info:为一个结构体,其格式如下,其中sz_unc为压缩后数据的大小,sz_cpr为压缩前数据的大小,b_method为使用的压缩方法。
{
unsigned sz_unc;
unsigned sz_cpr;
unsigned char b_method;
unsigned char b_ftid;
unsigned char b_cto8;
unsigned char b_unused;
}
other info:保存着可以传递给stub的参数,其中包括:dt_init地址,xct_off(sections中最后的地址)等
stub:为整个upx的核心部分,该部分用于运行时解压并还原compressed data。
2th LOAD:为so的第二个LOAD段,因为第二个LOAD段中的dynamic节涉及重定位环节,所以upx无法对其做压缩处理。
PackHeader:为upx的尾部,保存一些upx相关信息。
在upx的stub中主要有三个部分,分别为:main、supervise、hatch。
1.在main中使用mmap开辟新空间A,将compressed data、supervise、hatch拷贝到该空间A中。并将pc指针指向A中的supervise开始执行指令。
2.supervise中包含这解压过程中所需要的代码包括:
解压算法decompress、过滤器unfilter、其他。在该部分中使用mmap并使用MAP_FIXED参数来开辟内存,该内存地址为原so加载时的空间地址,MAP_FIXED这个参数能够覆盖原来mmap的内存空间,确保解压后数据与原so保持一致。对compressed data做解压处理,并将hatch拷贝到第一个LOAD末尾(多余空间)。在拷贝以及解压完成后,将pc指针跳转到hatch,执行hatch。
3.hatch用于unmap释放在1中开辟的空间A,并跳转到原so的dt_init中,如果原so没有dt_init则跳转到linker中。
该过程流程图如下:
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2019-1-3 16:45
被CCkicker编辑
,原因: 调整格式