csapp上的实验一直收到广泛好评,其中第二个实验bomblab则是一个学习C语言及其转化汇编指令的非常好的小项目,前段时间刚刚借鉴了很多教程完成了整个lab的6个小实验(还有一个secret phase实在太忙一直没顾得上),于是现在打算自己重新做一遍整个实验,好好回忆一下当初学到的知识。
电脑系统是Ubuntu20.04,采用objdump对二进制文件进行反汇编
首先把bomb.c文件贴上来
/***************************************************************************
* Dr. Evil's Insidious Bomb, Version 1.1
* Copyright 2011, Dr. Evil Incorporated. All rights reserved.
*
* LICENSE:
*
* Dr. Evil Incorporated (the PERPETRATOR) hereby grants you (the
* VICTIM) explicit permission to use this bomb (the BOMB). This is a
* time limited license, which expires on the death of the VICTIM.
* The PERPETRATOR takes no responsibility for damage, frustration,
* insanity, bug-eyes, carpal-tunnel syndrome, loss of sleep, or other
* harm to the VICTIM. Unless the PERPETRATOR wants to take credit,
* that is. The VICTIM may not distribute this bomb source code to
* any enemies of the PERPETRATOR. No VICTIM may debug,
* reverse-engineer, run "strings" on, decompile, decrypt, or use any
* other technique to gain knowledge of and defuse the BOMB. BOMB
* proof clothing may not be worn when handling this program. The
* PERPETRATOR will not apologize for the PERPETRATOR's poor sense of
* humor. This license is null and void where the BOMB is prohibited
* by law.
***************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include "support.h"
#include "phases.h"
/*
* Note to self: Remember to erase this file so my victims will have no
* idea what is going on, and so they will all blow up in a
* spectaculary fiendish explosion. -- Dr. Evil
*/
FILE *infile;
int main(int argc, char *argv[])
{
char *input;
/* Note to self: remember to port this bomb to Windows and put a
* fantastic GUI on it. */
/* When run with no arguments, the bomb reads its input lines
* from standard input. */
if (argc == 1) {
infile = stdin;
}
/* When run with one argument <file>, the bomb reads from <file>
* until EOF, and then switches to standard input. Thus, as you
* defuse each phase, you can add its defusing string to <file> and
* avoid having to retype it. */
else if (argc == 2) {
if (!(infile = fopen(argv[1], "r"))) {
printf("%s: Error: Couldn't open %s\n", argv[0], argv[1]);
exit(8);
}
}
/* You can't call the bomb with more than 1 command line argument. */
else {
printf("Usage: %s [<input_file>]\n", argv[0]);
exit(8);
}
/* Do all sorts of secret stuff that makes the bomb harder to defuse. */
initialize_bomb();
printf("Welcome to my fiendish little bomb. You have 6 phases with\n");
printf("which to blow yourself up. Have a nice day!\n");
/* Hmm... Six phases must be more secure than one phase! */
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. */
// Border relations with Canada have never been better.
printf("Phase 1 defused. How about the next one?\n");
/* The second phase is harder. No one will ever figure out
* how to defuse this... */
input = read_line();
phase_2(input);
phase_defused();
//1 2 4 8 16 32
printf("That's number 2. Keep going!\n");
/* I guess this is too easy so far. Some more complex code will
* confuse people. */
input = read_line();
phase_3(input);
phase_defused();
printf("Halfway there!\n");
// [0, 207] [1, 311] [2, 707] [3, 256] [4, 389] [5, 206] [6, 682]
/* Oh yeah? Well, how good is your math? Try on this saucy problem! */
input = read_line();
phase_4(input);
phase_defused();
printf("So you got that one. Try this one.\n");
//[7, 0]
/* Round and 'round in memory we go, where we stop, the bomb blows! */
input = read_line();
phase_5(input);
phase_defused();
printf("Good work! On to the next...\n");
//ionefg
/* This phase will never be used, since no one will get past the
* earlier ones. But just in case, make this one extra hard. */
input = read_line();
phase_6(input);
phase_defused();
//4 3 2 1 6 5
/* Wow, they got it! But isn't something... missing? Perhaps
* something they overlooked? Mua ha ha ha ha! */
return 0;
}
简要阅读一下代码可以看到有6个phase,每一个会检索你从终端的输入,如果符合要求就进入下一个phase,如果不符合就会错误。我们需要通过阅读反汇编出的汇编代码以及利用gdb调试功能来对输入进行推导。
直接来看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 call 401338 <strings_not_equal>
400eee: 85 c0 test %eax,%eax
400ef0: 74 05 je 400ef7 <phase_1+0x17>
400ef2: e8 43 05 00 00 call 40143a <explode_bomb>
400ef7: 48 83 c4 08 add $0x8,%rsp
400efb: c3 ret
从其它教程中学到的非常有利于阅读汇编代码的方法,就是先把带有汇编指令的代码转化为无需指令的方式,变量名称可以用寄存器名称代替。于是phase_1可以转化为:
rsp -= 8;
esi = 0x402400;
call strings_not_equal;
test eax, eax
je 400ef7;
explode_bomb();
400ef7:
rsp += 8;
首先这里test je指令的用法是:
x86平台上使用汇编如何判断一个值是否为0?
一般会使用该指令:
test %rax %rax
je xxx
test指令会判断后面两个操作数执行AND操作,结果为0就设置zero flag,然后搭配je跳转指令从而实现对一个值是否为0的判断。
如果%rax值为0,那么他们相与才会等于0,否则该值不会为0.
————————————————
版权声明:本文为优快云博主「程序猿Ricky的日常干货」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接: https://blog.youkuaiyun.com/rikeyone/article/details/112251708
还有一些需要加入的知识是,对于函数来说,一些寄存器有其独特的作用,比如edi寄存器用来保存函数的第一个参数,esi寄存器用来保存第二个参数,并且函数的(如果有)前六个参数都有其独特的寄存器保存,超过6个的参数则会保存在程序栈中,而函数的返回值会保存在eax寄存器中,我们将上述转化成c风格的代码:
phase_1(edi) {
rsp -= 8;
esi = 0x402400;
eax = strings_not_equal(edi,esi);
if(eax != 0) explode_bomb();
rsp += 8;
}
比较的字符串首地址就是0x402400这个首地址,我们可以在gdb中对这个地址进行查看

gdb中 x/s addr指令能以字符流的形式查看addr内存地址,由此我们可以得出第一题的答案。
马不停蹄我们来看phase_2的汇编:
0000000000400efc <phase_2>:
400efc: 55 push %rbp
400efd: 53 push %rbx
400efe: 48 83 ec 28 sub $0x28,%rsp
400f02: 48 89 e6 mov %rsp,%rsi
400f05: e8 52 05 00 00 call 40145c <read_six_numbers>
400f0a: 83 3c 24 01 cmpl $0x1,(%rsp)
400f0e: 74 20 je 400f30 <phase_2+0x34>
400f10: e8 25 05 00 00 call 40143a <explode_bomb>
400f15: eb 19 jmp 400f30 <phase_2+0x34>
400f17: 8b 43 fc mov -0x4(%rbx),%eax
400f1a: 01 c0 add %eax,%eax
400f1c: 39 03 cmp %eax,(%rbx)
400f1e: 74 05 je 400f25 <phase_2+0x29>
400f20: e8 15 05 00 00 call 40143a <explode_bomb>
400f25: 48 83 c3 04 add $0x4,%rbx
400f29: 48 39 eb cmp %rbp,%rbx
400f2c: 75 e9 jne 400f17 <phase_2+0x1b>
400f2e: eb 0c jmp 400f3c <phase_2+0x40>
400f30: 48 8d 5c 24 04 lea 0x4(%rsp),%rbx
400f35: 48 8d 6c 24 18 lea 0x18(%rsp),%rbp
400f3a: eb db jmp 400f17 <phase_2+0x1b>
400f3c: 48 83 c4 28 add $0x28,%rsp
400f40: 5b pop %rbx
400f41: 5d pop %rbp
400f42: c3 ret
代码长度一下子起来了,但是没关系,先按照上面的情况来,先把它变成能看得懂的方式(省略开头和结尾处的运行状态保存与复位代码):
rsp -= 40;
rsi = rsp;
call read_six_numbers;
if(*(rsp) == 1) goto 400f30
else explode_bomb();
400f30:
rbx = rsp + 4;
rbp = rsp + 24
goto 400f17;
400f17:
eax = *(rbx - 4);
eax += eax;
if(*(rbx) == eax) goto 400f25;
else explode_bomb();
400f25:
rbx += 4;
if(rbx != rbp) goto 400f17;
else goto 400f3c; //即运行正常退出进入下一个阶段
将它转化为c风格代码后为:
phase_2() {
//