北邮《深入理解计算机系统》实验思路与总结
开发环境篇
实验中出现了Xshell7输入无响应的问题:平时写代码时最后常使用Ctrl+S保存代码,而在Xshell中该热键是阻断向终端输出内容的指令。解决方案是通过 Ctrl+Q 恢复向终端输出内容。同时发现这个快捷键会和QQ冲突,可以退出QQ后执行。并且,可通过修改热键对应让工具更符合使用习惯。
ARM 拆炸弹
需要关注不同架构下,寄存器的不同机制。
也许是因为ARM的流水线机制等相关,传参和返回均使用x0;而x86其汇编代码中不涉及对%esi的修改,基本操作均在拷贝至%rbp等寄存器进行。
上述问题直接导致无法定位sscanf对应的格式化输入指令地址,需要stepi到对应位置后print 或 x/s。
注意负数除法的向上取整策略。
以下附部分实验报告(根据学术诚信,有略改,不公布代码,仅公布删减版思路,禁止抄袭):
【准备工作】
-
vim分析bomb.t,纵览发现explode_bomb和phase_1等函数,搜索phase和bomb找到后续阶段处理的一些目标函数:phase_1(2,…,6),secret_phase,以下是其中一次的截图:
vim分析bomb.s,分析结果同上。以下是截图:
【阶段一】
-
根据准备工作,explode_bomb语义上即为爆炸指令,显然我在运行gdb时会将其设置断点:
。以下拆炸弹过程会重复此步骤,不再赘述。
-
分析phase_1的汇编代码,
-
可见,x1为传入strings_not_equal的值。设置断点phase_1,通过step达到该函数后,得到x1对应的字符串:
-
将答案略写入ans,进入阶段二。
【阶段二】
- 吸取上一阶段的教训(见上下大点分析),现在我们准备好在当前函数分析汇编代码。
语义可见,我们需要找到正确的六个数字。由前面的cbnz和cmp指令可见,第一个数是0,第二个数是1,否则直接炸了;反汇编read_six_numbers可以发现,数字分别存储在:x1,x1+4,x1+8,x1+12,x1+16,x1+20地址。
确实是输入6个整数。
分析+56到+88代码可知,我们需要输入一个斐波那契数列。因此答案为:略 写入ans文件。
【阶段三】
- 反汇编phase_3函数,片段很长截图不完。
- 分析输入形式:
我们相对于上阶段找寻相关sscanf信息前做个预估计,观察到写入内存的位数差异(23-24-28),可以合理推定:输入形式:整数+字符+整数。由此可以省去了不断stepi以观察x1的步骤(参考下文阶段一的问题分析)。
验证正确:。
- 分析得到信息
-
由第一个cmp指令知道,返回了读入的数据量:3个。
-
接下来显然是一系列分支结构,设数据分别为(int)a,(char)b,(int)c,则a分为(<3,=3,=4,=5,=6,=7)六类(>7会爆炸),对应的分支执行两个操作——为b和c确定定值,不相等即爆炸。如我分析的a = 2情况,可对应出b = 116 , c = 656定值。与此匹配,得到答案:略写入ans。(答案不唯一)
【阶段四】
- 分析phase_4的汇编代码,并且得到需要读入两个数,记为a,b:且a==6.
- 可见当 b<略时,炸弹才不会爆炸,(w0 = b, w1 = 0, w2 =14)作为func4的参数进行调用,直到返回w0 = 6才退出,否则爆炸;下分析func4的汇编代码:
模拟执行:
w3 = w2 – w1; //=14 (1110B)
w3 = w3 + w3 >> 31;//加符号位。此处为+0,无影响。
w3 = w1 + w3 >> 1;// w3 = w1 + w3/2;此时为7.
进入 w3 < b分支。以上的移位处理可能是处理Tmin等情况。接下来是:
[1]当w3 < b, return 2 * func4(w3 + 1, w3) + 1;
[2]当w3 > b ,return 2 * func4(w1, w3 – 1);
[3]当w3 == b, return 0;
由我们需要返回值为6倒推,得到结果:b的值为略.由前面分析,后一个数不为略爆炸,得到结果略,写入ans。
推导过程:
6 = 2 * 3 = 2 * (2 * 1 + 1) = 2 * (2 * (2 * 0 + 1)+ 1 ) )
根据递归树,func4(0,14)对应的w3 = 7>b调用分支[2],即调用了func4(0,6);
根据递归树,func4(0,6)对应的w3 = 3<b调用分支[1],即调用了func4(4,6);
根据递归树,func4(4,6)对应的w3 = 5<b调用分支[1],即调用了func4(6,6);
根据递归树,func4(6,6)对应的w3=6==b,调用分支[3],返回0.
【阶段五】
-
得到汇编代码和输入格式——有一个和阶段一同名的string_length,那应该是字符串了,且长度为6.存在x2相关内存。
简单分析可知,循环判断六次,也就是对六个字符分别进行操作。
分析函数的执行过程,可知,对x2中的六个字符逐一取对应位数后四位作为下标获得数组中对应元素,要使加和的结果为略。
数组(4位可以对应16个状态):
我们可以凑出如下组合:
64 = 16+16+16+10+3+3,ASCii码后四位为0101有EeuU,0001的有aA,0111的有Gg等。
于是可以构造一个可行的答案:略。并写入ans。
【阶段六】
1.phase_6的汇编代码也很长,在此略去截图。注意到有函数“read_six_numbers”,说明我们需要输入6个数字。当然没有前面阶段三的分支简单,这段代码多次跳转,我在草稿纸上作了一定的流程梳理,发现:
- 拆弹条件是:w0 == w1;此前经过一系列的交换操作(ldr,str对),其多次大小判断可以推测其为按照一定规则交换输入的六个数字,目标是达到 : w1 始终 <= w0,也就是升序排列。也就是说,链表排序。
既然发现了这点,我们不妨先看看数据的存储地址和交换策略。其中先对于首元素值- 1和5的比较处理及后续跳转说明了取值范围——不能大于6;
-
并且,
上述处理完w21对应的数字后,继续处理w19 = x21 + 1,直到处理完六个数,其中当存在w0 == w1 时,炸弹也会爆炸,所以六个数字应该是互不相同的;由于涉及一些无符号数的比较,我们推测答案是略的一种排列;
-
以上处理结束后进行寻址,
其中以x3为下标,x5和x6为两端表示链表,w2表示w3对应的值。
- 接下来处理一连串的数据交换对,我们发现x1为adrp取的地址,不妨研究一下地址里面有什么:
很好,是六个节点和数据值。说明有数据域和指针域,符合字节数。于是:得到变序后的序列升序:略得到结果;
将答案写入ans。
【阶段七】秘密炸弹
-
我们发现了还有secret_phase和phase_defused函数,接下来研究一下如何拆掉这个炸弹。
-
运行时发现没有给我们输入的机会,需要寻找入口。于是很自然地反汇编涉及到的函数:
在phase_defused函数中找到了相关信息。分析之。
映入眼帘的是“老朋友”adrp,根据前面的经验我们当然应该观察附近的地址,于是发现了输入“两个整数和一个字符串”,合理推断应该在phase4进入,且加入“DrEvil”(恰好是<+112>中匹配的字符串~)
- 寻找拆弹方法
回到前面分析secret_phase 的汇编代码,可以发现:
(1)我们要使fun7的返回值为略;
(2)我们需要输入一个不超过1000的数字串;
(3)分析fun7的汇编代码,可见其递归调用返回:
[1]w3 > w1 时,返回 2fun7(w);
[2] else,返回 2fun7(w) + 1;
根据常识可知,应该对应二叉树的左右孩子;因为最终返回6,所以其从根节点到目标的路径为LRR(0-1-3-6).这种分析方式在phase_5也使用过。
(4)接下来我们就要寻找对应的树了。
断点至此,观察周围地址:
和以前思路一样,观察调用fun7函数时adrp附近的地址情况:
由二叉树的定义可知,我们取第略个数据域。得到答案:略.
【总览】答案
不公布
【总体思路与感受】本次实验均依照“分析汇编代码”的核心进行。其中大部分是通过纸上画出运行间调度关系处理的,遇到复杂问题时便通过观察地址信息找到突破口。其实拆弹的分析过程思路多样,且不唯一,以“运行时观察地址”也许比我这种直接还原代码逻辑来得简单有趣,但因为时间紧张我也无暇过多研究了。于是建议老师可以筛选展示一些优质、不同、有创意的分析思路和解决方案供我们参考学习,如此也能帮助我们深入理解这个实验。
【问题1】在拆炸弹一时,x1对应的串始终为“”(空)。
【解决方案】解决花了约1.5小时,十分酸爽,解决方案已展现在实验过程中:不应只设置explode_bomb断点,还要设置phase_1断点,在调用函数时,读取x1对应的字符串。为什么x1的值发生了变化呢?我们不妨观察一下strings_not_equal的汇编代码:
涉及到很多次对x1寄存器的修改,最后返回的应该是修改到最后——串的末尾的值,进而x指令只能打印到空串了。
而为什么x86版本没有出现这样的问题呢?我们不妨取其汇编代码进行对比:
发现x86其汇编代码中不涉及对%esi的修改,基本操作均在拷贝至%rbp等寄存器进行。说明我的猜测正确。
这也许是因为ARM的流水线机制等相关,传参和返回均使用x0。
同时,为了验证这个结论,我不小心把炸弹爆了一次……且验证了爆了之后x1的地址仍然是结果串末。
本来还想多研究一下两个架构的区别的,发现从phase_2开始题目就大不同了,后面就不多做对比分析了。
【问题3】phase_4中func函数的逻辑移位和算术移位作用是什么?
【解决方案】在拆炸弹时,由于未涉及负数,这个移位的妙处未能显现。实际上,可以看出w3>>31是一个符号位数,直接控制了后续w3>>1项中的结果,整合这两条语句可以说是:
-
当w3为非负数,该项 = w3/2;
-
当w3为负数,该项 = (w3 + 1)/2;
可以看出,这个移位控制了负数除法的向上取整。
【问题4】分析phase_6中的汇编代码时,逻辑混乱
【解决方案】转换角度。上面的实验中其实展现了adrp指令后的地址有一些重要信息,我们尝试通过观察地址附近的内容,顺藤摸瓜地找到了突破口,避免了繁琐的代码模拟。
缓冲区溢出
实验纲领:
画出栈,模拟指令的执行,即可解决。由于我校也使用的x86
网上有许多思路型教程了,在此略。
Linux0.11-IO
下面介绍我的字符串处理思路:
考虑在console.c中nr--的循环中:
-
设置计数器cnt = 0,每次数字匹配,则+1,若cnt = 10,则执行上述[1];
-
当当前处理的字符c = ‘-’且cnt = 10 时,执行上述[2]。
但是,若受到学号前干扰数字的影响,如“202020…….”,会导致异常。因此,建立临时字符串数组tmp,读入数字进行匹配,于此同时也产生了一个问题:数字连续超过tmp的大小,会出bug。
(由于修改的是console.c,需要按下回车,tty的数据流才会逐步传给console.c进行操作)