-
-
UPX代码buildloader函数分析
-
发表于: 2024-9-28 15:24 2120
-
buildloader函数代码存在于 src/p_w64pe_amd64.cpp文件中,主要功能是构建加载器,对PE文件后续的加载和运行提供良好的环境基础,因此研究UPX加壳逻辑,从这部分开始是一个很好的切入点。这个执行过程在PE文件打包过程中构建加载器,根据文件名可知是针对amd64架构下的PE文件。代码分析如下:
1.重新计算tlsindex
tlsindex 是一个与 TLS(Thread Local Storage,线程局部存储)有关的索引值。通过重新计算以保证TLS的正确性和有效性,确保能正确且安全地访问存储。
1 2 3 4 5 | unsigned tmp_tlsindex = tlsindex; const unsigned oam1 = ih.objectalign - 1 ; const unsigned newvsize = (ph.u_len + rvamin + ph.overlap_overhead + oam1) & ~oam1; if (tlsindex && ((newvsize - ph.c_len - 1024 + oam1) & ~oam1) > tlsindex + 4 ) tmp_tlsindex = 0 ; |
ih.objectalign 表示对齐的边界,而ih.objectalign - 1 的操作是为了计算出对齐掩码,这样在后续的位运算(~oam1)中可以方便地进行对齐计算。
ph.u_len:表示程序头(或节)在内存中占用的长度,用于表示加载到内存后该节的大小。
rvamin:一般代表基础地址(base address),是 PE 文件在内存中加载的起始地址。
ph.overlap_overhead:指的是重叠开销(overlap overhead),用于表示在加载过程中的额外空间需求,例如由于对齐或重叠导致的额外字节。
这些变量的组合用于计算新的虚拟大小 (newvsize),确保在分配内存时考虑到所有的开销和对齐需求。
然后在一个判断语句中,验证tlsindex的有效性;而(newvsize - ph.c_len - 1024 + oam1) & ~oam1)计算潜在的内存使用量(并满足对齐要求),并检查是否大于当前 tlsindex 加上一个安全值 4(这里的 +4 是为了确保在使用 TLS 时有足够的空间,防止可能的溢出)。
如果 tlsindex 有效,并且经过计算后新的对齐值超过了 tlsindex + 4,则说明当前的 TLS 索引不再适用,因此清除或重置 TLS 索引,以避免在后续使用中出错。
2.初始化加载器并处理加载逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | initLoader(stub_amd64_win64_pe, sizeof(stub_amd64_win64_pe), 2 ); addLoader( "START" ); if (ih.entry && isdll) addLoader( "PEISDLL0" ); if (isefi) addLoader( "PEISEFI0" ); addLoader(isdll ? "PEISDLL1" : " ", " PEMAIN01", icondir_count > 1 ? (icondir_count = = 2 ? "PEICONS1" : "PEICONS2" ) : "", tmp_tlsindex ? "PETLSHAK" : " ", " PEMAIN02", / / ph.first_offset_found = = 1 ? "PEMAIN03" : "", M_IS_LZMA(ph.method) ? "LZMA_HEAD,LZMA_ELF00,LZMA_DEC20,LZMA_TAIL" : M_IS_NRV2B(ph.method) ? "NRV_HEAD,NRV2B" : M_IS_NRV2D(ph.method) ? "NRV_HEAD,NRV2D" : M_IS_NRV2E(ph.method) ? "NRV_HEAD,NRV2E" : "UNKNOWN_COMPRESSION_METHOD" , / / getDecompressorSections(), / * multipass ? "PEMULTIP" : * / " ", " PEMAIN10"); if (ft - > id ) { const unsigned texv = ih.codebase - rvamin; assert (ft - >calls > 0 ); addLoader(texv ? "PECTTPOS" : "PECTTNUL" ); addLoader( "PEFILTER49" ); } |
调用 initLoader 函数初始化加载器,将 AMD64 架构的 Windows PE 文件的初始模板(stub_amd64_win64_pe)作为参数。
添加加载器的启动点START,并对不同的情况处理不同的加载逻辑:如果是dll文件,添加PEISDLL0;如果是efi文件,则添加PEISEFI0。
然后通过addLoader添加多个阶段的加载操作:PEMAINO1是主程序的入口点;根据图标数量添加对应的指令(PEICONS1、PEICONS2);如果 tmp_tlsindex 有效(不为 0),则添加指令 "PETLSHAK",用于处理 TLS,否则不添加;添加PEMAIN02,作为程序的下一个阶段;基于压缩方法的类型,根据多个条件判断,为头部添加相应的解压缩指令;添加PEMAIN10,表示主程序的最后阶段。
检查过滤器对象(ft)是否有效,如果有效,继续下一块代码。texv表示从代码基址(ih.codebase)到某个参考点(rvamin)的偏移量。检查 ft->calls (过滤器调用次数)是否大于 0,如果条件不成立(即无调用次数),程序将在调试模式终止。根据 texv 的值,添加不同的指令:如果 texv 非零,表示代码基址与参考点之间存在有效的偏移,则添加指令 "PECTTPOS";如果 texv 为零,则添加指令 "PECTTNUL",表示没有有效的偏移,不需要特殊处理。添加PEFILTER49指令,处理过滤器。
3.处理导入表和重定位表
1 2 3 4 5 6 7 8 9 10 11 12 | if (soimport) addLoader( "PEIMPORT" , importbyordinal ? "PEIBYORD" : " ", kernel32ordinal ? " PEK32ORD " : " ", importbyordinal ? "PEIMORD1" : " ", " PEIMPOR2 ", isdll ? " PEIERDLL " : " PEIEREXE", "PEIMDONE" ); if (sorelocs) { addLoader(soimport = = 0 || soimport + cimports ! = crelocs ? "PERELOC1" : "PERELOC2" , "PERELOC3" , big_relocs ? "REL64BIG" : " ", " RELOC64J"); if ( 0 ) { addLoader(big_relocs & 6 ? "PERLOHI0" : " ", big_relocs & 4 ? " PERELLO0 " : " ", big_relocs & 2 ? "PERELHI0" : ""); } } |
soimport表示导入表,如果存在则处理导入表部分,并根据是否按序号导入决定使用不同的加载器。
sorelocs表示导出表,如果存在则处理导出表部分,用于程序加载到内存中后修正地址。
都是对加载器执行初始化和处理逻辑。
4.其他加载部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | if (use_dep_hack) addLoader( "PEDEPHAK" ); / / NEW: TLS callback support PART 1 , the callback handler installation - Stefan Widmann if (use_tls_callbacks) addLoader( "PETLSC" ); addLoader( "PEMAIN20" ); if (use_clear_dirty_stack) addLoader( "CLEARSTACK" ); addLoader( "PEMAIN21" ); if (ih.entry && isdll) addLoader( "PEISDLL9" ); if (isefi) addLoader( "PEISEFI9" ); addLoader(ih.entry || !ilinker ? "PEDOJUMP" : "PERETURN" ); / / NEW: TLS callback support PART 2 , the callback handler - Stefan Widmann if (use_tls_callbacks) addLoader( "PETLSC2" ); addLoader( "IDENTSTR,UPX1HEAD" ); |
在加载器中添加与依赖处理、TLS(线程局部存储)回调和程序执行相关的指令。分别有:
1)检查是否使用依赖处理黑客技术
2)检查是否启用 TLS 回调
3)添加指令 "PEMAIN20",标志着主程序的某一阶段
4)检查是否需要清理脏堆栈
5)添加指令 "PEMAIN21",继续主程序的执行流程
6)检测入口点且是 DLL 文件
7)检测 EFI 文件
8)检测入口点 (ih.entry) 或者没有链接器 (!ilinker),则添加指令 "PEDOJUMP";否则添加指令 "PERETURN"
9)再次检查是否启用 TLS 回调
10)添加指令 "IDENTSTR,UPX1HEAD",用于标识字符串和 UPX 压缩相关的处理。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
赞赏
- [原创]UPX加载逻辑的处理细节分析 6494
- dll文件判断方法——UPX加壳器中isdll变量 8110
- UPX代码buildloader函数分析 2121