加壳学习系列(二)-壳代码

上一节写到了,壳代码是写在shell.dll里的(看雪的大佬写的)。所以分析dll文件就很有必要。

一.Shell.dll的源码和二进制文件#ifdef SHELL_EXPORTS #define SHELL_API __declspec(dllexport) #else #define SHELL_API __declspec(dllimport) #endif #include<Windows.h> //导出ShellData结构体 extern"C" typedef struct _SHELL_DATA { DWORD dwStartFun; //启动函数 DWORD dwPEOEP; //程序入口点 DWORD dwXorKey; //解密KEY DWORD dwCodeBase; //代码段起始地址 DWORD dwXorSize; //代码段加密大小 DWORD dwPEImageBase; //PE文件映像基址 IMAGE_DATA_DIRECTORY stcPERelocDir; //重定位表信息 IMAGE_DATA_DIRECTORY stcPEImportDir; //导入表信息 DWORD dwIATSectionBase; //IAT所在段基址 DWORD dwIATSectionSize; //IAT所在段大小 BOOL bIsShowMesBox; //是否显示MessageBox }SHELL_DATA, *PSHELL_DATA; //导出ShellData结构体变量 extern"C" SHELL_API SHELL_DATA g_stcShellData; //Shell部分用到的函数的类型定义,均用函数指针类型表示 typedef DWORD(WINAPI *fnGetProcAddress)(_In_ HMODULE hModule, _In_ LPCSTR lpProcName); typedef HMODULE(WINAPI *fnLoadLibraryA)(_In_ LPCSTR lpLibFileName); typedef HMODULE(WINAPI *fnGetModuleHandleA)(_In_opt_ LPCSTR lpModuleName); typedef BOOL(WINAPI *fnVirtualProtect)(_In_ LPVOID lpAddress, _In_ SIZE_T dwSize, _In_ DWORD flNewProtect, _Out_ PDWORD lpflOldProtect); typedef LPVOID(WINAPI *fnVirtualAlloc)(_In_opt_ LPVOID lpAddress, _In_ SIZE_T dwSize, _In_ DWORD flAllocationType, _In_ DWORD flProtect); typedef void(WINAPI *fnExitProcess)(_In_ UINT uExitCode); typedef int(WINAPI *fnMessageBox)(HWND hWnd, LPSTR lpText, LPSTR lpCaption, UINT uType);

shell.h中导出了变量g_stcShellDatafnGetProcAddressfnLoadLibraryA等相当于自定义的类型。

shell.cpp

这段有点长,是完整代码。觉得又臭又长的话可以先跳到下面一个部分。

#include"Shell.h"  #pragma comment(linker, "/merge:.data=.text")  #pragma comment(linker, "/merge:.rdata=.text") #pragma comment(linker, "/section:.text,RWE")  //函数和变量的声明 DWORD MyGetProcAddress();		//自定义GetProcAddress HMODULE	GetKernel32Addr();		//获取Kernel32加载基址 void Start();					//启动函数(Shell部分的入口函数) void InitFun();					//初始化函数指针和变量 void DeXorCode();				//解密操作 void RecReloc();				//修复重定位操作 void RecIAT();					//修复IAT操作 SHELL_DATA g_stcShellData = { (DWORD)Start }; // 注意这里,首先将g_stcShellData ->dwStartFun设为start()函数的地址 //Shell用到的全局变量结构体 DWORD dwImageBase = 0;		//整个程序的镜像基址 DWORD dwPEOEP = 0;		//PE文件的OEP  //Shell部分用到的函数指针定义 fnGetProcAddress	g_pfnGetProcAddress = NULL; fnLoadLibraryA		g_pfnLoadLibraryA = NULL; fnGetModuleHandleA	g_pfnGetModuleHandleA = NULL; fnVirtualProtect	g_pfnVirtualProtect = NULL; fnVirtualAlloc		g_pfnVirtualAlloc = NULL; fnExitProcess		g_pfnExitProcess = NULL; fnMessageBox		g_pfnMessageBoxA = NULL;  //************************************************************ // 函数名称:	Start // 函数说明:	启动函数(Shell部分的入口函数) // 作	者:	cyxvc // 时	间:	2015/12/28 // 返 回	值:	void //************************************************************  //__declspec(naked)是编译器直接拿来用的汇编函数代码,所以一定要记得在开始的// 时候保存上下文标志位(压栈),在结束的时候要记得恢复上下文(出栈)。 __declspec(naked) void Start() { 	__asm pushad  	InitFun();  	DeXorCode();  	if (g_stcShellData.stcPERelocDir.VirtualAddress) 	{ 		RecReloc(); 	} 	RecIAT();  	if (g_stcShellData.bIsShowMesBox) 	{ 		g_pfnMessageBoxA(0, "欢迎使用CyxvcProtect, by 15PB !", "Hello!", 0); 	}  	__asm popad  	//获取OEP信息 	dwPEOEP = g_stcShellData.dwPEOEP + dwImageBase; 	__asm jmp dwPEOEP  	g_pfnExitProcess(0);	//实际不会执行此条指令 }  //************************************************************ // 函数名称:	RecIAT // 函数说明:	修复IAT操作 // 作	者:	cyxvc // 时	间:	2015/12/28 // 返 回	值:	void //************************************************************ void RecIAT() { 	//1.获取导入表结构体指针 	PIMAGE_IMPORT_DESCRIPTOR pPEImport = 		(PIMAGE_IMPORT_DESCRIPTOR)(dwImageBase + g_stcShellData.stcPEImportDir.VirtualAddress);  	//2.修改内存属性为可写 	DWORD dwOldProtect = 0; 	g_pfnVirtualProtect( 		(LPBYTE)(dwImageBase + g_stcShellData.dwIATSectionBase), g_stcShellData.dwIATSectionSize, 		PAGE_EXECUTE_READWRITE, &dwOldProtect);  	//3.开始修复IAT 	while (pPEImport->Name) 	{ 		//获取模块名 		DWORD dwModNameRVA = pPEImport->Name; 		char* pModName = (char*)(dwImageBase + dwModNameRVA); 		HMODULE hMod = g_pfnLoadLibraryA(pModName);  		//获取IAT信息(有些PE文件INT是空的,最好用IAT解析,也可两个都解析作对比) 		PIMAGE_THUNK_DATA pIAT = (PIMAGE_THUNK_DATA)(dwImageBase + pPEImport->FirstThunk);  		//获取INT信息(同IAT一样,可将INT看作是IAT的一个备份) 		//PIMAGE_THUNK_DATA pINT = (PIMAGE_THUNK_DATA)(dwImageBase + pPEImport->OriginalFirstThunk);  		//通过IAT循环获取该模块下的所有函数信息(这里之获取了函数名) 		while (pIAT->u1.AddressOfData) 		{ 			//判断是输出函数名还是序号 			if (IMAGE_SNAP_BY_ORDINAL(pIAT->u1.Ordinal)) 			{ 				//输出序号 				DWORD dwFunOrdinal = (pIAT->u1.Ordinal) & 0x7FFFFFFF; 				DWORD dwFunAddr = g_pfnGetProcAddress(hMod, (char*)dwFunOrdinal); 				*(DWORD*)pIAT = (DWORD)dwFunAddr; 			} 			else 			{ 				//输出函数名 				DWORD dwFunNameRVA = pIAT->u1.AddressOfData; 				PIMAGE_IMPORT_BY_NAME pstcFunName = (PIMAGE_IMPORT_BY_NAME)(dwImageBase + dwFunNameRVA); 				DWORD dwFunAddr = g_pfnGetProcAddress(hMod, pstcFunName->Name); 				*(DWORD*)pIAT = (DWORD)dwFunAddr; 			} 			pIAT++; 		} 		//遍历下一个模块 		pPEImport++; 	}  	//4.恢复内存属性 	g_pfnVirtualProtect( 		(LPBYTE)(dwImageBase + g_stcShellData.dwIATSectionBase), g_stcShellData.dwIATSectionSize, 		dwOldProtect, &dwOldProtect); }  //************************************************************ // 函数名称:	RecReloc // 函数说明:	修复重定位操作 // 作	者:	cyxvc // 时	间:	2015/12/28 // 返 回	值:	void //************************************************************ void RecReloc() { 	typedef struct _TYPEOFFSET 	{ 		WORD offset : 12;		//偏移值 		WORD Type : 4;			//重定位属性(方式) 	}TYPEOFFSET, *PTYPEOFFSET;  	//1.获取重定位表结构体指针 	PIMAGE_BASE_RELOCATION	pPEReloc = 		(PIMAGE_BASE_RELOCATION)(dwImageBase + g_stcShellData.stcPERelocDir.VirtualAddress);  	//2.开始修复重定位 	while (pPEReloc->VirtualAddress) 	{ 		//2.1修改内存属性为可写 		DWORD dwOldProtect = 0; 		g_pfnVirtualProtect((PBYTE)dwImageBase + pPEReloc->VirtualAddress, 			0x1000, PAGE_EXECUTE_READWRITE, &dwOldProtect);  		//2.2修复重定位 		PTYPEOFFSET pTypeOffset = (PTYPEOFFSET)(pPEReloc + 1); 		DWORD dwNumber = (pPEReloc->SizeOfBlock - 8) / 2; 		for (DWORD i = 0; i < dwNumber; i++) 		{ 			if (*(PWORD)(&pTypeOffset[i]) == NULL) 				break; 			//RVA 			DWORD dwRVA = pTypeOffset[i].offset + pPEReloc->VirtualAddress; 			//FAR地址 			DWORD AddrOfNeedReloc = *(PDWORD)((DWORD)dwImageBase + dwRVA); 			*(PDWORD)((DWORD)dwImageBase + dwRVA) = 				AddrOfNeedReloc - g_stcShellData.dwPEImageBase + dwImageBase; 		}  		//2.3恢复内存属性 		g_pfnVirtualProtect((PBYTE)dwImageBase + pPEReloc->VirtualAddress, 			0x1000, dwOldProtect, &dwOldProtect);  		//2.4修复下一个区段 		pPEReloc = (PIMAGE_BASE_RELOCATION)((DWORD)pPEReloc + pPEReloc->SizeOfBlock); 	} }   //************************************************************ // 函数名称:	DeXorCode // 函数说明:	解密操作 // 作	者:	cyxvc // 时	间:	2015/12/28 // 返 回	值:	void //************************************************************ void DeXorCode() { 	PBYTE pCodeBase = (PBYTE)g_stcShellData.dwCodeBase + dwImageBase;  	DWORD dwOldProtect = 0; 	g_pfnVirtualProtect(pCodeBase, g_stcShellData.dwXorSize, PAGE_EXECUTE_READWRITE, &dwOldProtect);  	for (DWORD i = 0; i < g_stcShellData.dwXorSize; i++) 	{ 		pCodeBase[i] ^= i; 	}  	g_pfnVirtualProtect(pCodeBase, g_stcShellData.dwXorSize, dwOldProtect, &dwOldProtect); }  //************************************************************ // 函数名称:	InitFun // 函数说明:	初始化函数指针和变量 // 作	者:	cyxvc // 时	间:	2015/12/28 // 返 回	值:	void //************************************************************ void InitFun() { 	//从Kenel32中获取函数 	HMODULE hKernel32 = GetKernel32Addr(); 	g_pfnGetProcAddress = (fnGetProcAddress)MyGetProcAddress(); 	g_pfnLoadLibraryA = (fnLoadLibraryA)g_pfnGetProcAddress(hKernel32, "LoadLibraryA"); 	g_pfnGetModuleHandleA = (fnGetModuleHandleA)g_pfnGetProcAddress(hKernel32, "GetModuleHandleA"); 	g_pfnVirtualProtect = (fnVirtualProtect)g_pfnGetProcAddress(hKernel32, "VirtualProtect"); 	g_pfnExitProcess = (fnExitProcess)g_pfnGetProcAddress(hKernel32, "ExitProcess"); 	g_pfnVirtualAlloc = (fnVirtualAlloc)g_pfnGetProcAddress(hKernel32, "VirtualAlloc");  	//从user32中获取函数 	HMODULE hUser32 = g_pfnLoadLibraryA("user32.dll"); 	g_pfnMessageBoxA = (fnMessageBox)g_pfnGetProcAddress(hUser32, "MessageBoxA");  	//初始化镜像基址 	dwImageBase = (DWORD)g_pfnGetModuleHandleA(NULL); }   //************************************************************ // 函数名称:	GetKernel32Addr // 函数说明:	获取Kernel32加载基址 // 作	者:	cyxvc // 时	间:	2015/12/28 // 返 回	值:	HMODULE //************************************************************ HMODULE GetKernel32Addr() { 	HMODULE dwKernel32Addr = 0; 	__asm 	{ 		push eax 			mov eax, dword ptr fs : [0x30]   // eax = PEB的地址 			mov eax, [eax + 0x0C]            // eax = 指向PEB_LDR_DATA结构的指针 			mov eax, [eax + 0x1C]            // eax = 模块初始化链表的头指针InInitializationOrderModuleList 			mov eax, [eax]                   // eax = 列表中的第二个条目 			mov eax, [eax]                   // eax = 列表中的第三个条目 			mov eax, [eax + 0x08]            // eax = 获取到的Kernel32.dll基址(Win7下第三个条目是Kernel32.dll) 			mov dwKernel32Addr, eax 			pop eax 	} 	return dwKernel32Addr; }   //************************************************************ // 函数名称:	MyGetProcAddress // 函数说明:	自定义GetProcAddress // 作	者:	cyxvc // 时	间:	2015/12/28 // 返 回	值:	DWORD //************************************************************ DWORD MyGetProcAddress() { 	HMODULE hModule = GetKernel32Addr();  	//1.获取DOS头 	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)(PBYTE)hModule; 	//2.获取NT头 	PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((PBYTE)hModule + pDosHeader->e_lfanew); 	//3.获取导出表的结构体指针 	PIMAGE_DATA_DIRECTORY pExportDir = 		&(pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]);  	PIMAGE_EXPORT_DIRECTORY pExport = 		(PIMAGE_EXPORT_DIRECTORY)((PBYTE)hModule + pExportDir->VirtualAddress);  	//EAT 	PDWORD pEAT = (PDWORD)((DWORD)hModule + pExport->AddressOfFunctions); 	//ENT 	PDWORD pENT = (PDWORD)((DWORD)hModule + pExport->AddressOfNames); 	//EIT 	PWORD pEIT = (PWORD)((DWORD)hModule + pExport->AddressOfNameOrdinals);  	//4.遍历导出表,获取GetProcAddress()函数地址 	DWORD dwNumofFun = pExport->NumberOfFunctions; 	DWORD dwNumofName = pExport->NumberOfNames; 	for (DWORD i = 0; i < dwNumofFun; i++) 	{ 		//如果为无效函数,跳过 		if (pEAT[i] == NULL) 			continue; 		//判断是以函数名导出还是以序号导出 		DWORD j = 0; 		for (; j < dwNumofName; j++) 		{ 			if (i == pEIT[j]) 			{ 				break; 			} 		} 		if (j != dwNumofName) 		{ 			//如果是函数名方式导出的 			//函数名 			char* ExpFunName = (CHAR*)((PBYTE)hModule + pENT[j]); 			//进行对比,如果正确返回地址 			if (!strcmp(ExpFunName, "GetProcAddress")) 			{ 				return pEAT[i] + pNtHeader->OptionalHeader.ImageBase; 			} 		} 		else 		{ 			//序号 		} 	} 	return 0; } 

start函数

关于shell.cpp,首先注意最上面几行中SHELL_DATA g_stcShellData = { (DWORD)Start }; 这里将导出变量g_stcShellDatadwStartFun成员变量设为start()函数的地址。

其次是start函数

//************************************************************ // 函数名称:	Start // 函数说明:	启动函数(Shell部分的入口函数) // 作	者:	cyxvc // 时	间:	2015/12/28 // 返 回	值:	void //************************************************************  //__declspec(naked)是编译器直接拿来用的汇编函数代码,所以一定要记得在开始的// 时候保存上下文标志位(压栈),在结束的时候要记得恢复上下文(出栈)。 __declspec(naked) void Start() { 	__asm pushad  	InitFun();  	DeXorCode();  	if (g_stcShellData.stcPERelocDir.VirtualAddress) 	{ 		RecReloc(); 	} 	RecIAT();  	if (g_stcShellData.bIsShowMesBox) 	{ 		g_pfnMessageBoxA(0, "欢迎使用CyxvcProtect, by 15PB !", "Hello!", 0); 	}  	__asm popad  	//获取OEP信息 	dwPEOEP = g_stcShellData.dwPEOEP + dwImageBase; 	__asm jmp dwPEOEP  	g_pfnExitProcess(0);	//实际不会执行此条指令 } 

start函数是壳代码的入口函数,那它什么时候执行呢?就是把目标程序(Test.exe)被加壳之后。双击Test.exe,首先执行start函数,再执行原Test.exe的入口程序。

InitFun();为初始化函数,初始化些变量,有的已经被加壳器设置好了。DoXorCode定义如下:

void DeXorCode() { 	PBYTE pCodeBase = (PBYTE)g_stcShellData.dwCodeBase + dwImageBase; //获取被加壳代码代码段地址,示例代码中的计算出来为 0x401000  	DWORD dwOldProtect = 0; 	// g_pfnVirtualProtect为kernel32.dll的VirtualProtect函数 	g_pfnVirtualProtect(pCodeBase, g_stcShellData.dwXorSize, PAGE_EXECUTE_READWRITE, &dwOldProtect);  	for (DWORD i = 0; i < g_stcShellData.dwXorSize; i++) 	{ 		pCodeBase[i] ^= i; 	}  	g_pfnVirtualProtect(pCodeBase, g_stcShellData.dwXorSize, dwOldProtect, &dwOldProtect); } 

执行shell中的代码时,原始代码已经被加壳了,所以需要先脱壳。g_stcShellData.dwXorSize为加壳器设置好的变量,即代码段长度,示例中的为0xA00。PAGE_EXECUTE_READWRITE(0x40)表示修改脱壳后的代码段可读可写可执行。

RecReloc();表示修复重定位表,有个if说明不一定执行,这个不太明白,先放着。

RecIAT为修复IAT表,执行过程如下

void RecIAT() { 	//1.获取导入表结构体指针 	PIMAGE_IMPORT_DESCRIPTOR pPEImport = 		(PIMAGE_IMPORT_DESCRIPTOR)(dwImageBase + g_stcShellData.stcPEImportDir.VirtualAddress); // 0x402244  	//2.修改内存属性为可写(IAT所在区段) 	DWORD dwOldProtect = 0; 	g_pfnVirtualProtect( 		(LPBYTE)(dwImageBase + g_stcShellData.dwIATSectionBase), g_stcShellData.dwIATSectionSize, 		PAGE_EXECUTE_READWRITE, &dwOldProtect);  	//3.开始修复IAT 	while (pPEImport->Name) 	{ 		//获取模块名 		DWORD dwModNameRVA = pPEImport->Name; 		char* pModName = (char*)(dwImageBase + dwModNameRVA); 		HMODULE hMod = g_pfnLoadLibraryA(pModName);  		//获取IAT信息(有些PE文件INT是空的,最好用IAT解析,也可两个都解析作对比) 		PIMAGE_THUNK_DATA pIAT = (PIMAGE_THUNK_DATA)(dwImageBase + pPEImport->FirstThunk);  		//获取INT信息(同IAT一样,可将INT看作是IAT的一个备份) 		//PIMAGE_THUNK_DATA pINT = (PIMAGE_THUNK_DATA)(dwImageBase + pPEImport->OriginalFirstThunk);  		//通过IAT循环获取该模块下的所有函数信息(这里之获取了函数名) 		while (pIAT->u1.AddressOfData) 		{ 			//判断是输出函数名还是序号 			if (IMAGE_SNAP_BY_ORDINAL(pIAT->u1.Ordinal)) 			{ 				//输出序号 				DWORD dwFunOrdinal = (pIAT->u1.Ordinal) & 0x7FFFFFFF; 				DWORD dwFunAddr = g_pfnGetProcAddress(hMod, (char*)dwFunOrdinal); 				*(DWORD*)pIAT = (DWORD)dwFunAddr; 			} 			else 			{ 				//输出函数名 				DWORD dwFunNameRVA = pIAT->u1.AddressOfData; 				PIMAGE_IMPORT_BY_NAME pstcFunName = (PIMAGE_IMPORT_BY_NAME)(dwImageBase + dwFunNameRVA); 				DWORD dwFunAddr = g_pfnGetProcAddress(hMod, pstcFunName->Name); 				*(DWORD*)pIAT = (DWORD)dwFunAddr; 			} 			pIAT++; 		} 		//遍历下一个模块 		pPEImport++; 	}  	//4.恢复内存属性 	g_pfnVirtualProtect( 		(LPBYTE)(dwImageBase + g_stcShellData.dwIATSectionBase), g_stcShellData.dwIATSectionSize, 		dwOldProtect, &dwOldProtect); } 

示例代码IAT表结构体信息如下,其VirtualAddress为0x2244
加壳学习系列(二)-壳代码
加壳学习系列(二)-壳代码
其中,g_stcShellData.dwIATSectionBase为IAT所在区段的起始,示例代码有.text, .rdata, .data等5个区段,IAT base为0x2244,.rdata为0x2000, .data为0x3000。所以IAT在.rdata段。经过一系列操作(中间还没太弄懂),最后需要恢复内存属性不可执行。

然后start函数输出"欢迎使用CyxvcProtect, by 15PB !", "Hello!"表示执行了壳代码,之后便用__asm jmp dwPEOEP跳到原先示例代码的OEP。

1.2 加壳器代码

加壳器代码太多了,就选几个重点的

入口函数

BOOL CPACK::Pack(CString strFilePath, BOOL bIsShowMesBox) { 	//1.读取PE文件信息并保存 	CPE objPE; 	if (objPE.InitPE(strFilePath) == FALSE) 		return FALSE;  	//2.加密代码段操作 	DWORD dwXorSize = 0; 	dwXorSize=objPE.XorCode(0x15);  	//3.将必要的信息保存到Shell 	HMODULE hShell = LoadLibrary(L"Shell.dll"); 	if (hShell == NULL) 	{ 		MessageBox(NULL, _T("加载Shell.dll模块失败,请确保程序的完整性!"), _T("提示"), MB_OK); 		//释放资源 		delete[] objPE.m_pFileBuf; 		return FALSE; 	}  	PSHELL_DATA pstcShellData = (PSHELL_DATA)GetProcAddress(hShell, "g_stcShellData");  	pstcShellData->dwXorKey = 0x15; 	pstcShellData->dwCodeBase = objPE.m_dwCodeBase; 	pstcShellData->dwXorSize = dwXorSize; 	pstcShellData->dwPEOEP = objPE.m_dwPEOEP; 	pstcShellData->dwPEImageBase = objPE.m_dwImageBase; 	pstcShellData->stcPERelocDir = objPE.m_PERelocDir; 	pstcShellData->stcPEImportDir = objPE.m_PEImportDir; 	pstcShellData->dwIATSectionBase = objPE.m_IATSectionBase; 	pstcShellData->dwIATSectionSize = objPE.m_IATSectionSize; 	pstcShellData->bIsShowMesBox = bIsShowMesBox;  	//4.将Shell附加到PE文件 	//4.1.读取Shell代码 	MODULEINFO modinfo = { 0 }; 	GetModuleInformation(GetCurrentProcess(), hShell, &modinfo, sizeof(MODULEINFO)); 	PBYTE  pShellBuf = new BYTE[modinfo.SizeOfImage]; 	memcpy_s(pShellBuf, modinfo.SizeOfImage, hShell, modinfo.SizeOfImage); 	//4.2.设置Shell重定位信息 	objPE.SetShellReloc(pShellBuf, (DWORD)hShell);	 	//4.3.修改被加壳程序的OEP,指向Shell 	DWORD dwShellOEP = pstcShellData->dwStartFun - (DWORD)hShell; 	objPE.SetNewOEP(dwShellOEP); 	//4.4.合并PE文件和Shell的代码到新的缓冲区 	LPBYTE pFinalBuf = NULL; 	DWORD dwFinalBufSize = 0; 	objPE.MergeBuf(objPE.m_pFileBuf, objPE.m_dwImageSize, 		pShellBuf, modinfo.SizeOfImage,  		pFinalBuf, dwFinalBufSize);  	//5.保存文件(处理完成的缓冲区) 	SaveFinalFile(pFinalBuf, dwFinalBufSize, strFilePath); 	 	//6.释放资源 	delete[] objPE.m_pFileBuf; 	delete[] pShellBuf; 	delete[] pFinalBuf; 	objPE.InitValue();  	return TRUE; } 

InitPE负责读取PE基础信息。XorCode从代码段起始加密代码。之后就是从shell.dll加载壳代码,首先就是获取shell.dll中导出的结构体变量pstcShellData,并对其进行设置。之后就是一些设置和设置重定向信息(这部分不是很懂)。

InitPE

这里InitPE并不只是简单读取文件,而是将PE文件以内存映像的方式读取,也就是会创建更大的缓冲区来模拟内存分布,所以加密代码段时并不需要考虑磁盘文件分布。

BOOL CPE::InitPE(CString strFilePath) { 	//打开文件 	if (OpenPEFile(strFilePath) == FALSE) 		return FALSE;  	//将PE以文件分布格式读取到内存 	m_dwFileSize = GetFileSize(m_hFile, NULL); 	m_pFileBuf = new BYTE[m_dwFileSize]; 	DWORD ReadSize = 0; 	ReadFile(m_hFile, m_pFileBuf, m_dwFileSize, &ReadSize, NULL);	 	CloseHandle(m_hFile); 	m_hFile = NULL;  	//判断是否为PE文件 	if (IsPE() == FALSE) 		return FALSE;  	//将PE以内存分布格式读取到内存 	//修正没镜像大小没有对齐的情况 	m_dwImageSize = m_pNtHeader->OptionalHeader.SizeOfImage; 	m_dwMemAlign = m_pNtHeader->OptionalHeader.SectionAlignment; 	m_dwSizeOfHeader = m_pNtHeader->OptionalHeader.SizeOfHeaders; 	m_dwSectionNum = m_pNtHeader->FileHeader.NumberOfSections;  	if (m_dwImageSize % m_dwMemAlign) 		m_dwImageSize = (m_dwImageSize / m_dwMemAlign + 1) * m_dwMemAlign; 	LPBYTE pFileBuf_New = new BYTE[m_dwImageSize]; 	memset(pFileBuf_New, 0, m_dwImageSize); 	//拷贝文件头 	memcpy_s(pFileBuf_New, m_dwSizeOfHeader, m_pFileBuf, m_dwSizeOfHeader); 	//拷贝区段 	PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(m_pNtHeader); 	for (DWORD i = 0; i < m_dwSectionNum; i++, pSectionHeader++) 	{ 		memcpy_s(pFileBuf_New + pSectionHeader->VirtualAddress, 			pSectionHeader->SizeOfRawData, 			m_pFileBuf+pSectionHeader->PointerToRawData, 			pSectionHeader->SizeOfRawData); 	} 	delete[] m_pFileBuf; 	m_pFileBuf = pFileBuf_New; 	pFileBuf_New = NULL;  	//获取PE信息 	GetPEInfo(); 	 	return TRUE; } 

拷贝shell.dll中的壳代码

下面这段代码可以看出这是将整个shell.dll的代码拷贝出来

MODULEINFO modinfo = { 0 }; GetModuleInformation(GetCurrentProcess(), hShell, &modinfo, sizeof(MODULEINFO)); PBYTE  pShellBuf = new BYTE[modinfo.SizeOfImage]; memcpy_s(pShellBuf, modinfo.SizeOfImage, hShell, modinfo.SizeOfImage); 

之后就是将被加壳后程序的OEP设置成壳代码start函数相对于shell.dll的OEP。这里注意,这个objPE.SetNewOEP展开如下:

void CPE::SetNewOEP(DWORD dwShellOEP) { 	m_dwShellOEP = dwShellOEP + m_dwImageSize; 	m_pNtHeader->OptionalHeader.AddressOfEntryPoint = m_dwShellOEP; } 

也就是加上了原PE文件的Image Size。

MergeBuf

然后就是MergeBufpShellBuf为shell.dll内容的首地址,pShellBufSizeshellBuf的大小。

void CPE::MergeBuf(LPBYTE pFileBuf, DWORD pFileBufSize, 	LPBYTE pShellBuf, DWORD pShellBufSize,  	LPBYTE& pFinalBuf, DWORD& pFinalBufSize) { 	//获取最后一个区段的信息 	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pFileBuf; 	PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)(pFileBuf + pDosHeader->e_lfanew); 	PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(pNtHeader); 	PIMAGE_SECTION_HEADER pLastSection = 		&pSectionHeader[pNtHeader->FileHeader.NumberOfSections - 1];  	//1.修改区段数量 	pNtHeader->FileHeader.NumberOfSections += 1;  	//2.编辑区段表头结构体信息 	PIMAGE_SECTION_HEADER AddSectionHeader = 		&pSectionHeader[pNtHeader->FileHeader.NumberOfSections - 1]; 	memcpy_s(AddSectionHeader->Name, 8, ".cyxvc", 7);  	//VOffset(1000对齐) 	DWORD dwTemp = 0; 	dwTemp = (pLastSection->Misc.VirtualSize / m_dwMemAlign) * m_dwMemAlign; 	if (pLastSection->Misc.VirtualSize % m_dwMemAlign) 	{ 		dwTemp += 0x1000; 	} 	AddSectionHeader->VirtualAddress = pLastSection->VirtualAddress + dwTemp;  	//Vsize(实际添加的大小) 	AddSectionHeader->Misc.VirtualSize = pShellBufSize;  	//ROffset(旧文件的末尾) 	AddSectionHeader->PointerToRawData = pFileBufSize;  	//RSize(200对齐) 	dwTemp = (pShellBufSize / m_dwFileAlign) * m_dwFileAlign; 	if (pShellBufSize % m_dwFileAlign) 	{ 		dwTemp += m_dwFileAlign; 	} 	AddSectionHeader->SizeOfRawData = dwTemp;  	//标志 	AddSectionHeader->Characteristics = 0XE0000040;  	//3.修改PE头文件大小属性,增加文件大小 	dwTemp = (pShellBufSize / m_dwMemAlign) * m_dwMemAlign; 	if (pShellBufSize % m_dwMemAlign) 	{ 		dwTemp += m_dwMemAlign; 	} 	pNtHeader->OptionalHeader.SizeOfImage += dwTemp;   	//4.申请合并所需要的空间 	pFinalBuf = new BYTE[pFileBufSize + dwTemp]; 	pFinalBufSize = pFileBufSize + dwTemp; 	memset(pFinalBuf, 0, pFileBufSize + dwTemp); 	memcpy_s(pFinalBuf, pFileBufSize, pFileBuf, pFileBufSize); 	memcpy_s(pFinalBuf + pFileBufSize, dwTemp, pShellBuf, dwTemp); } 

这里面包括几个步骤

  • 在原先PE中将节的数量+1 pNtHeader->FileHeader.NumberOfSections += 1;
  • 设置好新的节数据区AddSectionHeader,名字叫.cyxvc
  • 将原PE文件头中文件大小加大,pNtHeader->OptionalHeader.SizeOfImage += dwTemp;
  • 将壳代码插入到PE文件尾部。

二.DLL基础

2.1 DllMain简介

跟exe有个main或者WinMain入口函数一样,DLL也有一个入口函数,就是DllMain。以“DllMain”为关键字。在MSDN的介绍中。The DllMain function is an optional method of entry into a dynamic-link library (DLL) 。也就是说,DllMain是可有可无的。

BOOL APIENTRY DllMain(HMODULE hModule,                         DWORD  ul_reason_for_call,                         LPVOID lpReserved)   {        return TRUE;   }   

2.2 何时调用DllMain

系统是在什么时候调用DllMain函数的呢?静态链接时,或动态链接时调用LoadLibraryFreeLibrary都会调用DllMain函数。DllMain的第二个参数ul_reason_for_call指明了系统调用Dll的原因,它可能有以下取值

  • DLL_PROCESS_ATTACH:1,当Dll被进程第一次调用时,导致DllMain函数被调用, 如果同一个进程后来再次调用此Dll时,操作系统只会增加Dll的使用次数,不会再用DLL_PROCESS_ATTACH调用DLL的DllMain函数。
  • DLL_PROCESS_DETACH: 0,当Dll被从进程的地址空间解除映射到进程地址空间中的所有Dll文件映像,并用值DLL_THREAD_ATTACH调用Dll的DllMain函数。
  • DLL_THREAD_DETACH:3,如果线程调用了ExitThread来结束线程(线程函数返回时,系统也会自动调用ExitThread),系统查看当前映射到进程空间中的所有Dll文件映像,并用DLL_THREAD_DETACH来调用DllMain函数。

2.3 Dll导出函数

这里用到了__declspec(dllexport)修饰符,但是shell.cpp里并没有导出函数,而是导出了一个结构体变量g_stcShellData

extern"C" SHELL_API SHELL_DATA g_stcShellData; 

三.加壳后的示例代码

3.1 010Editor

  • NT可选头:
    加壳学习系列(二)-壳代码可以看到AddressEntryPoint从0x1159变成了0x7230。SizeOfImage变成了0xC000,以前是0x6000。其它重要信息并无变化

  • 节表
    加壳学习系列(二)-壳代码可以看到多了一个cyxvc节。其它数据并无变化

加壳学习系列(二)-壳代码cyxvc的磁盘偏移,RVA,Size均为0x6000。

  • 代码段
    加壳学习系列(二)-壳代码 可以看到代码段数据全变0了。

  • 节区
    加壳学习系列(二)-壳代码跟之前相比多了一个cyxvc节,并且长度为0x6000(一个Image的长度)

3.2 IDA

start函数
加壳学习系列(二)-壳代码
这个start就是shell代码的start,也就是先执行壳代码
加壳学习系列(二)-壳代码4441的16进制为0x1159,也就是赋值操作是重新设置OEP,jump即跳到OEP。

执行效果和加壳前一模一样。下一节,我会来调试以下加壳程序和加壳后的test程序运行过程。

版权声明:玥玥 发表于 2021-07-21 11:08:33。
转载请注明:加壳学习系列(二)-壳代码 | 女黑客导航