CSAPP Lab2:Bomb Lab

news2024/11/15 6:52:41

说明

6关卡,每个关卡需要输入一个字符串,通过逆向工程来获取对应关卡的字符串

准备工作

环境

需要用到gdb调试器

apt-get install gdb

系统: Ubuntu 22.04

本实验会用到的gdb调试器的指令如下

r或者 run或者run filename 运行程序,run filename就是用filename中的内容作为输入
b *address 在某个地址设置断点
d或delete 删除所有断点
d 断点号 删除指定断点
info b 查看所有断点信息
x/参数 地址 查看指针解引用后的值,参数可以是s(字符串),d(十进制),x(十六进制),地址若是寄存器需要加上$
info register 或info r 查看所有寄存器的值
disas functionName 生成functionName的汇编代码
stepi 执行一个汇编指令
layout asm 窗口分为两部分,上面是将要执行的汇编代码,下面输入gdb调试命令

前置知识

寄存器的东西

这里面有一些寄存器的知识,了解即可
%rsp (register stack pointer) 栈指针
%esi:通用寄存器,长应用于指针或索引
%rax:存储函数的返回值,存储临时数据,系统调用号
%r12,%rbx:通用寄存器,参数传递的
%rbp(register base pointer):通常是当作基址指针来用

指令相关

test destination,source

destination和source可以是寄存器,内存地址,立即数.对两个操作数进行按位逻辑与操作,会更新下面寄存器的状态

  • Zero flag(ZF):若结果为0,则设置为1,反之为0,这个若为1表明两个操作数相等或者某个操作数等于0
  • Sign flag (SF):若结果最高位为1,则为1,反之为0
  • Overflow flag(OF):有符号数溢出则为1,反之为0
  • Carry flag(CF):无符号数溢出则为1,反之为0
  • Parity flag(PF):结果的低8位包含奇数个1则为1,反之为0

je destination

若ZF为1则跳转到destination处

开干

Phase 1 字符串比较

终端输入gdb bomb
在输入disas phase_1,结果如下

   0x0000000000400ee0 <+0>:	sub    $0x8,%rsp // 把栈指针减少8,给局部变量提供空间
   0x0000000000400ee4 <+4>:	mov    $0x402400,%esi // 将0x402400存储到%esi中,这个有可能是存放我们输入的值或者存放内置字符串的
   0x0000000000400ee9 <+9>:	call   0x401338 <strings_not_equal>//调用了函数strings_not_equal,估计是判断输入的字符串和内置字符串是否相同,相同返回0
   0x0000000000400eee <+14>:	test   %eax,%eax  //判断%eax(函数strings_not_equal的返回值)是否为0,若为0则ZF=1
   0x0000000000400ef0 <+16>:	je     0x400ef7 <phase_1+23> // ZF为1就跳转,反之顺序执行
   0x0000000000400ef2 <+18>:	call   0x40143a <explode_bomb>//拆弹失败,炸弹爆炸
   0x0000000000400ef7 <+23>:	add    $0x8,%rsp //回收栈指针
   0x0000000000400efb <+27>:	ret    

把断点打到0x0000000000400ee9的位置,开始run,随便输点东西

b *0x400ee9
run
x/s 0x402400	

结果发现0x402400就是我们想要的东西:Border relations with Canada have never been better.
那么我们输入的东西到哪里去了?断点打到strings_not_equal里面,查看strings_not_equal汇编代码(在gdb中输入disas strings_not_equal)

Dump of assembler code for function strings_not_equal:
   0x0000000000401338 <+0>:	push   %r12
   0x000000000040133a <+2>:	push   %rbp 
   0x000000000040133b <+3>:	push   %rbx
   0x000000000040133c <+4>:	mov    %rdi,%rbx
   0x000000000040133f <+7>:	mov    %rsi,%rbp
   0x0000000000401342 <+10>:	call   0x40131b <string_length>
   0x0000000000401347 <+15>:	mov    %eax,%r12d
   0x000000000040134a <+18>:	mov    %rbp,%rdi
   0x000000000040134d <+21>:	call   0x40131b <string_length>
   0x0000000000401352 <+26>:	mov    $0x1,%edx
   0x0000000000401357 <+31>:	cmp    %eax,%r12d
   0x000000000040135a <+34>:	jne    0x40139b <strings_not_equal+99>
   0x000000000040135c <+36>:	movzbl (%rbx),%eax
   0x000000000040135f <+39>:	test   %al,%al
   0x0000000000401361 <+41>:	je     0x401388 <strings_not_equal+80>
   0x0000000000401363 <+43>:	cmp    0x0(%rbp),%al
   0x0000000000401366 <+46>:	je     0x401372 <strings_not_equal+58>
   0x0000000000401368 <+48>:	jmp    0x40138f <strings_not_equal+87>
   0x000000000040136a <+50>:	cmp    0x0(%rbp),%al
   0x000000000040136d <+53>:	nopl   (%rax)
   0x0000000000401370 <+56>:	jne    0x401396 <strings_not_equal+94>
   0x0000000000401372 <+58>:	add    $0x1,%rbx

断点打到0x401338的位置,运行程序
不停的stepi,知道运行到了第一次调用string_length函数,字符串一般来说是需要一个基址的,所以找能充当基指指针的寄存器,下面是试探过程

x/s $rbp //这个是内置字符串
x/s $rbx //这个是我们输入的字符串

答案

Border relations with Canada have never been better.

进入strings_not_equal函数才能看到我们输入的字符串是保存在%rbx这个寄存器当中的

phase_2 循环

disas 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> //这里从名字可以知道要输入6个数字,那么是整形还是浮点数呢?这里先输入6个整形试试

输入stepi进入到read_six_numbers函数中,disas read_six_numbers得到下面的代码

Dump of assembler code for function read_six_numbers:
   0x000000000040145c <+0>:	sub    $0x18,%rsp  // 这个不用管
   0x0000000000401460 <+4>:	mov    %rsi,%rdx
   0x0000000000401463 <+7>:	lea    0x4(%rsi),%rcx
   0x0000000000401467 <+11>:	lea    0x14(%rsi),%rax
   0x000000000040146b <+15>:	mov    %rax,0x8(%rsp)
   0x0000000000401470 <+20>:	lea    0x10(%rsi),%rax
   0x0000000000401474 <+24>:	mov    %rax,(%rsp)
   0x0000000000401478 <+28>:	lea    0xc(%rsi),%r9
   0x000000000040147c <+32>:	lea    0x8(%rsi),%r8
   0x0000000000401480 <+36>:	mov    $0x4025c3,%esi   //看看%esi寄存器的内容
   0x0000000000401485 <+41>:	mov    $0x0,%eax
   0x000000000040148a <+46>:	call   0x400bf0 <__isoc99_sscanf@plt>
   0x000000000040148f <+51>:	cmp    $0x5,%eax
   0x0000000000401492 <+54>:	jg     0x401499 <read_six_numbers+61>
   0x0000000000401494 <+56>:	call   0x40143a <explode_bomb>
   0x0000000000401499 <+61>:	add    $0x18,%rsp
   0x000000000040149d <+65>:	ret    

注意这一行mov $0x4025c3,%esi,因为字符串是不可变的,地址固定死了,所以找立即数
esi寄存器的内容为
在这里插入图片描述
六个整形数据猜测是对的,我们输入1 2 3 4 5 6试试
继续阅读phase_2后部分的代码

0x0000000000400f05 <+9>:	call   0x40145c <read_six_numbers>
   0x0000000000400f0a <+14>:	cmpl   $0x1,(%rsp) // (%rsp)=*rsp 就是我们输入的第一个数字1
   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 // eax保存的是我们输入的第一个数,把rbx存放的值减4
   0x0000000000400f1a <+30>:	add    %eax,%eax  // eax*=2
   0x0000000000400f1c <+32>:	cmp    %eax,(%rbx) // 比较eax的值和rbx(就是第二个值)是否相等
   0x0000000000400f1e <+34>:	je     0x400f25 <phase_2+41> // 相等就跳转
   0x0000000000400f20 <+36>:	call   0x40143a <explode_bomb> //反之爆炸
   0x0000000000400f25 <+41>:	add    $0x4,%rbx // 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 // 0x4(%rsp)=我们输入的第二个数,加4的原因是因为int类型是4个字节
   0x0000000000400f35 <+57>:	lea    0x18(%rsp),%rbp// 0x18转换为十进制数为24,也就是第6个数字后面的第一个存储单元
   0x0000000000400f3a <+62>:	jmp    0x400f17 <phase_2+27>
   0x0000000000400f3c <+64>:	add    $0x28,%rsp

意思如下
程序开始时,将 (%rsp) 的值与立即数$0x1进行比较,所以第一个输入数必须为1,跳转至400f30,用lea指令分别加载%rsp+4%rsp+24对应的地址到%rbx和%rbp,因为int型数据占4个字节,所以%rbx和%rbp分别存放第2个输入数的地址和第6个输入数的后一块的地址
后跳转至400f17,此时(%rbx-4)对应的值即(%rsp)对应的值,将其存放值%eax中,将该值*2后与(%rbx)对应的值(即第二个输入值)进行比较,即后一个数是前一个数的2倍,所以第二个输入值必须为2,后跳转至400f25,得到%rbx=%rbx+4,与%rbp进行比较(%rbx对应的值(地址)是否为%rbp对应的值(地址)),若不相等则又跳转至400f17重复操作,若相等,则跳转至400f3c,结束循环,可知这是一个循环操作,看是否比较完6个数。

循环中寄存器对应的值为:

%rbx%rbp%eax
%rsp+4%rsp+24(%rsp)*2=2
%rsp+8(%rsp)*2=4
%rsp+12(%rsp)*2=8
%rsp+16(%rsp)*2=16
%rsp+20(%rsp)*2=32
%rsp+24

c代码如下

int main(){
	int[] array = new int[6];
	for(int i=1;i<6;i++)
		array[i]=array[i-1]*2;
	return 0;
}

答案

1 2 4 8 16 32

输入的第一个数时保存在(%rsp)中

phase_3 分支语句

对汇编代码进行分析

Dump of assembler code for function phase_3:
   0x0000000000400f43 <+0>:	sub    $0x18,%rsp
   0x0000000000400f47 <+4>:	lea    0xc(%rsp),%rcx //这个可能是存储第二个数的
   0x0000000000400f4c <+9>:	lea    0x8(%rsp),%rdx//这个可能是存储第一个数的
   0x0000000000400f51 <+14>:	mov    $0x4025cf,%esi  //出现立即数了,后面调用了scanf,这里应该是初始化的,在gdb 中用x/s $esi 可以得到 %d %d
   0x0000000000400f56 <+19>:	mov    $0x0,%eax
   0x0000000000400f5b <+24>:	call   0x400bf0 <__isoc99_sscanf@plt>
   0x0000000000400f60 <+29>:	cmp    $0x1,%eax//%eax存储函数的返回值的,scanf的函数返回值就是输入数据的个数
   0x0000000000400f63 <+32>:	jg     0x400f6a <phase_3+39>//若大于1就跳转
   0x0000000000400f65 <+34>:	call   0x40143a <explode_bomb>//否则就爆炸
   0x0000000000400f6a <+39>:	cmpl   $0x7,0x8(%rsp)
   0x0000000000400f6f <+44>:	ja     0x400fad <phase_3+106>//无符号大于则跳转,跳转就爆炸了,所以第一个数必须小于7,可以等于,但是不能是负数
   0x0000000000400f71 <+46>:	mov    0x8(%rsp),%eax//%eax存储的是输入的第一个数
   0x0000000000400f75 <+50>:	jmp    *0x402470(,%rax,8) //*0x402470 = 124,通过x/d 0x402470得到, rax是64位的,eax是32位的,就是说eax是rax的低32位,这里应该是124+%rax*8(%rax就是我们输入的第一个数)来实现跳转。
   0x0000000000400f7c <+57>:	mov    $0xcf,%eax
   0x0000000000400f81 <+62>:	jmp    0x400fbe <phase_3+123>
   0x0000000000400f83 <+64>:	mov    $0x2c3,%eax
   0x0000000000400f88 <+69>:	jmp    0x400fbe <phase_3+123>
   0x0000000000400f8a <+71>:	mov    $0x100,%eax
   0x0000000000400f8f <+76>:	jmp    0x400fbe <phase_3+123>
   0x0000000000400f91 <+78>:	mov    $0x185,%eax
   0x0000000000400f96 <+83>:	jmp    0x400fbe <phase_3+123>
   0x0000000000400f98 <+85>:	mov    $0xce,%eax
--Type <RET> for more, q to quit, c to continue without paging--
   0x0000000000400f9d <+90>:	jmp    0x400fbe <phase_3+123>
   0x0000000000400f9f <+92>:	mov    $0x2aa,%eax
   0x0000000000400fa4 <+97>:	jmp    0x400fbe <phase_3+123>
   0x0000000000400fa6 <+99>:	mov    $0x147,%eax
   0x0000000000400fab <+104>:	jmp    0x400fbe <phase_3+123>
   0x0000000000400fad <+106>:	call   0x40143a <explode_bomb>
   0x0000000000400fb2 <+111>:	mov    $0x0,%eax
   0x0000000000400fb7 <+116>:	jmp    0x400fbe <phase_3+123>
   0x0000000000400fb9 <+118>:	mov    $0x137,%eax
   0x0000000000400fbe <+123>:	cmp    0xc(%rsp),%eax
   0x0000000000400fc2 <+127>:	je     0x400fc9 <phase_3+134>
   0x0000000000400fc4 <+129>:	call   0x40143a <explode_bomb>
   0x0000000000400fc9 <+134>:	add    $0x18,%rsp
   0x0000000000400fcd <+138>:	ret    
End of assembler dump.

第一个数的范围是在[0,7]之间,开始试探
n1=0,跳转到0x400f7c,若n2!=0xcf,则爆炸
n1=1,跳转到0x400fb9,若n2!=0x137,则爆炸
n1=2,跳转到0x400f83,若n2!=0x2c3,则爆炸
n1=3,跳转到0x400f8a,若n2!=0x100,则爆炸
n1=4,跳转到0x400f91,若n2!=0x185,则爆炸
n1=5,跳转到0x400f98,若n2!=0xce,则爆炸
n1=6,跳转到0x400f9f,若n2!=0x2aa,则爆炸
n1=7,跳转到0x400fa6,若n2!=0x147,则爆炸

c代码

void phase_3(char* input){
	//0x8(%rsp) 0xc(%rsp)
	int n1,n2;
	//res存放返回输入数据的个数
	int res = sscanf(input,"%d %d",&n1,&n2);
	if(res<=1)
		explode_bomb();
	switch(n1){
		case 0:
		if(n2!=0xcf)
			explode_bomb();
		break;
		case 1:
		if(n2!=0x137)
			explode_bomb();
		break;
		case 2:
		if(n2!=0x2c3)
			explode_bomb();
		break;
		case 3:
		if(n2!=0x100)
			explode_bomb();
		break;
		case 4:
		if(n2!=0x185)
			explode_bomb();
		break;
		case 5:
		if(n2!=0xce)
			explode_bomb();
		break;
		case 6:
		if(n2!=0x2aa)
			explode_bomb();
		break;
		case 7:
		if(n2!=0x147)
			explode_bomb();
		break;
	}
}

答案

  • 0 207
  • 1 311
  • 2 707
  • 3 256
  • 4 389
  • 5 206
  • 6 682
  • 7 327

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/887984.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

8----代码块

一、行内代码​ 使用一对反引号()来创建行内代码。 如果在行内代码中需要包含反引号本身&#xff0c;可以使用两个反引号对加前后空格来创建。(但是这样的代码块不会进行语法高亮&#xff0c;只是简单地将代码以等宽字体显示) 注&#xff1a;反引号在键盘上位于左上角&#xff…

用Python打造复古风格的游戏:回归8位时代【俄罗斯方块】

大家好&#xff0c;我是辣条&#xff01; 今天带大家来写一个说难不难&#xff0c;说简单也不算接单的复古小游戏&#xff1a;俄罗斯方块游戏&#xff01; 目录 前言&#xff1a;步骤首先接下来然后接下来最后 上代码&#xff1a;总结: 前言&#xff1a; 俄罗斯方块是一款经典…

PyTorch深度学习实战——使用卷积神经网络执行图像分类

PyTorch深度学习实战——使用卷积神经网络执行图像分类 0. 前言1. Fashion-MNIST 数据集图像分类2. 模型测试相关链接 0. 前言 我们已经在《卷积神经网络详解》一节中介绍了传统神经网络在面对图像平移时的问题以及卷积神经网络 (Convolutional Neural Network, CNN) 的工作原…

CSS 字体修饰属性

前言 字体修饰属性 属性说明font-family指定文本显示字体font-size设置字体的大小font-weight设置字体的粗细程度font-style设置字体的倾斜样式text-decoration给文本添加装饰线text-indent设置文本的缩进text-align设置文本的对齐方式line-height设置行高color设置文本的颜色…

IDEA常用插件推荐(个人)

分享下个人在大厂工作四五年的一个常用配置插件 一、Alibaba Java Coding Guidelines 代码规范插件(必备) 阿里巴巴代码规范检查 人手必备。减少你的垃圾代码 各种不良提示代码全靠它了。 代码划线的嘎嘎 crtlenter优化得了 二、Atom Material File Icons 图标主题插件(提示…

Java学习手册——第二篇面向对象程序设计

Java学习手册——第二篇面向对象 1. 结构化程序设计2. 面向对象 第一章我们已经介绍了Java语言的基础知识&#xff0c;也知道他能干什么了&#xff0c; 那我们就从他的设计思想开始入手吧。 接触一个语言之前首先要知道他的大方向&#xff0c;设计思想是什么样的&#xff0c; 这…

【高阶数据结构】红黑树详解

文章目录 前言1. 红黑树的概念及性质1.1 红黑树的概念1.2 红黑树的性质1.3 已经学了AVL树&#xff0c;为啥还要学红黑树 2. 红黑树结构的定义3. 插入&#xff08;仅仅是插入过程&#xff09;4. 插入结点之后根据情况进行相应调整4.1 cur为红&#xff0c;p为红&#xff0c;g为黑…

Redis——哨兵模式(docker部署redis哨兵)+缓存穿透和雪崩

哨兵模式 自动选取主机的模式。 概述 主从切换技术的方法是:当主服务器宕机后&#xff0c;需要手动把一台从服务器切换为主服务器&#xff0c;这就需要人工干预&#xff0c;费事费力&#xff0c;还会造成段时间内服务不可用。这不是一种推荐的方式&#xff0c;更多时候&…

LabVIEW调用DLL传递结构体参数

LabVIEW 中调用动态库接口时&#xff0c;如果是值传递的结构体&#xff0c;可以根据字段拆解为多个参数&#xff1b;如果参数为结构体指针&#xff0c;可用簇&#xff08;Cluster&#xff09;来匹配&#xff0c;其内存连续相当于单字节对齐。 1.值传递 接口定义&#xff1a; …

交叉导轨的内部结构

相对于直线导轨&#xff0c;交叉导轨的知名度是没那么高的&#xff0c;但随着技术水平的提高&#xff0c;精度更高&#xff0c;安装高度更低的交叉导轨也慢慢走近大众的视野&#xff0c;得到更多厂商的青睐&#xff0c;使用范围也更加广泛。 交叉导轨是由两根具有V型滚道的导轨…

数据结构之动态内存管理机制

目录 数据结构之动态内存管理机制 占用块和空闲块 系统的内存管理 可利用空间表 分配存储空间的方式 空间分配与回收过程产生的问题 边界标识法管理动态内存 分配算法 回收算法 伙伴系统管理动态内存 可利用空间表中结点构成 分配算法 回收算法 总结 无用单元收…

leetcode-413. 等差数列划分(java)

等差数列划分 leetcode-413. 等差数列划分题目描述双指针 上期经典算法 leetcode-413. 等差数列划分 难度 - 中等 原题链接 - 等差数列划分 题目描述 如果一个数列 至少有三个元素 &#xff0c;并且任意两个相邻元素之差相同&#xff0c;则称该数列为等差数列。 例如&#xff0…

【Linux操作系统】Linux系统编程实现递归遍历目录,详细讲解opendir、readdir、closedir、snprintf、strcmp等函数的使用

在Linux系统编程中&#xff0c;经常需要对目录进行遍历操作&#xff0c;以获取目录中的所有文件和子目录。递归遍历目录是一种常见的方法&#xff0c;可以通过使用C语言来实现。本篇博客将详细介绍如何使用C语言实现递归遍历目录的过程&#xff0c;并提供相应的代码示例&#x…

高阶数据结构-图

高阶数据结构-图 图的表示 图由顶点和边构成&#xff0c;可分为有向图和无向图 邻接表法 图的表示方法有邻接表法和邻接矩阵法&#xff0c;以上图中的有向图为例&#xff0c;邻接表法可以表示为 A->[(B,5),(C,10)] B->[(D,100)] C->[(B,3)] D->[(E,7)] E->[…

AgentBench::AI Agent 是大模型的未来

最有想象力、最有前景的方向 “Agent 是 LLM(大语言模型)的最有前景的方向。一旦技术成熟,短则几个月,长则更久,它可能就会创造出超级个体。这解释了我们为何对开源模型和 Agent 兴奋,即便投产性不高,但是我们能想象自己有了 Agent 之后就可以没日没夜地以百倍效率做现在…

Collada .dae文件格式简明教程【3D】

当你从互联网下载 3D 模型时&#xff0c;可能会在格式列表中看到 .dae 格式。 它是什么&#xff1f; 推荐&#xff1a;用 NSDT编辑器 快速搭建可编程3D场景。 1、Collada DAE概述 COLLADA是COLLAborative Design Activity&#xff08;中文&#xff1a;协作设计活动&#xff09…

剑指offer43.1~n整数中1出现的次数

看到这么大的数据规模就直到用暴力法肯定会超时&#xff0c;但是还是花一分钟写了一个试一下&#xff0c;果然超时 class Solution {public int countDigitOne(int n) {int count 0;for(int i1;i<n;i){countdigitOneInOneNum(i);}return count;}public int digitOneInOneNu…

从零实战SLAM-第九课(后端优化)

在七月算法报的班&#xff0c;老师讲的蛮好。好记性不如烂笔头&#xff0c;关键内容还是记录一下吧&#xff0c;课程入口&#xff0c;感兴趣的同学可以学习一下。 --------------------------------------------------------------------------------------------------------…

字符个数统计(同类型只统计一次)

思路&#xff1a;因为题目圈定出现的字符都是 ascii 值小于等于127的字符&#xff0c;因此只需要定义一个标记数组大小为128 &#xff0c;然后将字符作为数组下标在数组中进行标记&#xff0c;若数组中没有标记过表示第一次出现&#xff0c;进行计数&#xff0c;否则表示重复字…

Layui列表复选框根据条件禁用

// 禁用客服回访id有值的复选框res.data.forEach(function (item, i) {if (item.feedbackEmpId) {let index res.data[i][LAY_TABLE_INDEX];$(".layui-table tr[data-index"index"] input[typecheckbox]").prop(disabled,true);$(".layui-table tr[d…