深入理解计算机系统—CSAPP实验2

CSAPP实验——编程小杨的进阶之路

实验二 bomb

该实验主要是熟悉X86的汇编指令,能够看懂可执行文件disassemble后的汇编代码逻辑。该实验一共有6个问题,接下来逐一解答。

那么问题是什么呐?别着急,先通过gdb bomb命令,进入gdb界面,输入run指令,运行一下bomd文件瞧一瞧。不瞧不知道,一瞧吓一跳,文件输出了以下几段英文,就停住了

Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!

难道卡死了??不不不,以菜鸡小杨的第一直觉来看,应该是需要输入文字,然后他输入了I am Xiao Yang. Hello!,按下回车

BOOM!!!
The bomb has blown up.

爆炸了??直接退出程序了,what??这是干啥呐?动不动就要原地爆炸?小杨暂时还没明白这个程序要干嘛,于是,他打开对应文件夹下的bomblab.pdf文件,看到Introduction中写道:A binary bomb is a program that consists of a sequence of phases. Each phase expects you to type a particular string on stdin. If you type the correct string, then the phase is defused and the bomb proceeds to the next phase. Otherwise, the bomb explodes by printing "BOOM!!!" and then terminating. The bomb is defused when every phase has been defused.,原来这个bomb有一系列的阶段,每个阶段都要输入正确的字符串才能进入下一阶段,其中任何一个阶段输入错误,直接原地爆炸结束(这火力也太猛了吧),只有所有的阶段都通过了这个bomb才算拆除。好了,问题明白了,那怎么输入正确的字符串排除炸弹呐?小杨惊奇的发现,文件夹下竟然有.c的源文件哎!有了源文件,这拆炸弹还不是信手拈来;激动的心,颤抖的手,双击鼠标打开源文件,一看,火热的心凉的透透的,怎么都仅仅是函数调用,函数的实现没有啊!小杨还是太年轻啊,so naive;悲痛的心,无力的手,滑动着鼠标的滚轮,审视着恍人的源代码,乍一看,核心的源文件是没有,但是给出了整个程序流程的框架,阶段1到阶段6,一共6个问题,每个问题的流程都差不多,主要分为读入字符串、阶段处理、炸弹处理和输出四个部分;小杨洼凉洼凉的心,又重新燃起了一丝小火苗。接下来,就根据整体的代码流程逐个击破吧!

问题1

问题一的主要C代码如下:

input = read_line();   /* Get input*/
phase_1(input);        /* Run the phase*/
phase_defused();       /* Drat!  They figured it out!
				        * Let me know how they did it.*/
printf("Phase 1 defused. How about the next one?\n");

没有了主要的核心源代码,既然bomd程序可以执行,那么其内部肯定已经包含了整个文件的实现,所以,对其进行反编译操作,实乃上策啊!为了查看可执行文件的反编译代码,可以通过objdump -d bomd > asm.txt命令,将可执行文件bomb反编译,并将反编译后的汇编结果重定向到asm.txt文件中,方便查看。

得到结果后,小杨通过查看生成的汇编代码,找到了main函数的入口地址和其对应的区间代码如下:

0000000000400da0 <main>:
  400da0:	53                   	push   %rbx
  400da1:	83 ff 01             	cmp    $0x1,%edi
  400da4:	75 10                	jne    400db6 <main+0x16>
  ......
  400ed0:	b8 00 00 00 00       	mov    $0x0,%eax
  400ed5:	5b                   	pop    %rbx
  ......
  400edf:	90                   	nop

类似400da0的标记,表示的是其后指令在内存中地址的十六进制形式;其后面的53代表的是push %rbx指令二进制代码的十六进制形式,也就是CPU实际执行的机器码。

通过结合bomb.c源文件,小杨以以迅雷不及掩耳盗铃之势,快速找到以下部分代码:

  400e19:	e8 84 05 00 00       	callq  4013a2 <initialize_bomb>
  400e1e:	bf 38 23 40 00       	mov    $0x402338,%edi
  400e23:	e8 e8 fc ff ff       	callq  400b10 <puts@plt>
  400e28:	bf 78 23 40 00       	mov    $0x402378,%edi
  400e2d:	e8 de fc ff ff       	callq  400b10 <puts@plt>
  400e32:	e8 67 06 00 00       	callq  40149e <read_line>
  400e37:	48 89 c7             	mov    %rax,%rdi
  400e3a:	e8 a1 00 00 00       	callq  400ee0 <phase_1>
  400e3f:	e8 80 07 00 00       	callq  4015c4 <phase_defused>
printf("Welcome to my fiendish little bomb. You have 6 phases with\n");
printf("which to blow yourself up. Have a nice day!\n");

input = read_line();             /* Get input*/
phase_1(input);                  /* Run the phase*/
phase_defused(); 

其中,<initialize_bomb>对应的就是C文件中initialize_bomb()代码的入口,两个<puts>分别对应两个printf的输出,<read_line>对应read_line();寄存器rax中,保存的是read_line函数调用后,返回的结果;寄存器rdi,通过mov %rax,%rdi指令,得到了寄存器rax的结果,对应源代码input = read_line();的逻辑实现,其中rdi实现类似input的功能(在phase_1中作为第一个函数参数进行传递),其中inputchar *类型的指针,那么rdi中存储的应该是输入字符串在内存的地址。

看问题呐!咱就要抓重点,直奔主题。小杨这个明白人,大眼一看,这个phase_1(input);就是问题的关键,那么,对应反编译的结果,就是查找callq 400ee0 <phase_1>调用在400ee0处的<phase_1>

0000000000400ee0 <phase_1>:
  400ee0:	48 83 ec 08          	sub    $0x8,%rsp
  400ee4:	be 00 24 40 00       	mov    $0x402400,%esi
  400ee9:	e8 4a 04 00 00       	callq  401338 <strings_not_equal>
  400eee:	85 c0                	test   %eax,%eax
  400ef0:	74 05                	je     400ef7 <phase_1+0x17>
  400ef2:	e8 43 05 00 00       	callq  40143a <explode_bomb>
  400ef7:	48 83 c4 08          	add    $0x8,%rsp
  400efb:	c3                   	retq 

找到了<phase_1>,细瞅了一下,发现对传递第二个函数参数的寄存器esirsi的低32位)进行了赋值,之后,通过callq调用了<strings_not_equal>函数,同之前read_line一样,<strings_not_equal>函数调用后,返回的结果同样保存在rax中,接下来的test %eax,%eax指令,用来测试rax的低32位是否为0,如果为0,那么条件码寄存器的ZF就会被置1,此时,执行到je 400ef7 <phase_1+0x17>,条件满足,就会跳转到400ef7: 48 83 c4 08 add $0x8,%rsp,然后通过retq返回到main函数;反之,如果不为0,那么ZF将不会发生置位操作,同时在je 400ef7 <phase_1+0x17>也不会发生跳转,而是顺序执行下去,并通过callq 40143a <explode_bomb>指令,调用爆炸函数,进行自爆。所以,在关键<phase_1>中的关键就是在通过callq 401338 <strings_not_equal>指令调用<strings_not_equal>函数的时候,让其返回值为0,即,使得rax为0。因此,排爆关键中的关键就是<strings_not_equal>函数。

0000000000401338 <strings_not_equal>:
  401338:	41 54                	push   %r12
  40133a:	55                   	push   %rbp
  40133b:	53                   	push   %rbx
  40133c:	48 89 fb             	mov    %rdi,%rbx
  40133f:	48 89 f5             	mov    %rsi,%rbp
  401342:	e8 d4 ff ff ff       	callq  40131b <string_length>
  401347:	41 89 c4             	mov    %eax,%r12d
  40134a:	48 89 ef             	mov    %rbp,%rdi
  40134d:	e8 c9 ff ff ff       	callq  40131b <string_length>
  401352:	ba 01 00 00 00       	mov    $0x1,%edx
  401357:	41 39 c4             	cmp    %eax,%r12d
  40135a:	75 3f                	jne    40139b <strings_not_equal+0x63>
  40135c:	0f b6 03             	movzbl (%rbx),%eax
  40135f:	84 c0                	test   %al,%al
  401361:	74 25                	je     401388 <strings_not_equal+0x50>
  401363:	3a 45 00             	cmp    0x0(%rbp),%al
  401366:	74 0a                	je     401372 <strings_not_equal+0x3a>
  401368:	eb 25                	jmp    40138f <strings_not_equal+0x57>
  40136a:	3a 45 00             	cmp    0x0(%rbp),%al
  40136d:	0f 1f 00             	nopl   (%rax)
  401370:	75 24                	jne    401396 <strings_not_equal+0x5e>
  401372:	48 83 c3 01          	add    $0x1,%rbx
  401376:	48 83 c5 01          	add    $0x1,%rbp
  40137a:	0f b6 03             	movzbl (%rbx),%eax
  40137d:	84 c0                	test   %al,%al
  40137f:	75 e9                	jne    40136a <strings_not_equal+0x32>
  401381:	ba 00 00 00 00       	mov    $0x0,%edx
  401386:	eb 13                	jmp    40139b <strings_not_equal+0x63>
  401388:	ba 00 00 00 00       	mov    $0x0,%edx
  40138d:	eb 0c                	jmp    40139b <strings_not_equal+0x63>
  40138f:	ba 01 00 00 00       	mov    $0x1,%edx
  401394:	eb 05                	jmp    40139b <strings_not_equal+0x63>
  401396:	ba 01 00 00 00       	mov    $0x1,%edx
  40139b:	89 d0                	mov    %edx,%eax
  40139d:	5b                   	pop    %rbx
  40139e:	5d                   	pop    %rbp
  40139f:	41 5c                	pop    %r12
  4013a1:	c3                   	retq 

找到<strings_not_equal>函数的入口,鼠标刷刷的往下滑,XX(滴滴声),怎么这么多!我还是个孩子,放过我吧!小杨掩面道;不!小杨,你已经是个成熟的孩子了,要学会迎难而上啊!上就上,who怕who啊!整顿好心情,小杨向着<strings_not_equal>进发。整体扫了一眼,包含两个调用<string_length>函数的部分,再结合<strings_not_equal>的中文意思,小杨大胆的推测到,这两个<string_length>函数应该是分别计算第一个参数rdi和第二个参数rsi中的字符长度,然后判断这两个长度是否相同,不同的话<strings_not_equal>返回结果1,相同的话的返回结果0,所以,结合前面的推断,目标就是使两个参数中字符的长度相等,哈哈!也不过如此嘛!小杨得意起来;前面已经得知,rdi存储的是read_line函数调用后结果,也就是我们输入的字符串,那么第二个参数呐?已知第二个参数rsi<phase_1>中被进行了赋值,那么rsi中存储的应该就是另一个字符串在内存中地址了吧!目前为止,都还是猜测,不知道是否正确,小杨打算通过gdb打断点,测试一下,毕竟实验才是检验真理的唯一标准。废话不多说,开整!

重新通过gdb bomb进入gdb界面,通过b *0x40133cmov %rdi,%rbx指令处打上一个断点,断点打好后,输入run,使程序运行起来,输出以下几行信息,还是原来的配方,熟悉的味道啊!

Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!

抬起食指,按下数字12键,回车,程序在刚才打得第一个断点处停下,运行print /x *(int *) ($rdi),得到以rdi存储的数值作为内存地址,对应内存地址处的信息,输出了0x3231,哎嘞??这是什么玩yi,小杨陷入沉思。哎!对了,小杨猛的一拍手,字符在机器中都是以二进制的ASCII码的形式进行存储的,那么,Ctrl+Alt+T打开terminal,输入man ascii得到了ASCII码表,找到十六进制的31和32分别对应字符的1和2,按照小端的存储模式,刚好匹配输入的1、2,rdi得到验证,那么rsi呐?运行print /x *(int *) ($rsi),得到0x64726f42,通过ASCII码表进行转换,得到Bord字符,这个字符暂时没办法进行验证。到此,小杨有百分之50的概率确认猜测的正确性(这和瞎蒙有啥区别,瞎蒙不也是50%),那么,继续按照这条思路,开整!

现在,是停留在<string_not_equal>函数内,此时还没有调用<string_length>函数,那么,考虑输入b *0x401347,在第一个<string_length>函数被调用后打端点,断点打好,输入continue,程序运行到断点处停止,通过print $rax打印<string_length>被调用后的返回值,得到十进制的2,大胆猜测一下,应该是输入字符的长度,输入的字符通过rdi寄存器,传递给<string_length>函数;那第二个<string_length>函数呐?通过40133f:处的mov %rsi,%rbp40134a:处的mov %rbp,%rdi,第二个参数rsi中的值被移动到了寄存器rdi中,此时,rdi作为第二个<string_length>函数的第一个参数,把rsi对应的字符串传给了第二个<string_length>,那么第二个函数的返回值会是的第二个字符串的长度呐?通过b *0x401352在第二个函数后面打断点,运行continue,在该处停下,print $rax打印得到52,嗯…难道第二个字符串的长度为52???蜜汁问号,那…那…如果是这样的话,现在rdi寄存器中存储的值对应的内存位置中,应该按照顺序整齐的排列着52个字符的ASCII码,使用print /x *(long int *) ($rdi)指令,打印rdi对应内存位置的值,可以得到0x7220726564726f42,其中后8位对应还没有调用<string_length>函数之前,打印rsi寄存器对应内存位置得到的值,也就是字符Bord

到目前为止,一切都按照小杨的思路,如丝顺滑般的顺畅运行着。这也不用再看了,<strings_not_equal>里面含有两个<string_length>函数,用来比较两个字符串的长度,大概率可能是比较长度后,字符长度相等输出0,不等输出1;好,有了逻辑,咱就重新输入,来测试以下,重新在gdb中输入runStart it from the beginning? (y or n),当然y,接着老朋友出现,闭着眼睛,点52次数字1,潇洒的回车键一按,在之前的第一个断点40133c处停下了,哎呀~停啥停,继续前进,输入c,到达第一个<string_length>函数后面,print $rax得到52,也就是说输入的字符串长度为52,继续前进,第二个<string_length>函数后面,print $rax得到52,rdi对应内存处的字符长度依旧为52。完美!两个字符长度都为52了,直接输入b *0x400eee,在<phase_1><strings_not_equal>函数调用返回后打断点,print $rax查看一下它的返回值,结果为1???是玻璃掉地上了嘛?不然我怎么听到什么东西碎了一地的声音,小杨自言自语道;难道之前的判断都是错误的嘛?重回汇编代码,一行挨着一行,瞅死这个鬼东西,看到<strings_not_equal>中的401352处,没错啊!逻辑和之前的推断相吻合啊!难道?是后面的代码在搞鬼??继续看下去,首先先是给edx赋值为1,然后比较eaxr12d,前者存储的是第二个<string_length>函数计算得到的字符串长度52,后者是从键盘输入字符串计算得到的长度52,经过cmp对两者的比较后,ZF标志位置1,此时,在jne 40139b <strings_not_equal+0x63>处不发生跳转(如果前一步cmp时两个寄存器的值不相等,此时会发生跳转,直接跳到40139b: mov %edx,%eax处,将edx中的1赋值给rax,接下来返回到<strings_not_equal>函数中,此时该函数的返回值就是1了,按照前面代码的逻辑,就会引爆炸弹了),继续movzbl (%rbx),%eax,将键盘输入的第一个字符的八位ASCII码赋值给eaxtest测试eax中的后八位al的值是否为0,接下来的指令为je 401388 <strings_not_equal+0x50>,嗯?前面如果等于0了,此处跳转到mov $0x0,%edxedx被赋值为0,那么继续执行下去,最终<strings_not_equal>函数的返回结果就会为0了哎!这不是我们想要的结果嘛!!看来,这里的关键是edx这个参数的值啊!!终于,抓到一个搞鬼的小兔崽子!!但是,小杨此时却陷入了沉思,前面testal,代表的就是输入第一个字符的ASCII码,但是,没有办法在第一字符位置输入ASCII码为0的字符啊!看来此路不通;那么,回到跳转后的下一条指令cmp 0x0(%rbp),%al,判断<strings_not_equal>的第二个函数参数rsi(经过之前的mov %rsi,%rbp指令,此时的rbp,存储的是原来rsi的值)与输入字符的第一个字符的八位ASCII码进行比较,按照之前打印出第二个参数对应字符的ASCII码0x7220726564726f42,此时应该是0x420x31进行比较,明显的不同啊!接下来执行到jmp 40138f <strings_not_equal+0x57>,然后跳转到mov $0x1,%edx,给edx赋值为1,这不是踩雷了嘛!肯定爆炸啊!得想办法越过这个判断啊!思来想去,只有让输入的第一个字符对应的ASCII码和参数rsi对应的第一个字符的ASCII码相等啊,也就是得在键盘上输入0x42对应的字符B,那么,假设现在输入了字符B,应该执行je 401372 <strings_not_equal+0x3a>,跳转到401372: add $0x1,%rbx,将寄存器rbx的值加1,也就是目前rbx存储的是键盘输入字符串的第二个字符对应的ASCII码的内存地址,接下来的add $0x1,%rbp,相当于将<strings_not_equal>对应第二个函数参数rsi的数值改变为其对应字符串中第二个字符的内存位置,也就说,现在机器分别指向了两个字符串的第二个字符处,之后通过movzbl (%rbx),%eax test %al,%al,判断目前指向的输入字符对应的ASCII码是否为0,输入的第二个字符明显ASCII码不是零,通过jne 40136a <strings_not_equal+0x32>回到了cmp 0x0(%rbp),%al,哎?这不是又回到了“我们最初的起点了嘛?”,但是这个起点,已经不是“当年的那副模样啦”,现在是判断目前两个字符串对应的第二个字符的ASCII码是否相等,按照之前的逻辑,只有相等才不会引爆炸弹;那么,已经知道两个字符的长度都为52,让它们每个字符都相等,是不是需要在这段逻辑里面转圈圈了啊!姑且先转他个51圈(在cmp 0x0(%rbp),%al处,通过si 400,可以执行50圈,因为从cmp 0x0(%rbp),%aljne 40136a <strings_not_equal+0x32>需要执行八条指令,转50次的话,就是408个指令,加上之前指向第一个字符逻辑判断的1次,一共52次),不行,有点儿晕了,容小杨先缓一缓,站稳了,此时,比较的是两个字符串的第52个字符,都是仅存的最后一个字符啦!比较完成后,add $0x1,%rbx指令执行后,不是到达输入字符串的末尾了吗?末尾??末尾…末尾…,字符串的末尾是/0哎!对应的ASCII码为0,也就是说,再经过movzbl (%rbx),%eax test %al,%al两个指令的一顿操作,终于离开这个圈圈了啊!到达新的指令mov $0x0,%edx,此时,edx被赋值为0了哎!哎呀!哎呀!哎呀呀!这个小兔崽子终于给解决了,接下来通过jmp 40139b <strings_not_equal+0x63>飞跃到最后,通过mov %edx,%eax,将0赋值给了<strings_not_equal>的返回值,接着从<strings_not_equal>返回,回到熟悉的<phase_1>,这时,我们可以成功跳过炸弹了哎!yeah!幸福来的太突然了!yeah!

接下来重新run起来,输入与对应的52个字符,蜻蜓点水般的按下Enter,哎??出现了Phase 1 defused. How about the next one?,还来啊??小杨当场吓晕

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 《计算机系统 第三版》是一本经典的计算机系统概念教材,其中的习题是帮助读者加深对计算机系统原理的理解和应用的工具。以下是对该书的习题的回答。 在《计算机系统 第三版》的习题中,涵盖了计算机系统的多个方面,包括计算机体系结构、操作系统、存储器系统、并发控制、网络通信等。这些习题的目的是帮助读者巩固对教材内容的理解,并通过实践问题的解决来培养解决复杂问题的能力。 举例来说,其中的习题可能包括: 1. 计算机系统体系结构的习题:如理解多级存储器体系结构的原理,设计一个具有多级缓存的计算机系统,并对性能进行评估。 2. 操作系统习题:如深入理解进程管理和线程管理的概念,编写一个多线程的程序,并通过调试和性能分析优化程序。 3. 存储器系统习题:如设计一个虚拟内存系统,讨论页表大小对页面错误率的影响,以及页表的压缩和局部性原理。 4. 并发控制习题:如理解并发控制的一致性和可见性概念,讨论分布式系统中多个进程的并发访问共享资源的问题,如何保证数据一致性。 5. 网络通信习题:如深入理解网络协议的工作原理,设计一个网络协议的通信实验,并进行性能测试与分析。 通过解答这些习题,读者可以加深对计算机系统原理的理解,并且培养解决实际问题的能力。同时,通过实践习题,读者能够更好地应用所学知识,加强计算机系统的实际应用能力。 ### 回答2: "计算机系统 第三版 csapp"是由深入了解计算机系统设计与实现的大师级教材。在这本书中,习题是重要的一部分,旨在帮助读者巩固和扩展他们对所学知识的理解。下面是对该书习题的一些回答和解释。 习题的目的是让读者进一步思考和实践书中所介绍的主题。这些习题包括了各个层面的知识点,例如机器级代码、汇编语言、处理器体系结构、内存层次结构、并发控制、网络编程等等。回答这些习题需要读者对这些主题有着清晰的理解和运用能力。 在回答习题时,读者应该从书中对应的章节中找到相关的信息来辅助解答。这有助于加深对知识点的理解,并且提高问题解决的效率。另外,在解答习题时,也可以参考书中的例子和实验,这样能更好地应用所学知识。 对于习题解答的思路,首先要仔细阅读题目,并且理解问题的要求。其次,要分析问题,找出解决问题所需的关键知识和技巧。然后,运用所学知识和技巧来解答问题,并进行验证和检查。最后,总结解题过程,并且思考问题的拓展和应用。 解答习题需要耐心和毅力。有些习题可能涉及到复杂的概念和技术,需要更多的时间和努力来理解和解答。但通过习题的实践和思考,读者可以更加深入地理解计算机系统的运作原理,提高解决问题的能力。 总之,“计算机系统 第三版 csapp”的习题是深入学习计算机系统设计与实现的重要途径,通过回答习题可以加深对知识点的理解,并提高自己解决问题的能力。在解答习题时,读者需要仔细阅读题目,理解问题的要求,分析问题,并运用所学知识和技巧来解答。通过实践和思考,读者可以更好地理解计算机系统,并提高自己的技术水平。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值