殼代碼
- 一.Shell.dll的原始碼和二進制檔案資訊
- 1.1 殼代碼
- shell.h
- shell.cpp
- start函式
- 1.2 加殼器代碼
- 入口函式
- InitPE
- 拷貝shell.dll中的殼代碼
- MergeBuf
- 二.DLL基礎
- 2.1 DllMain簡介
- 2.2 何時呼叫DllMain
- 2.3 Dll匯出函式
- 三.加殼后的示例代碼
- 3.1 010Editor
- 3.2 IDA
上一節寫到了,殼代碼是寫在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_stcShellData,fnGetProcAddress,fnLoadLibraryA等相當于自定義的型別,
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_stcShellData的dwStartFun成員變數設為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
然后就是MergeBuf,pShellBuf為shell.dll內容的首地址,pShellBufSize為shellBuf的大小,
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函式的呢?靜態鏈接時,或動態鏈接時呼叫LoadLibrary和FreeLibrary都會呼叫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來呼叫DLL的DllMain函式,這就意味著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程式運行程序,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/289169.html
標籤:其他
上一篇:opencv檢測動態物體
下一篇:資訊搜集(一)
