吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1908|回复: 29
上一主题 下一主题
收起左侧

[原创] 《深入理解计算机系统》Bomb Lab 题解

  [复制链接]
跳转到指定楼层
楼主
asciibase64 发表于 2025-5-8 23:51 回帖奖励
本帖最后由 asciibase64 于 2025-5-9 00:06 编辑

前言

阅读本文需要拥有《深入理解计算机系统》第三章前置知识。本文不会对基础指令做过多说明,但会说明部分较难知识。笔者学识稍浅,若有疏漏或错误处,欢迎各位大佬指正。

安装Lab

切换到想安装Lab的目录后,使用wget下载Lab。

wget csapp.cs.cmu.edu/3e/bomb.tar

解压Lab。

tar xvf bomb.tar

前置GDB知识

运行

gdb <filename>使用gdb调试文件

r运行程序,遇到断点则停止

c继续运行,直到遇到下一个断点或程序结束

si单步步入(遇到函数则进入)

ni单步步过(遇到函数则直接执行完函数,不进入)

until运行到循环结束

until <line>运行到行号处

断点

b <line>在第n行设置断点

b <function name>在某个函数入口设置断点

b *(<address>)在地址处设置断点

info b显示当前设置的断点

delete <breakpoint n>删除第n个断点

delete breakpoints删除所有断点

disable <breakpoint n>暂停第n个断点

enable <breakpoint n>开啟第n个断点

窗口

layout regs显示寄存器与反汇编窗口

layout asm显示反汇编窗口

显示内容

x/<count><format> <address/registers>打印内存值。从所给地址处开始,以format格式显示count个值。
d: 十进制
x: 十六进制
t: 二进制
f: 浮点数
i: 指令
c: 字符
s: 字符串

example:

x/s $rax
x/100c 0x401000

Ctrl + L清屏

Phase_1

首先使用objdump将反汇编代码储存,以方便览阅。

objdump -d bomb > bomb.asm

然后使用gdb打开程序,并给Phase_1打上断点。

gdb bomb
b phase_1

运行程序r,随机输入一些字符串后发现程序运行到断点处。

显示寄存器与反汇编窗口。

layout regs

观察显示的反汇编代码。

发现在地址0x400ee9处调用了函数strings_not_equal,然后在0x400eee处测试返回值(%eax)使否等於0,如果等於0则跳转到0x400ef7(je指令,即jump equal),否则调用0x400ef2处的explode_bomb

复习CSAPP第三章内容,得知函数调用时数据传输的顺序(第1个参数~第6个参数使用寄存器,如参数大於6个则使用栈传递)。

操作数大小(位) 1 2 3 4 5 6
64 %rdi %rsi %rdx %rcx %r8 %r9
32 %edi %esi %edx %ecx %r8d %r9d
16 %di %si %dx %cx %r8w %r9w
8 %dil %sil %dl %cl %r8b %r9b

由以上推断我们可以猜测string_not_equal的两个参数就是我们的输入与标準答案。运行到0x400ee9后,我们查看%rdi%rsi的值观察看看。

x/s $rdi
x/s $rsi

得到答案

Border relations with Canada have never been better.

Phase_2

phase_2处打断点,随机输入几个数字(我选择1 2 4 8 16 32),观察其代码。

发现有名為read_six_numbers的函数,跟进查看代码。

发现有一个函数sscanf,查询后发现sscanf函数是用来从指定字符串中按格式提取数据。

其原型為:

int sscanf(const char *str, const char *format, ...);

参数解释:

  • str:待解析的字符串
  • format:格式化字符串
  • ...:可变参数列表,用来接收解析后的数据 (须為变量地址)

我们可以猜测sscanf就是用来提取用户输入。通过phase_1中展示的数据传输顺序,查看%rdi%rsi

可以发现我们的猜测是正确的,故调用sscanf的参数应该是:

sscanf("<user input>", "%d %d %d %d %d %d", &rdx, &rcx, &r8, &r9, &rsp, &(rsp+8))

通过阅读read_six_numbers函数的代码可以发现各参数存放的地址:

步过这个函数,继续观察phase_2的代码。

首先0x400f0a检查(%rsp)是否為0x1,如果不等则直接爆炸。由0x400f02处的代码可知%rsp = %rsi,且在函数read_six_numbers%rsi = %rdx,所以(%rsp)中的值就是用户输入的第一个数字(即检查用户输入的第一个数字是否等於1)。

如果通过该检查,则跳转到0x400f30处,初始化%rbx%rbp。通过观察,可以发现0x400f17~0x400f2c是一个for循环。

仔细观察循环,发现%eax读取-0x4(%rbx)中的值。因為int类型占4字节,所以若%rbx指向用户输入的第i个数,M[%rbx - 0x4]会读取用户输入的第i - 1个数。同时,因為执行add指令比执行mul指令速度快,所以程序使用add指令优化%eax *= 2

最后两条指令比对%eax(%rbx)的值,若是不相等则爆炸。由此我们可以构造出一个长度為6的数组,首项為1,其后每一项都是前一项的两倍。

得到答案:1 2 4 8 16 32。

Phase_3

在phase_3处打断点,输入1 2,观察该处代码。

发现了熟悉的sscanf函数,如我们做phase_2时一样,查看sscanf的几个参数。

透过观察0x400f470x400f4c处的lea指令发现将用户输入读入%rsp+0x8%rsp+0xc处。為更好理解,我们假设%rsp+0x8%rsp+0xc為一个数组userinput[2]%rsp+0x8userinput[0]%rsp+0xcuserinput[1]

0x400f60~0x400f65处的代码检查输入长度,通过查看sscanf函数参数我们知道输入应该為两位。0x400f6a处的代码检查userinput[0],若大於7则爆炸。然后0x400f71处的指令将%eax赋值userinput[0]

特别注意0x400f75处的代码,发现其為switch指令的跳转表,跳转的公式即:<基地址> + <寄存器值> * <表项类型大小>。查看0x402470中的值,即跳转表的基地址。由0x400f6a处的判断可以推测该跳转表大小应為7项,因為跳转表储存的值佔8字节,所以跳转表所佔的字节大小為$7 \times 8 = 56$字节。

因為该机器為小端序,所以地址是倒序的,恢复后地址如下。

观察phase_3剩餘代码,在跳转表跳转到的地址处,我标註了跳转过来对应的userinput[0]值。我们可以看到在每个跳转后代码都会给%eax赋予一个特定的值。

观察0x400fbe处的代码,是一个判断语句。若赋予%eax的特定值等於userinput[1]则成功,所以此题不仅一个解。其中一个解為1 311(0x137)

Phase_4

依照惯例,在phase_4处打断点,随便输入几个数字,开始阅读反汇编代码。

注意观察0x40103a~0x401048处的代码,调用了func4函数。我们可以看到0x40104d要求func4的返回值要等於0才可正确解弹,我们还可以发现0x401051处要求用户输入的第二个数字等於0才可正确解弹。

查看func4的反汇编代码。

直接阅读汇编码并不好理解这段代码的运行规则,我们人肉把这段代码转换成C++代码,试试这样可读性会不会提高。

// func4(userinput[0], 0, 14);
int func4(int input, int a, int b) {
    int tmp1, tmp2;
    tmp1 = b - a;
    tmp2 = tmp1 >> 31;  // Get sign
    tmp1 += tmp2;       // Add sign
    tmp1 /= 2;
    tmp2 = tmp1 + a;
    if (tmp2 < input) {
        func4(input, tmp2 + 1, b);
        return 1;
    }
    else if (tmp2 == input) {
        return 0;
    }
    else {
        func4(input, a, tmp2 - 1);
        return tmp1 * 2;
    }
}

这样阅读起来方便多了。还记得我们的目标是使func4返回0,最简单的方式是使tmp2 == input。通过初始传入的参数计算tmp2的值,我们可以得到tmp2等於7。

得到答案:7 0(不仅一组解)。

Phase_5

对phase_5打断点,随便输入几个数字,开始我们的分析。

读者可能会疑惑0x40106a处的mov %fs:0x28, %rax是甚麼?复习一下CSAPP第三章可以发现,这个位置存放著程序的金丝雀值(canary),用来检测栈溢出。

在代码的末尾我们可以看到程序再次取出栈中之前储存的金丝雀值,通过xor比对是否与真正的金丝雀值相等,如果不相等则代表发生栈溢出。

0x40107a~0x40107f0x4010d2~0x4010d7处代码检测输入长度是否為6,并置零%rax的值。

接下来是重中之重,分析代码中的循环,即0x40108b~0x4010a8处的代码。

通过汇编代码很难看懂代码逻辑,我们同样将其转成C++观察。

void EncryptLoop() {
    char* tmp;      // %rdx
    char msg[6];    // %rsp
    char userinput[6];
    for (int i = 0; i < 6; i++) {
        *tmp = userinput[i] & 0xf; // 1111
        tmp = (char*)(0x4024b0 + (int)tmp);
        msg[i+0x10] = *tmp;
    }
}

我们发现在0x401096处的and指令,只取%rdx的最低一位。0x401099处的代码中发现一个内存地址0x4024b0,查看其中内容。

我们用不到那麼长的内容,只取前面第一串内容即可。

tmp = (char*)(0x4024b0 + (int)tmp);
msg[i+0x10] = *tmp;

对於这两段代码,作用是将从0x4024b0开始的第(int)tmp个字符放入msg[i+0x10](M[%rsp+0x10])中,作為后续字符串对比的输入。

观察0x4010ae~0x4010bd处代码,我们可以发现strings_not_equal调用的两个参数分别是处理后的用户输入M[%rsp+0x10]与硬性写入的内存地址0x40245e。查看0x40245e存放的内容。

对比0x4024b0处的字符串,我们需要计算如何使用"maduiersnfotvbylSo"凑出"flyers"。通过计算我们很容易可以得出每个字浮需要的偏移為"0x9 0xf 0xe 0x5 0x6 0x7"。(即:0x4024b0 + 0x9'f'0x4024b0 + 0xf'l'...其餘类推)

现在我们需要思考甚麼输入可以造成%rdx依序為"0x9 0xf 0xe 0x5 0x6 0x7"。我们需要<输入> & 0xf后,可以得到所需的偏移,查询ASCII码对照表后,我们可以找到其中一组解:9/.567(十六进制為:39 2f 2e 35 36 37)。

Phase_6

这题有点难度,读者们可以先去休息下,精神充沛后我们继续解phase_6。

我们可以看到phase_6中又出现了熟悉的read_six_numbers函数。打上断点,随便输入六个数字,我们开始分析。

观察代码,我们可以看到0x40110e~0x401151是一个以%r12d為计数器的大循环(Check 1 Loop)。在这个大循环裡面,还套嵌著另一层循环0x401135~0x401148(Loop 1)。

Loop 1的作用是利用循环检测userinput[i]之后的所有数字是否有任何一个数字与userinput[i]相等。Check 1 Loop的作用在递增i,确保整个数组中的数字都不相等且皆不大於6。相等於以下代码:

for (int i = 0; i < 6; i++) {
        for(int j = i+1; j < 6; j++) {
                if (userinput[i] == userinput[j])
                        explode_bomb();
        }
}

0x401153~0x40116d处代码遍歷userinput,将每一项修改為0x7 - userinput[i]。相当於以下代码:

for(int i = 0; i < 6; i++) {
        userinput[i] = 0x7 - userinput[i];
}

0x401183处我们发现一个内存地址0x6032d0,通过gdb我们查看一下该内存地址内储存的值。

通过名字与内存中的值,我们可以猜测这是一个链表的头节点。链表的定义如下:

struct Node {
        int val;
        int key;        // 说明该节点為node几
        Node *next;     // 佔8字节,指向下一个链表节点的地址
};

首先看一进入此部分就跳转到的地址0x401197处的代码:首次执行时如果修改后的userinput[0]等於0x1则将M[%rsp+0x20]处写入0x6032d0(即Node 1的内存地址),否则则跳转到Loop 4执行循环。观察Loop 4的循环,我们可以发现这一段代码就是读取第%ecx个链表节点。Loop 3则将读取的节点存入M[%rsp + %rsi*2 + 0x20]中。

我们可以将其转换成等价的C++代码观察。

// 令%rsp+0x20 ~ %rsp+0x48為指向链表的指针,即:Node **NodeList[6]
// 令原本的链表按数字排序(即Node1后接Node2...依此类推)
// 此处的userinput為处理过的(0x7 - userinput[i])的数组
Node *head;
for (int i = 0; i < 6; i++) {
        head = 0x6032d0;    // Node类型為指针,所以可以直接将第一个节点的内存地址赋予它
        for (int j = 0; j < userinput[i]; j++) {
                head = head->next;
        }
        *NodeList[i] = head;
}

这一段代码稍微较好了解,比较容易困惑的点在0x4011b5处的%rsp + 0x50。读者可能会想这个值好像不在我们的链表范围内,但是仔细阅读代码后可以发现0x4011c40x4011c8处的代码使用的是%rax(计数器) + 0x8的值与%rsp + 0x50比对,所以循环真正运行的范围在%rsp + 0x20~ %rsp + 0x48的40个字节。

Node *head = *NodeList[0];
for (int i = 1; i < 6; i++) {
        Node *next = *NodeList[i];
        head -> next = next;
        head = head -> next;
}
head -> next = nullptr;

Loop 6的功能是取得每个节点的值,并检验链表是否依节点的值由大排到小。与以下C++代码功能相等

Node *head = 0x6032d0;
Node *next = head -> next;
for (int i = 0; i < 5; i++) {
        if (head -> val < next -> val) {
                explode_bomb();
        }
        head = head -> next;
        next = next -> next;
}

综合以上分析,可以得知我们需要让链表依据节点的值由大排到小,首先查看各节点的值。

Node1->val: 0x14c = 332

Node2->val: 0xa8 = 168

Node3->val: 0x39c = 924

Node4->val: 0x2b3 = 691

Node5->val: 0x1dd = 477

Node6->val: 0x1bb = 443

所以我们可以得知,被程序修改过的userinput(0x7 - userinput[i])应该為3 4 5 6 1 2。反推原始值我们可以得到答案:4 3 2 1 6 5

以上,phase_1到phase_6全部被我们解开。

Lab在最后还留有一个secret_phase,在此就留作读者读完本文的课后练习。笔者写完这篇题解準备溜去早点休息,為读者们写下一个Attack Lab题解做準备与学习。

后记

笔者仍是一位正在学习的菜鸟,正利用高中毕业后与上大学前的空閒时间增强学习稍微底层一点的东西。这段时间也会是笔者发文的高发期,不知道进入大学后还有没有这麼多时间进行创作或自由时间学习。若发现本文某些地方代码错误或分析逻辑错误,欢迎各位大佬指正,笔者会虚心学习。日后可能会发一些CSAPP相关Lab的题解、密码学、逆向工程相关的笔记或者教学创作,虽然笔者学识尚浅,但是仍希望可以将这些知识传递给拥有相同困惑或相同兴趣的网友们。

免费评分

参与人数 4吾爱币 +4 热心值 +3 收起 理由
ghimi + 1 我很赞同!
2033676756 + 1 我很赞同!
熊猫拍板砖 + 2 + 1 热心回复!
mxkgb + 1 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

沙发
爱飞的猫 发表于 2025-5-9 03:20
图片形式的代码建议使用代码框渲染,而非截图。
3#
mxkgb 发表于 2025-5-9 07:33
4#
asi7 发表于 2025-5-9 08:45
5#
hummel 发表于 2025-5-9 08:49
汇编玩的好,啥都能搞!
6#
Yang3 发表于 2025-5-9 09:08
分析的很详细
7#
buaa32060902 发表于 2025-5-9 09:10
不明觉厉
8#
HuskyHappy 发表于 2025-5-9 09:26
太深奥了,如同天书
9#
BBG77 发表于 2025-5-9 09:38
有点意思,感谢分享
10#
artour 发表于 2025-5-9 10:16
《深入理解计算机系统》(原书第3版)谁有文字版的分享一下,我想学习一下,谢谢!
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-5-16 04:04

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表