-
-
[原创]C语言改造一个壳程序
-
发表于: 2013-6-27 14:59 3767
-
最近在逆向一个游戏的时候,碰到了加壳的。壳,“知其然,不知其所以然”。先找个简单的来瞧瞧吧,刚好加密解密(第三版)里有一个。
好好认识了一下(压缩壳)加壳的流程
//读取文件内容
(1)打开源文件读取DOS头,读取PE头到局部变量空间
(2)文件对和节对齐大小等,根据节对齐大小修正映像大小并申请空间装映像
(3)读取文件头(DOS + NT Header)到新内存空间,修正文件头大小
(4)定位节表头,按照文件和节对齐大小修正节的大小,并检查最后一个节大小
(5)读取各节的内容到新的内存空间,修正可选头中的映像大小
(6)提取额外数据
//处理文件内容
(1)保存重定位表
(2)按照一定的文件格式处理输入表(所需空间大小通过MoveImpTable函数跑一遍得到)
(3)提取运行时需要的资源
(4)合并区段(貌似只是把节表头给抹了,抹掉的节的大小加到第一个节里面)
(5)压缩各个节
1、清除IAT目录信息
2、重新计算文件头大小(最后一个节表地址(考虑增加一个节) - 映像基址),用文件对齐大小修正,并修正第一个节的RAW地址
3、写回各种修正后的文件头到源文件中
4、写回各个节数据;重新计算节大小(扣除数据尾部的零数据);判断当前节是否可以压缩,执行压缩,写回压缩数据(紧接着文件头),填充对齐零数据,按文件对齐大小修正节大小,保存压缩前节的信息;对于可压缩资源区段,先获取资源目录大小,写入资源目录,压缩资源,写回压缩后的数据,修正资源节大小并填充零数据,记录压缩前节的信息;对于不可区段,直接保存数据,修正下一个节的起始偏移地址,循环执行
//写回文件内容
(1)拼装外壳段,外壳分两段,对于第二段进行压缩;在外壳段中保存各种信息,同时修正文件头的导入表和TLS表,使其指向外壳段对应位置,修两表的各种参数,
(2)添加一个新节,设置节表相关变量的值,按文件对齐大小修正节大小,按节对齐大小修正虚拟大小,修改文件头
(3)若是DLL,则把重定位表指向外壳段的虚构重定位表
(4)写入外壳段,末尾填充零数据
(5)定位源文件末尾,写入额外数据
//壳运行流程
(1)外壳第一段代码
1、从导入表获取GetModuleHandleA、GetProcAddress、VirtualAlloc函数的函数地址
2、申请一块内存,用于解压外壳第二段代码
3、解压外壳第二段代码,构造jmp xxxx跳到第二段代码执行
(2)外壳第二段代码
1、获取LoadLibraryA、aP_Depack函数地址
2、解压缩各节,解压后写到源文件对应位置
3、回复原文件的输入表
4、若有重定位数据,则修正重定位数据
5、准备返回OEP
Copy完文件处理部分,嗯都是C语言的,还比较好理解,到了外壳部分,汇编写的,先瞧瞧,理解得比较慢。看完后,感觉那个汇编写的壳代码,应该也可以整成C的吧,“那导入表,那重定位表和那自定义数据表,不就是一个个结构体吗?程序运行的时候,都有个堆栈啥的,把自定义数据表项弄到堆栈上……”……搞起!
首先,外壳代码在文件加壳过程是不被执行的,而且需要计算代码长度。在win32 asm中,代码的LABEL 可以作为变量直接进行计算(计算代码长度用的),而内联的汇编的 代码中插入的LABEL不能作为变量直接参与计算。所以就这么写:
上述函数执行,跳过外壳代码,只进行外壳代码计算,并返回结果。计算代码长度,解决了,之前还想到一个方法,就是在代码头和尾部插入特定的代码序列,通过扫描代码序列,来计算代码长度。
我们知道,函数内的一般的局部变量编译后都会翻译成[esp + xxx] 或者 [ebp + xxx],相当在代码中的一个占位符。由于外壳代码段一,和代码段2之间还有其他数据,中间的间隔不要预留,所以只能把这两段代码写到两个函数里面。在汇编代码中我们看到,两段代码是通过jmp xxx过度的,只要ESP 和 EBP 没变,两段代码相当于在一个函数里面执行。
这么写:
注意,两个函数中变量数量和排列顺序保持一致,那么程序运行过程中,引用的地址位置才准确。而且有一个好处,第一段已经赋值过的函数指针等,第二段不用再进行赋值,可以直接使用。
好好认识了一下(压缩壳)加壳的流程
//读取文件内容
(1)打开源文件读取DOS头,读取PE头到局部变量空间
(2)文件对和节对齐大小等,根据节对齐大小修正映像大小并申请空间装映像
(3)读取文件头(DOS + NT Header)到新内存空间,修正文件头大小
(4)定位节表头,按照文件和节对齐大小修正节的大小,并检查最后一个节大小
(5)读取各节的内容到新的内存空间,修正可选头中的映像大小
(6)提取额外数据
//处理文件内容
(1)保存重定位表
(2)按照一定的文件格式处理输入表(所需空间大小通过MoveImpTable函数跑一遍得到)
(3)提取运行时需要的资源
(4)合并区段(貌似只是把节表头给抹了,抹掉的节的大小加到第一个节里面)
(5)压缩各个节
1、清除IAT目录信息
2、重新计算文件头大小(最后一个节表地址(考虑增加一个节) - 映像基址),用文件对齐大小修正,并修正第一个节的RAW地址
3、写回各种修正后的文件头到源文件中
4、写回各个节数据;重新计算节大小(扣除数据尾部的零数据);判断当前节是否可以压缩,执行压缩,写回压缩数据(紧接着文件头),填充对齐零数据,按文件对齐大小修正节大小,保存压缩前节的信息;对于可压缩资源区段,先获取资源目录大小,写入资源目录,压缩资源,写回压缩后的数据,修正资源节大小并填充零数据,记录压缩前节的信息;对于不可区段,直接保存数据,修正下一个节的起始偏移地址,循环执行
//写回文件内容
(1)拼装外壳段,外壳分两段,对于第二段进行压缩;在外壳段中保存各种信息,同时修正文件头的导入表和TLS表,使其指向外壳段对应位置,修两表的各种参数,
(2)添加一个新节,设置节表相关变量的值,按文件对齐大小修正节大小,按节对齐大小修正虚拟大小,修改文件头
(3)若是DLL,则把重定位表指向外壳段的虚构重定位表
(4)写入外壳段,末尾填充零数据
(5)定位源文件末尾,写入额外数据
//壳运行流程
(1)外壳第一段代码
1、从导入表获取GetModuleHandleA、GetProcAddress、VirtualAlloc函数的函数地址
2、申请一块内存,用于解压外壳第二段代码
3、解压外壳第二段代码,构造jmp xxxx跳到第二段代码执行
(2)外壳第二段代码
1、获取LoadLibraryA、aP_Depack函数地址
2、解压缩各节,解压后写到源文件对应位置
3、回复原文件的输入表
4、若有重定位数据,则修正重定位数据
5、准备返回OEP
代码Copy完一遍,第一个感觉就是各种大小修正,各种计算公式(部分): //映像大小修正 ImageSize = AlignSize(nImage,nSectionAlign); //映像大小计算(psececHeader 最后一个节表指针) ImageSize = psecHeader->VirtualAddress + psecHeader->Misc.VirtualSize; //定位NTHeader pNtHeader = ImageBase + DosHeader.e_lfanew; //定位NtHeader.OptionalHeader数据目录的数据 pTarget = m_pImageBase + pDataDir->VirtualAddress; //定位节表 pSecHeader = m_pNtHeader + NtHeader.FileHeader.SizeOfOptionalHeader; //节表数据起始地址 psecHeader[1].PointerToRawData = psecHeader->PointerToRawData + psecHeader->SizeOfRawData; //额外数据大小计算 nMapOfSDataSize = nFileSize - (psecHeader->PointerToRawData + psecHeader->SizeOfRawData); //NtHeader大小计算 nNtHeaderSize = sizeof(NtHeader.FileHeader) + sizeof(NtHeader.Signature) + NtHeader.FileHeader.SizeOfOptionalHeader; //节表相关大小修正 psecHeader->SizeOfRawData = AlignSize(nRawDataSize,nFileAlign); psecHeader->Misc.VirtualSize= AlignSize(nVirtualSize,nSectionAlign); //节表大小修正后,检测最后一个节大小的处理 if(nIndex == nSectionNum - 1 && psecHeader->VirtualAddress + psecHeader->SizeOfRawData > m_nImageSize){ psecHeader->SizeOfRawData = m_nImageSize - psecHeader->VirtualAddress; }
Copy完文件处理部分,嗯都是C语言的,还比较好理解,到了外壳部分,汇编写的,先瞧瞧,理解得比较慢。看完后,感觉那个汇编写的壳代码,应该也可以整成C的吧,“那导入表,那重定位表和那自定义数据表,不就是一个个结构体吗?程序运行的时候,都有个堆栈啥的,把自定义数据表项弄到堆栈上……”……搞起!
首先,外壳代码在文件加壳过程是不被执行的,而且需要计算代码长度。在win32 asm中,代码的LABEL 可以作为变量直接进行计算(计算代码长度用的),而内联的汇编的 代码中插入的LABEL不能作为变量直接参与计算。所以就这么写:
//关于代码长度计算 void FirstShellFunction0(PDWORD StartAddr,PULONG length_code) { if(0){ _asm{ ShellStart0: …… ShellEnd0: nop } } if(StartAddr && length_code){ _asm{ lea eax,ShellEnd0 lea ebx,ShellStart0 mov ecx,StartAddr mov [ecx],ebx sub eax,ebx mov ecx,length_code mov [ecx],eax } } }
上述函数执行,跳过外壳代码,只进行外壳代码计算,并返回结果。计算代码长度,解决了,之前还想到一个方法,就是在代码头和尾部插入特定的代码序列,通过扫描代码序列,来计算代码长度。
我们知道,函数内的一般的局部变量编译后都会翻译成[esp + xxx] 或者 [ebp + xxx],相当在代码中的一个占位符。由于外壳代码段一,和代码段2之间还有其他数据,中间的间隔不要预留,所以只能把这两段代码写到两个函数里面。在汇编代码中我们看到,两段代码是通过jmp xxx过度的,只要ESP 和 EBP 没变,两段代码相当于在一个函数里面执行。
这么写:
//关于两段代码使用同一局部变量 void FirstShellFunction1(PDWORD StartAddr,PULONG length_code) { 函数一.变量1; 函数一.变量2; …… 函数二.变量1; 函数二.变量2; if(0){ _asm{ jmp secondFunctionCode } } } void SecondShellFunction(PDWORD StartAddr,PULONG length_code) { 函数一.变量1; 函数一.变量2; …… 函数二.变量1; 函数二.变量2; …… if(0){ _asm{ push OEP retn } } } //各种指针 volatile PDEFINE_IMPORT_TABLE pImportTable = NULL; volatile PDEFINE_RELOCBASE_TABLE pRelocTable = NULL; PDEFINE_SHELLDATA_0 pData0 = NULL; PDEFINE_SHELLDATA_1 pData1 = NULL; //临时变量 ULONG Temp1 = NULL; ULONG Temp2 = NULL; ULONG Temp3 = NULL; ULONG Temp4 = NULL; ULONG Temp5 = NULL; //循环变量 ULONG i = NULL; //函数指针 PGETMODULEHANDLEA D_GetModuleHandleA = NULL; PGETPROCADDRESS D_GetProcAddress = NULL; PLOADLIBRARYA D_LoadLibraryA = NULL; PVIRTUALALLOC D_VirtualAlloc = NULL; PAPDEPACK D_aP_Depack = NULL; PVIRTUALFREE D_VirtualFree = NULL;
注意,两个函数中变量数量和排列顺序保持一致,那么程序运行过程中,引用的地址位置才准确。而且有一个好处,第一段已经赋值过的函数指针等,第二段不用再进行赋值,可以直接使用。
赞赏记录
参与人
雪币
留言
时间
心游尘世外
为你点赞~
2024-5-31 05:26
QinBeast
为你点赞~
2024-5-31 05:18
飘零丶
为你点赞~
2024-4-3 00:35
shinratensei
为你点赞~
2024-2-13 00:39
一笑人间万事
为你点赞~
2023-3-7 00:31
赞赏
他的文章
- [原创]简单分析R3磁盘格式化的函数 7964
- [原创]C语言改造一个壳程序 19956
- [原创]C语言改造一个壳程序 3768
- [求助]用OD打开某加壳软件,OD自动退出,求大概 4527
看原图
赞赏
雪币:
留言: