二进制计算题炸弹第三题

二进制炸弹(第二次实验)
本实验通过要求你使用课程所学知识拆除一个&binary bombs&来增强对程序的机器级表示、汇编语言、调试器和等方面原理与技能的掌握。 一个&binary bombs&(二进制炸弹,下文将简称为炸弹)是一个可执行程序,包含了6个阶段(或层次、关卡)。炸弹运行的每个阶段要求你输入一个特定字符串,你的输入符合程序预期的输入,该阶段的炸弹就被拆除引信即解除了,否则炸弹&爆炸&打印输出 &BOOM!!!&。
实验的目标是拆除尽可能多的炸弹层次。
每个炸弹阶段考察了机器级程序语言的一个不同方面,难度逐级递增:
阶段1:字符串比较
阶段2:循环
阶段3:条件/分支
阶段4:递归调用和栈
阶段5:指针
阶段6:链表/指针/结构
另外还有一个隐藏阶段,只有当你在第4阶段的解后附加一特定字符串后才会出现。
为完成二进制炸弹拆除任务,你需要使用gdb调试器和objdump来反汇编炸弹的可执行文件并单步跟踪调试每一阶段的机器代码,从中理解每一汇编语言代码的行为或作用,进而设法推断拆除炸弹所需的目标字符串。比如在每一阶段的开始代码前和引爆炸弹的函数前设置断点。
为了使分析清晰明了,每个阶段都有等价的代码,隐藏关留到最后分析。
在开始之前,首先介绍两个库函数:
1.int sscanf(char *input,char *format,arg3,arg4&.);这个函数和scanf功能相似,只是从input里读数据,而不是从stdin里读数据,返回成功读取数据的个数
2.long int strtol(const char *nptr,char **endptr,int base,int group);这个函数以base为进制将nptr字符串转化为对应的数值并返回
08048b20 :
$0x80497c0
由上面的代码可以发现,调用了strings_not_equal比较input和pattern,用gdb查看0x80497c处的值:
(gdb) x/20x 0x80497c0
0x80497c0:
0x6cxx6bx20676e69
0xxx6425002e
根据字节顺序对照ASCII码表可知pattern为&Public speaking is easy.&,故阶段一的正确输入就是pattern。
08048b48 :
$0x20,%esp
0x8(%ebp),%edx
/edx=input/
$0xfffffff8,%esp
-0x18(%ebp),%eax
这里我们可以看到,代码分配了int a[6]的存储空间,并调用了
read_six_numbers(input,a):
08048fd8 :
08048b48 :
0x8(%ebp),%ecx
0xc(%ebp),%edx
0x14(%edx),%eax
0x10(%edx),%eax
0xc(%edx),%eax
0x8(%edx),%eax
0x4(%edx),%eax
$0x8049b1b
这里可以清楚地看到read_six_numbers将input,format,&a[0],&a[1]&&a[5]保存在esp指向的栈里面,接着调用sscanf,从input里读入数据存入数组a里。使用gdb查看format值:
(gdb) x/20x 0x8049b1b
0x8049b1b:
0x8049b2b:
通过ASCII码表得知,format确实是&%d %d %d %d %d %d&,接着:
08048fd8 :
$0x20,%esp
/sscanf从input中读取数字个数/
/if eax&=5,explode/
当sscanf返回值&=5时,引爆,否则返回phase_2。继续分析phase_2:
08048b48 :
$0x1,-0x18(%ebp)
/if (a[0]!=1/
/if (a[0]==1)/
-0x18(%ebp),%esi
/esi=&a[0]/
0x1(%ebx),%eax
/eax=ebx-1/
-0x4(%esi,%ebx,4),%eax
/eax*=a[ebx]/
%eax,(%esi,%ebx,4)
/eax!=a[ebx]/
goto .loop
这段代码首先比较a[0]和1,如果不相等,爆炸,否则进入for循环,比较每个数组元素的值,整个阶段二的等价C语言代码为:
void read_six_numbers(char *input,int a[])
Int eax=sscanf(input,&%d %d %d %d %d %d&,
a,a+1,a+2,a+3,a+4,a+5);
if (eax&=5)
explode_bomb();
void phase_2(char *input)
read_six_numbers(input,a);
if (a[0]!=1)
explode_bomb();
for (int ebx=1;ebx&=5;ebx++)
if (a[ebx]!=(ebx+1)*a[ebx-1])
explode_bomb();
由此可以知道阶段二的字符串必须以&1 2 6 24 120 720 &为开头,由于read_six_numbers仅限定了读入数据个数不小于6,因此正确答案不唯一
08048b98 :
-0x4(%ebp),%eax
-0x5(%ebp),%eax
-0xc(%ebp),%eax
$0x80497de
同样用gdb可以知道format的值是&%d %c %d&,因此调用了sscanf(input,&%d %c %d&,&a,&b,&c),之后同样比较其返回值,如果小于3就引爆,接着:
08048b98 :
$0x7,-0xc(%ebp)
-0xc(%ebp),%eax
*0x80497e8(,%eax,4)
/case 0,1,2,3,4,5,6,7/
这里可以看到这是一个switch语句,用gdb查看跳转表:
(gdb) x/8x 0x80497e8
0x80497e8:
0x0xxx08048c28
0x80497f8:
0xxxx08048c76
其中,从上到下,从左到右依次为a=0,1,..7时跳转地址,以a=0时为例:
08048b98 :
$0x309,-0x4(%ebp)
/比较777和c/
-0x5(%ebp),%bl
/比较b和&q&/
-0x18(%ebp),%ebx
由此可知,当a=0时,b=&q&且c=777才不会引爆,其余情况类似。
C语言等价代码为:
void phase_3(char *input)
int eax=sscanf(input,&%d %c %d&,&a,&b,&c);
if (eax&=2)
explode_bomb();
switch (a){
if (c!=777||b!=&q&)
explode_bomb();
if (c!=214||b!=&b&)
explode_bomb();
if (c!=755||b!=&b&)
explode_bomb();
if (c!=251||b!=&k&)
explode_bomb();
if (c!=160||b!=&o&)
explode_bomb();
if (c!=458||b!=&t&)
explode_bomb();
if (c!=780||b!=&v&)
explode_bomb();
if (c!=524||b!=&b&)
explode_bomb();
default:explode_bomb();
由此可知,阶段三字符串必须是以0q777,1b214,2b755,3k251,4o111,5t458,6v780,7b524中的一个为开头且后续字符串不能以数字为开头的字符串。
08048ce0 :
$0x18,%esp
0x8(%ebp),%edx
$0xfffffffc,%esp
-0x4(%ebp),%eax
$0x8049808
同前分析可知,这里调用了sscanf(input,&%d&,&x),并且读到的数据个数不为1时,引爆,如果读入了一个数据:
08048ce0 :
$0x0,-0x4(%ebp)
/比较x和0/
$0xfffffff4,%esp
-0x4(%ebp),%eax
$0x10,%esp
$0x37,%eax
/比较func4(x)和55/
/func4(x)==55,return/
可以看出,如果读入值x&=0,引爆,如果读入值&0,比较func4(x)和55的大小,如果不相等,引爆,接下来分析func4:
08048ca0 :
/比较x和1/
/x&=1,return 1/
$0xfffffff4,%esp
-0x1(%ebx),%eax
/push x-1/
/func4(x-1)/
/esi=func4(x-1)/
$0xfffffff4,%esp
-0x2(%ebx),%eax
/push x-2/
/func4(x-2)/
/return func4(x-1)+func4(x-2)/
可以发现func4首先比较x和1,如果x&=1返回1,否则返回func4(x-1)+func4(x-2;
C语言等价代码为:
int func4(int x)
return func4(x-1)+func4(x-2);
void phase_4(char *input)
int eax=sscanf(input,&%d&,&x);
if (eax!=1)
explode_bomb();
explode_bomb();
if (func4(x)!=55)
explode_bomb();
可知这是一个斐波拉契数列:
x 0 1 2 3 4 5 6 7 8 9
func4 1 1 2 3 5 8 13 21 34 55
由此可知,阶段4的正确输入是以9为开头,后续字符串以非数字开头的字符串,关于隐藏关是什么,我们留到最后分析。
08048d2c :
$0x10,%esp
可以知道这里比较input的长度,如果不是6,引爆,接着:
08048d2c :
-0x8(%ebp),%ecx
/ecx=&s[0]/
$0x804b220,%esi
/esi=&p[0]/
(%edx,%ebx,1),%al
/al=input[edx]/
movsbl %al,%eax
/eax=(int)al/
(%eax,%esi,1),%al
/al=p[eax]/
%al,(%edx,%ecx,1)
/s[edx]=al/
goto .loop
同样用gdb可以知道p的值是&isrveawhobpnutfg&,上述循环的功能是令
s[edx]=p[(int)(input[edx]&0xf)],接着:
08048d2c :
$0x0,-0x2(%ebp)
$0x804980b
-0x8(%ebp),%eax
$0x10,%esp
/字符串相等,return/
上面的代码是比较s和pattern是否相同,如果不相同,引爆;同样利用gdb可以知道pattern的值是&giant&。
等价c代码为:
char p[17]=&isrveawhobpnutfg&;
void phase_5(char *input)
char s[7];
if (string_length(input)!=6)
explode_bomb();
for (int edx=0;edx&=5;edx++)
int eax=(int)(input[edx]&0xf);
s[edx]=p[eax];
s[7]='\0';
if (strings_not_equal(s,&giants&))
explode_bomb();
由此我们知道:input[edx]必须从下面取值,最后的,最后取任意组合即可:
0, ,@,P,`,p
%,5,E,U,e,u
+,;,K,[,k,{
-,=,M,],m,}
!,1,A,Q,a,q
用gdb查看0x804b26c附近的内容:
(gdb) x/20x 0x804b26c
0x804b26c :
0x000000fd
根据之后的代码和第3个值,推测node1的类型是结构,其中有一个指针,顺着这个指针继续查找:
(gdb) x/20x 0x804b260
0x804b260 :
0xxx000000fd
可以确定结构由三个元素组成,两个整形数据,一个结构类型的指针,重复操作可以发现这是一个有6个元素的链表,称此结构为node,此时的链表为:
node(1)-&node(2)-&node(3)-&node(4)-&node(5)-&node(6)-&NULL
下面分析代码:
08048d98 :
从input中读6个数到num[]中,接着:
08048d98 :
$0x10,%esp
0x0(%esi),%esi
-0x18(%ebp),%eax
(%eax,%edi,4),%eax
/eax=num[edi]-1/
/确保num[edi]&=6/
0x1(%edi),%ebx
/保证第一次内循环ebx&=5/
0x0(,%edi,4),%eax
/eax=4*edi/
%eax,-0x38(%ebp)
-0x18(%ebp),%esi
-0x38(%ebp),%edx
/edx=4*edi/
(%edx,%esi,1),%eax
/eax=num[edi]/
(%esi,%ebx,4),%eax
/比较num[edi]和num[edx]/
/确保两者不相等/
goto .loop1
goto .loop
这个二重循环的作用一是保证(unsigned)(num[edi]-1)&6,由补码数和无符号数转换关系知这样可以确保num[edi]为正且&7;另一作用是保证num中元素两两互异,接着:
08048d98 :
-0x18(%ebp),%ecx
-0x30(%ebp),%eax
%eax,-0x3c(%ebp)
0x0(%esi),%esi
-0x34(%ebp),%esi /esi=&node1/
0x0(,%edi,4),%eax
/eax=4edi/
/edx=4edi/
(%eax,%ecx,1),%ebx
/判断第一次内循环是否执行/
/ebx&=num[edi]/
(%edx,%ecx,1),%eax
goto .loop
上述二重循环的功能是使p[edi]为指向node(num[edi])的指针,接着:
08048d98 :
-0x30(%ebp),%esi
/esi=p[0]/
%esi,-0x34(%ebp)
-0x30(%ebp),%edx
(%edx,%edi,4),%eax
/eax=p[edi]/
%eax,0x8(%esi)
/esi-&next=p[edi]/
/esi=p[edi]/
goto .loop
$0x0,0x8(%esi)
/node(num[5]).next=NULL/
上述循环的功能是使node(num[edi]).next=&node(num[edi+1]),此时的链表为:
node(num[0])-&node(num[1])-&node(num[2])-&node(num[3])-&node(num[4])-&node(num[5])-&NULL
接着进入下一个循环:
08048d98 :
-0x34(%ebp),%esi
/esi=p[0]=&node(num[0])/
0x0(%esi,%eiz,1),%esi
0x8(%esi),%edx
/edx=esi-&next/
(%esi),%eax
/eax=esi-&value/
(%edx),%eax
/比较eax和edx-&value/
/链表元素出现升序,引爆/
0x8(%esi),%esi
/esi=esi-&next/
goto .loop
上述循环检查是否经上一步操作后的链表是否为非增的(从链表头开始)。
等价c代码如下:
typedef struct node{
struct node *
node node1={0xfd,1,&node2};
node node2={0x2d5,2,&node3};
node node3={0x12d,3,&node4};
node node4={0x3e5,4,&node5};
node node5={0xd4,5,&node6};
node node6={0x1b0,6,NULL};
void phase_6(input)
int num[6];
read_six_numbers(input,num);
for (int edi=0;edi&=5;edi++)
if ((unsigned)(num[edi]-1)&5)
explode_bomb();
for (int ebx=edi+1;ebx&=5;ebx++)
if (num[edi]==num[ebx])
explode_bomb();
}/*确保元素&0且&7*/
node *p[6];
for (int edi=1;edi&=5;edi++)
node *esi=&node1;
for (int ebx=1;ebx
}/*令p[edi]为指向第num[edi]个节点的指针*/
node *esi=p[0];
for (int edi=1;edi&=5;edi++)
esi-&next=p[edi];
esi=p[edi];
}/*令node(num[edi]).next=&node(num[edi+1])*/
for (int edi=0;edi&=4;edi++)
if (esi-&value & esi-&next-&p-&value)
explode_bomb();
}/*确保重新排序的链表是不增序列*/
将链表元素按value值由大到小排序,得到链表如下:
node(4)-&node(2)-&node(6)-&node(3)-&node(1)-&node(5)-&NULL
故num={4,2,6,3,1,5},对应的,input值是以&4 2 6 3 1 5&为开头且后续字符串以非数字字符开头的字符串。
首先来分析phase_defused:
0804952c :
$0x6,0x804b480
通过gdb 查看0x804b480处的值:
(gdb) x/20x 0x804b480
0x804b480 :
0xxx ...(省略)
阶段1通过后,再次查看此处的值:
0x804b480 :
0xxxx7c010001...(省略)
重复上述操作,发现此处的值依次为2,3,4,5,6,因此这是输入字符串的个数,如果num_input_strings!=6,退出,反之继续分析:
0804952c :
-0x50(%ebp),%ebx
-0x54(%ebp),%eax
$0x8049d03
$0x804b770
用gdb查看0x8049d03,发现其值为&%d %s&再查看$0x804b770,其值如下:
(gdb) x/20x 0x804b770
0x804b770 :
0xxxx...(省略)
此时如果改变阶段4的输入,同样可以发现此处的值总是等于阶段4的输入字符串,说明read_line在这里存储了输入字符串的副本!相当于调用了sscanf(input,&%d %s&,&n,s),并且如果读入数据个数不是2就退出,接下来继续分析:
0804952c :
$0x8049d09
利用gdb可以判断此处将s和&austinpowers&比较,如果不同就退出,相同就调用secret_phase,下面分析secret_phase:
08048ee8 :
80487f0 &__strtol_internal@plt&
上面的代码是将input以十进制转换为对应的数,之后截断为int类型,判断其返回值x是否在1~1001(包括边界值),是则继续,反之引爆,继续分析:
08048ee8 :
$0x804b320
首先我们用gdb查看0x804b320处的值:
(gdb) x/3x 0x804b320
0x804b320 : 0xxx
(gdb) x/3x 0x804b314
0x804b314 :
0x0804b2fc
(gdb) x/3x 0x804b308
0x804b308 :
可以推测n1,n21,n22以及他们指向的节点都是树的节点,其结构如图所示:
因此可以推断,该处调用了fun7(&n1,x),fun7的原型为vcD4NCjxwcmUgY2xhc3M9"brush:">
int fun7(tnode *pnode,int x);
下面分析fun7:
08048e94 :
/pnode是否为NULL/
/不是,继续/
$0xffffffff,%eax
/是的,返回-1/
上面的代码保证递归到树的底层时,返回-1,继续分析:
08048e94 :
(pnode in %edx)
(%edx),%eax
/比较x,pnode-&value/
$0xfffffff8,%esp
0x4(%edx),%eax
/eax= pnode-&l/
/push pnode-&l/
/return 2*fun7(pnode-&l,x)/
(%edx),%eax
/x&=pnode-&value,继续比较/
$0xfffffff8,%esp
/x&pnode-&value/
0x8(%edx),%eax
/eax=pnode-&r/
/push pnode-&r/
/return 2*fun7(pnode-&r,x)+1/
0x0(%esi,%eiz,1),%esi
/x==pnode-&value,return 0/
从上面可以看出有两种可能的递归调用路径,下面是整个隐藏关等价c语言代码:
char *input_strings_4;
num_input_
typedef struct tnode{
struct tnode *l,*r;
void secret_phase();
void phase_defused()
if (num_input_strings!=6)
char s[80];
if (sscanf(input_strings_4,&%d %s&,&n,s)!=2)
printf(&Congratulations! You've defused the bomb!\n&);
if (strings_not_equal(s,&austinpowers&))
printf(&Congratulations! You've defused the bomb!\n&);
printf(&Curses, you've found the secret phase!\n&);
printf(&But finding it and solving it are quite different...\n&);
secret_phase();
printf(&Congratulations! You've defused the bomb!\n&);
int fun7(tnode *pnode,int x)
if (pnode==NULL)
return -1;
if (xvalue)
return 2*fun7(pnode-&l,x);
if (x!=s-&value)
return 2*fun7(pnode-&r,x)+1;
void secret_phase()
input=read_line();
int x=(int)__strtol_internal(input,0,10,0);
if ((unsigned)(x-1)&1000)
explode_bomb();
if (fun7(&n1,x)!=7)
explode_bomb();
printf(&Wow! You've defused the secret stage!\n&);
fun7的返回值是7,由此可以推知,递归产生的返回值依次是0,1,3,7,即递归是沿着最右边的路径下降的,并且在最后一层x=pnode-&value=1001,故可推知正确的字符串是以&1001&为开头且后续字符串以非数字字符的字符串。
综上所述,解除各阶段炸弹的一种输入为:
运行结果为:二进制炸弹实验报告 - CSDN博客
二进制炸弹实验报告
接触《深入理解计算机系统》这本书很久了,但一直都处于看的阶段,但懂是一回事,做出来又是另一回事。
于是就试着做了几个实验,发现这个二进制炸弹特别好玩,于是乎就有了此文。
停在地址处:&& break &*0x8012345
打印寄存器:&& print& &$eax
显示所有寄存器值:I reg
:x& /nfu&addr&&
& & & & & & & & & & & & x 检测内存值, n 查看几个内存单元 f进制显示 u显示单字节双字节等。
: 伪寄存器,值始终为0
为什么要用: NOP 可以用来占位,但只是一个字节,长指令比短指令执行快,所以选用其占位。同样的占位指令还有: lea 0x0(%edi) %edi
:LEA src, dst=&要求src必须为粗出去地址。而寻址方式多种多样。
重要知识点:
switch语句的实现:跳转表
objdump –t 查看符号表,即所使用的所有符号。
objdump –d 反汇编
strings 显示程序所用的全部字符串
main函数:
8048a52: e8
a5 07 00 00
80491fc &read_line&
$0xfffffff4,%esp
8048a5a: 50
8048a5b: e8 c0 00 00 00
8048b20 &phase_1&
通过调用库函数readline读入一行数据,字符串地址通过eax返回,然后将字符串传给phase_1函数。
同样,其后几个阶段遵循这种处理方式:读取一行字符串,调用phase_n,传入字符串参数。
阶段1比较简单,判断输入的字符串与程序内部某个字符串是否相当,不等即爆炸。
8048b2c: 68 c0 97 04 08
$0x80497c0
8048b32: e8 f9 04 00 00
8049030 &strings_not_equal&
所以解题的关键在于,找到程序中的字符串,使用gdb进行调试,打印地址0x80497c0的字符串。
答案显然:Public speaking is very easy.
-0x18(%ebp),%eax
8048b5a: 52
8048b5b: e8 78 04 00 00
8048fd8 &read_six_numbers&
$0x10,%esp
$0x1,-0x18(%ebp)
8048b6e &phase_2+0x26&
8048b69: e8 8e 09 00 00
80494fc &explode_bomb&
8048b6e: bb 01 00 00 00
-0x18(%ebp),%esi
//esi = array
0x1(%ebx),%eax
//eax = ebx + 1
f af 44 9e fc
-0x4(%esi,%ebx,4),%eax
//eax = esi + ebx * 4 - 0x4
8048b7e: 39 04 9e
%eax,(%esi,%ebx,4)
//eax == esi + ebx * 4
8048b88 &phase_2+0x40&
8048b83: e8 74 09 00 00
80494fc &explode_bomb&
8048b8c: 7e e8
8048b76 &phase_2+0x2e&
1. 输入六个数,通过read_six_sumbers将字符串拆分成6个数,存入 ebp -0x18的位置上。
2. cmpl&&$0x1,-0x18(%ebp) 如果数组第一个元素不是1,则爆炸。即第一个数要为数字1.
3. 8b8c为一个循环。伪码如下:
if (arr[0] != 1)
explode_bomb();
for (int i = 1; i &= 5; i++) {
if ( (i+1) * arr[i-1] != arr[i])
explode_bomb();
答案显然:1 26 24 120 720
第三阶段,有些繁琐。
内部含有sscanf函数,猜想应该是格式字符串。
sscanf传入的参数为:ebp-0x4,ebp-0x5, ebp-0xc地址;格式字符串地址;输入的字符参数地址。
ebp-0xc存第一个数, ebp-0x5存第二数,ebp-0x4第三个数。
因而,输入数据为:整数, 字符,整数。
$0x7,-0xc(%ebp)
8048bcd: 0f 87 b5 00 00 00
8048c88 &phase_3+0xf0&
-0xc(%ebp),%eax
8048bd6: ff 24 85 e8 97 04 08
*0x80497e8(,%eax,4)
输入的第一个数要小于等于7,而跳转到0x80497e8这个地址(汇编代码中没有显示这个地址),而同时,代码中又都可以跳转到&phase_3+0xf7&这个位置,猜想是不是switch语句,而要有switch,就需要查找跳转表。
可见,全部为phase_3内的地址,所以,验证为switch语句。
&既然要求第一个数要小于等于7,那么使用最简单0进行测试。
-0xc(%ebp),%eax
8048bd6: ff 24 85 e8 97 04 08
*0x80497e8(,%eax,4)
第一个数为0,则eax 为0,则直接跳转到 (0x80497e8) 指向的位置。0x80497eb存储的内容为:
由上图可知:0x8048be0
7d fc 09 03 00 00
$0x309,-0x4(%ebp)
f 84 a0 00 00 00
8048c8f &phase_3+0xf7&
8048bef: e8 08 09 00 00
80494fc &explode_bomb&
ebp-0x4存储的为第三个数,大小为0x309, 转10进制为:777。 同时,设置了bl 为 0x71。并跳转到0x8048c8f。
8048c8f: 3a 5d fb
-0x5(%ebp),%bl
8048c99 &phase_3+0x101&
8048c94: e8 63 08 00 00
80494fc &explode_bomb&
字符串值为0x71,换算成字符为 ‘q’。
所以答案为: 0 q 777。 当然,答案不为一,根据第一个参数的选择不同而不同。
8048cec: 8d 45 fc
-0x4(%ebp),%eax
8048cef: 50
08 98 04 08
$0x8049808
8048cf6: e8 65 fb ff ff
8048860 &sscanf@plt&
又是一个sscanf,查看0x8049808字符串内容。
可见,输入的是一个整数。
-0x4(%ebp),%eax
8048d15: e8 86 ff ff ff
8048ca0 &func4&
8048d1a: 83 c4 10
$0x10,%esp
8048d1d: 83 f8 37
$0x37,%eax
8048d27 &phase_4+0x47&
将输入的数据传入func4函数中,如果返回值为0x37, 则成功,所以,关键要看func4,推出输入的参数。
0x8(%ebp),%ebx
8048cab: 83 fb 01
8048cae: 7e 20
8048cd0 &func4+0x30&
//小于等于 1, 返回1
$0xfffffff4,%esp
-0x1(%ebx),%eax
//参数-1, 调用func
8048cb7: e8 e4 ff ff ff
8048ca0 &func4&
8048cbc: 89 c6
//返回值保存在esi中
8048cbe: 83 c4 f4
$0xfffffff4,%esp
-0x2(%ebx),%eax
//参数-2, 调用func
8048cc5: e8 d6 ff ff ff
8048ca0 &func4&
8048cca: 01 f0
//两次返回值相加,的返回结果
8048ccc: eb 07
8048cd5 &func4+0x35&
8048cce: 89 f6
8048cd0: b8 01 00 00 00
存在自身调用自身的情况,应该是一个递归。代码应如下结构:
inf func4(int val) {
if (val &= 1)
return func4(val-1) + func4(val-2);
val为何值,返回结果为0x37, 10进制的55. 最简单,写个程序暴力破解一下,值9.
8048d3b: e8 d8 02 00 00
8049018 &string_length&
$0x10,%esp
可见输入的字符串,长度必须为6.
一个循环:
8048d4d: 31 d2
8048d4f: 8d 4d f8
-0x8(%ebp),%ecx
8048d52: be 20 b2 04 08
$0x804b220,%esi
(%edx,%ebx,1),%al
//依次取用户输入串中字符
8048d5a: 24 0f
//只保留最后四位,其它位清零
8048d5c: 0f be c0
//带符号扩展送于eax中
8048d5f: 8a 04 30
(%eax,%esi,1),%al
//取模式串第patt[eax]的字符
%al,(%edx,%ecx,1)
//存入另一数组中,ebp-0x8
8048d57 &phase_5+0x2b&
ebx存放用户输入字符串首地址,而0x804b220肯定也为一个字符串,打印出来,将该字符串称为模式串。
整体思路就是,用户输入的字符串,取低四位,值作为索引,找到模式串中该索引对应的字符,放于一个新数组中。
0b 98 04 08
$0x804980b
-0x8(%ebp),%eax
8048d7a: 50
8048d7b: e8 b0 02 00 00
8049030 &strings_not_equal&
用新数组中字符与0x804980b字符比较,相等即破译。
所以,在模式串中的下标应为:15 0 5 11 13 1
而,要将这些字符换算成可打印的字符,然后输入。每个字符8位,最低4位不能动,所以,在高四位可以任意加1.
转变为:79, 80, 69, 75, 77, 65 对应字符:OPEKMA
首先,阶段六太不容易解决啦,整整花了7-8个小时。关键点:链表。
1. 读6个数
-0x18(%ebp),%eax
e820 02 00 00
8048fd8&read_six_numbers&读取6个数,存放在ebp-18的地方,所以,ebp-18应该是一个数组首地址。
2. 一个大循环,检测输入数据是否满足要求
//清零,控制变量i
$0x10,%esp
0x0(%esi),%esi
-0x18(%ebp),%eax
//取数组首地址
(%eax,%edi,4),%eax
//取arr[i] –》 val
//必须小于等于5,即原小于等于6
8048dd1 &phase_6+0x39&
e82b 07 00 00
80494fc&explode_bomb&
0x1(%edi),%ebx
// ebx = edi + 1, j = I + 1
// 要小于等于5
8048dfc &phase_6+0x64&
8d04 bd 00 00 00 00 lea
0x0(,%edi,4),%eax
//eax = 4 * edi 用户获取元素地址
%eax,-0x38(%ebp)
-0x18(%ebp),%esi
//取arr首地址
-0x38(%ebp),%edx
(%edx,%esi,1),%eax
//获取元素arr[i]
(%esi,%ebx,4),%eax
//比较arr[j] arr[i]
8048df6 &phase_6+0x5e& // 不能相同
e806 07 00 00
80494fc&explode_bomb&
8048de6 &phase_6+0x4e&
8048dc0 &phase_6+0x28&
伪代码如下:
for (int i =0; i&=5; i++){
if (arr[i]&= 6)
explode_bomb();
for (int j = i+1; j&=5; j++){
if (arr[i]== arr[j])
explode_bomb();
检测元素,即每个元素必须不同。同时,必须都是大于等于0的,因为cmp&&& $0x5,%eax都是按照无符号数进行处理的,如果为负数,则换算后为一个大数,因此,不能出现这样的情况。所以,推断处理数据因为:1 2 3 4 5 6,至于排列顺序,目前未知。
3. 最关键的一段代码
-0x18(%ebp),%ecx
//数组首地址arr
-0x30(%ebp),%eax
//ebp-0x30的地址
%eax,-0x3c(%ebp)
//存放于ebp-0x3c中
0x0(%esi),%esi
//无用代码,对齐用
-0x34(%ebp),%esi
//取ebp-34中值
bb01 00 00 00
8d04 bd 00 00 00 00 lea
0x0(,%edi,4),%eax
//eax = edi * 4
(%eax,%ecx,1),%ebx
//arr[i] 与 j比较
8048e38 &phase_6+0xa0&
//小于的话,进入循环
(%edx,%ecx,1),%eax
//eax = arr[i]
8db4 26 00 00 00 00
0x0(%esi,%eiz,1),%esi
//对齐,占位,无用
0x8(%esi),%esi
//esi+0x8的值给esi
//arr[i] == j 停止循环
8048e30 &phase_6+0x98&
-0x3c(%ebp),%edx
//获得ebp-0x30地址
%esi,(%edx,%edi,4)
//esi存入新数组中
8048e10 &phase_6+0x78&
首先,在进入函数后,一句重要代码:
8048da4: c745 cc 6c b2 04 08 &&&&&& movl&& $0x804b26c,-0x34(%ebp)
又是一个地址,就以为是一个字符串,但尝试打印的时候出现问题:
显示一个&node1&,难道是一个结构体。而8048e30:&&&& 8b 76 08&& mov&&&0x8(%esi),%esi,esi值为0x804b26c,如果为结构体,
这明显是一个结构体的操作,相当于一个next指针。
打印出来试试:
可见,就是一个链表,首尾相接。
&伪代码如下:
for (int i =0; i&=5; i++){
for (int j =0; arr[i]& j++){
node = node.
比如输入 3 1 2 4,这样,就让链表第三个元素排到第一位置,链表第1位置元素排到第二,链表第二元素排到第三,……
4. 重新串接链表
-0x30(%ebp),%esi
//取ptr地址
%esi,-0x34(%ebp)
//存于ebp – 0x34中
bf01 00 00 00
-0x30(%ebp),%edx
//ebp-0x30地址
(%edx,%edi,4),%eax
//ptr[i] =& eax
%eax,0x8(%esi)
//ptr[i-1]-&next = ptr[i]
//esi = ptr[i]
8048e52 &phase_6+0xba&
c746 08 00 00 00 00
$0x0,0x8(%esi)
-0x34(%ebp),%esi
//最后一个结点next 赋0
5.检测链表值
8d74 26 00
0x0(%esi,%eiz,1),%esi
//无用代码
0x8(%esi),%edx
//esi+8 的值为下一个链表地址
(%esi),%eax
//取当前链表第一个元素值
(%edx),%eax
//与下一个链表第一个元素值比较
8048e7e &phase_6+0xe6&
//当前大于下一个,则成功
e87e 06 00 00
80494fc&explode_bomb&
0x8(%esi),%esi
8048e70 &phase_6+0xd8&总结:也就是说,要让第一个元素的值从大到小排列。初始链表元素第一个值从第一到第五分别为:
fd&2d5& 12d&& 3e5&&d4&& 1b0
让其从大到小排列:3e5 2d5 1b0 12d fd d4
对应输入值:4 2 6 3 1 5
附上答案:
Public speaking is very easy.
1 2 6 24 120 720
4 2 6 3 1 5
啊,终于成功拆解了!!!!!
本文已收录于以下专栏:
相关文章推荐
实验目的本实验通过要求你使用课程所学知识拆除一个“binary bombs”来增强对程序的机器级表示、汇编语言、调试器和逆向工程等方面原理与技能的掌握。 一个“binary bombs”(二进制炸弹,...
本实验设计为一个黑客拆解二进制炸弹的游戏。我们仅给黑客(同学)提供一个二进制可执行文件bomb和主函数所在的源程序bomb.c,不提供每个关卡的源代码。程序运行中有6个关卡(6个phase),每个关卡...
一个关于破解的初级实验。考的就是汇编代码的熟练程度和分析能力。不过有几个函数长的让人吐血。本着不轻易爆炸的原则,只好慢慢调咯。
1. 反汇编bomb
    用objdump直接反汇编出汇编代码。
卡耐基梅隆大学著名的实验之一:二进制炸弹bomb第一弹
二进制拆弹bomb实验第二弹
《深入理解计算机系统》第三章的bomb lab,拆弹实验:给出一个linux的可执行文件bomb,执行后文件要求分别进行6次输入,每一次输入错误都会导致炸弹爆炸,程序终止。需要通过反汇编来逆向关键代码...
这是来自于CS:APP的一个著名实验“拆解二进制炸弹”,也是我们的计算机组成与体系结构课程的家庭作业
这个实验也让我学会了怎么进行反汇编和使用GDB调试程序
解决问题的过程如下:Phase_1
首先查看整个bomb.c的代码,发现整个炸弹组是由6个小炸弹(函数)组成的。整个main函数比较简单,函数间变量几乎没有影响。因此,只需要依次解除6个小炸弹即可。
所以,接下来便...
最近在看Coursera上的软硬件接口,学习CSAPP,一直都听说这个二进制炸弹实验非常有趣,跟着课程设置就自己动手做了做。打算把实验的过程和结果都纪录一下。
熟悉Linux系统确实花了一番功夫。大二...
bomb炸弹实验
首先对bomb这个文件进行反汇编,得到一个1000+的汇编程序,看的头大。
400ef0: 48 83 ec 08...
他的最新文章
讲师:宋宝华
讲师:何宇健
您举报文章:
举报原因:
原文地址:
原因补充:
(最多只允许输入30个字)}

我要回帖

更多关于 二进制转十进制题目 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信