偶然翻了一下手机日历,原来今天是夏至啊,时间过的真快。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. 在进程中确定进程的大小imagesize3. 把进程中的数据保存到文件
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文件的SizeOfImageBOOL 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=RVSBOOL 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是:X\n修整的image size是:X"; 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 sizeUNICODE_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决赛去。
-------------------------------分割线-----------------------
不知道怎么结尾,就这样了吧, 跑步去。
转载于:https://www.cnblogs.com/LittleHann/p/3148349.html