首页
社区
课程
招聘
[原创]C语言改造一个壳程序
发表于: 2013-6-27 14:59 3767

[原创]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完一遍,第一个感觉就是各种大小修正,各种计算公式(部分):
//映像大小修正
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;


注意,两个函数中变量数量和排列顺序保持一致,那么程序运行过程中,引用的地址位置才准确。而且有一个好处,第一段已经赋值过的函数指针等,第二段不用再进行赋值,可以直接使用。

[课程]Linux pwn 探索篇!

收藏
免费 5
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//