需求:一个可以执行的加壳器、和一个植入宿主文件的stub
这里的项目参考了黑客免杀攻防的如何用c++写一个加壳器的项目
首先给main函数的执行流程,来巩固我们对程序执行流程的熟悉
void start()
{
// 1. 初始化所有API
if ( !InitializationAPI() ) return;
// 2. 解密宿主程序
Decrypt();
// 3. 询问是否执行解密后的程序
if ( g_stcParam.bShowMessage )
{
int nRet = g_funMessageBox(NULL,L"解密完成,是否运行原程序?",L"解密完成",MB_OKCANCEL);
if (IDCANCEL == nRet) return;
}
// 4. 跳转到OEP
__asm jmp g_stcParam.dwOEP;
}
接下来我们来一步步分析InitializationAPI()这个函数
bool InitializationAPI()
{
HMODULE hModule;
// 1. 初始化基础API
g_funGetProcAddress = (LPGETPROCADDRESS)GetGPAFunAddr();
g_funLoadLibraryEx =
(LPLOADLIBRARYEX)g_funGetProcAddress((HMODULE)GetKernel32Base(),"LoadLibraryExW");
// 2. 初始化其他API
hModule = NULL;
if ( !(hModule=g_funLoadLibraryEx(L"kernel32.dll",NULL,NULL)) ) return false;
g_funExitProcess = (LPEXITPROCESS)g_funGetProcAddress(hModule,"ExitProcess");
hModule = NULL;
if ( !(hModule=g_funLoadLibraryEx(L"user32.dll",NULL,NULL)) ) return false;
g_funMessageBox = (LPMESSAGEBOX)g_funGetProcAddress(hModule,"MessageBoxW");
hModule = NULL;
if ( !(hModule=g_funLoadLibraryEx(L"kernel32.dll",NULL,NULL)) ) return false;
g_funGetModuleHandle = (LPGETMODULEHANDLE)g_funGetProcAddress(hModule,"GetModuleHandleW");
hModule = NULL;
if ( !(hModule=g_funLoadLibraryEx(L"kernel32.dll",NULL,NULL)) ) return false;
g_funVirtualProtect = (LPVIRTUALPROTECT)g_funGetProcAddress(hModule,"VirtualProtect");
return true;
}
首先我们获得Kernel32.dll的地址,因为这个dll在所有的进程中都存在,并且其中的一个函数对获取宿主PE的API函数至关重要。这里用了内联汇编,因为这样比较方便。
DWORD GetKernel32Base()
{
DWORD 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+0x08] // eax = 获取到的Kernel32.dll基址(Win7下获取的是KernelBase.dll的基址)
mov dwKernel32Addr,eax
pop eax
}
return dwKernel32Addr;
}
TEB(Thread Environment Block ),它记录的相关线程的信息,每一个线程都有自己的TEB,FS:[0]即是当前线程的TEB。
PEB(Process Environment Block,进程环境块)存放进程信息,每个进程都有自己的PEB信息,TEB偏移0x30即当前进程的PEB。
获取Kernel32.dll的基地址后,我们就可以遍历模块中的信息,来找到我们需要的函数了。
g_funGetProcAddress = (LPGETPROCADDRESS)GetGPAFunAddr();
跟踪这个函数
DWORD GetGPAFunAddr()
{
DWORD dwAddrBase = GetKernel32Base();
// 1. 获取DOS头、NT头
PIMAGE_DOS_HEADER pDos_Header;
PIMAGE_NT_HEADERS pNt_Header;
pDos_Header = (PIMAGE_DOS_HEADER)dwAddrBase;
pNt_Header = (PIMAGE_NT_HEADERS)(dwAddrBase + pDos_Header->e_lfanew);
// 2. 获取导出表项
PIMAGE_DATA_DIRECTORY pDataDir;
PIMAGE_EXPORT_DIRECTORY pExport;
pDataDir = pNt_Header->OptionalHeader.DataDirectory+IMAGE_DIRECTORY_ENTRY_EXPORT;
pExport = (PIMAGE_EXPORT_DIRECTORY)(dwAddrBase + pDataDir->VirtualAddress);
// 3. 获取导出表详细信息
PDWORD pAddrOfFun = (PDWORD)(pExport->AddressOfFunctions + dwAddrBase);
PDWORD pAddrOfNames = (PDWORD)(pExport->AddressOfNames + dwAddrBase);
PWORD pAddrOfOrdinals = (PWORD) (pExport->AddressOfNameOrdinals + dwAddrBase);
// 4. 处理以函数名查找函数地址的请求,循环获取ENT中的函数名,并与传入值对比对,如能匹配上
// 则在EAT中以指定序号作为索引,并取出其地址值。
DWORD dwFunAddr;
for (DWORD i=0; i<pExport->NumberOfNames; i++)
{
PCHAR lpFunName = (PCHAR)(pAddrOfNames[i]+dwAddrBase);
if ( !strcmp(lpFunName, "GetProcAddress") )
{
dwFunAddr = pAddrOfFun[pAddrOfOrdinals[i]] + dwAddrBase;
break;
}
if ( i == pExport->NumberOfNames-1 )
return 0;
}
return dwFunAddr;
}
所以现在我们获得了GetProcAdress的地址,通过函数指针,我们就可以随意调用它。因此我们用它来那到几个重要的函数。
// 2. 初始化其他API
hModule = NULL;
if ( !(hModule=g_funLoadLibraryEx(L"kernel32.dll",NULL,NULL)) ) return false;
g_funExitProcess = (LPEXITPROCESS)g_funGetProcAddress(hModule,"ExitProcess");
hModule = NULL;
if ( !(hModule=g_funLoadLibraryEx(L"user32.dll",NULL,NULL)) ) return false;
g_funMessageBox = (LPMESSAGEBOX)g_funGetProcAddress(hModule,"MessageBoxW");
hModule = NULL;
if ( !(hModule=g_funLoadLibraryEx(L"kernel32.dll",NULL,NULL)) ) return false;
g_funGetModuleHandle = (LPGETMODULEHANDLE)g_funGetProcAddress(hModule,"GetModuleHandleW");
hModule = NULL;
if ( !(hModule=g_funLoadLibraryEx(L"kernel32.dll",NULL,NULL)) ) return false;
g_funVirtualProtect = (LPVIRTUALPROTECT)g_funGetProcAddress(hModule,"VirtualProtect");
因为植入的stub劫持了原oep,是用来解密宿主PE的代码段的,接下就可以解密了。因为加的是一个十分简单的加密壳,所以异或就好了
void Decrypt()
{
// 在导出的全局变量中读取需解密区域的起始于结束VA
PBYTE lpStart = g_stcParam.lpStartVA;
PBYTE lpEnd = g_stcParam.lpEndVA;
// 循环解密
while ( lpStart<lpEnd )
{
*lpStart -= 0x88;
*lpStart ^= 0xA1;
lpStart++;
}
}
这一下PE解密完了,就可以跳转到原OEP来执行原来的程序逻辑了,这里也用了内联汇编
// 3. 询问是否执行解密后的程序
if ( g_stcParam.bShowMessage )
{
int nRet = g_funMessageBox(NULL,L"解密完成,是否运行原程序?",L"解密完成",MB_OKCANCEL);
if (IDCANCEL == nRet) return;
}
// 4. 跳转到OEP
__asm jmp g_stcParam.dwOEP;
到这里,stub部分就完成了,接下来我们就要一个加壳器,对宿主PE修改和注入我们的stub
补充:stub一般用DLL的形式,据说因为自带重定位的原因,还没弄明白: ( 下次再说。
Comments NOTHING