AI 声明
本文由 AI 辅助整理,基于参考资料进行结构化编辑。
概述:Shellcode 是一段机器码,常作为漏洞利用的载荷。本文介绍通过 Visual Studio 编写 C 代码生成 Shellcode 的三种方法。
什么是 Shellcode
Shellcode 是一段可以直接执行的机器码,通常用作漏洞利用中的载荷(Payload)。在渗透测试中,最简单的方式是通过 Metasploit 生成,但在某些场景下需要定制开发。
编写 Shellcode 的三种方式
| 方式 | 优点 | 缺点 |
|---|---|---|
| 直接编写十六进制操作码 | 精确控制 | 难度高、易出错 |
| 高级语言编译后反汇编 | 可读性好 | 需要额外提取步骤 |
| 汇编程序提取 | 高效、可控 | 需要汇编知识 |
一、利用 VC6.0 DEBUG 模式获取 Shellcode
原理
通过调试器下断点,查看 C 代码对应的汇编指令,再从内存中提取机器码。
步骤
1. 编写测试程序并提取汇编代码
#include "stdafx.h"
#include <windows.h>
int main(int argc, char* argv[])
{
MessageBoxA(NULL, NULL, NULL, 0);
return 0;
}在 MessageBoxA 处按 F9 下断点,F5 调试运行,Alt+8 查看汇编:
00401028 mov esi,esp
0040102A push 0
0040102C push 0
0040102E push 0
00401030 push 0
00401032 call dword ptr [__imp__MessageBoxA@16 (0042528c)]获取真实地址
按 Alt+6 打开内存窗口,跳转到
0x0042528c,读取前 4 字节并倒序排列:
- 内存数据:
EA 07 D5 77- 实际地址:
0x77D507EA
2. 编写内联汇编程序
#include "stdafx.h"
#include <windows.h>
int main(int argc, char* argv[])
{
LoadLibrary("user32.dll");
_asm
{
push 0
push 0
push 0
push 0
mov eax, 0x77D507EA
call eax
}
return 0;
}从内存提取机器码(范围 0040103C - 0040104A):
6A 00 6A 00 6A 00 6A 00 B8 EA 07 D5 77 FF D0
3. 加载 Shellcode 测试
#include "stdafx.h"
#include <windows.h>
int main(int argc, char* argv[])
{
LoadLibrary("user32.dll");
char shellcode[] = "\x6A\x00\x6A\x00\x6A\x00\x6A\x00\xB8\xEA\x07\xD5\x77\xFF\xD0";
((void(*)(void))&shellcode)();
return 0;
}ASLR 限制
Windows 7 及以上系统引入了 ASLR(地址空间布局随机化),固定内存地址的方法不再通用。
二、ShellcodeCompiler 自动生成工具
工具特点
- GitHub: NytroRST/ShellcodeCompiler
- C++ 开发,开源
- 借助 NASM 汇编器
- 支持封装 API,生成 bin 格式和 asm 代码
使用方法
Source.txt:
function MessageBoxA("user32.dll");
function ExitProcess("kernel32.dll");
MessageBoxA(0, "This is a MessageBox example", "Shellcode Compiler", 0);
ExitProcess(0);
命令行:
ShellcodeCompiler.exe -r Source.txt -o Shellcode.bin -a Assembly.asm添加 -t 参数可直接测试生成的 Shellcode。
Shellcode 加载器
#include <windows.h>
size_t GetSize(char *szFilePath)
{
size_t size;
FILE* f = fopen(szFilePath, "rb");
fseek(f, 0, SEEK_END);
size = ftell(f);
rewind(f);
fclose(f);
return size;
}
unsigned char* ReadBinaryFile(char *szFilePath, size_t *size)
{
unsigned char *p = NULL;
FILE* f = NULL;
size_t res = 0;
*size = GetSize(szFilePath);
if (*size == 0) return NULL;
f = fopen(szFilePath, "rb");
if (f == NULL) {
printf("Binary file does not exists!\n");
return 0;
}
p = new unsigned char[*size];
rewind(f);
res = fread(p, sizeof(unsigned char), *size, f);
fclose(f);
if (res == 0) {
delete[] p;
return NULL;
}
return p;
}
int main(int argc, char* argv[])
{
char *szFilePath = argv[1];
unsigned char *BinData = NULL;
size_t size = 0;
BinData = ReadBinaryFile(szFilePath, &size);
void *sc = VirtualAlloc(0, size, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (sc == NULL) return 0;
memcpy(sc, BinData, size);
(*(int(*)()) sc)();
return 0;
}三、纯 C++ 编写(支持 x64)
问题
VC 在 64 位下默认不支持内联汇编,ShellcodeCompiler 无法生成 64 位 Shellcode。
解决方案
使用纯 C++ 编写,动态获取 API 地址并调用,最后反汇编提取 Shellcode。
编写 Shellcode 的核心步骤
- 获取
kernel32.dll基地址 - 定位
GetProcAddress函数地址 - 使用
GetProcAddress获取LoadLibrary地址 - 加载所需 DLL
- 获取目标函数地址
- 指定参数并调用
Visual Studio 配置要点
| 配置项 | 设置 | 原因 |
|---|---|---|
| 编译模式 | Release | Debug 模式可能产生逆序函数和位置相关调用 |
| 优化 | 禁用 | 防止编译器优化掉”未使用”的函数 |
| 栈检查 | 禁用 (/Gs) | 栈检查函数位于特定位置,导致无法重定位 |
开源项目
完整代码见 GitHub: 3gstudent/Shellcode-Generater
- 支持 x86 和 x64
- 纯 C++ 实现
- 动态获取函数地址
参考资料
- Windows Shellcode学习笔记——通过VisualStudio生成shellcode - 3gstudent
- X64上恢复VS关键字__asm的方法 - 看雪论坛