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

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

一.Shell.dll的源码和二进制文件信息

1.1 壳代码

这里就直接粘贴了,涉及到的细节下面讨论。觉得太长了可以直接跳过

shell.h

#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被从进程的地址空间解除映射时,系统调用了它的DllMain,传递的ul_reason_for_call值是DLL_PROCESS_DETACH。如果进程的终结是因为调用了TerminateProcess,系统就不会用DLL_PROCESS_DETACH来调用DLLDllMain函数。这就意味着DLL在进程结束前没有机会执行任何清理工作。
  • DLL_THREAD_ATTACH:2,当进程创建一线程时,系统查看当前映射到进程地址空间中的所有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程序运行过程。

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值