探究CSAPP实验二-bomb lab-第一节

探究CSAPP实验二-bomb lab

前言

之前学过一点汇编但是已经忘了,在工作中遇到所以重新开始学习,bomb lab即有趣又可以学习gdb的调试以及汇编代码的阅读,所以重点学习。此项目的地址为csapp实验地址以下为学习笔记。计划分为三篇文章来详细记录过程,本文为系列中的第一篇。

项目介绍

下载好项目后里面有个pdf文档,对这个项目进行了讲解。
在这里插入图片描述
意思概括为要成功拆除一个炸弹分为几个阶段,每个阶段都要输入相应的字符串才能拆除成功(stdin-标准输入),如果中间一个阶段拆除失败则炸弹爆炸。
文件目录如下:
在这里插入图片描述
来看看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. */
    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();
    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");

    /* 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");
    
    /* 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");

    /* 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();

    /* Wow, they got it!  But isn't something... missing?  Perhaps
     * something they overlooked?  Mua ha ha ha ha! */
    
    return 0;
}

argc指的是参数个数,比如如下:
在这里插入图片描述
这个./bomb就是一个参数,这里的argc为1,argv[0]=“./bomb”。那么根据代码
在这里插入图片描述
我们可以直接运行bomb文件,也可以后面跟上文件名,比如命令行中输入./bomb 1.txt的格式1.txt里面应该写入6个阶段对应字段,这里argc=2,argv[0]=“./bomb”,argv[1]=“1.txt”。从代码可以看出一共有六个阶段。
接下来开始我们的摸索之旅。

阶段1

使用gdb进行调试。
在这里插入图片描述
首先看看initialize_bomb函数。可以看看它的汇编代码,使用disassemble initialize_bomb命令。
在这里插入图片描述
从rsp寄存器大致可以看出这是64位汇编(32位汇编的栈顶寄存器是esp),需要注意的栈是后进先出的结构,在系统中栈是高地址往低地址走的,所以这里是sub,还需要注意的是在linux中是AT&T汇编格式,与intel稍有不同,比如mov A,B是A移到B。以上汇编其实没啥亮点,调用了信号量。
接下来看看phase_1汇编代码:
在这里插入图片描述
这里调用了strings_not_equal函数,所以输入的是一个字符串与它内置的字符串进行比较,这里test如果结果为0(即相同)就je(跳转)到add $0x8,%rsp指令,若不同则调用explode_bomb。在调用strings_not_equal函数前执行的汇编是mov指令,来看看$0x402400是什么值,已知是字符串,可以使用x/s查看(x可以查看内存地址中的值)。
在这里插入图片描述
来验证一下:
在这里插入图片描述
可以看到一阶段成功拆除。

答案:
Border relations with Canada have never been better.

阶段2

来查看phase_2函数的汇编代码,可在gdb中输入disasse phase_2。
在这里插入图片描述
前三行push %rbp; push %rbx; sub %0x28,%rsp与倒数二三四行对称(pop %rbp; pop %rbx; add %0x28,%rsp),可以看到在汇编指令中前面行中出现了+52而后面行中中出现了+27,所以一定是有循环的。如果想调试寄存器的变化情况可以在phase_2打入断点(b phase_2),然后运行,si是汇编语言级别的单步调试,i r可以显示寄存器信息,若想看具体的寄存器信息如rsp,可以i r rsp查看。x/i 地址可以看到这行地址对应的汇编代码。
以上汇编代码中调用了地址在0x40145c的read_six_numbers函数。我们可以来看看read_six_numbers函数的汇编代码:
在这里插入图片描述
前面的sub,mov,lea就不用看了,到地址0x0000000000401480地址处的汇编代码mov $0x4025c3,%esi,来看看地址0x4025c3装了什么东西,使用x/s 0x4025c3命令(以下是输入的格式,中间是空格)。
在这里插入图片描述
<+41>那里eax寄存器的值置为0x0,然后调用库函数(从plt可以知道)sscanf,结束后再<+51> eax与0x5进行比较,再jg(大于,即eax>0x5后这个函数结束),我分析是这里每输入一个整数(%d)eax加1,有六个所以输入后从0x0到0x6,输入六个后结束这个函数否则调用炸弹爆炸函数。
现在仔细分析下phase_2的汇编

   0x0000000000400efc <+0>:		push   %rbp
   0x0000000000400efd <+1>:		push   %rbx
   0x0000000000400efe <+2>:		sub    $0x28,%rsp
   0x0000000000400f02 <+6>:		mov    %rsp,%rsi
   0x0000000000400f05 <+9>:		call   0x40145c <read_six_numbers>
   0x0000000000400f0a <+14>:	cmpl   $0x1,(%rsp)
   0x0000000000400f0e <+18>:	je     0x400f30 <phase_2+52>
   0x0000000000400f10 <+20>:	call   0x40143a <explode_bomb>
   0x0000000000400f15 <+25>:	jmp    0x400f30 <phase_2+52>
   0x0000000000400f17 <+27>:	mov    -0x4(%rbx),%eax
   0x0000000000400f1a <+30>:	add    %eax,%eax
   0x0000000000400f1c <+32>:	cmp    %eax,(%rbx)
   0x0000000000400f1e <+34>:	je     0x400f25 <phase_2+41>
   0x0000000000400f20 <+36>:	call   0x40143a <explode_bomb>
   0x0000000000400f25 <+41>:	add    $0x4,%rbx
   0x0000000000400f29 <+45>:	cmp    %rbp,%rbx
   0x0000000000400f2c <+48>:	jne    0x400f17 <phase_2+27>
   0x0000000000400f2e <+50>:	jmp    0x400f3c <phase_2+64>
   0x0000000000400f30 <+52>:	lea    0x4(%rsp),%rbx
   0x0000000000400f35 <+57>:	lea    0x18(%rsp),%rbp
   0x0000000000400f3a <+62>:	jmp    0x400f17 <phase_2+27>
   0x0000000000400f3c <+64>:	add    $0x28,%rsp
   0x0000000000400f40 <+68>:	pop    %rbx
   0x0000000000400f41 <+69>:	pop    %rbp
   0x0000000000400f42 <+70>:	ret    

调用完read_six_numbers函数后,先比较0x1与(%rsp),(%rsp)是rsp指针指向的值,之前输入了六个整数,又栈是后进先出的结构,所以第一个数应该在esp位置,那么第一个整数就是1,才会执行jmp(跳转到<+52>),否则会爆炸。
大致画一下栈的图(六个整数):
在这里插入图片描述
0x4(%rsp)就是rsp的位置加上0x4即往高地址走(需要注意的是int型占4字节)。那么这里就是rsp移到第二个整数的位置,将地址加载到rbx,然后rsp的地址向上移动0x18,加载到rbp。再到<+27>,这已经很明显是一个循环程序了。重点解释下如下汇编(这里的编号为自定):

1 mov    -0x4(%rbx),%eax
2 add    %eax,%eax
3 cmp    %eax,(%rbx)
4 je     0x400f25 <phase_2+41>
5 call   0x40143a <explode_bomb>
6 add    $0x4,%rbx
7 cmp    %rbp,%rbx
8 jne    0x400f17 <phase_2+27>
9 jmp    0x400f3c <phase_2+64>

现在的rbx的地址就是之前的0x4(%rsp)也就是第二个整数的地址,第一行第二个整数地址又回到第一行的地址,并将第一个整数地址对应的值赋值给eax,现在eax=1,再执行第二行 add %eax,%eax,现在的eax=2。将eax=2与rbx的地址里面的值进行比较((%rbx)相当于*(int *) rbx),如果相等跳到add $0x4,%rbx,即%rbx往高地址走,再rbp与rbx进行比较,如果二者不等跳到mov -0x4(%rbx),%eax,否则跳出循环(到<+64>)。
所以以上流程其实大致可以推测就是如下代码逻辑:

//1.输入六个数
array[0]=1;
//如果第一个不为1爆炸
for(int i=1;i<6;i++){
	array[i]=array[i-1]+array[i-1];
	//如果不等爆炸
}

那么这六个整数就是1 2 4 8 16 32。
验证一下,在phase_2处打断点(使用b phase_2指令,并且先要拆除phase_1),然后r起来,一行一行调试(汇编级别调试是si与ni指令,如果想进入read_six_numbers函数建议si)
在这里插入图片描述
这里eax,rbx的值都是地址,比如0x2,右边的2是0x2的十进制。
现在进行验证:
在这里插入图片描述

总结

看来以后见到mov 某个地址,esi的指令可以看看这个地址里的值是什么,阶段1与阶段2都用到了,还需要注意的是比如(%rbx)的意义。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值