之前一直学习如何脱壳,接触到的第一种壳就是UPX。经过一段脱壳训练后,逐渐对UPX的压缩流程有了兴趣。本文是笔者对UPX源码的学习记录,包括代码学习过程和源码简单修改,希望对大家有所帮助。
分析环境:
UPX版本: 3.96 (https://github.com/upx/upx/releases/download/v3.96/upx-3.96-src.tar.xz)
linux系统: centOS 7 - Linux localhost.localdomain 3.10.0-862.el7.x86_64 #1 SMP Fri Apr 20 16:44:24 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
kali: Linux kali 5.9.0-kali1-amd64 #1 SMP Debian 5.9.1-1kali2 (2020-10-29) x86_64 GNU/Linux
010 Editor版本: v10.0 (64 bit)
源码分析
为了方便调试,笔者只分析了x86-64位ELF文件的加壳流程代码,本文所用程序见结尾文件 demo
。代码跟踪过程主要参考文章UPX源码分析——加壳篇,大家有兴趣可详细阅读。这里只介绍大体流程。
加壳流程
UPX的核心加壳代码是 upx-3.96/src/p_unix.cpp 文件的 pack 函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | void PackUnix::pack(OutputFile * fo)
{
Filter ft(ph.level);
ft.addvalue = 0 ;
b_len = 0 ;
progid = 0 ;
/ / set options
blocksize = opt - >o_unix.blocksize;
if (blocksize < = 0 )
blocksize = BLOCKSIZE;
if ((off_t)blocksize > file_size)
blocksize = file_size;
/ / init compression buffers
ibuf.alloc(blocksize);
obuf.allocForCompression(blocksize);
fi - >seek( 0 , SEEK_SET);
pack1(fo, ft); / / generate Elf header, etc.
p_info hbuf;
set_te32(&hbuf.p_progid, progid);
set_te32(&hbuf.p_filesize, file_size);
set_te32(&hbuf.p_blocksize, blocksize);
fo - >write(&hbuf, sizeof(hbuf));
/ / append the compressed body
if (pack2(fo, ft)) {
/ / write block end marker (uncompressed size 0 )
b_info hdr; memset(&hdr, 0 , sizeof(hdr));
set_le32(&hdr.sz_cpr, UPX_MAGIC_LE32);
fo - >write(&hdr, sizeof(hdr));
}
pack3(fo, ft); / / append loader
pack4(fo, ft); / / append PackHeader and overlay_offset; update Elf header
/ / finally check the compression ratio
if (!checkFinalCompressionRatio(fo))
throwNotCompressible();
}
|
该函数调用了4个关键函数,分别为pack1、pack2、pack3和pack4,代表了加壳的四个步骤。
pack1 函数功能是,写入新文件的elf头,写入程序头表,写入1个初始化的l_info结构。
pack2 函数功能是,对所有类型为PT_LOAD的段进行压缩存储。
其中,在对第一个类型为PT_LOAD的段(该块一般包含原文件的文件头和程序头表)进行压缩时,会将该段分为两个部分分别压缩写入。这两部分为:一、原文件的elf头和程序头表;二、该段数据的其他部分。
例如,demo文件中第一个PT_LOAD段如下
第一段文件偏移为0,大小为0x5F0。从图中可以看到文件头+程序头表的大小为0x270,这就是需要压缩的第一块数据。第二块数据就是文件偏移为0x270,大小为0x380。
pack3 函数的功能是,写入loader,压缩原文件中除PT_LOAD段之外的其余数据并写入,修正程序头表内容
pack4 函数的功能是,写入PackHeader和overlay_offset。这里overlay_offset值为p_info字段的文件偏移。本demo中为0xf4。
文件格式
经过UPX处理后,压缩后的文件格式如下。
new eheader(64 bytes) (文件头)
+ new pheader(56 bytes) * 3 (程序头表)
+ l_info(12 bytes)
+ p_info(12 bytes)
+ b_info(12 bytes) + compressed block (原程序文件头和程序头表)
+ b_info(12 bytes) + compressed block (第一个类型为PT_LOAD的段中除原程序文件头和程序头表的部分)
+ b_info(12 bytes) + compressed block (第二个类型为PT_LOAD的段)
+ ......
+ fpad8 (8字节对齐)
+ int(4 bytes) (第一个b_info的文件偏移)
+ int(4 bytes) (当前位置的文件偏移,也就是之前所有数据总长度)
+ loader (加载器,也就是脱壳代码)
+ b_info(12 bytes) + compressed block (第一个PT_LOAD和第二个PT_LOAD中间的数据)
+ b_info(12 bytes) + compressed block (第二个PT_LOAD和第三个PT_LOAD中间的数据)
+ ......
+ b_info(12 bytes) + compressed block (最后一个PT_LOAD到文件末尾之间的数据)
+ 00 00 00 00 55 50 58 21 00 00 00 00 (b_info)
+ fpad4 (4字节对齐)
+ PackHeader(32 bytes)
+ int(4 bytes) (p_info的文件偏移)
其中,b_info、l_info和p_info是三个结构体。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | / / 在每个压缩块之前,存放压缩前和压缩后的数据大小
struct b_info { / / 12 - byte header before each compressed block
uint32_t sz_unc; / / uncompressed_size
uint32_t sz_cpr; / / compressed_size
unsigned char b_method; / / compression algorithm
unsigned char b_ftid; / / filter id
unsigned char b_cto8; / / filter parameter
unsigned char b_unused;
};
/ / 存放校验数据和 "UPX!" 魔数
struct l_info / / 12 - byte trailer in header for loader (offset 116 )
{
uint32_t l_checksum;
uint32_t l_magic;
uint16_t l_lsize;
uint8_t l_version;
uint8_t l_format;
};
/ / 全文只有一个该结构体,存储的是原文件的大小
struct p_info / / 12 - byte packed program header follows stub loader
{
uint32_t p_progid;
uint32_t p_filesize;
uint32_t p_blocksize;
};
|
loader
针对demo文件的loader生成代码在 upx-3.96/src/p_lx_elf.cpp 文件的PackLinuxElf64::buildLinuxLoader()函数中,loader中各section的相应顺序由函数PackLinuxElf::addStubEntrySections()确定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | void
PackLinuxElf::addStubEntrySections( Filter const * )
{
addLoader( "ELFMAINX" , NULL);
if (hasLoaderSection( "ELFMAINXu" )) {
/ / brk() trouble if static
addLoader( "ELFMAINXu" , NULL);
}
/ / addLoader(getDecompressorSections(), NULL);
addLoader(
( M_IS_NRV2E(ph.method) ? "NRV_HEAD,NRV2E,NRV_TAIL"
: M_IS_NRV2D(ph.method) ? "NRV_HEAD,NRV2D,NRV_TAIL"
: M_IS_NRV2B(ph.method) ? "NRV_HEAD,NRV2B,NRV_TAIL"
: M_IS_LZMA(ph.method) ? "LZMA_ELF00,LZMA_DEC20,LZMA_DEC30"
: NULL), NULL);
if (hasLoaderSection( "CFLUSH" ))
addLoader( "CFLUSH" );
addLoader( "ELFMAINY,IDENTSTR" , NULL);
if (hasLoaderSection( "ELFMAINZe" )) { / / ppc64 big - endian only
addLoader( "ELFMAINZe" , NULL);
}
addLoader( "+40,ELFMAINZ" , NULL);
if (hasLoaderSection( "ANDMAJNZ" )) { / / Android trouble with args to DT_INIT
if (opt - >o_unix.android_shlib) {
addLoader( "ANDMAJNZ" , NULL); / / constant PAGE_SIZE
}
else {
addLoader( "ELFMAJNZ" , NULL); / / PAGE_SIZE from AT_PAGESZ
}
addLoader( "ELFMAKNZ" , NULL);
}
if (hasLoaderSection( "ELFMAINZu" )) {
addLoader( "ELFMAINZu" , NULL);
}
addLoader( "FOLDEXEC" , NULL);
}
|
除了"FOLDEXEC",其余section的汇编代码在upx-3.96/src/stub/src/amd64-linux.elf-entry.S文件中,编译后的二进制数据在文件upx-3.96/src/stub/amd64-linux.elf-entry.h中。loader直接使用*.h文件中的二进制数据。
最终,demo文件压缩后loader的结构如下
1 2 3 4 5 6 7 8 9 | ELFMAINX
NRV_HEAD
NRV2E
NRV_TAIL
ELFMAINY
IDENTSTR
+ 40 ( 4 字节对齐)
ELFMAINZ
FOLDEXEC
|
FOLDEXEC
FOLDEXEC节存放的是压缩后的数据,源数据是编译后的二进制数据,存放在upx-3.96/src/stub/amd64-linux.elf-fold.h文件中。
编译前的代码分为两部分,一部分是upx-3.96/src/stub/src/amd64-linux.elf-fold.S文件中的汇编代码,一部分是upx-3.96/src/stub/src/amd64-linux.elf-main.c文件中的C代码。该文件核心是upx_main()函数,此函数返回值为解压后程序(原程序)的入口点.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
/ / upx_main - called by our entry code
/ /
/ / This function is optimized for size.
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
void *
upx_main( / / returns entry address
struct b_info const * const bi, / / 1st block header
size_t const sz_compressed, / / total length
Elf64_Ehdr * const ehdr, / / temp char[sz_ehdr] for decompressing
Elf64_auxv_t * const av,
f_expand * const f_exp,
f_unfilter * const f_unf
, Elf64_Addr elfaddr / / In: &Elf64_Ehdr for stub
, Elf64_Addr * p_reloc / / In: &Elf64_Ehdr for stub; Out: 'slide' for PT_INTERP
, size_t const PAGE_MASK
, Elf64_Addr elfaddr
, size_t const PAGE_MASK
)
{
......
}
|
010模板
在阅读UPX源码过程中,经常需要对压缩后的文件格式进行分析,以验证自己的猜想。因为笔者经常使用010作为二进制分析工具,遂决定自己写一个010模板。这里是对标准的 ELF.bt(V2.5.5)进行了修改。
首先,将上节所述的三个关键结构体加入到模板中,为了增加可读性,在个别字段添加属性值。添加的结构体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | typedef uint uint32_t;
typedef ushort uint16_t;
typedef uchar uint8_t;
struct b_info / / 12 - byte header before each compressed block
{
uint32_t sz_unc< format = hex , comment = "Uncompressed size" >; / / uncompressed_size
uint32_t sz_cpr< format = hex , comment = "Compressed size" >; / / compressed_size
unsigned char b_method<comment = "Compression algorithm" >; / / compression algorithm
unsigned char b_ftid; / / filter id
unsigned char b_cto8; / / filter parameter
unsigned char b_unused;
};
struct l_info / / 12 - byte trailer in header for loader (offset 116 )
{
uint32_t l_checksum;
uint32_t l_magic< format = hex , comment = "UPX!" >;
uint16_t l_lsize;
uint8_t l_version;
uint8_t l_format;
};
struct p_info / / 12 - byte packed program header follows stub loader
{
uint32_t p_progid;
uint32_t p_filesize;
uint32_t p_blocksize;
};
typedef struct
{
b_info bheader;
char data[bheader.sz_cpr];
}cblock;
typedef struct
{
uint32 offset_b_info;
uint32 data_size;
}endheader;
|
然后,将原来模板中file结构体中对section的解析代码去掉,因为UPX压缩后的文件没有相关字段。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | / / 需要删除的代码
/ / Find the header name location 869 行
local quad section_name_off =
file .elf_header.e_shoff_SECTION_HEADER_OFFSET_IN_FILE +
( file .elf_header.e_shentsize_SECTION_HEADER_ENTRY_SIZE *
file .elf_header.e_shtrndx_STRING_TABLE_INDEX);
/ / Find the header name block
if ( file .elf_header.e_ident.ei_class_2 = = ELFCLASS32) {
......
} else {
......
}
local int sec_tbl_cur_elem;
/ / Find the section headers
if ( file .elf_header.e_shnum_NUMBER_OF_SECTION_HEADER_ENTRIES > 0 ) {
......
}
local int sym_sect;
local int sym_name_sect;
/ / Find the symbol section
sym_sect = FindNamedSection( ".symtab" );
if (sym_sect > = 0 ) {
......
}
/ / Find the dynamic symbol section
sym_sect = FindNamedSection( ".dynsym" );
if (sym_sect > = 0 ) {
......
}
|
最后,在file结构体中program_header_table结构体生成之后,添加l_info结构之后的数据解析代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | / / 需要添加的代码 869 行
FSeek( file .elf_header.e_phoff_PROGRAM_HEADER_OFFSET_IN_FILE + file .elf_header.e_phentsize_PROGRAM_HEADER_ENTRY_SIZE_IN_FILE * file .elf_header.e_phnum_NUMBER_OF_PROGRAM_HEADER_ENTRIES);
l_info linfone;
p_info pinfone<comment = "Original file size" >;
/ / Compressed block of PT_LOAD
local uint64 now_offsize;
now_offsize = file .elf_header.e_phoff_PROGRAM_HEADER_OFFSET_IN_FILE + file .elf_header.e_phentsize_PROGRAM_HEADER_ENTRY_SIZE_IN_FILE * file .elf_header.e_phnum_NUMBER_OF_PROGRAM_HEADER_ENTRIES + 24 ;
local uint64 tmp_offsize = 0 ;
local uint64 tmp = 0 ;
struct
{
while (true)
{
tmp_offsize = now_offsize + ( 7u & ( 0 - now_offsize));
tmp = ReadInt64(tmp_offsize);
if (((tmp& 0xffffffff ) = = 0x100 ) && (((tmp>> 32 )& 0xffffffff ) = = (tmp_offsize + 4 )))
{
break ;
}
else
{
FSeek(now_offsize);
cblock a;
now_offsize = now_offsize + a.bheader.sz_cpr + 12 ;
}
}
} cpr_load<comment = "Compressed block of PT_LOAD" >;
FSeek(tmp_offsize);
endheader headerEND;
local uint64 sizeloader = program_header_table.program_table_element[ 0 ].p_filesz_SEGMENT_FILE_LENGTH - tmp_offsize - 8 ;
uchar loader[sizeloader];
/ / Compressed block of GAPS
FSeek(program_header_table.program_table_element[ 0 ].p_filesz_SEGMENT_FILE_LENGTH);
now_offsize = program_header_table.program_table_element[ 0 ].p_filesz_SEGMENT_FILE_LENGTH;
tmp_offsize = 0 ;
tmp = 0 ;
struct
{
while (true)
{
tmp_offsize = now_offsize;
tmp = ReadInt64(tmp_offsize);
if (((tmp & 0xffffffff ) = = 0 ) && (((tmp >> 32 ) & 0xffffffff ) = = ( 0x21585055 )))
{
break ;
}
else
{
FSeek(now_offsize);
cblock b;
now_offsize = now_offsize + b.bheader.sz_cpr + 12 ;
}
}
} cpr_gaps<comment = "Compressed block of GAPS" >;
b_info check_upx<comment = "Block end marker (uncompressed size 0, compressed size 559435861)" >;
tmp_offsize = tmp_offsize + 12 ;
tmp_offsize = tmp_offsize + ( 3u & ( 0 - tmp_offsize));
FSeek(tmp_offsize);
uchar PackHeader[ 32 ]<comment = "PackHeader" >;
uint32 size_p_info< format = hex , comment = "Offset of p_info" >;
|
最终效果如下,红框中是我们添加的新结构。
修改完成的模板见结尾文件 upx.bt
。因为文件格式问题,该模板只适用于 demo 文件压缩后的文件。大家有其他需求可以在此基础上自己修改。
源码修改
之前做病毒分析时,碰到了一个无法使用标准UPX程序解压的程序。后来通过搜寻资料和学习,了解到有多种方式可实现该效果。基本思路是修改替换程序中的特征字符,常见的修改方法如下:
- UPX两个区段名称修改
- PE中DOS头修改
- 三个UPX特征码修改(参考EXEINFO等壳识别工具中的特征匹配规则)
- UPX相关字符串去除或修改
据此,笔者萌生直接对源码进行修改的想法,所以有以下两个简单尝试。
- 入口点字段修改
- 压缩数据修改
入口点字段修改
本次尝试主要是修改源程序的入口点位置,使常规的UPX解压程序后无法快速找到正确的入口点位置。
代码修改
存储入口点之前,处理该值,这里异或一个常量0xdeafdeaf。文件upx-3.96/src/p_unix.cpp添加如下代码
1 2 3 4 5 6 7 8 | 334 int l = fi - >readx(hdr_ibuf, hdr_u_len);
335 / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
336 / / add 2022 - 11 - 29 20 : 02
337 / / edit 2022 - 12 - 12 10 : 19
338 unsigned char * ttt = (unsigned char * )hdr_ibuf;
339 Elf64_Ehdr * tmp = (Elf64_Ehdr * )ttt;
340 tmp - >e_entry = (tmp - >e_entry ^ 0xdeafdeaf );
341 / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
|
解压得到入口点之后,处理该值,也是异或常量0xdeafdeaf。这里有两种方式,实现的功能一样。
- 文件upx-3.96/src/stub/src/amd64-linux.elf-fold.S添加汇编代码
xor $3736067759,%eax
1 2 3 4 5 6 7 8 9 | movq % rbp, % arg5
call upx_main
/ * entry = upx_main(b_info * arg1, total_size arg2, Elf64_Ehdr * arg3,
Elf32_Auxv_t * arg4, f_decompr arg5, f_unf arg6,
Elf64_Addr elfaddr )
* / xor $ 3736067759 , % eax / / add 2022 - 12 - 12
addq $ 1 * NBPW + OVERHEAD, % rsp
movq % rax, 4 * NBPW( % rsp)
pop % rbx
|
- 文件upx-3.96/src/stub/amd64-linux.elf-fold.h修改二进制数据,修改三处代码
1 2 3 | unsigned char stub_amd64_linux_elf_fold[ 2251 ] = {
- >
unsigned char stub_amd64_linux_elf_fold[ 2256 ] = {
|
1 2 3 | / * 0x01d0 * / 137 , 232 , 232 , 172 , 5 , 0 , 0 , 72 , 129 , 196 , 8 , 8 , 0 , 0 , 72 , 137 ,
- >
/ * 0x01d0 * / 137 , 232 , 232 , 177 , 5 , 0 , 0 , 53 , 175 , 222 , 175 , 222 , 72 , 129 , 196 , 8 , 8 , 0 , 0 , 72 , 137 ,
|
xor eax, 3736067759 -> 35 AF DE AF DE -> 53,175,222,175,222
验证
- 使用修改源码后的程序加壳,程序可正常执行。这里的UPX版本是3.96
- 在kali上执行加壳后的程序,可正常执行
- 使用kali上的UPX程序解压缩该程序(UPX版本为3.96),执行程序提示段错误
压缩数据修改
上面对于入口点的修改虽然成功了,但是压缩后的程序仍然可被解压。而且从原理来看,解压后是可以看到原程序代码执行逻辑的。所以笔者有了第二次代码修改。主要修改思路是,对压缩之前的数据进行处理,然后再进行压缩。这样标准UPX就无法解压我们自己压缩的程序,比只对入口点的修改效果更好。
代码修改
压缩前修改数据,我们做一个简单的异或处理,这里对所有的数据异或0xe9(十进制为233)
文件:upx-3.96/src/compress.cpp
upx_compress函数修改,核心部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 96
97 / / debug
98 cresult - >method = method;
99 cresult - >level = level;
100 cresult - >u_len = src_len;
101 cresult - >c_len = 0 ;
102
103 / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
104 const unsigned char * tmp = (const unsigned char * )src;
105 unsigned char * tmp0 = (unsigned char * )malloc(src_len * sizeof(char));
106 unsigned char * tmp1 = tmp0;
107 memcpy(tmp0, tmp, src_len);
108 size_t i = 0 ;
109 for (i = 0 ;i<src_len;i + + )
110 {
111 ( * tmp0) = ( * tmp0)^ 0xe9 ;
112 tmp0 = tmp0 + 1 ;
113 }
114 src = tmp1;
115 / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
|
upx_decompress函数修改,此处修改主要是因为在文件upx-3.96/src/packer.cpp的Packer::compress函数中,会对压缩后的数据进行解压验证,需要保持压缩和解压函数逻辑对称。
1 2 3 4 5 6 7 8 9 10 11 12 13 | 190 else {
191 throwInternalError( "unknown decompression method" );
192 }
193 / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
194 unsigned char * dst_tmp = dst;
195 size_t i = 0 ;
196 for (i = 0 ;i<( * dst_len);i + + )
197 {
198 ( * dst) = ( * dst)^ 0xe9 ;
199 dst = dst + 1 ;
200 }
201 dst = dst_tmp;
202 / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
|
upx在加载loader是会对其进行压缩处理,而解压算法已写成汇编代码,修改起来比较麻烦。这里我就对loader数据多进行一次处理,以抵消upx-3.96/src/compress.cpp文件upx_compress函数中添加的异或处理。
文件:upx-3.96/src/p_lx_elf.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 1222 unsigned h_sz_cpr = h.sz_cpr;
1223 / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
1224 / / add 2022 - 12 - 29 13 : 58
1225 unsigned char * tmp = (unsigned char * )malloc(h.sz_unc * sizeof(char));
1226 memcpy(tmp,uncLoader,h.sz_unc);
1227 unsigned char * tmp0 = tmp;
1228 size_t i = 0 ;
1229 for (i = 0 ;i<h.sz_unc;i + + )
1230 {
1231 ( * tmp) = ( * tmp)^ 0xe9 ;
1232 tmp = tmp + 1 ;
1233 }
1234 / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
1235 int r = upx_compress(tmp0, h.sz_unc, sizeof(h) + cprLoader, &h_sz_cpr,
1236 NULL, ph.method, 10 , NULL, NULL); / / edit uncLoader - > tmp0
|
因为原数据压缩前进行了处理,为保证执行时程序逻辑不变,需要对解压后的数据进行逆处理。下面就是对loader的源码进行修改。
文件:upx-3.96/src/stub/src/amd64-linux.elf-main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 194 int const j = ( * f_exp)((unsigned char * )xi - >buf, h.sz_cpr,
195 (unsigned char * )xo - >buf, &out_len,
196
197 * ( int * )(void * )&h.b_method
198
199 h.b_method
200
201 );
202 / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
203 unsigned char * tmp = (unsigned char * )xo - >buf;
204 size_t i = 0 ;
205 for (i = 0 ; i < h.sz_unc; i + + )
206 {
207 * tmp = ( * tmp)^ 0xe9 ;
208 tmp = tmp + 1 ;
209 }
210 / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
|
验证
- 编译后的程序版本为3.96
对demo文件进行压缩处理,可以看到处理后的文件大小,文件也可以正常执行
- 将该文件拷贝到kali中,也可以正常执行
- 使用kali自带的UPX,版本3.96对压缩后的文件进行解压,提示解压失败。
总结
本文主要介绍了三个部分的内容:
- UPX源码简单介绍,包括加壳的流程和加壳后的文件格式。
- 根据第一部分介绍的文件格式,对标准ELF.bt文件进行修改,使其可以解析加壳后的文件。
- 介绍两种UPX源码简单修改方式:一是修改文件入口点,二是修改加壳的数据。
文章较短,如有不足,恳请指正,不胜感激。
https://www.cnblogs.com/ichunqiu/p/7245329.html
https://bbs.kanxue.com/thread-257797.htmtmnxue.com/thread-257797.htm
[2022冬季班]《安卓高级研修班(网课)》月薪三万班招生中~
最后于 56分钟前
被luoye_ATL编辑
,原因: 标题格式有问题,进行修正