文章

北邮《深入理解计算机系统》实验思路与总结

开发环境篇

实验中出现了Xshell7输入无响应的问题:平时写代码时最后常使用Ctrl+S保存代码,而在Xshell中该热键是阻断向终端输出内容的指令。解决方案是通过 Ctrl+Q 恢复向终端输出内容。同时发现这个快捷键会和QQ冲突,可以退出QQ后执行。并且,可通过修改热键对应让工具更符合使用习惯。

vi-vim-cheat-sheet-sch

ARM 拆炸弹

需要关注不同架构下,寄存器的不同机制。

也许是因为ARM的流水线机制等相关,传参和返回均使用x0;而x86其汇编代码中不涉及对%esi的修改,基本操作均在拷贝至%rbp等寄存器进行。

上述问题直接导致无法定位sscanf对应的格式化输入指令地址,需要stepi到对应位置后print 或 x/s。

注意负数除法的向上取整策略。

以下附部分实验报告(根据学术诚信,有略改,不公布代码,仅公布删减版思路,禁止抄袭)

【准备工作】

  1. vim分析bomb.t,纵览发现explode_bomb和phase_1等函数,搜索phase和bomb找到后续阶段处理的一些目标函数:phase_1(2,…,6),secret_phase,以下是其中一次的截图:

    vim分析bomb.s,分析结果同上。以下是截图:

    【阶段一】

  2. 根据准备工作,explode_bomb语义上即为爆炸指令,显然我在运行gdb时会将其设置断点:。以下拆炸弹过程会重复此步骤,不再赘述。

  3. 分析phase_1的汇编代码,

  4. 可见,x1为传入strings_not_equal的值。设置断点phase_1,通过step达到该函数后,得到x1对应的字符串:

  5. 将答案写入ans,进入阶段二。

【阶段二】

  1. 吸取上一阶段的教训(见上下大点分析),现在我们准备好在当前函数分析汇编代码。

语义可见,我们需要找到正确的六个数字。由前面的cbnz和cmp指令可见,第一个数是0,第二个数是1,否则直接炸了;反汇编read_six_numbers可以发现,数字分别存储在:x1,x1+4,x1+8,x1+12,x1+16,x1+20地址。

确实是输入6个整数。

分析+56到+88代码可知,我们需要输入一个斐波那契数列。因此答案为: 写入ans文件。

【阶段三】

  1. 反汇编phase_3函数,片段很长截图不完。

  1. 分析输入形式:

我们相对于上阶段找寻相关sscanf信息前做个预估计,观察到写入内存的位数差异(23-24-28),可以合理推定:输入形式:整数+字符+整数。由此可以省去了不断stepi以观察x1的步骤(参考下文阶段一的问题分析)。

验证正确:

  1. 分析得到信息
  • 由第一个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。(答案不唯一)

【阶段四】

  1. 分析phase_4的汇编代码,并且得到需要读入两个数,记为a,b:且a==6.

  1. 可见当 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.

【阶段五】

  1. 得到汇编代码和输入格式——有一个和阶段一同名的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对应的值。

  1. 接下来处理一连串的数据交换对,我们发现x1为adrp取的地址,不妨研究一下地址里面有什么:

很好,是六个节点和数据值。说明有数据域和指针域,符合字节数。于是:得到变序后的序列升序:得到结果;

将答案写入ans。

【阶段七】秘密炸弹

  1. 我们发现了还有secret_phase和phase_defused函数,接下来研究一下如何拆掉这个炸弹。

  2. 运行时发现没有给我们输入的机会,需要寻找入口。于是很自然地反汇编涉及到的函数:

在phase_defused函数中找到了相关信息。分析之。

映入眼帘的是“老朋友”adrp,根据前面的经验我们当然应该观察附近的地址,于是发现了输入“两个整数和一个字符串”,合理推断应该在phase4进入,且加入“DrEvil”(恰好是<+112>中匹配的字符串~)

  1. 寻找拆弹方法

回到前面分析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项中的结果,整合这两条语句可以说是:

  1. 当w3为非负数,该项 = w3/2;

  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进行操作)

License:  CC BY 4.0