- UID
- 3808
- 精华
- 积分
- 1480
- 威望
- 点
- 宅币
- 个
- 贡献
- 次
- 宅之契约
- 份
- 最后登录
- 1970-1-1
- 在线时间
- 小时
|
本帖最后由 watermelon 于 2019-2-20 01:18 编辑
元宵节快乐!前两天我的vs2013出了点毛病,实际上不影响什么使用,但是我总是感觉很不爽,使用它的“修复”功能来进行修复,结果发现没有什么用,我就卸载准备重装,卸载它以后我要重装wdk8.1。当天晚上发现虚拟机也出了毛病,修复没有什么用,应该是少了msvc的什么库,好的接着卸载重装,鉴于百度网盘的速度,要想下载vs2013,wdk8.1(这个快,从微软网站下),vmware14.0,win7 x64.iso等玩意儿,时间要好长。
由于SSDT unhook貌似要用到PE的一些知识,所以小弟我想趁这个机会认识一下PE结构,起码也要有一个印象。
由于PE文件结构知识很多,小弟的文中可能会有一些说法欠妥甚至知识型错误的地方,希望各位大大直接批评指正。
先介绍一下啥是PE文件:PE的全称是Portable Executable意思是可以移植的执行体,有挺多文件都是PE格式,比如常见的EXE,DLL,SYS等格式的文件。PE文件都遵循下图的文件结构(32位PE文件,本文只讨论32位的PE文件):
本图片来自于:https://www.0xaa55.com/forum.php?mod=viewthread&tid=529
这些结构体在我们的winnt.h都有定义,所以我们进行学习并且实践的时候可以直接声明定义来用(直接#include <windows.h>即可)。
PE文件可以使用winhex进行查看:
我们可以看到图中圈红线的两部分:4D 5A对应的字符是MZ,这里是PE文件的DOS头开始地方,接下来我们可以看到蓝圈的50 45对应的字符是PE,说明我们这里是一个PE文件。然后PE文件每个部分每个结构各个成员的意义可以看最上面的那张PE结构图。
我主要是学习PE从以下几个方面进行的:1,打印PE文件结构的一些成员的值。2,我自己想改写一下DOS Stub部分的内容,就是把它的“This program cannot be run in DOS mode”(有没有感觉有些语法不对?)改成自己定义的字符串
3,打印我的EXE所用到输出表的函数
第一个比较好写,由于这个PE文件我们用winhex进行查看的时候他是在磁盘上的,并且winnt.h还给我们定义了他们的结构体,所以我们可以用读文件的方式来进行对相应结构体的读取,怎么对相应结构体进行读取呢?用文件偏移来移动文件指针进行读取。
第二个也是很好写,和第一个一样,只不过多了一个写文件的步骤。
比如PE文件初始时候在winhex上显示的是:
运行程序后我们对图中画红圈的字符串进行改写成自定义的(用这个东西去表白不错):
并且更改完这个以后我们的“hello world”程序还是可以正常运行的:
最让人恼火的是第三个,打印输出表的函数。
我们可以从第一张图片中看到要想找到输入表这个结构体,必经之路是IMAGE_DATA_DIRECTORY,我们通过结构体IMAGE_NT_HEADERS.OptionalHeader.DataDirectory[1]一串儿找下去即可。但是IMAGE_DATA_DIRECTORY这个结构体里面只有两个成员,有用的就是那个VirtualAddress这个RVA(相对偏移地址),这个对于我前两个问题用文件读写结构体来说是个问题。一开始我想的是使用RVA+ImageBase来进行内存读写结构体,结果程序崩溃,调试发现指针访问了无法读写的地址,后来发现进程空间,内存空间这两个东东有些区别,并且有的博客说可以用文件映射,将PE文件映射进内存后用文件映射的基址+RVA进行读写,这个方法可以的。(其实一开始我不信那个邪,一直用ImageBase+RVA来找输入表,我知道输入表里面的有些成员不好验证对不对,我就来找调试信息表,因为PEiD可以查看到调试信息表中的一些成员,结果一直不对,程序崩溃,调试显示内存访问不该访问的地址,浪费了挺多时间和白开水)
所以我们就用文件映射的基址和IMAGE_DATA_DIRECTORY中的VirtualAddress在内存中找输入表,找到输入表后我们开始进行对输入表中的函数来查找。
从第一张PE结构图中我们可以看出结构体IMAGE_IMPORT_DIRECTORY中有两个成员OriginalFirstThunk和FirstThunk后面划着线指向别的东西(IMAGE_THUNK_DATA),这两个成员是非常重要的,在PE文件没有被绑定的情况下,OriginalFirstThunk和FirstThunk是一样的。通过我查找一些资料知道了,在一开始的IAMGE_IMPORT_DIRECTORY是一个承载着DLL的数组,每一个元素是一个DLL并且以全0结尾,而我们从PE结构图中看到这个里面还包含了IMAGE_THUNK_DATA这个结构体,这个结构体指向了一个IMAGE_IMPORT_BY_NAME的结构体,而IMAGE_IMPORT_BY_NAME这个结构体里面有我们要的函数名字。所以我们在通过文件映射+RVA找到导入表iid以后通过OriginalFirstThunk或者FirstThunk来找到IAT(建议用OriginalFirstThunk,为什么可以看文末的参考资料),通过IMAGE_THUNK_DATA里的一个RVA(成员是AddressOfData)找到IMAGE_IMPORT_BY_NAME来打印最后的函数名称。
我在这里列出来IMAGE_THUNK_DATA和IMAGE_IMPORT_BY_NAME两个结构体的定义:
- typedef struct _IMAGE_THUNK_DATA32 {
- union {
- DWORD ForwarderString; // PBYTE
- DWORD Function; // PDWORD
- DWORD Ordinal;
- DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME
- } u1;
- } IMAGE_THUNK_DATA32;
复制代码
- typedef struct _IMAGE_IMPORT_BY_NAME {
- WORD Hint;
- CHAR Name[1];
- } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
复制代码
最后是全部的程序实现:
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <windows.h>
- #include <Dbghelp.h>
- // 打印指定PE文件的一些信息
- int PrintPEInfo()
- {
- HANDLE hFile;
- IMAGE_DOS_HEADER ImageDosHeader;
- IMAGE_NT_HEADERS ImageNtHeader;
- IMAGE_SECTION_HEADER *pImageSectionHeader;
- BOOL bStatus;
- DWORD dwRetSize;
- LARGE_INTEGER FileOffset;
- // 打开目标文件
- hFile = CreateFile(L"C:\\Users\\Administrator\\Desktop\\target.exe",
- FILE_ALL_ACCESS,
- FILE_SHARE_READ,
- NULL,
- OPEN_ALWAYS,
- FILE_ATTRIBUTE_NORMAL,
- NULL);
- if (hFile == INVALID_HANDLE_VALUE)
- {
- printf("CreateFile error:%d\n", GetLastError());
- return 1;
- }
- // 将PE文件的DOS头部的内容读到结构体ImageDosHeader中
- bStatus = ReadFile(hFile,
- &ImageDosHeader,
- sizeof(IMAGE_DOS_HEADER),
- &dwRetSize,
- NULL);
- if (bStatus == FALSE)
- {
- printf("Read image_dos_header failed:%d\n", GetLastError());
- goto cleanup;
- }
- // 打印MZ和PE头偏移量
- printf("ImageDosHeader.e_magic:%s\nImageDosHeader.e_lfanew:%X\n", &ImageDosHeader.e_magic, ImageDosHeader.e_lfanew);
- // 设置文件的读写指针
- FileOffset.QuadPart = ImageDosHeader.e_lfanew;
- bStatus = SetFilePointerEx(hFile, FileOffset, NULL, FILE_BEGIN);
- if (bStatus == FALSE)
- {
- printf("SetFilePointerEx error:%d\n", GetLastError());
- goto cleanup;
- }
- // 读取PE头
- bStatus = ReadFile(hFile,
- &ImageNtHeader,
- sizeof(IMAGE_NT_HEADERS),
- &dwRetSize,
- NULL);
- if (bStatus == FALSE)
- {
- printf("ReadFile PE error:%d\n", GetLastError());
- goto cleanup;
- }
- // 打印“PE”
- printf("ImageNtHeader.Signature:%s\n", &ImageNtHeader.Signature);
- // 打印镜像基址
- printf("ImageBase:%x\n", ImageNtHeader.OptionalHeader.ImageBase);
- // 打印区块的数量
- printf("ImageNtHeader.FileHeader.NumberOfSections:%d\n", ImageNtHeader.FileHeader.NumberOfSections);
- FileOffset.QuadPart = FileOffset.QuadPart + sizeof(IMAGE_NT_HEADERS);
- bStatus = SetFilePointerEx(hFile, FileOffset, NULL, FILE_BEGIN);
- if (bStatus == FALSE)
- {
- printf("SetFilePointerEx error:%d\n", GetLastError());
- goto cleanup;
- }
- // 用一个数组来承载描述每个区块的信息,便于到时候打印
- pImageSectionHeader = (IMAGE_SECTION_HEADER*)malloc(sizeof(IMAGE_SECTION_HEADER)*ImageNtHeader.FileHeader.NumberOfSections);
- if (pImageSectionHeader == NULL)
- {
- printf("pImageSectionHeader malloc error:%d\n", GetLastError());
- goto cleanup;
- }
- bStatus = ReadFile(hFile,
- pImageSectionHeader,
- sizeof(IMAGE_SECTION_HEADER)*ImageNtHeader.FileHeader.NumberOfSections,
- &dwRetSize,
- NULL);
- if (bStatus == FALSE)
- {
- printf("Read image_section_header error:%d\n", GetLastError());
- free(pImageSectionHeader);
- goto cleanup;
- }
- // 把区块的名字打印出来
- for (int i = 0; i < ImageNtHeader.FileHeader.NumberOfSections; i++)
- {
- printf("pImageSectionHeader[%d]:%s\n", i + 1, pImageSectionHeader[i].Name);
- }
- free(pImageSectionHeader);
- cleanup:
- CloseHandle(hFile);
- return 0;
- }
- // 改写一小部分的DOS stub
- void WritePE()
- {
- HANDLE hFile;
- DWORD dwRetSize;
- char buffer[1024];
- char me[] = "Watermelon is watermelon, forever is forever!";
- LARGE_INTEGER FileOffset;
- BOOL bStatus;
- hFile = CreateFile(L"C:\\Users\\Administrator\\Desktop\\target.exe",
- FILE_ALL_ACCESS,
- FILE_SHARE_WRITE,
- NULL,
- OPEN_ALWAYS,
- FILE_ATTRIBUTE_NORMAL,
- NULL);
- if (hFile == INVALID_HANDLE_VALUE)
- {
- printf("[WritePE] CreateFile error:%d\n", GetLastError());
- goto cleanup;
- }
- bStatus = ReadFile(hFile,
- buffer,
- 0x100,
- &dwRetSize,
- NULL);
- if (bStatus = FALSE)
- {
- printf("[WritePE] ReadFile error:%d\n", GetLastError());
- goto cleanup;
- }
- for (int i = 0; i < 0x100; i++)
- {
- if (buffer[i] == 'T' && buffer[i + 1] == 'h' && buffer[i + 2] == 'i' && buffer[i + 3] == 's')
- {
- FileOffset.QuadPart = i;
- break;
- }
- }
- // 重置文件指针
- SetFilePointerEx(hFile,
- FileOffset,
- NULL,
- FILE_BEGIN);
- bStatus = WriteFile(hFile,
- me,
- strlen(me),
- &dwRetSize,
- NULL);
- if (bStatus == FALSE)
- {
- printf("[WritePE] WriteFile error:%d\n", GetLastError());
- goto cleanup;
- }
- printf("WriteFile has done...\n");
- cleanup:
- CloseHandle(hFile);
-
- }
- // 读取导入表,打印函数
- void PrintPEFunction()
- {
- HANDLE hFile = CreateFile(L"C:\\Users\\Administrator\\Desktop\\target.exe",
- GENERIC_READ,
- FILE_SHARE_READ,
- NULL,
- OPEN_EXISTING,
- FILE_ATTRIBUTE_NORMAL,
- NULL);
- if (hFile == INVALID_HANDLE_VALUE)
- {
- printf("[test] CreateFile errror:%d\n", GetLastError());
- return;
- }
- // 创建文件映射,使用内存来找导入表
- HANDLE hMap = CreateFileMapping(hFile, NULL,
- PAGE_READONLY,
- 0,
- 0,
- NULL);
- if (hMap == INVALID_HANDLE_VALUE)
- {
- printf("[test] CreateFileMapping error:%d\n", GetLastError());
- goto clean;
- }
- UCHAR *lpBaseAddress = (UCHAR*)MapViewOfFile(hMap,
- FILE_MAP_READ, 0, 0, 0);
- if (lpBaseAddress == NULL)
- {
- printf("[test] MapViewOfFile error:%d\n", GetLastError());
- goto cleanup;
- }
- PIMAGE_DOS_HEADER pImageDosHeader = (PIMAGE_DOS_HEADER)lpBaseAddress;
- PIMAGE_NT_HEADERS pImageNtHeader = (PIMAGE_NT_HEADERS)(lpBaseAddress + pImageDosHeader->e_lfanew);
- // 获取导入表的RVA
- DWORD VirtualAddress = pImageNtHeader->OptionalHeader.DataDirectory[1].VirtualAddress;
- if (VirtualAddress == 0)
- {
- printf("VirtualAddress为0,请更换别的PE文件或者联系我:[email]starlight_chou@163.com[/email]");
- goto cleanup;
- }
- // 获取导入表地址
- PIMAGE_IMPORT_DESCRIPTOR pImportTable = (PIMAGE_IMPORT_DESCRIPTOR)ImageRvaToVa(pImageNtHeader,
- lpBaseAddress,
- VirtualAddress,
- NULL);
- IMAGE_IMPORT_DESCRIPTOR iid;
- IMAGE_THUNK_DATA thunk;
- // 由于以0(NULL)结尾,所以我们在这里置零
- memset(&iid, 0, sizeof(IMAGE_IMPORT_DESCRIPTOR));
- memset(&thunk, 0, sizeof(IMAGE_THUNK_DATA));
- // 开始进行遍历
- for (int i = 0; memcmp(pImportTable + i, &iid, sizeof(IMAGE_IMPORT_DESCRIPTOR)); i++)
- {
- // 获取dll名字,见32位PE结构表
- printf("\n第%d个DLL,名称:%s\n", i+1, (CHAR*)ImageRvaToVa(pImageNtHeader,
- lpBaseAddress,
- pImportTable[i].Name,
- NULL));
- // 开始遍历target.exe从对应的DLL中用到了那些函数(IAT)
- PIMAGE_THUNK_DATA32 pThunk = (PIMAGE_THUNK_DATA32)ImageRvaToVa(pImageNtHeader,
- lpBaseAddress,
- pImportTable[i].OriginalFirstThunk, // 无绑定情况下和pImportTable[i].FirstThunk一样的。
- NULL);
- for (int j = 0; memcmp(pThunk + j, &thunk, sizeof(IMAGE_THUNK_DATA)); j++)
- {
- // 通过RVA最高位判断函数的导入方式,
- // 如果最高位为1,按序号导入,否则按照名称导入
- if (pThunk[j].u1.AddressOfData & IMAGE_ORDINAL_FLAG32)
- {
- printf("[%d]\t%ld", j+1, pThunk[j].u1.AddressOfData & 0xffff);
- }
- else
- {
- // 按照名称导入
- // 重新判断地址
- PIMAGE_IMPORT_BY_NAME pFuncName = (PIMAGE_IMPORT_BY_NAME)ImageRvaToVa(pImageNtHeader,
- lpBaseAddress,
- pThunk[j].u1.AddressOfData,
- NULL);
- printf("[%d]\t%ld\t%s\n", j+1, pFuncName->Hint, pFuncName->Name);
- }
- }
- }
- cleanup:
- CloseHandle(hMap);
- clean:
- CloseHandle(hFile);
- return;
- }
- int main(void)
- {
- printf("\n\n#########################################\n\n");
- PrintPEInfo();
- printf("\n\n#########################################\n\n");
- WritePE();
- printf("\n\n#########################################\n\n");
- PrintPEFunction();
- printf("\n\n#########################################\n\n");
- getchar();
- return 0;
- }
复制代码
运行结果(更改DOS stub的那部分运行结果见上文):
- #########################################
- ImageDosHeader.e_magic:MZ?
- ImageDosHeader.e_lfanew:E8
- ImageNtHeader.Signature:PE
- ImageBase:65766572
- ImageNtHeader.FileHeader.NumberOfSections:5
- pImageSectionHeader[1]:.text
- pImageSectionHeader[2]:.rdata
- pImageSectionHeader[3]:.data
- pImageSectionHeader[4]:.rsrc
- pImageSectionHeader[5]:.reloc
- #########################################
- WriteFile has done...
- #########################################
- 第1个DLL,名称:MSVCR120.dll
- [1] 576 _configthreadlocale
- [2] 500 __setusermatherr
- [3] 781 _initterm_e
- [4] 780 _initterm
- [5] 439 __initenv
- [6] 674 _fmode
- [7] 575 _commode
- [8] 592 _crt_debugger_hook
- [9] 428 __crtUnhandledException
- [10] 427 __crtTerminateProcess
- [11] 559 _cexit
- [12] 309 ?terminate@@YAXXZ
- [13] 425 __crtSetUnhandledExceptionFilter
- [14] 916 _lock
- [15] 1284 _unlock
- [16] 558 _calloc_crt
- [17] 430 __dllonexit
- [18] 1082 _onexit
- [19] 788 _invoke_watson
- [20] 579 _controlfp_s
- [21] 634 _except_handler4_common
- [22] 643 _exit
- [23] 1614 exit
- [24] 498 __set_app_type
- [25] 438 __getmainargs
- [26] 535 _amsg_exit
- [27] 363 _XcptFilter
- [28] 1682 getchar
- [29] 1789 printf
- 第2个DLL,名称:KERNEL32.dll
- [1] 726 GetSystemTimeAsFileTime
- [2] 526 GetCurrentThreadId
- [3] 522 GetCurrentProcessId
- [4] 1069 QueryPerformanceCounter
- [5] 877 IsProcessorFeaturePresent
- [6] 871 IsDebuggerPresent
- [7] 289 EncodePointer
- [8] 254 DecodePointer
- #########################################
复制代码
实验的目标文件target.exe是一个helloworld程序:
- #include <stdio.h>
- #include <stdlib.h>
- int main(void)
- {
- printf("hello world\n");
- getchar();
- return 0;
- }
复制代码
参考资料:
https://www.0xaa55.com/forum.php ... 13&highlight=PE
https://www.0xaa55.com/forum.php ... 29&highlight=PE
https://bbs.pediy.com/thread-21932.htm
https://baike.baidu.com/item/pe% ... /6488140?fr=aladdin
https://baike.baidu.com/item/%E8 ... /1628932?fr=aladdin
以及众多前辈的博客和文章,由于当时没有做好记录,属实不好意思,感谢前辈们的分享。
最后附上看雪的(参考资料中的第三个连接)中的附件,可以用这个对照着PE结构表进行分析学习。
|
|