程序崩溃分析报告
背景
突然接到一个任务:分析程序启动后崩溃的问题。
遇到的困难:
- 程序使用 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.cpp 中 on_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;
QString tool_accuracy;
QString tool_basic;
QString inertial_coefficient;
QString unscrew_speed;
QString tool_flexibility;
QString tool_compnate;
QString display_dev;
} 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 | 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);
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);
|
问题代码:
4. 崩溃原因分析
4.1 内存访问流程
mov r12, cs:off_6F6D20 ; 从全局地址 off_6F6D20 加载目标对象指针
off_6F6D20 是全局变量地址,对应源码中的 curtps
r12 后续被用作目标对象的基址
- 崩溃原因:这个地址无效(空指针/野指针/已释放内存)
4.2 根本原因链
- 程序启动初始化:
taskForm 构造函数执行
- 查找工具型号:遍历
tps 列表寻找匹配的 tool_model
- 未找到匹配项:
index 保持为 -1
- 数组越界访问:
tps.at(-1) 触发未定义行为
- 野指针赋值:将无效对象赋值给全局变量
curtps
- 延迟崩溃:后续代码访问
curtps 时,尝试访问无效内存地址
- 程序崩溃:访问
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 分析方法总结
在没有调试符号的情况下,通过以下手段成功定位问题:
- 反汇编代码模式识别:识别出 8 次固定间隔的 QString 赋值
- 源码搜索:在代码中找到匹配的赋值模式(
on_btn_save_2_clicked 函数)
- 结构体反推:根据源码中的赋值操作反推出
ToolParam 结构体定义
- 验证假设:确认反推的结构体与反汇编代码完全匹配
- 定位崩溃代码:根据结构体类型找到问题代码位置
- 逻辑分析:分析构造函数中的数组访问逻辑,确认
index=-1 会触发越界访问
关键技巧: 在没有直接获得结构体定义的情况下,通过实际使用代码反推数据结构,这是逆向分析的核心思路。
6. 修复建议
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | curtps = tps.at(index);
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编辑
,原因: