名称:二进制分析模式 描述:掌握二进制分析模式,包括反汇编、反编译、控制流分析和代码模式识别。用于分析可执行文件、理解编译代码或对二进制文件执行静态分析。
二进制分析模式
全面的模式和技术,用于分析编译二进制文件、理解汇编代码和重构程序逻辑。
反汇编基础
x86-64 指令模式
函数序言/尾声
; 标准序言
push rbp ; 保存基指针
mov rbp, rsp ; 设置栈帧
sub rsp, 0x20 ; 分配局部变量
; 叶函数(无调用)
; 可能跳过帧指针设置
sub rsp, 0x18 ; 仅分配局部变量
; 标准尾声
mov rsp, rbp ; 恢复栈指针
pop rbp ; 恢复基指针
ret
; Leave 指令(等效)
leave ; mov rsp, rbp; pop rbp
ret
调用约定
System V AMD64 (Linux, macOS)
; 参数:RDI, RSI, RDX, RCX, R8, R9,然后栈
; 返回:RAX(和 RDX 用于 128 位)
; 调用者保存:RAX, RCX, RDX, RSI, RDI, R8-R11
; 被调用者保存:RBX, RBP, R12-R15
; 示例:func(a, b, c, d, e, f, g)
mov rdi, [a] ; 第一个参数
mov rsi, [b] ; 第二个参数
mov rdx, [c] ; 第三个参数
mov rcx, [d] ; 第四个参数
mov r8, [e] ; 第五个参数
mov r9, [f] ; 第六个参数
push [g] ; 第七个参数在栈上
call func
Microsoft x64 (Windows)
; 参数:RCX, RDX, R8, R9,然后栈
; 阴影空间:在栈上保留 32 字节
; 返回:RAX
; 示例:func(a, b, c, d, e)
sub rsp, 0x28 ; 阴影空间 + 对齐
mov rcx, [a] ; 第一个参数
mov rdx, [b] ; 第二个参数
mov r8, [c] ; 第三个参数
mov r9, [d] ; 第四个参数
mov [rsp+0x20], [e] ; 第五个参数在栈上
call func
add rsp, 0x28
ARM 汇编模式
ARM64 (AArch64) 调用约定
; 参数:X0-X7
; 返回:X0(和 X1 用于 128 位)
; 帧指针:X29
; 链接寄存器:X30
; 函数序言
stp x29, x30, [sp, #-16]! ; 保存 FP 和 LR
mov x29, sp ; 设置帧指针
; 函数尾声
ldp x29, x30, [sp], #16 ; 恢复 FP 和 LR
ret
ARM32 调用约定
; 参数:R0-R3,然后栈
; 返回:R0(和 R1 用于 64 位)
; 链接寄存器:LR (R14)
; 函数序言
push {fp, lr}
add fp, sp, #4
; 函数尾声
pop {fp, pc} ; 通过弹出 PC 返回
控制流模式
条件分支
; if (a == b)
cmp eax, ebx
jne skip_block
; ... if 主体 ...
skip_block:
; if (a < b) - 有符号
cmp eax, ebx
jge skip_block ; 如果大于或等于则跳转
; ... if 主体 ...
skip_block:
; if (a < b) - 无符号
cmp eax, ebx
jae skip_block ; 如果高于或等于则跳转
; ... if 主体 ...
skip_block:
循环模式
; for (int i = 0; i < n; i++)
xor ecx, ecx ; i = 0
loop_start:
cmp ecx, [n] ; i < n
jge loop_end
; ... 循环主体 ...
inc ecx ; i++
jmp loop_start
loop_end:
; while (条件)
jmp loop_check
loop_body:
; ... 主体 ...
loop_check:
cmp eax, ebx
jl loop_body
; do-while
loop_body:
; ... 主体 ...
cmp eax, ebx
jl loop_body
Switch 语句模式
; 跳转表模式
mov eax, [switch_var]
cmp eax, max_case
ja default_case
jmp [jump_table + eax*8]
; 顺序比较(小型 switch)
cmp eax, 1
je case_1
cmp eax, 2
je case_2
cmp eax, 3
je case_3
jmp default_case
数据结构模式
数组访问
; array[i] - 4 字节元素
mov eax, [rbx + rcx*4] ; rbx=基址,rcx=索引
; array[i] - 8 字节元素
mov rax, [rbx + rcx*8]
; 多维数组 array[i][j]
; arr[i][j] = base + (i * cols + j) * element_size
imul eax, [cols]
add eax, [j]
mov edx, [rbx + rax*4]
结构访问
struct Example {
int a; // 偏移 0
char b; // 偏移 4
// 填充 // 偏移 5-7
long c; // 偏移 8
short d; // 偏移 16
};
; 访问结构字段
mov rdi, [struct_ptr]
mov eax, [rdi] ; s->a (偏移 0)
movzx eax, byte [rdi+4] ; s->b (偏移 4)
mov rax, [rdi+8] ; s->c (偏移 8)
movzx eax, word [rdi+16] ; s->d (偏移 16)
链表遍历
; while (node != NULL)
list_loop:
test rdi, rdi ; node == NULL?
jz list_done
; ... 处理节点 ...
mov rdi, [rdi+8] ; node = node->next (假设 next 在偏移 8)
jmp list_loop
list_done:
常见代码模式
字符串操作
; strlen 模式
xor ecx, ecx
strlen_loop:
cmp byte [rdi + rcx], 0
je strlen_done
inc ecx
jmp strlen_loop
strlen_done:
; ecx 包含长度
; strcpy 模式
strcpy_loop:
mov al, [rsi]
mov [rdi], al
test al, al
jz strcpy_done
inc rsi
inc rdi
jmp strcpy_loop
strcpy_done:
; memcpy 使用 rep movsb
mov rdi, dest
mov rsi, src
mov rcx, count
rep movsb
算术模式
; 乘以常数
; x * 3
lea eax, [rax + rax*2]
; x * 5
lea eax, [rax + rax*4]
; x * 10
lea eax, [rax + rax*4] ; x * 5
add eax, eax ; * 2
; 除以 2 的幂(有符号)
mov eax, [x]
cdq ; 符号扩展到 EDX:EAX
and edx, 7 ; 对于除以 8
add eax, edx ; 调整负数
sar eax, 3 ; 算术右移
; 模 2 的幂
and eax, 7 ; x % 8
位操作
; 测试特定位
test eax, 0x80 ; 测试位 7
jnz bit_set
; 设置位
or eax, 0x10 ; 设置位 4
; 清除位
and eax, ~0x10 ; 清除位 4
; 切换位
xor eax, 0x10 ; 切换位 4
; 计数前导零
bsr eax, ecx ; 位扫描反向
xor eax, 31 ; 转换为前导零
; 人口计数(popcnt)
popcnt eax, ecx ; 计数设置位
反编译模式
变量恢复
; 局部变量在 rbp-8
mov qword [rbp-8], rax ; 存储到局部
mov rax, [rbp-8] ; 从局部加载
; 栈分配数组
lea rax, [rbp-0x40] ; 数组起始于 rbp-0x40
mov [rax], edx ; array[0] = 值
mov [rax+4], ecx ; array[1] = 值
函数签名恢复
; 通过寄存器使用识别参数
func:
; rdi 用作第一个参数(System V)
mov [rbp-8], rdi ; 保存参数到局部
; rsi 用作第二个参数
mov [rbp-16], rsi
; 通过结尾的 RAX 识别返回
mov rax, [result]
ret
类型恢复
; 1 字节操作建议 char/bool
movzx eax, byte [rdi] ; 零扩展字节
movsx eax, byte [rdi] ; 符号扩展字节
; 2 字节操作建议 short
movzx eax, word [rdi]
movsx eax, word [rdi]
; 4 字节操作建议 int/float
mov eax, [rdi]
movss xmm0, [rdi] ; 浮点数
; 8 字节操作建议 long/double/指针
mov rax, [rdi]
movsd xmm0, [rdi] ; 双精度浮点数
Ghidra 分析技巧
改进反编译
// 在 Ghidra 脚本中
// 修复函数签名
Function func = getFunctionAt(toAddr(0x401000));
func.setReturnType(IntegerDataType.dataType, SourceType.USER_DEFINED);
// 创建结构类型
StructureDataType struct = new StructureDataType("MyStruct", 0);
struct.add(IntegerDataType.dataType, "field_a", null);
struct.add(PointerDataType.dataType, "next", null);
// 应用到内存
createData(toAddr(0x601000), struct);
模式匹配脚本
# 查找所有对危险函数的调用
for func in currentProgram.getFunctionManager().getFunctions(True):
for ref in getReferencesTo(func.getEntryPoint()):
if func.getName() in ["strcpy", "sprintf", "gets"]:
print(f"危险调用在 {ref.getFromAddress()}")
IDA Pro 模式
IDAPython 分析
import idaapi
import idautils
import idc
# 查找所有函数调用
def find_calls(func_name):
for func_ea in idautils.Functions():
for head in idautils.Heads(func_ea, idc.find_func_end(func_ea)):
if idc.print_insn_mnem(head) == "call":
target = idc.get_operand_value(head, 0)
if idc.get_func_name(target) == func_name:
print(f"调用 {func_name} 在 {hex(head)}")
# 基于字符串重命名函数
def auto_rename():
for s in idautils.Strings():
for xref in idautils.XrefsTo(s.ea):
func = idaapi.get_func(xref.frm)
if func and "sub_" in idc.get_func_name(func.start_ea):
# 使用字符串作为命名提示
pass
最佳实践
分析工作流
- 初始分类:文件类型、架构、导入/导出
- 字符串分析:识别有趣字符串、错误消息
- 函数识别:入口点、导出、交叉引用
- 控制流映射:理解程序结构
- 数据结构恢复:识别结构、数组、全局变量
- 算法识别:加密、哈希、压缩
- 文档化:注释、重命名符号、类型定义
常见陷阱
- 优化器伪影:代码可能与源结构不匹配
- 内联函数:函数可能被内联展开
- 尾调用优化:使用
jmp代替call+ret - 死代码:优化产生的不可达代码
- 位置无关代码:RIP 相对寻址