偶然翻了一下手機日歷,原來今天是夏至啊,時間過的真快。ISCC的比賽已經持續了2個多月了,我也跟着比賽的那些題目學了2個月.......雖然過程很辛苦,但感覺還是很幸運的,能在大三的時候遇到ISCC,不管怎樣,對我來說都會是一個很好的鍛煉機會。
在做綜合關的逆向破解的題目,遇到了很多蛋疼的問題,磕磕碰碰把《加密與解密》看完了。還是老習慣,把這1個多星期以后學到的,想到的做一個總結,希望能有一個思想上的提高,也希望能對其他人有一個借鑒的效果吧。這里想起lenus大神的一句話:
Hacker的精神里面除了學習(learn)的第一精神以外,還應該是共享(share)的精神。
好,廢話不多說,開始吧。
1. 逆向?破解?
一開始接觸逆向,我直接是使用的一些加殼脫殼工具,都是鼠標點一下就出來了,這段時間深入了解了逆向的原理之后,我感覺逆向是一個綜合性很強的技術,中間還需要windows編程,PE結果,匯編等知識。
對於脫殼,我的理解是:脫殼不意味着它字面上的意思,把外殼直接拿掉,然后就扔掉(像吃水果一樣),脫殼本質上是一個高級的crack技術,也就是繞過破解。
這是一張關於殼的圖示:
而我們不管用lordPE, procDump, 還是手工脫殼也好,其實本質上就是要改變程序的執行流,讓程序跳過頭部(實際中不一定是頭部,這里只是邏輯上的)的一段殼代碼,而直接來到原程序的入口代碼處,也就是常說的OEP,那問題就來了,那脫殼就這么簡單?
當然不是,我個人感覺,脫殼並不僅僅是改變程序執行流這么簡單,因為被加過殼的程序往往IAT,重定位表,節區都被加密,壓縮處理過了。如果你直接強行跳轉過來,程序也是不能執行的。所以,網上常說的找OEP的原理就在這里。為什么要找OEP呢?其實本質上來說,這並不是最重要的,不要被這些形式上的東西框住了思維,我們從原理上思考,一個程序要運行,無非需要幾種東西:
1. 可以執行的機器碼
2. IAT
3. 重定位表(DLL)
4. 資源代碼(不太重要就是了,因為並不影響逆向的結果)
轉換了思維之后,我就豁然開朗了,我們要做的就是找到代碼中的某一行,恰好已經全部完成了代碼的解壓縮和解密,IAT的重寫,重定位表的重寫,做好這些事后,我們就可以dump下來了,至於是不是OEP其實不是很重要,所謂的找OEP是因為,一般情況下在OEP的時候恰好都滿足了上面的條件,而且也比較好找到,所以,OEP是dump的首選,如果你理解了原理,這么做也沒有錯。
第二點就是:為什么要用ImportREC來重建IAT表呢?它的原理又是什么呢?
這里首先要從加殼的原理說起,不管你是UPX壓縮殼,ASProtect,穿山甲加密殼,還是VMP虛擬機殼...基本上來說都會破壞原本的IAT,將原本的IAT移到一個新的節區里面並加密等處理,然后構建自己的IAT表,並修改PE頭。為什么要這么做呢?
因為殼必須保證它能調用到自己需要的函數,但是原程序並不能保證這點,所以殼必須自己構造自己的IAT三劍客:
調用LoadLibrary將dll文件映射到調用進程的地址空間中
調用GetModualHandle獲得dll模塊句柄
調用GetProcAddress獲取輸入函數的地址
一般殼程序都會構造這三個API,然后重建自己的IAT表,這樣殼程序就能保證調用到自己需要的所有winAPI了。
而我們脫殼的是內存中的映像,即使這時候原程序的IAT已經恢復過來了(解壓縮,解密),但是位置已經變了,原程序無法直接調用了。而ImportREC的作用就是找到這里API對用RVA,然后在一段空的地方重建這些結構,逆向構造出一個IAT出來,然后修改PE頭,完成IAT的修復。明白了原理,自己手工修復IAT也是一樣的。
修復重定位表和資源表的原理也是一樣的,就不在詳述了。
下面,我們通過一個dump實例的編程過程來深刻的理解一個dump的思想。
接下來的程序是我們對看雪上的一篇帖子的學習筆記,是lenus大神的脫殼教程,我這里就當是做一個學習筆記。
一:dump小程序的目標
對於dump來說,他的英文翻譯就是“轉存”。也就是說把內存中或者其他的輸入轉存到另一個位置,當然對於我們現在說的dump就是把內存中運行的PE進程的數據,從內存中抓取出來,然后在用文件的形式保存下來。
根據上面的分析我們基本上得到了一個這樣的思維。Dump程序要做的事分幾個基本的步驟:
1. 在系統中找到目標進程
2. 在進程中確定進程的大小imagesize
3. 把進程中的數據保存到文件
Code:
resource.h
#define ID_FLESH 2
#define IDD_DIALOG1 101
#define ICO_MAIN 103
#define IDC_PROCESS 1028
#define IDC_CORRECT 1033
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 105
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1034
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif
#include <windows.h>
#include <tlhelp32.h>
#include "resource.h"
BOOL CALLBACK DlgProc (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);
BOOL GetProcessListFunc(HWND hDlg,HWND hWindList); // 列出各個進程的函數,刷新時也用到
LPCTSTR SaveAsFunc(HWND hDlg); //通用對話框函數
BOOL DumpFunc(HWND hDlg,HWND hWindList); // 主要的dump函數,調用其他的小功能函數實現其功能
BOOL CreateDumpFile(HWND hDlg,LPCTSTR Dump_Name,HGLOBAL hMem); //生成dump文件函數,把dump的東西寫到磁盤上
HGLOBAL ReadProcess(HWND hDlg,DWORD IDProcess); //讀取目標進程空間,放置到本空間申請的堆中
int GetSizeOfImage(HWND hDlg,DWORD IDProcess); // 獲取pe文件的SizeOfImage
BOOL CheckPEFunc(HWND hDlg,HANDLE hProcess); //檢查pe文件的完整性
BOOL CorrectSizeFunc(HWND hDlg,HWND hWindList); //糾正文件的大小
LPCTSTR GetFilePath(HWND hDlg,DWORD IDProcess);//獲取目標exe的絕對路徑
BOOL ModifySectionFunc(HWND hDlg,LPCTSTR Dump_Name);//修改文件的節表使其RA=RVA ,RS=RVS
BOOL CopyThePEHead(HWND hDlg,LPCTSTR Dump_Name); //把原來PE文件的頭部復制到dump文件中
////////////////全局變量/////////////////////
int sizeoffile=0;
int sizeofimage=0; //當使用了CorrectSizeFunc后這個有了具體數值,就不需要再次獲取了
int BaseAddress=0x400000;
DWORD ID=0; //這個是用來控制進程的切換的
///////////////////////////////
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
DialogBoxParam (hInstance,MAKEINTRESOURCE(IDD_DIALOG1),NULL,DlgProc,(LPARAM)hInstance);
return TRUE;
}
BOOL CALLBACK DlgProc (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
static HINSTANCE hInstance;
static HWND hWindList;
switch (message)
{
case WM_CLOSE:
EndDialog (hDlg, 0) ;
return TRUE ;
case WM_INITDIALOG :
hInstance = (HINSTANCE) lParam;
SendMessage(hDlg,WM_SETICON,ICON_BIG,(LPARAM)LoadIcon(hInstance,MAKEINTRESOURCE(ICO_MAIN)));
hWindList = GetDlgItem(hDlg,IDC_PROCESS);
if(!GetProcessListFunc(hDlg,hWindList))
{
MessageBox(hDlg,TEXT("Fail to get the process"),TEXT("Sorry"),MB_OK | MB_ICONSTOP);
EndDialog(hDlg,0);
}
return TRUE ;
case WM_COMMAND :
switch (LOWORD (wParam))
{
case IDOK :
DumpFunc(hDlg,hWindList);
return TRUE ;
case ID_FLESH:
if(!GetProcessListFunc(hDlg,hWindList))
{
MessageBox(hDlg,TEXT("Fail to get the process"),TEXT("Sorry"),MB_OK | MB_ICONSTOP);
EndDialog(hDlg,0);
}
return TRUE;
case IDC_CORRECT:
if(!CorrectSizeFunc(hDlg,hWindList))
MessageBox(hDlg,TEXT("The correct size function is faile..."),TEXT("Fail..."),MB_OK | MB_ICONWARNING);
return TRUE;
}
break ;
}
return FALSE ;
}
BOOL GetProcessListFunc(HWND hDlg,HWND hWindList)
{
//此函數是進程列表的作用,在此不作過多介紹
HANDLE hProcessSnap = NULL;
PROCESSENTRY32 pe32 = {0};
SendMessage(hWindList,LB_RESETCONTENT,0,0);
pe32.dwSize = sizeof(PROCESSENTRY32);
hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hProcessSnap == INVALID_HANDLE_VALUE)
return FALSE;
if (!Process32First(hProcessSnap, &pe32))
{
CloseHandle (hProcessSnap);
return FALSE;
}
do
{
WPARAM tmp=(WPARAM)SendMessage(hWindList,LB_ADDSTRING,0,(LPARAM)pe32.szExeFile);
SendMessage(hWindList,LB_SETITEMDATA,tmp,(LPARAM)pe32.th32ProcessID);
}
while (Process32Next(hProcessSnap, &pe32));
CloseHandle (hProcessSnap);
return TRUE;
}
BOOL DumpFunc(HWND hDlg,HWND hWindList)
{
HGLOBAL hMem;
LPCTSTR Dump_Name=NULL; //感覺有點問題,這個只是指針沒有開辟足夠的空間。
//從數組(規定好的)到指針(未規定好的)就可以,
//從指針到數組就不可以
WPARAM tmp=(WPARAM)SendMessage(hWindList,LB_GETCURSEL,0,0);
if (tmp==LB_ERR)
{
MessageBox(hDlg,TEXT("Please choose a process..."),TEXT("oh...no,no,no..."),MB_OK);
return FALSE;
}
DWORD IDProcess=SendMessage(hWindList,LB_GETITEMDATA,tmp,0); //獲得此列單里面的進程ID
ID=IDProcess;
hMem=ReadProcess(hDlg,IDProcess);
if(hMem) //如果返回的hMen不正確說明沒有正確的申請到空間
{
if(sizeoffile!=0) //沒有大小的dump 是沒有意義的
{
Dump_Name=SaveAsFunc(hDlg); //要保存的文件名
if(Dump_Name) //如果得到的文件名是空就不繼續執行
{
CreateDumpFile(hDlg,Dump_Name,hMem); //把數據寫入文件中
GlobalFree(hMem); //資源是可貴的,釋放空間
}
}
}
return TRUE;
}
LPCTSTR SaveAsFunc(HWND hDlg)
{
//獲取你要保存的文件名,默認為dumped.exe
HANDLE hFile;
static char szFileName[MAX_PATH]="dumped";
OPENFILENAME stOF={0};
stOF.hwndOwner=hDlg;
stOF.lStructSize=sizeof(stOF);
stOF.lpstrFilter="*.*";
stOF.lpstrDefExt="exe";
stOF.nMaxFile=MAX_PATH;
stOF.lpstrFile=szFileName;
if(!GetSaveFileName(&stOF))
return FALSE;
char szBuffer[100];
char szMsg[]="%s 已存在。要替換它嗎?";
wsprintf(szBuffer,szMsg,szFileName);
hFile=CreateFile(szFileName,GENERIC_READ,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if(hFile != INVALID_HANDLE_VALUE)
{
if(IDNO==MessageBox(hDlg,szBuffer,TEXT("另存為"),MB_YESNO | MB_ICONWARNING))
{
CloseHandle(hFile);
return FALSE;
}
}
CloseHandle(hFile);
return szFileName;
}
BOOL CreateDumpFile(HWND hDlg,LPCTSTR Dump_Name,HGLOBAL hMem)
{
//創建一個新的dump文件
HANDLE hFile=CreateFile(Dump_Name,GENERIC_WRITE | GENERIC_READ,0,0,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
if(hFile==INVALID_HANDLE_VALUE)
{
MessageBox(hDlg,TEXT("Maybe you have alreadly had a this name file:("),
TEXT("Can't create a file"),MB_OK | MB_ICONWARNING);
GlobalFree(hMem);
return FALSE;
}
int NumberOfBytesWritten;
WriteFile(hFile,hMem,sizeoffile,(LPDWORD)&NumberOfBytesWritten,NULL); //注意這個函數第三個參數是必要的!
CloseHandle(hFile);
if(!CopyThePEHead(hDlg,Dump_Name))
{
//復制PE頭
MessageBox(hDlg,TEXT("復制PE頭失敗了"),TEXT("失敗了"),MB_OK | MB_ICONWARNING);
}
if(!ModifySectionFunc(hDlg,Dump_Name))
{
//節表對齊
MessageBox(hDlg,TEXT("修改節表失敗了"),TEXT("失敗了"),MB_OK | MB_ICONWARNING);
}
MessageBox(hDlg,TEXT("文件已經dump成功"),TEXT("Lenus'ExeDump"),MB_OK | MB_ICONINFORMATION);//勝利的號角!
return TRUE;
}
HGLOBAL ReadProcess(HWND hDlg,DWORD IDProcess)
{
//此函數是讀取目標進程的空間,並把他寫入到自己內存空間里面的一個內存塊中
HANDLE hProcess=OpenProcess(PROCESS_ALL_ACCESS,0,IDProcess);//使用上面獲得的進程id
if(!hProcess)
{
MessageBox(hDlg,TEXT("I can't open the process:("),TEXT("oh my god.."),MB_OK);
return FALSE;
}
if(sizeofimage==0 || ID!=IDProcess)
//當更換當前的進程或者沒有使用修正的功能的時候需要重新的獲取
//由於不知道在更換選項的時候會發出什么消息,所以只能這么干 so foolish!!
{
sizeofimage=GetSizeOfImage(hDlg,IDProcess);
}
if(!sizeofimage)
{
return FALSE;
}
//為了以防萬一,讓sizeofimage增加一個文件對齊度。
if(!(sizeofimage%0x1000)) //如果是文件對齊度的整數倍的時候就不處理
sizeoffile=sizeofimage;
else
sizeoffile=(sizeofimage/0x1000+1)*0x1000; //如果不是就增加一個文件對齊度
//申請一個文件空間的內存塊
static HGLOBAL hMem=0;
hMem=GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT,sizeoffile);
if(!hMem)
{
MessageBox(hDlg,TEXT("I think i have enough space to get!:("),TEXT("Wrong!!!"),MB_OK | MB_ICONSTOP);
return FALSE;
}
//將這個pe文件在內存中的大小全部讀到申請的塊中
DWORD NumberOfBytesReadorWrite;
if(!ReadProcessMemory(hProcess,(LPCVOID)BaseAddress,hMem,sizeofimage,&NumberOfBytesReadorWrite))
{
MessageBox(hDlg,TEXT("I can't read the process:("),TEXT("oh my god.."),MB_OK);
return FALSE;
}
CloseHandle(hProcess); //有開始就,有關閉
Sleep(200); //等待一會
return hMem;
}
int GetSizeOfImage(HWND hDlg,DWORD IDProcess)
{
//這個函數的作用是獲取SizeOfImage的數值
//當函數執行失敗返回的是0
//成功返回的是非0
HANDLE hModuleSnap = NULL;
MODULEENTRY32 stModE = {0};
stModE.dwSize = sizeof(MODULEENTRY32);
hModuleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,IDProcess); //快照,對本進程中所有的模塊進行snap
if (hModuleSnap == INVALID_HANDLE_VALUE)
{
MessageBox(hDlg,TEXT("The Module snapshot can't get!"),TEXT("Error!"),MB_OK | MB_ICONSTOP);
return FALSE; //返回0
}
if (!Module32First(hModuleSnap, &stModE))
{
MessageBox(hDlg,TEXT("The Module32First can't work!"),TEXT("Error!"),MB_OK | MB_ICONSTOP);
CloseHandle (hModuleSnap);
return FALSE;
}
CloseHandle (hModuleSnap);
return stModE.modBaseSize;//初始化為0
}
BOOL CheckPEFunc(HWND hDlg,HANDLE hProcess)
{
//檢查pe文件,檢查兩個標志
//如果不是pe文件那么會迫使GetSizeOfImage直接返回
//下面不是重點,所以不介紹了
int BaseAddress=0x400000;
IMAGE_DOS_HEADER DosHead;
_IMAGE_NT_HEADERS NtHead;
if(!ReadProcessMemory(hProcess,(LPCVOID)BaseAddress,&DosHead.e_magic,2,NULL))
{
MessageBox(hDlg,TEXT("I can't read the IMAGE_DOS_SIGNATURE:("),TEXT("oh my god.."),MB_OK);
return FALSE;
}
if(DosHead.e_magic != IMAGE_DOS_SIGNATURE)
{
return FALSE;
}
if(!ReadProcessMemory(hProcess,(LPCVOID)(BaseAddress+0x3c),&DosHead.e_lfanew,4,NULL))
{
MessageBox(hDlg,TEXT("I can't read the e_lfanew:("),TEXT("oh my god.."),MB_OK);
return FALSE;
}
if(!ReadProcessMemory(hProcess,(LPCVOID)(BaseAddress+DosHead.e_lfanew),&NtHead.Signature,4,NULL))
{
MessageBox(hDlg,TEXT("I can't read the e_lfanew:("),TEXT("oh my god.."),MB_OK);
return FALSE;
}
if(NtHead.Signature != IMAGE_NT_SIGNATURE)
{
return FALSE;
}
return TRUE;
}
BOOL CorrectSizeFunc(HWND hDlg,HWND hWindList)
{
//函數能獲取文件的PE頭部的SizeOfImage,作為正確的SizeOfImage
LPCTSTR File_Name=NULL;
WPARAM tmp=(WPARAM)SendMessage(hWindList,LB_GETCURSEL,0,0);
if (tmp==LB_ERR)
{
MessageBox(hDlg,TEXT("Please choose a process..."),TEXT("oh...no,no,no..."),MB_OK);
return FALSE;
}
DWORD IDProcess=SendMessage(hWindList,LB_GETITEMDATA,tmp,0); //獲得此列單里面的進程ID
ID=IDProcess;//全局變量ID的作用是控制在不同的進程的切換
File_Name=GetFilePath(hDlg,IDProcess);
if(!File_Name)
return FALSE;
//打開文件
HANDLE hFile;
hFile=CreateFile(File_Name,GENERIC_READ,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if (hFile == INVALID_HANDLE_VALUE )
{
return FALSE ;
}
//創建文件映射內核對象
HANDLE hMapping;
hMapping = CreateFileMapping (hFile, NULL, PAGE_READONLY,0,0,NULL);
if (hMapping == NULL )
{
CloseHandle (hFile ) ;
return FALSE;
}
//創建文件視圖
LPVOID ImageBase ;
ImageBase =MapViewOfFile(hMapping,FILE_MAP_READ,0,0,0) ;
if (ImageBase == NULL)
{
CloseHandle (hMapping) ;
return FALSE;
}
//下面的代碼就是從文件的PE頭找到SizeOfImage的
PIMAGE_DOS_HEADER DosHead = NULL ;
PIMAGE_NT_HEADERS32 pNtHeader = NULL ;
PIMAGE_FILE_HEADER pFileHeader = NULL ;
PIMAGE_OPTIONAL_HEADER pOptionalHeader = NULL ;
PIMAGE_SECTION_HEADER pSectionHeader = NULL ;
DosHead=(PIMAGE_DOS_HEADER)ImageBase;
pNtHeader = ( PIMAGE_NT_HEADERS32 ) ((DWORD)ImageBase + DosHead->e_lfanew ) ;
pOptionalHeader = &pNtHeader->OptionalHeader;
sizeofimage=(int)pOptionalHeader->SizeOfImage;
//找到了以后,輸出結果
char szBuffer[100];
char szMsg[]="原來的image size是:%08X\n修整的image size是:%08X";
wsprintf(szBuffer,szMsg,GetSizeOfImage(hDlg,IDProcess),sizeofimage);
MessageBox(hDlg,szBuffer,TEXT("糾正結果"),MB_OK );
CloseHandle (hMapping);
CloseHandle (hFile) ;
Sleep(200);
return TRUE;
}
LPCTSTR GetFilePath(HWND hDlg,DWORD IDProcess)
{
//此函數獲得目標進程的絕對路徑
//如果獲取失敗返回NULL
HANDLE hModuleSnap = NULL;
MODULEENTRY32 a = {0};
a.dwSize = sizeof(MODULEENTRY32);
hModuleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,IDProcess); //快照,對本進程中所有的模塊進行snap
if (hModuleSnap == INVALID_HANDLE_VALUE)
{
MessageBox(hDlg,TEXT("The Module snapshot can't get!"),TEXT("Error!"),MB_OK | MB_ICONSTOP);
return FALSE; //返回0
}
if (!Module32First(hModuleSnap, &a))
{
MessageBox(hDlg,TEXT("The Module32First can't work!"),TEXT("Error!"),MB_OK | MB_ICONSTOP);
CloseHandle (hModuleSnap);
return FALSE;
}
CloseHandle (hModuleSnap);
return a.szExePath;
}
BOOL ModifySectionFunc(HWND hDlg,LPCTSTR Dump_Name)
{
//此函數的將修改dump下來的exe,使其RA=RVA ,RS=RVS
//首先是打開dump文件
HANDLE hFile=CreateFile(Dump_Name,GENERIC_WRITE | GENERIC_READ,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if(hFile==INVALID_HANDLE_VALUE)
{
MessageBox(hDlg,TEXT("I can open the dump file..."),TEXT("Error!!"),MB_OK | MB_ICONWARNING);
return FALSE;
}
//下面移動到節表前面
IMAGE_DOS_HEADER myDosHeader;
DWORD NumberOfBytesReadorWrite;
ReadFile(hFile,(LPVOID)&myDosHeader,sizeof(IMAGE_DOS_HEADER),&NumberOfBytesReadorWrite,NULL);
SetFilePointer(hFile,myDosHeader.e_lfanew+sizeof(DWORD),NULL,FILE_BEGIN);
IMAGE_FILE_HEADER myNtHeader;
ReadFile(hFile,(LPVOID)&myNtHeader,sizeof(IMAGE_FILE_HEADER),&NumberOfBytesReadorWrite,NULL);
int nSectionCount;
nSectionCount = myNtHeader.NumberOfSections; // 保存Section個數
// 過了IMAGE_NT_HEADERS結構就是IMAGE_SECTION_HEADER結構數組了,注意是結構數組,有幾個Section該結構就有幾個元素
// 這里動態開辟NumberOfSections個內存來存儲不同的Section信息
IMAGE_SECTION_HEADER *pmySectionHeader = (IMAGE_SECTION_HEADER *)calloc(nSectionCount, sizeof(IMAGE_SECTION_HEADER));
SetFilePointer(hFile,myDosHeader.e_lfanew + sizeof(IMAGE_NT_HEADERS),NULL,FILE_BEGIN);
ReadFile(hFile,(LPVOID)pmySectionHeader,sizeof(IMAGE_SECTION_HEADER)*nSectionCount,
&NumberOfBytesReadorWrite,NULL);
//移動回到節表的開始,准備寫入
SetFilePointer(hFile,myDosHeader.e_lfanew + sizeof(IMAGE_NT_HEADERS),NULL,FILE_BEGIN);
for (int i = 0; i < nSectionCount; i++, pmySectionHeader++)
{
//將RA=RVA ,RS=RVS
pmySectionHeader->SizeOfRawData=pmySectionHeader->Misc.VirtualSize;
pmySectionHeader->PointerToRawData=pmySectionHeader->VirtualAddress;
//將修改好的數值寫回
WriteFile(hFile,(LPVOID)pmySectionHeader,sizeof(IMAGE_SECTION_HEADER),&NumberOfBytesReadorWrite,NULL);
}
// 恢復指針
pmySectionHeader -=nSectionCount;
if (pmySectionHeader != NULL) // 釋放內存
{
free(pmySectionHeader);
pmySectionHeader = NULL;
}
// 最后不要忘記關閉文件
CloseHandle(hFile);
return TRUE;
}
BOOL CopyThePEHead(HWND hDlg,LPCTSTR Dump_Name)
{
//此函數的作用是將原來PE文件的PE頭部完整的copy到dump文件中
HANDLE hFile=CreateFile(GetFilePath(hDlg,ID),GENERIC_READ,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if(hFile==INVALID_HANDLE_VALUE)
{
MessageBox(hDlg,TEXT("I can open the object file..."),TEXT("Error!!"),MB_OK | MB_ICONWARNING);
return FALSE;
}
//下面移動到節表前面
IMAGE_DOS_HEADER myDosHeader;
DWORD NumberOfBytesReadorWrite;
ReadFile(hFile,(LPVOID)&myDosHeader,sizeof(IMAGE_DOS_HEADER),&NumberOfBytesReadorWrite,NULL);
SetFilePointer(hFile,myDosHeader.e_lfanew+sizeof(DWORD),NULL,FILE_BEGIN);
IMAGE_FILE_HEADER myNtHeader;
ReadFile(hFile,(LPVOID)&myNtHeader,sizeof(IMAGE_FILE_HEADER),&NumberOfBytesReadorWrite,NULL);
IMAGE_SECTION_HEADER mySectionHeader;
SetFilePointer(hFile,myDosHeader.e_lfanew + sizeof(IMAGE_NT_HEADERS),NULL,FILE_BEGIN);
ReadFile(hFile,(LPVOID)&mySectionHeader,sizeof(IMAGE_SECTION_HEADER),&NumberOfBytesReadorWrite,NULL);
SetFilePointer(hFile,NULL,NULL,FILE_BEGIN);
HGLOBAL hMem=0;
//讀出節表的第一個文件位置,以確PE頭的大小
//申請同樣大小的空間
hMem=GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT,mySectionHeader.PointerToRawData);
if(!hMem)
{
MessageBox(hDlg,TEXT("I can't get the Memory space!"),TEXT("Error!!!"),MB_OK | MB_ICONSTOP);
return FALSE;
}
//將文件中的PE頭部讀取到申請的空間中
ReadFile(hFile,hMem,mySectionHeader.PointerToRawData,&NumberOfBytesReadorWrite,NULL);
CloseHandle(hFile);
//////////////////上面是讀///////////////////////
//////////////////下面是寫///////////////////////
hFile=CreateFile(Dump_Name,GENERIC_WRITE,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if(hFile==INVALID_HANDLE_VALUE)
{
MessageBox(hDlg,TEXT("I can open the dump file..."),TEXT("Error!!"),MB_OK | MB_ICONWARNING);
return FALSE;
}
//下面是將空間中的數據寫到dump文件的頭部
WriteFile(hFile,hMem,mySectionHeader.PointerToRawData,&NumberOfBytesReadorWrite,NULL);
CloseHandle(hFile);
GlobalFree(hMem);
return TRUE;
}
這里:
1. 獲取當前進程列表使用的是:CreateToolhelp32Snapshot,Process32First, Process32Next 這三個函數,即創建一個進程快照,然后遍歷它,之前做ring3的進程注入的時候用的也是這個技術,原理不是很難理解,本質上就是一個鏈表的操作。
2. 在進程中確定進程的大小imagesize 使用的是從磁盤的文件PE頭中讀取SizeOfImage這個字段來獲取。這里就有一個很有趣的知識點了,我們先從原始的方法開始講。
2.1 用ReadProcessMemory函數從從當前內存的進程映像工具PE格式的偏移獲取到SizeOfImage的值的,這種方法不太可靠,要是加殼程序修改了映像中的PE頭信息,就要出錯了。
2.2 LordPE使用的方法,采用了對Module32Next來獲取dump的進程的基本信息的。
BOOL WINAPI Module32First(
HANDLE hSnapshot, //這是先前的CreateToolhelp32Snapshot函數返回的快照
LPMODULEENTRY32 lpme //這個是指向MODULEENTRY32結構的指針
);
下面是MUDULEENTRY32結構:
typedef struct tagMODULEENTRY32 {
DWORD dwSize;
DWORD th32ModuleID;
DWORD th32ProcessID;
DWORD GlblcntUsage;
DWORD ProccntUsage;
BYTE *modBaseAddr;
DWORD modBaseSize; //這個是是我們要獲取的關鍵
HMODULE hModule;
TCHAR szModule[MAX_PATH];
TCHAR szExePath[MAX_PATH];
DWORD dwFlags;
} MODULEENTRY32, *PMODULEENTRY32, *LPMODULEENTRY32;
這種方法會受到anti-dump技術的影響。
因為MUDULEENTRY32是從內核中PEB(進程環境塊)獲取的數據。
下面是PEB的結構。
struct _PEB (sizeof=488)
+000 byte InheritedAddressSpace
+001 byte ReadImageFileExecOptions
+002 byte BeingDebugged
+003 byte SpareBool
+004 void *Mutant
+008 void *ImageBaseAddress
+00c struct _PEB_LDR_DATA *Ldr
+010 struct _RTL_USER_PROCESS_PARAMETERS *ProcessParameters
+014 void *SubSystemData
+018 void *ProcessHeap
+01c void *FastPebLock
+020 void *FastPebLockRoutine
+024 void *FastPebUnlockRoutine
+028 uint32 EnvironmentUpdateCount
+02c void *KernelCallbackTable
+030 uint32 SystemReserved[2]
+038 struct _PEB_FREE_BLOCK *FreeList
+03c uint32 TlsExpansionCounter
+040 void *TlsBitmap
+044 uint32 TlsBitmapBits[2]
+04c void *ReadOnlySharedMemoryBase
+050 void *ReadOnlySharedMemoryHeap
+054 void **ReadOnlyStaticServerData
+058 void *AnsiCodePageData
+05c void *OemCodePageData
+060 void *UnicodeCaseTableData
+064 uint32 NumberOfProcessors
+068 uint32 NtGlobalFlag
+070 union _LARGE_INTEGER CriticalSectionTimeout
+070 uint32 LowPart
+074 int32 HighPart
+070 struct __unnamed3 u
+070 uint32 LowPart
+074 int32 HighPart
+070 int64 QuadPart
+078 uint32 HeapSegmentReserve
+07c uint32 HeapSegmentCommit
+080 uint32 HeapDeCommitTotalFreeThreshold
+084 uint32 HeapDeCommitFreeBlockThreshold
+088 uint32 NumberOfHeaps
+08c uint32 MaximumNumberOfHeaps
+090 void **ProcessHeaps
+094 void *GdiSharedHandleTable
+098 void *ProcessStarterHelper
+09c uint32 GdiDCAttributeList
+0a0 void *LoaderLock
+0a4 uint32 OSMajorVersion
+0a8 uint32 OSMinorVersion
+0ac uint16 OSBuildNumber
+0ae uint16 OSCSDVersion
+0b0 uint32 OSPlatformId
+0b4 uint32 ImageSubsystem
+0b8 uint32 ImageSubsystemMajorVersion
+0bc uint32 ImageSubsystemMinorVersion
+0c0 uint32 ImageProcessAffinityMask
+0c4 uint32 GdiHandleBuffer[34]
+14c function *PostProcessInitRoutine
+150 void *TlsExpansionBitmap
+154 uint32 TlsExpansionBitmapBits[32]
+1d4 uint32 SessionId
+1d8 void *AppCompatInfo
+1dc struct _UNICODE_STRING CSDVersion
+1dc uint16 Length
+1de uint16 MaximumLength
+1e0 uint16 *Buffer
我們從FS:[30]就可以獲得這個PEB的首地址。然后在0C處的_PEB_LDR_DATA *Ldr是一個關鍵通過它,我們能訪問到
typedef struct _PEB_LDR_DATA {
ULONG Length;
BOOLEAN Initialized;
PVOID SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;
該結構的后三個成員是指向LDR_MODULE鏈表結構中相應三條雙向鏈表頭的指針,分別是按照加載順序、在內存中的地址順序和初始化順序排列的模塊信息結構的指針。於是通過它,我們能訪問到_LDR_MODULE結構,而這里面包括了本進程的SizeOfImage。
_LDR_MODULE結構如下:
typedef struct _LDR_MODULE {
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
PVOID BaseAddress;
PVOID EntryPoint;
ULONG SizeOfImage; //進程的image size
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
SHORT LoadCount;
SHORT TlsIndex;
LIST_ENTRY HashTableEntry;
ULONG TimeDateStamp;
} LDR_MODULE, *PLDR_MODULE;
所以,我們得到關鍵的代碼就是:
//這里的幾個代碼是修改PEB的關鍵
__asm
{
mov eax,fs:[30h] //獲得PEB地址
mov eax,[eax+0ch] // +00c struct _PEB_LDR_DATA *Ldr
mov eax,[eax+0ch] // _LDR_MODULE的首地址
mov dword ptr [eax+20h],1000h //eax+20是保存image size的地方
}
上面的代碼的作用就是把image size的大小改為了1000h,這樣我們用MODULEENTRY32得到的大小是不准確的。
2.3 針對上面的問題,必須使用LordPE的corect image size 技術了,它的原理很簡單,就是從磁盤PE文件頭中讀取真實的image size來修正獲取到的數據,從而避免了anti-dump的影響,當然,anti-dump技術還有很多,不止這一種,我也要慢慢去摸索。
3. 對齊節表問題
學過PE結構的朋友都指導,FileAlignment(磁盤對齊值)和SectionAlignment(內存對齊值)是不一樣的。
FileAlignment:0x200h
SectionAlignment:0x1000h
在PE加載器加載文件映像的時候,就會在對齊值之間的差值中填0,當然我們dump的時候連着這些0也一並dump下來了。這也就是為什么脫殼的程序普遍都比源程序大很多的原理(當然實際情況不止這些,IAT重建也會造成大小擴大)。
另外,還造成了一個問題,就是 RA!=RVA ,RS!=RVS的問題:
因為我們是從內從中直接dump出來的數據,所以這時正常的情況應該是RA=RVA ,RS=RVS,這里可以手動修改,或者編程實現即可,原理上之前學PE的時候寫的PEInfo差不多,主要考察的PE結構的知識。
這是修改后的。
到這里,dump就完成了,還差IAT沒修復。
可以采取手工的方法在文件中找一塊空位,逆向的重構IAT,在修改PE頭。
或者用ImportREC來自動修復。
修復完成后,脫殼成功,至此,一個簡單的脫殼工具就出來了。
這個學習筆記就當這段時間學習逆向脫殼的總結了,留下了幾個問題沒解決:
1. DLL脫殼后的重定位問題
2. VMP虛擬機殼尋找OEP問題。
留待以后解決了.............
信安的路果然還很長,希望以后能繼續多多學習,思考,總結,分享,向看雪上那些大神看齊。
下一階段准備研究一些緩沖區溢出的內核的知識點,繼續看《0DAY》。離ISCC結束還有10天,繼續加油啦,暑假一定要到北京ISCC決賽去。
-------------------------------分割線-----------------------
不知道怎么結尾,就這樣了吧, 跑步去。