Loading...

文章背景图

阶段三:进阶编程

a0yark a0yark
|
2025-12-03
|
0
|
-
|
- min
|

阶段三:进阶编程

📅 创建日期: 2025-01-XX
⏱️ 预计学时: 6-8 周
🎯 学习目标: 掌握 C++ 面向对象编程、x64 汇编、保护研究基础
📋 前置要求: [[阶段二:初级逆向]]
🔗 返回: [[逆向与驱动开发学习路径(详细版)]]


📚 本阶段内容概览

  • [[#3.1 C++编程|3.1 C++编程]]
    • [[#3.1.1 面向对象编程|面向对象编程]]
    • [[#3.1.2 虚表机制|虚表机制]]
    • [[#3.1.3 STL容器逆向|STL容器逆向]]
  • [[#3.2 x64汇编|3.2 x64汇编]]
    • [[#3.2.1 x64寄存器|x64寄存器]]
    • [[#3.2.2 x64调用约定|x64调用约定]]
    • [[#3.2.3 x64特有指令|x64特有指令]]
  • [[#3.3 保护研究入门|3.3 保护研究入门]]

3.1 C++编程

3.1.1 面向对象编程

📝 学习笔记

类与对象的内存布局

// 简单类的内存布局
class SimpleClass {
public:
    int a;      // offset: 0
    char b;     // offset: 4
    // padding: 3 bytes
    double c;   // offset: 8
};
// sizeof(SimpleClass) = 16 (考虑内存对齐)

// 带虚函数的类
class VirtualClass {
public:
    virtual void func1() {}  // 虚表指针在最前
    virtual void func2() {}
    int member;              // offset: 8 (x64)
};
// sizeof(VirtualClass) = 16 (x64: 8字节vptr + 4字节int + 4字节padding)

继承的内存布局

class Base {
public:
    int baseValue;           // offset: 0
    virtual void baseFunc() {}
};

class Derived : public Base {
public:
    int derivedValue;        // offset: 基类大小之后
    virtual void derivedFunc() {}
};

// 多重继承
class A { int a; virtual void funcA() {} };
class B { int b; virtual void funcB() {} };
class C : public A, public B {
    int c;
    // C有两个虚表指针!
};

[!important] 关键概念

  • 单继承: 只有一个虚表指针,派生类虚函数追加到基类虚表
  • 多重继承: 每个有虚函数的基类都有自己的虚表指针
  • 虚继承: 使用虚基类表(vbtable)解决菱形继承问题

3.1.2 虚表机制

📝 学习笔记

虚表结构深度分析

class Animal {
public:
    virtual void speak() { cout << "Animal" << endl; }
    virtual void move() { cout << "Moving" << endl; }
    int age;
};

class Dog : public Animal {
public:
    virtual void speak() override { cout << "Woof!" << endl; }
    virtual void fetch() { cout << "Fetching" << endl; }  // 新虚函数
    int breed;
};

内存布局图解

Dog对象内存布局 (x64):
+------------------+
| vptr (8 bytes)   | --> Dog虚表
+------------------+
| age (4 bytes)    |
+------------------+
| breed (4 bytes)  |
+------------------+

Dog虚表:
+------------------+
| &Dog::speak      | --> 重写的函数
+------------------+
| &Animal::move    | --> 继承的函数
+------------------+
| &Dog::fetch      | --> 新增的虚函数
+------------------+

虚表在逆向中的识别

; 虚函数调用的汇编特征 (x64)
mov rax, [rcx]          ; rcx = this指针, [rcx] = 虚表指针
call qword ptr [rax+8]  ; 调用虚表第二个函数 (move)

; 构造函数中初始化虚表
lea rax, [Dog_vtable]
mov [rcx], rax          ; 设置虚表指针

[!tip] 逆向技巧

  1. 在IDA中查找 lea rax, offset xxx 后紧跟 mov [rcx], rax 的模式
  2. 虚表通常位于 .rdata
  3. 虚表第一个条目前可能有RTTI信息(如果启用)

RTTI 结构 (Run-Time Type Information)

// 编译器生成的RTTI结构
struct type_info {
    void* vfptr;      // type_info的虚表
    char* _M_d_name;  // mangled name
    // ...
};

// 可通过 typeid() 访问
const type_info& ti = typeid(Dog);
cout << ti.name() << endl;  // 输出: class Dog

3.1.3 STL容器逆向

📝 学习笔记

std::vector 内部结构

// MSVC实现 (简化)
template<class T>
class vector {
    T* _Myfirst;    // 指向第一个元素
    T* _Mylast;     // 指向最后一个元素之后
    T* _Myend;      // 指向分配内存的末尾
};

// 计算方式
size() = _Mylast - _Myfirst
capacity() = _Myend - _Myfirst

vector 在内存中的识别

; vector访问元素
mov rax, [rcx]          ; _Myfirst
mov rdx, [rcx+8]        ; _Mylast
mov r8, [rcx+16]        ; _Myend

; 计算size
sub rdx, rax
sar rdx, 2              ; 除以元素大小(这里是4字节int)

; vector[i]访问
mov rax, [rcx]          ; _Myfirst
mov eax, [rax+rdi*4]    ; 元素访问, rdi=索引

std::string 内部结构 (MSVC)

// MSVC的SSO (Small String Optimization)
class string {
    union {
        char _Buf[16];      // 短字符串直接存储
        char* _Ptr;         // 长字符串堆指针
    };
    size_t _Mysize;         // 字符串长度
    size_t _Myres;          // 容量
};

// 判断逻辑: 如果 _Myres < 16, 使用_Buf; 否则使用_Ptr

std::map 红黑树结构

// map节点结构
struct _Tree_node {
    _Tree_node* _Left;
    _Tree_node* _Parent;
    _Tree_node* _Right;
    char _Color;        // 红或黑
    bool _Isnil;
    pair<Key, Value> _Myval;
};

// 遍历特征:中序遍历递归

[!important] STL 逆向要点

容器 关键特征 识别方法
vector 三指针结构 连续的3个相邻指针
string SSO判断 检查size与16的比较
map/set 红黑树节点 Left/Parent/Right指针模式
list 双向链表 prev/next指针对
unordered_map 哈希桶 桶数组 + 链表结构

3.2 x64汇编

3.2.1 x64寄存器

📝 学习笔记

通用寄存器扩展

64位      32位      16位      8位高    8位低
RAX       EAX       AX        AH       AL
RBX       EBX       BX        BH       BL
RCX       ECX       CX        CH       CL
RDX       EDX       DX        DH       DL
RSI       ESI       SI        -        SIL
RDI       EDI       DI        -        DIL
RBP       EBP       BP        -        BPL
RSP       ESP       SP        -        SPL
R8        R8D       R8W       -        R8B
R9        R9D       R9W       -        R9B
R10       R10D      R10W      -        R10B
R11       R11D      R11W      -        R11B
R12       R12D      R12W      -        R12B
R13       R13D      R13W      -        R13B
R14       R14D      R14W      -        R14B
R15       R15D      R15W      -        R15B

[!warning] 重要注意事项

  • 写入32位寄存器(如EAX)会自动清零高32位
  • 写入16位或8位寄存器不会清零高位
  • R8-R15是x64新增寄存器

特殊用途寄存器

寄存器 用途
RSP 栈指针
RBP 帧指针(可选)
RIP 指令指针
RFLAGS 标志寄存器
FS/GS 段寄存器(TEB/TLS访问)

Windows x64 TEB 访问

; 获取TEB
mov rax, gs:[0x30]      ; TEB指针

; 获取PEB
mov rax, gs:[0x60]      ; PEB指针

; 获取当前线程ID
mov eax, gs:[0x48]

; 获取当前进程ID
mov eax, gs:[0x40]

3.2.2 x64调用约定

📝 学习笔记

Windows x64 调用约定 (Microsoft x64)

参数传递:
- 整数/指针: RCX, RDX, R8, R9 (前4个)
- 浮点数: XMM0, XMM1, XMM2, XMM3 (前4个)
- 超过4个参数: 栈传递

返回值:
- 整数/指针: RAX
- 浮点数: XMM0
- 大结构体: 调用者分配空间,RCX传递指针

栈对齐:
- 调用前RSP必须16字节对齐
- 调用者分配32字节"影子空间"(shadow space)

影子空间示例

; 调用 func(a, b, c, d, e)
sub rsp, 40h          ; 32字节影子空间 + 8字节第5参数 + 8字节对齐
mov [rsp+20h], r9     ; 可选:保存寄存器参数
mov [rsp+28h], param5 ; 第5个参数
mov r9, d
mov r8, c
mov rdx, b
mov rcx, a
call func
add rsp, 40h

Linux x64 调用约定 (System V AMD64 ABI)

参数传递:
- 整数/指针: RDI, RSI, RDX, RCX, R8, R9 (前6个)
- 浮点数: XMM0-XMM7 (前8个)
- 超过的参数: 栈传递

返回值:
- 整数: RAX, RDX(大返回值)
- 浮点: XMM0, XMM1

注意: 没有影子空间要求

[!important] 对比表

特性 Windows x64 Linux x64
整数参数 RCX,RDX,R8,R9 RDI,RSI,RDX,RCX,R8,R9
影子空间 必需(32字节) 不需要
红区 128字节
调用者保存 RAX,RCX,RDX,R8-R11 RAX,RCX,RDX,RSI,RDI,R8-R11
被调用者保存 RBX,RBP,RDI,RSI,R12-R15 RBX,RBP,R12-R15

3.2.3 x64特有指令

📝 学习笔记

RIP 相对寻址

; x86 全局变量访问
mov eax, [global_var]       ; 绝对地址

; x64 RIP相对寻址
mov eax, [rip+offset]       ; 相对于下一条指令的偏移
lea rax, [rip+offset]       ; 获取地址

; IDA显示形式
mov eax, cs:global_var      ; IDA会计算出实际地址

新增 / 增强指令

; MOVSXD - 带符号扩展从32位到64位
movsxd rax, dword ptr [rcx]

; LEA增强 - 可用于复杂计算
lea rax, [rcx+rdx*8+10h]   ; rax = rcx + rdx*8 + 16

; 条件移动(x86已有但x64更常用)
cmovz rax, rbx             ; if ZF==1, rax=rbx
cmovnz rax, rbx            ; if ZF==0, rax=rbx

; SYSCALL (替代INT 2E/SYSENTER)
mov r10, rcx               ; 系统调用号
syscall

; CMPXCHG16B - 128位原子比较交换
lock cmpxchg16b [rdi]

SSE/AVX 指令

; 128位操作
movaps xmm0, [rax]         ; 对齐的128位加载
movups xmm0, [rax]         ; 非对齐的128位加载

; 浮点运算
addsd xmm0, xmm1           ; 双精度加法
mulss xmm0, xmm1           ; 单精度乘法

; 向量化字符串操作
pcmpeqb xmm0, xmm1         ; 16字节并行比较
pmovmskb eax, xmm0         ; 获取比较结果掩码

3.3 保护研究入门

📝 学习笔记

常见软件保护技术

保护类型 描述 识别特征
反调试 检测调试器 IsDebuggerPresent, NtQueryInformationProcess
代码混淆 增加分析难度 控制流平坦化、虚假分支
加壳 运行时解压 入口点异常、节区熵值高
虚拟机保护 字节码解释执行 dispatch循环、handler表
完整性校验 检测修改 CRC/Hash计算

反调试技术示例

// 方法1: IsDebuggerPresent
if (IsDebuggerPresent()) {
    // 检测到调试器
}

// 方法2: PEB.BeingDebugged
bool IsDebugged() {
    PPEB peb = (PPEB)__readgsqword(0x60);
    return peb->BeingDebugged;
}

// 方法3: NtGlobalFlag检查
bool CheckNtGlobalFlag() {
    PPEB peb = (PPEB)__readgsqword(0x60);
    DWORD flag = *(DWORD*)((BYTE*)peb + 0xBC);  // x64 offset
    return (flag & 0x70) != 0;  // FLG_HEAP_*标志
}

// 方法4: 时间检测
DWORD start = GetTickCount();
// 敏感代码
DWORD end = GetTickCount();
if (end - start > 1000) {
    // 可能被调试
}

绕过反调试

// 方法1: 直接patch IsDebuggerPresent返回值
// 在函数入口处: xor eax, eax; ret

// 方法2: 修改PEB.BeingDebugged
PPEB peb = (PPEB)__readgsqword(0x60);
peb->BeingDebugged = FALSE;

// 方法3: 使用x64dbg的ScyllaHide插件
// 或手动Hook相关API

加壳识别与脱壳

常见壳识别:
- UPX: 节名 UPX0, UPX1
- VMProtect: .vmp段, 高熵值代码
- Themida: 复杂的虚拟机结构

脱壳基本流程:
1. 运行到OEP (Original Entry Point)
2. dump内存
3. 修复IAT (Import Address Table)
4. 重建PE头

[!tip] 工具推荐

  • DIE (Detect It Easy): 壳识别
  • x64dbg + Scylla: 脱壳dump
  • UPX: upx -d 直接脱壳
  • Import Reconstructor: IAT修复

✅ 阶段检查点

C++编程检查

  • [ ] 理解类的内存布局和对齐规则
  • [ ] 掌握虚表结构和虚函数调用机制
  • [ ] 能在IDA中识别虚表和构造函数
  • [ ] 理解多重继承和虚继承的内存布局
  • [ ] 能识别STL容器的内存结构(vector, string, map)

x64汇编检查

  • [ ] 熟悉R8-R15新寄存器
  • [ ] 掌握Windows x64调用约定
  • [ ] 理解影子空间的作用
  • [ ] 能阅读RIP相对寻址代码
  • [ ] 了解Linux与Windows调用约定的区别

保护研究检查

  • [ ] 了解常见反调试技术
  • [ ] 能识别和绕过基本反调试
  • [ ] 了解加壳原理和脱壳流程
  • [ ] 使用DIE等工具识别保护类型

📖 学习资料

资源类型 名称 说明
📕 书籍 C++ Primer (第5版) C++经典教材
📕 书籍 Inside the C++ Object Model 深入理解C++对象模型
📕 书籍 逆向工程核心原理 韩国作者,涵盖保护技术
🌐 网站 godbolt.org 在线查看编译器输出
🌐 网站 cppinsights.io 查看编译器对C++的处理
📹 视频 CppCon会议视频 C++深入讲解

🔗 导航

← [[阶段二:初级逆向]] | [[逆向与驱动开发学习路径(详细版)| 返回主目录]] | [[阶段四:Windows 开发]] →

分享文章

未配置分享平台

请在主题设置中启用分享平台

评论