惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

IT之家
IT之家
Microsoft Azure Blog
Microsoft Azure Blog
人人都是产品经理
人人都是产品经理
博客园 - 聂微东
博客园_首页
阮一峰的网络日志
阮一峰的网络日志
V
V2EX
小众软件
小众软件
F
Fortinet All Blogs
Microsoft Security Blog
Microsoft Security Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
H
Hackread – Cybersecurity News, Data Breaches, AI and More
量子位
Google DeepMind News
Google DeepMind News
Jina AI
Jina AI
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
aimingoo的专栏
aimingoo的专栏
B
Blog RSS Feed
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
宝玉的分享
宝玉的分享
有赞技术团队
有赞技术团队
J
Java Code Geeks
WordPress大学
WordPress大学
The Cloudflare Blog

Watermelonabc的Blog

又一个黑客模拟游戏 又一个黑客模拟游戏 家庭辅导一晚,有点感想 家庭辅导一晚,有点感想 福州大学单片机原理与应用(跨学科)复习 福州大学单片机原理与应用(跨学科)复习 福州大学毛概期末复习 福州大学毛概期末复习 x86 汇编语言详解 - 2 x86 汇编语言详解 -2 CCB 2026 Half-Final Guangzhou CCB 2026 Half-Final Guangzhou 视频也可以含恶意代码吗 视频也可以含恶意代码吗 Hello,2026 Hello,2026 2025 福州大学算法与数据结构复习 2025 福州大学算法与数据结构复习 2025 福州大学习概期末复习 2025 福州大学习概期末复习 Frida 入门 Frida 入门 记一场贴吧交流 记一场贴吧交流 虚拟机保护 虚拟机保护 2025 强网杯 S9 Reverse 复现 2025 强网杯 S9 Reverse 复现 你如何在 FZU 取到快递 你如何在 FZU 取到快递
CSAPP Lab2 Note
Watermelonabc · 2025-07-17 · via Watermelonabc的Blog

CASPP 的 LAB 放在 Lab Assignments 上,在 “Self-Study Handout” 出下载对应 LAB 的资源。

课程 Lab 说明:“二进制炸弹” 是一个提供给学生的可执行文件。运行时,它会提示用户输入 6 个不同的字符串。如果这些字符串中有任何一个不正确,炸弹就会 “爆炸” 并打印错误信息。学生必须通过反汇编和逆向自己的炸弹来确定这 6 个字符串应该是什么。这个实验让学生理解汇编语言,并强迫他们学习如何使用调试器(GDB)。这是一个你可以自己尝试的 Linux/x86-64 二进制炸弹。

我们选择的是 “自学” 模块,没有远程服务端检查和扣分机制,可以随意调试。但是,CMU 的同学就会有这些机制,输错了就扣分,所以需要小心调试。

解压 tar 包,得到三个文件 bombbomb.c 和一个 READMEREADME 只说了这是一个 x86-64 程序。

bomb.c 解读

bomb.c 应该就是 bomb 的主代码了。

代码引入了两个自定义库 support.hphases.h,也就是说我们想要的字符串必须从程序中得到。

然后是命令行参数要求,总结如下:

  1. bomb:标准的按行输入
  2. bomb <file>:先从 <file> 中读取输入,结束后再转到标准输入
  3. 不可提供更多命令行参数,否则打印用法

然后就是打印提示信息并要求输入了。整个代码解读完毕,现在转向程序。

bomb 逆向 - GDB

在 Linux 上使用包管理器安装 GDB,使用 gdb bomb 开始调试

基本操作:

bomb 逆向 - IDA

这个 Lab 是需要在 Linux 环境下运行的,不过我们在 Windows 上,所以先用 IDA 分析一下。这里可以用 bomb.c 修复一下函数参数表,有些参数是没有用的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
int __fastcall main(int argc, const char **argv)
{
const char *line;
const char *v3;
const char *v4;
const char *v5;
const char *v6;
const char *v7;

if ( argc == 1 )
{
infile = (FILE *)stdin;
}
else
{
if ( argc != 2 )
{
__printf_chk(1, "Usage: %s [<input_file>]\n", *argv);
exit(8);
}
infile = fopen(argv[1], "r");
if ( !infile )
{
__printf_chk(1, "%s: Error: Couldn't open %s\n", *argv, argv[1]);
exit(8);
}
}
initialize_bomb();
puts("Welcome to my fiendish little bomb. You have 6 phases with");
puts("which to blow yourself up. Have a nice day!");
line = read_line();
phase_1(line);
phase_defused();
puts("Phase 1 defused. How about the next one?");
v3 = read_line();
phase_2(v3);
phase_defused();
puts("That's number 2. Keep going!");
v4 = read_line();
phase_3(v4);
phase_defused();
puts("Halfway there!");
v5 = read_line();
phase_4(v5);
phase_defused();
puts("So you got that one. Try this one.");
v6 = read_line();
phase_5((__int64)v6);
phase_defused();
puts("Good work! On to the next...");
v7 = read_line();
phase_6(v7);
phase_defused();
return 0;
}

先来看 Phase1,点进 phase_1(line),即可得到内容:

1
result = strings_not_equal(line, "Border relations with Canada have never been better.");

Phase1 就是 Border relations with Canada have never been better.

接下来是 Phase2。有了 Phase1 的经验,我们点开 phase_2(v5)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
__int64 __fastcall phase_2(__int64 a1)
{
__int64 result;
char *v2;
int v3;
char v4;
char v5;

read_six_numbers(a1, &v3);
if ( v3 != 1 )
explode_bomb();
v2 = &v4;
do
{
result = (unsigned int)(2 * *((_DWORD *)v2 - 1));
if ( *(_DWORD *)v2 != (_DWORD)result )
explode_bomb();
v2 += 4;
}
while ( v2 != &v5 );
return result;
}

先看第一个读取函数 read_six_numbers(a1, &v3)

1
2
3
4
5
6
7
8
9
__int64 __fastcall read_six_numbers(__int64 a1, __int64 a2)
{
__int64 n5;

n5 = __isoc99_sscanf(a1, "%d %d %d %d %d %d", a2, a2 + 4, a2 + 1, a2 + 12, a2 + 2, a2 + 20);
if ( (int)n5 <= 5 )
explode_bomb();
return n5;
}

核心就是 __isoc99_sscanf。查询函数定义[1] 可知,这里是从我们输入的 Phase2 中读取格式化的 “%d %d %d %d %d %d” 并写入 v3[i]

我们可以在这里修复一下函数定义,修复后的 phase_2(v5)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int __fastcall phase_2(const char *a1)
{
int result;
int *v2;
int v3[6];
int v4;

read_six_numbers(a1, v3);
if ( v3[0] != 1 )
explode_bomb();
v2 = &v3[1];
do
{
result = 2 * *(v2 - 1);
if ( *v2 != result )
explode_bomb();
++v2;
}
while ( v2 != &v4 );
return result;
}

可以知道,v3[0] ,即第一个输入值必然是 1v2 对应第二个输入值的地址。

然后进入一个 do-while 循环,循环条件是 v2 不等于 v4地址

循环体的逻辑是计算 2 * v2[i-1] 的值,然后和 v2[i] 进行比较。如果不相等,就引爆炸弹。然后移动到下一个元素。

v4 和我们的输入无关且分配内存时接在 v3 之后,基于内存连续性,我们认为数组 v3 结束于地址 v4


  1. C 库函数 int sscanf ↩︎