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 调用约定
调用者规则
- 保存调用者保存的寄存器(EAX, ECX, EDX)
- 参数以反向顺序压栈
- 使用
call调用子程序 - 子程序返回后:
- 从栈中移除参数
- 恢复调用者保存的寄存器
被调用者规则
-
序言:
push ebp mov ebp, esp sub esp, N ; 为局部变量分配空间 push edi ; 保存被调用者保存的寄存器 push esi -
尾声:
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. 关键要点总结
-
寄存器使用:理解通用寄存器及其特殊用途,特别是ESP和EBP在栈操作中的作用
-
内存寻址:掌握各种寻址模式,特别是基址+偏移量寻址方式
-
调用约定:严格遵守调用约定,确保栈的正确使用和寄存器状态的保存
-
Shellcode构造:
- 动态获取API函数地址
- 正确构造字符串参数
- 注意参数压栈顺序
- 处理内存对齐问题
-
现代系统保护:
- 使用
VirtualAlloc分配可执行内存绕过DEP - 考虑ASLR的影响,可能需要动态解析函数地址
- 使用
-
Shellcode优化:
- 避免空字节(\x00)
- 尽量减小体积
- 使用寄存器间接寻址减少硬编码地址
通过掌握这些x86汇编基础和Shellcode编写技术,可以深入理解计算机底层工作原理和漏洞利用技术。