前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >无可执行权限加载 ShellCode

无可执行权限加载 ShellCode

作者头像
红队蓝军
发布2024-02-26 17:24:44
2040
发布2024-02-26 17:24:44
举报
文章被收录于专栏:红队蓝军

简单来说就是可以直接加载可读内存中的加密 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[] = { '\0' };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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-02-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 红队蓝军 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档