x86汇编与编写shellcode
字数 1198 2025-08-22 12:23:18

x86汇编与Shellcode编写全面指南

1. x86汇编基础

1.1 寄存器系统

现代x86处理器(386及更高版本)具有8个32位通用寄存器:

  • EAX:累加器(Accumulator),常用于算术运算
  • EBX:基址寄存器(Base)
  • ECX:计数器(Counter),用于循环计数
  • EDX:数据寄存器(Data)
  • ESI:源索引寄存器(Source Index)
  • EDI:目的索引寄存器(Destination Index)
  • ESP:栈指针(Stack Pointer)
  • EBP:基址指针(Base Pointer)

子寄存器

  • 16位:AX, BX, CX, DX
  • 8位:AL, BL, CL, DL (低8位);AH, BH, CH, DH (高8位)

1.2 内存与寻址模式

静态数据声明

使用.DATA指令声明静态数据区域:

.DATA
var DB 64      ; 声明一个字节,值为64
var2 DB ?      ; 声明未初始化的字节
X DW ?         ; 声明2字节未初始化值
Y DD 30000     ; 声明4字节值,初始化为30000

数组声明:

Z DD 1, 2, 3           ; 三个4字节值
bytes DB 10 DUP(?)     ; 10个未初始化字节
arr DD 100 DUP(0)      ; 100个4字节字,初始化为0
str DB 'hello', 0      ; 6字节,包含hello和空字符

内存寻址

x86使用32位内存地址,支持多种寻址模式:

mov eax, [ebx]         ; EBX地址处的4字节数据到EAX
mov [var], ebx         ; EBX内容到var地址
mov eax, [esi-4]       ; ESI-4地址处的数据到EAX
mov [esi+eax], cl      ; CL内容到ESI+EAX地址
mov edx, [esi+4*ebx]   ; ESI+4*EBX地址处的数据到EDX

无效寻址

mov eax, [ebx-ecx]        ; 不支持寄存器相减
mov [eax+esi+edi], ebx    ; 最多2个寄存器相加

大小指令

当内存操作数大小不明确时使用:

mov BYTE PTR [ebx], 2    ; 1字节操作
mov WORD PTR [ebx], 2    ; 2字节操作
mov DWORD PTR [ebx], 2   ; 4字节操作

1.3 常用指令

数据移动指令

  • mov:数据移动

    mov eax, ebx
    mov byte ptr [var], 5
    
  • push/pop:栈操作

    push eax
    push [var]
    pop edi
    pop [ebx]
    
  • lea:加载有效地址

    lea edi, [ebx+4*esi]
    lea eax, [var]
    

算术与逻辑指令

  • add/sub:加减法

    add eax, 10
    sub al, ah
    
  • inc/dec:自增/自减

    inc eax
    dec DWORD PTR [var]
    
  • imul/idiv:乘除法

    imul eax, [var]
    idiv ebx
    
  • and/or/xor/not:逻辑运算

    and eax, 0fH
    xor edx, edx
    not BYTE PTR [var]
    
  • shl/shr:移位

    shl eax, 1
    shr ebx, cl
    

控制流指令

  • jmp:无条件跳转

    jmp begin
    
  • 条件跳转

    je label    ; 等于
    jne label   ; 不等于
    jg label    ; 大于
    jl label    ; 小于
    
  • cmp:比较

    cmp eax, ebx
    jle done
    
  • call/ret:子程序调用与返回

    call sub_func
    ret
    

1.4 调用约定

调用者规则

  1. 保存调用者保存的寄存器(EAX, ECX, EDX)
  2. 参数以反向顺序压栈
  3. 使用call调用子程序
  4. 子程序返回后:
    • 从栈中移除参数
    • 恢复调用者保存的寄存器

被调用者规则

  1. 序言

    push ebp
    mov ebp, esp
    sub esp, N    ; 为局部变量分配空间
    push edi       ; 保存被调用者保存的寄存器
    push esi
    
  2. 尾声

    pop esi        ; 恢复寄存器
    pop edi
    mov esp, ebp   ; 释放局部变量空间
    pop ebp        ; 恢复调用者的基址指针
    ret
    

2. Shellcode编写实战

2.1 MessageBoxA示例

C语言原型:

MessageBoxA(NULL, "Hello", "Message", MB_OK|MB_ICONINFORMATION);

获取函数地址

#include <windows.h>
#include <stdio.h>

int main() {
    HINSTANCE LibHandle = LoadLibraryA("user32.dll");
    printf("user32 Address = 0x%p\n", LibHandle);
    
    LPTSTR getaddr = (LPTSTR)GetProcAddress(LibHandle, "MessageBoxA");
    printf("MessageBoxA Address = 0x%p\n", getaddr);
    
    getchar();
    return 0;
}

汇编实现

push ebp
mov ebp, esp
xor ebx, ebx        ; 清零EBX
sub esp, 16         ; 分配栈空间

; 构造"Hello"字符串 (6字节)
lea eax, [ebp-12]
mov byte ptr [eax], 'H'
mov byte ptr [eax+1], 'e'
mov byte ptr [eax+2], 'l'
mov byte ptr [eax+3], 'l'
mov byte ptr [eax+4], 'o'
mov byte ptr [eax+5], 0

; 构造"Message"字符串 (8字节)
lea eax, [ebp-6]
mov byte ptr [eax], 'M'
mov byte ptr [eax+1], 'e'
mov byte ptr [eax+2], 's'
mov byte ptr [eax+3], 's'
mov byte ptr [eax+4], 'a'
mov byte ptr [eax+5], 'g'
mov byte ptr [eax+6], 'e'
mov byte ptr [eax+7], 0

; 压入参数(反向顺序)
push 0x40           ; MB_OK|MB_ICONINFORMATION
lea ebx, [ebp-6]    ; "Message"
push ebx
lea ebx, [ebp-12]   ; "Hello"
push ebx
push 0              ; hWnd = NULL

; 调用MessageBoxA
mov eax, 0x759E0B30 ; MessageBoxA地址
call eax

; 清理栈
add esp, 16
pop ebp
ret

Shellcode机器码

\x55\x89\xE5\x31\xDB\x83\xEC\x10\x8D\x45\xF4\xC6\x00\x48\xC6\x40\x01
\x65\xC6\x40\x02\x6C\xC6\x40\x03\x6C\xC6\x40\x04\x6F\xC6\x40\x05\x00
\x8D\x45\xFA\xC6\x00\x4D\xC6\x40\x01\x65\xC6\x40\x02\x73\xC6\x40\x03
\x73\xC6\x40\x04\x61\xC6\x40\x05\x67\xC6\x40\x06\x65\xC6\x40\x07\x00
\x6A\x40\x8D\x5D\xFA\x53\x8D\x5D\xF4\x53\x6A\x00\xB8\x30\x0B\x37\x76
\xFF\xD0\x83\xC4\x10\x5D\xC3

2.2 Shellcode执行

现代系统有DEP和ASLR保护,需要使用VirtualAlloc分配可执行内存:

#include <stdio.h>
#include <windows.h>

unsigned char shellcode[] = "..."; // 上面的机器码

int main() {
    // 获取MessageBoxA地址
    HMODULE hUser32 = LoadLibraryA("user32.dll");
    FARPROC pMessageBoxA = GetProcAddress(hUser32, "MessageBoxA");
    
    // 分配可执行内存
    void *exec_mem = VirtualAlloc(NULL, sizeof(shellcode), 
                                 MEM_COMMIT|MEM_RESERVE, 
                                 PAGE_READWRITE);
    
    // 复制Shellcode
    memcpy(exec_mem, shellcode, sizeof(shellcode));
    
    // 修改内存保护为可执行
    DWORD oldProtect;
    VirtualProtect(exec_mem, sizeof(shellcode), 
                  PAGE_EXECUTE_READ, &oldProtect);
    
    // 执行Shellcode
    void (*execute_shellcode)() = (void(*)())exec_mem;
    execute_shellcode();
    
    // 清理
    VirtualFree(exec_mem, 0, MEM_RELEASE);
    return 0;
}

3. 关键要点总结

  1. 寄存器使用:理解通用寄存器及其特殊用途,特别是ESP和EBP在栈操作中的作用

  2. 内存寻址:掌握各种寻址模式,特别是基址+偏移量寻址方式

  3. 调用约定:严格遵守调用约定,确保栈的正确使用和寄存器状态的保存

  4. Shellcode构造

    • 动态获取API函数地址
    • 正确构造字符串参数
    • 注意参数压栈顺序
    • 处理内存对齐问题
  5. 现代系统保护

    • 使用VirtualAlloc分配可执行内存绕过DEP
    • 考虑ASLR的影响,可能需要动态解析函数地址
  6. Shellcode优化

    • 避免空字节(\x00)
    • 尽量减小体积
    • 使用寄存器间接寻址减少硬编码地址

通过掌握这些x86汇编基础和Shellcode编写技术,可以深入理解计算机底层工作原理和漏洞利用技术。

x86汇编与Shellcode编写全面指南 1. x86汇编基础 1.1 寄存器系统 现代x86处理器(386及更高版本)具有8个32位通用寄存器: EAX :累加器(Accumulator),常用于算术运算 EBX :基址寄存器(Base) ECX :计数器(Counter),用于循环计数 EDX :数据寄存器(Data) ESI :源索引寄存器(Source Index) EDI :目的索引寄存器(Destination Index) ESP :栈指针(Stack Pointer) EBP :基址指针(Base Pointer) 子寄存器 : 16位:AX, BX, CX, DX 8位:AL, BL, CL, DL (低8位);AH, BH, CH, DH (高8位) 1.2 内存与寻址模式 静态数据声明 使用 .DATA 指令声明静态数据区域: 数组声明: 内存寻址 x86使用32位内存地址,支持多种寻址模式: 无效寻址 : 大小指令 当内存操作数大小不明确时使用: 1.3 常用指令 数据移动指令 mov :数据移动 push/pop :栈操作 lea :加载有效地址 算术与逻辑指令 add/sub :加减法 inc/dec :自增/自减 imul/idiv :乘除法 and/or/xor/not :逻辑运算 shl/shr :移位 控制流指令 jmp :无条件跳转 条件跳转 : cmp :比较 call/ret :子程序调用与返回 1.4 调用约定 调用者规则 保存调用者保存的寄存器(EAX, ECX, EDX) 参数以 反向顺序 压栈 使用 call 调用子程序 子程序返回后: 从栈中移除参数 恢复调用者保存的寄存器 被调用者规则 序言 : 尾声 : 2. Shellcode编写实战 2.1 MessageBoxA示例 C语言原型: 获取函数地址 汇编实现 Shellcode机器码 2.2 Shellcode执行 现代系统有DEP和ASLR保护,需要使用 VirtualAlloc 分配可执行内存: 3. 关键要点总结 寄存器使用 :理解通用寄存器及其特殊用途,特别是ESP和EBP在栈操作中的作用 内存寻址 :掌握各种寻址模式,特别是基址+偏移量寻址方式 调用约定 :严格遵守调用约定,确保栈的正确使用和寄存器状态的保存 Shellcode构造 : 动态获取API函数地址 正确构造字符串参数 注意参数压栈顺序 处理内存对齐问题 现代系统保护 : 使用 VirtualAlloc 分配可执行内存绕过DEP 考虑ASLR的影响,可能需要动态解析函数地址 Shellcode优化 : 避免空字节(\x00) 尽量减小体积 使用寄存器间接寻址减少硬编码地址 通过掌握这些x86汇编基础和Shellcode编写技术,可以深入理解计算机底层工作原理和漏洞利用技术。