首页
社区
课程
招聘
[原创]无调试符号的崩溃分析:从汇编到结构体推断
发表于: 2025-10-17 18:16 716

[原创]无调试符号的崩溃分析:从汇编到结构体推断

2025-10-17 18:16
716

程序崩溃分析报告

背景

突然接到一个任务:分析程序启动后崩溃的问题。

遇到的困难:

  • 程序使用 MinGW 编译,不带 PDB 调试符号文件
  • 崩溃处无法直接定位到源码信息
  • 只能通过反汇编代码进行分析

1. 崩溃现场 - 反汇编代码

.text:00000000004A7C8F                 mov     rdx, rsi
.text:00000000004A7C92                 call    rdi ; QString::operator=(QString const&)
.text:00000000004A7C94                 mov     r12, cs:off_6F6D20        ; ⚠️ 崩溃点
.text:00000000004A7C9B                 lea     rdx, [rsi+8]
.text:00000000004A7C9F                 lea     rcx, [r12+8]
.text:00000000004A7CA4                 call    rdi ; QString::operator=(QString const&)
.text:00000000004A7CA6                 lea     rdx, [rsi+10h]
.text:00000000004A7CAA                 lea     rcx, [r12+10h]
.text:00000000004A7CAF                 call    rdi ; QString::operator=(QString const&)
.text:00000000004A7CB1                 lea     rdx, [rsi+18h]
.text:00000000004A7CB5                 lea     rcx, [r12+18h]
.text:00000000004A7CBA                 call    rdi ; QString::operator=(QString const&)
.text:00000000004A7CBC                 lea     rdx, [rsi+20h]
.text:00000000004A7CC0                 lea     rcx, [r12+20h]
.text:00000000004A7CC5                 call    rdi ; QString::operator=(QString const&)
.text:00000000004A7CC7                 lea     rdx, [rsi+28h]
.text:00000000004A7CCB                 lea     rcx, [r12+28h]
.text:00000000004A7CD0                 call    rdi ; QString::operator=(QString const&)
.text:00000000004A7CD2                 lea     rdx, [rsi+30h]
.text:00000000004A7CD6                 lea     rcx, [r12+30h]
.text:00000000004A7CDB                 call    rdi ; QString::operator=(QString const&)
.text:00000000004A7CDD                 lea     rdx, [rsi+38h]
.text:00000000004A7CE1                 lea     rcx, [r12+38h]
.text:00000000004A7CE6                 call    rdi ; QString::operator=(QString const&)
.text:00000000004A7CE8                 lea     rax, aDefault_9 ; "default"

1.1 崩溃位置

崩溃指令:

.text:00000000004A7C94    mov r12, cs:off_6F6D20

程序在访问全局变量地址 off_6F6D20 时崩溃。该指令试图从全局偏移地址加载指针到 r12 寄存器,但访问失败。

2. 反汇编代码特征分析

2.1 识别出 8 次连续的 QString 赋值操作

赋值次序 偏移量 汇编指令
第1次 0x0 lea rcx, [r12+0]
第2次 0x8 lea rcx, [r12+8]
第3次 0x10 lea rcx, [r12+10h]
第4次 0x18 lea rcx, [r12+18h]
第5次 0x20 lea rcx, [r12+20h]
第6次 0x28 lea rcx, [r12+28h]
第7次 0x30 lea rcx, [r12+30h]
第8次 0x38 lea rcx, [r12+38h]

2.2 关键特征

固定间隔 8 字节

  • 每个偏移量相差 8 字节
  • 这正好是 64 位系统中 QString 对象的大小
  • QString 本质上是指针包装类

结构体赋值的典型模式

这种连续、固定偏移的成员赋值,是 C++ 结构体拷贝构造或赋值操作的特征。编译器会为每个成员生成单独的赋值代码。

2.3 初步推断

这是一个包含 8 个 QString 成员的结构体赋值操作

3. 源代码定位与结构体反推

3.1 在源码中寻找匹配的赋值模式

根据反汇编代码的特征(8 个 QString 成员的连续赋值),在源码中搜索类似的赋值模式。

找到了 taskform.cppon_btn_save_2_clicked() 函数的代码:

1
2
3
4
5
6
7
8
9
10
11
12
void taskForm::on_btn_save_2_clicked() {
    ToolParam tp;
    tp.model = ui->cb_toolmodel->currentText();
    tp.tool_accuracy = tool_accuracy;
    tp.tool_basic = tool_basic;
    tp.inertial_coefficient = ui->le_inertial_coefficient->text();
    tp.unscrew_speed = ui->le_unscrew_speed->text();
    tp.display_dev = ui->le_display_dev->text();
    tp.tool_compnate = ui->le_tool_compnate->text();
    tp.tool_flexibility = ui->le_tool_flexibility->text();
    // ...
}

关键发现:

  • ToolParam 类型的对象 tp 进行赋值
  • 共赋值 8 个成员
  • 所有成员都是 QString 类型

3.2 反推 ToolParam 结构体定义

根据上述代码,可以反推出 ToolParam 结构体的定义应该是:

1
2
3
4
5
6
7
8
9
10
11
typedef struct _ToolParam
{
    QString model;                    // 偏移 0x0  - 第1个QString
    QString tool_accuracy;            // 偏移 0x8  - 第2个QString
    QString tool_basic;               // 偏移 0x10 - 第3个QString
    QString inertial_coefficient;     // 偏移 0x18 - 第4个QString
    QString unscrew_speed;            // 偏移 0x20 - 第5个QString
    QString tool_flexibility;         // 偏移 0x28 - 第6个QString
    QString tool_compnate;            // 偏移 0x30 - 第7个QString
    QString display_dev;              // 偏移 0x38 - 第8个QString
} ToolParam;

3.3 验证推断

将反推出的结构体与反汇编代码进行对比:

反汇编偏移 推断的成员 类型 匹配情况
0x0 model QString
0x8 tool_accuracy QString
0x10 tool_basic QString
0x18 inertial_coefficient QString
0x20 unscrew_speed QString
0x28 tool_flexibility QString
0x30 tool_compnate QString
0x38 display_dev QString

完美匹配! 在工程中找到了完全一致的结构体定义。

  • 成员数量:8 个 ✅
  • 成员类型:全部为 QString ✅
  • 偏移量:与反汇编代码一致 ✅

3.4 定位问题代码

根据反推出的结构体定义,继续在源码中查找 ToolParam 的使用位置。

taskform.cpp 的构造函数中找到问题代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// taskform::taskForm() 构造函数
int tool_model = (int(toolinfo.tool_type._value)) / 10;
QList<QString> models;
int index = -1;
for (int i = 0; i < tps.count(); ++i) {
    if (tps.at(i).model.contains(QString::number(tool_model))) {
        index = i;
    }
}
qDebug() << "--->index = " << index;
curtps = tps.at(index);  // ⚠️ 当 index=-1 时,访问越界
qDebug() << "index=" << index << tool_model << tps.at(index).model;
if (index != -1) {
    int i = ui->cb_toolmodel->findText(tps.at(index).model);
    ui->cb_toolmodel->setCurrentIndex(i);
}
ui->cb_toolmodel_2->addItem(tps.at(index).model);

问题代码:

1
curtps = tps.at(index);  // 当 index=-1 时触发数组越界

4. 崩溃原因分析

4.1 内存访问流程

mov r12, cs:off_6F6D20  ; 从全局地址 off_6F6D20 加载目标对象指针
  • off_6F6D20 是全局变量地址,对应源码中的 curtps
  • r12 后续被用作目标对象的基址
  • 崩溃原因:这个地址无效(空指针/野指针/已释放内存)

4.2 根本原因链

  1. 程序启动初始化taskForm 构造函数执行
  2. 查找工具型号:遍历 tps 列表寻找匹配的 tool_model
  3. 未找到匹配项index 保持为 -1
  4. 数组越界访问tps.at(-1) 触发未定义行为
  5. 野指针赋值:将无效对象赋值给全局变量 curtps
  6. 延迟崩溃:后续代码访问 curtps 时,尝试访问无效内存地址
  7. 程序崩溃:访问 off_6F6D20 时触发内存访问异常

4.3 问题代码流程图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
启动程序
   
taskForm 构造函数
   
查找 tool_model 匹配项
   
未找到 (index = -1)
   
tps.at(-1) ← 数组越界
   
返回无效对象/野指针
   
curtps = [无效对象] ← 污染全局变量
   
后续访问 curtps
   
mov r12, cs:off_6F6D20 ← 尝试加载无效地址
   
程序崩溃

5. 结论

5.1 崩溃定位

程序在执行 ToolParam 结构体赋值操作时崩溃,具体位置为:

  • 文件taskform.cpp
  • 函数taskForm::taskForm() 构造函数
  • 代码行curtps = tps.at(index);

5.2 技术原因

这段反汇编代码正在执行一个包含 8 个 QString 成员的 ToolParam 结构体赋值操作

  • 源对象tps.at(index) - 由于 index=-1 导致越界访问
  • 目标对象:全局变量 curtps(对应 off_6F6D20
  • 崩溃时机:尝试将无效对象赋值给全局变量时,访问无效内存地址

5.3 分析方法总结

在没有调试符号的情况下,通过以下手段成功定位问题:

  1. 反汇编代码模式识别:识别出 8 次固定间隔的 QString 赋值
  2. 源码搜索:在代码中找到匹配的赋值模式(on_btn_save_2_clicked 函数)
  3. 结构体反推:根据源码中的赋值操作反推出 ToolParam 结构体定义
  4. 验证假设:确认反推的结构体与反汇编代码完全匹配
  5. 定位崩溃代码:根据结构体类型找到问题代码位置
  6. 逻辑分析:分析构造函数中的数组访问逻辑,确认 index=-1 会触发越界访问

关键技巧: 在没有直接获得结构体定义的情况下,通过实际使用代码反推数据结构,这是逆向分析的核心思路。


6. 修复建议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 修复前
curtps = tps.at(index);  // index=-1 时崩溃
 
// 修复后
if (index != -1) {
    curtps = tps.at(index);
} else {
    // 处理未找到匹配项的情况
    qWarning() << "未找到匹配的工具型号,使用默认配置";
    // 使用默认值或第一个配置
    if (!tps.isEmpty()) {
        curtps = tps.at(0);
    }
}

[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!

最后于 2025-10-17 18:27 被_THINCT编辑 ,原因:
收藏
免费 1
支持
分享
最新回复 (1)
雪    币: 2855
活跃值: (12157)
能力值: (RANK:385 )
在线值:
发帖
回帖
粉丝
2
成就感满满
2025-10-20 10:15
0
游客
登录 | 注册 方可回帖
返回