Bomb LAB实验报告
目录
[TOC]
前置准备
tar -xvf bomb.tar 解压
objdump -d -M intel bomb >bomb.s
通过总体观察 可以看出主要需要理解的是phase函数 phase_defused 是共用的 ,猜测应该是成功时一些输出,先不管。
另外在gdb中 也可以使用disass phase_?查看反汇编,可以不用来回切换屏幕。
程序开启了PIE保护 但是gdb调试时。默认是关闭aslr的。这使得调试的时候可以简单的加上一个基地址来下断点,另外可以用phase_1 + offset 这样相对取值的方式下断点。
phase_1
Dump of assembler code for function phase_1: |
process
[rip+0x1b9a]是常用的加载某个数据的方式,从后面strings_not_equal可以看出很显然是在比较字符串,那么就需要知道rdi是什么,很显然rdi应该是在main里就传入的
0x00005555555554a0 <+87>: call 0x555555555c24 <read_line> |
后面的rdi也基本是这种形式 所以不用重复判断了
那么只需要用gdb 的 x/s指令读取字符串内容,复制了之后重新输入就行
pwndbg> x/s 0x555555557150 |
pwndbg是我平时一直在用的gdb,相比正常的只是多了一些废话,并不会很轮椅,所以我也就懒得调gdb配置文件了。
outcome
c |
phase_2
Dump of assembler code for function phase_2: |
process
0x00005555555555d1 <+6>: sub rsp,0x28 |
这可能在不熟悉canary保护的人看起来会觉得有点诡异。
这其实是canary保护的一个固定的形式,加载fs段里的随机值到栈rbp上方 ,函数退出时进行比较,和我现在要做的部分无关。
Q:为什么phase_1 没有这个保护?A:phase_1根本没用到栈。
具体关注开启栈帧和关闭栈帧中间的部分
read_six_numbers 可以猜测大概率是将rdi字符串拆成六个数值,然后存入rsi 。 rsi指向栈顶,这是栈上数组的表现。
这里可以具体看看read_six_numbers怎么实现的,但是我觉得不如猜测用空格隔开更方便。
后面取值用了大量的DWORD PTR 可以直接假设是int 类型的数组。下面记这个数组为 int buf[6]
js貌似不是一个很常见的指令,在这里它比较 buf[0] 与 0 ,如果buf[0]<0 则会跳转。这里不能跳转。
可以令buf[0] = 0
mov rbp,rsp 这里比较反常理,一般rbp是不会被使用到的,这里就当是被使用了。
+40 到 +84 是一个循环,但是确实有点杂乱
ebx 很显然就是数组下标,从 1-5 到5时退出循环。
那么后面的操作就是在把i + buf[i-1] 判断是否等于buf [i]
综上 buf 的一个解可以是buf [6]= {0,1,3,6,10,15}
outcome
c |
phase_3
Dump of assembler code for function phase_3: |
process
更加复杂!
首先调用了一个sscanf 。
这个函数和scanf差别就是,它读取数据是在第一个参数指向的位置
rdi仍然是readline传入的。
0x0000555555555655 <+24>: lea rcx,[rsp+0x4] |
这两步就是给出了”%d %d” 需要存储到的位置,第一个放到[rsp] 第二个放到[rsp+4]
cmp eax,0x1 后面是对于scanf一个检查 正常读取就可以不看
cmp DWORD PTR [rsp],0x7 要求第一个参数小于等于7
然后又来了个复杂的玩意!
0x0000555555555678 <+59>: mov eax,DWORD PTR [rsp] |
一通瞪眼法之后,我认为这个很像一个跳转表,rdx加载了
0x5555555571c0 , 0x5555555571c 后面存储了一系列相对偏移,然后加上这个相对偏移后,进行跳转。
那么[rsp]就是相对偏移的数组下标。那么就需要计算好这个switch! 可以先假设switch都是跳转到函数内的。这样就可以不盲目的计算,先看后面的逻辑。
发现在ret指令后面存了一堆jmp,很显然这不是正常的,可以直接假设跳转表就是跳到这里
0x00005555555556bb <+126>: cmp DWORD PTR [rsp],0x5 |
这三个指令显然就是最终的目标!
[rsp]>=5 时,直接bomb,直接假设[rsp]等于5,第二个参数就是运行的结果。
可以直接输入一个5,用gdb查看最后的rax,这样就跳过了繁琐的计算过程
下好断点
p $eax |
然后重新运行一遍程序
输入5 -204
outcome
c |
phase_4
Dump of assembler code for function phase_4: |
process
突然代码量就变少了!
还是和刚才一样 输入两个数字到[rsp] [rsp+4]
要求[rsp]<=15
然后跳到func4,要求其返回值等于7。可以先看外面的逻辑,再看func4具体干了什么
[rsp+4]只是单纯的要求等于7 ,为什么不只读入一个参数呢?
进入func4:
rdi = [rsp] rsi = 0 rdx = 0xe
可以看出这完全是一个递归!
先尝试直接让他跳出有没有解,但是直接跳出显然就不会等于7。
那只能尝试去翻译这个函数了。
func4(x,y,z){ |
前面那个部分的计算还是不像人类能理解的,于是我优化了一下。
经过一堆判断 我认为这个算法实际上执行了x+y / 2 。但是简单粗暴的相加/2 可能会有溢出。另外在负数时,需要向零截断,所以加上了一个flag。
那么就可以简化为
func4(x,y,z){ |
执行func(x,0,15),x是输入值。
通过逆推。函数最终节点的返回值必然是0 , 需要构造出0 1 3 7这样的返回流程
也就是执行四次后,让temp等于x
1:7,2:11,3:13,4:14 输入14尝试一下!
outcome
14 7 |
phase_5
Dump of assembler code for function phase_5: |
process
循环还是比递归看着舒服。
涉及到了两个数组
一个是char input[7]; 一个是 int array[16]
先是检查了字符串长度是否为6。
然后根据input[7]里每个char的值,用0xf取掩码(相当于%16) ,当作array数组下标,进行取值。
最终要使得取出来的值相加等于0x3e
使用x/16wx 0x5555555571e0 可以方便的列出这个数组的内容。
事实上可以规定数据类型 然后用print 但是这太麻烦了。
0x5555555571e0 <array.3471>: 0x00000002 0x0000000a 0x00000006 0x00000001 |
随便取几个6起来为0x3e就行了
p 0x3e-0xf-0xd-0x8-0xb-0x5-0xa |
对应数组下标 1 11 12-15
可以取大写英文字母 AKLMNO
outcome
c |
phase_6
0x0000555555555829 <+0>: endbr64 |
process
略显复杂 但还好 没有递归。
先大致看一下逻辑,可以分成四个部分。
第一部分
0x0000555555555829 <+0>: endbr64 |
主要做了两个检查
0x000055555555586f <+70>: mov eax,DWORD PTR [r12+rbx*4] |
检查每相邻两个数是不是不相等的
0x000055555555588e <+101>: sub eax,0x1 |
检查每个数-1是不是在0 到 5之间 就是要输入1到6
对于这种循环,有个技巧,就是rbx一般是for (int i …)里的i,当然不保证一定正确,但是确实有这个规律
第二部分
0x00005555555558a1 <+120>: mov esi,0x0 |
这里出现了个node1 。通过后面反复的[rdx+0x8],可以推测这是一个链表
并且节点结构应该是
struct node{ |
可以从内存里面验证这个观点
pwndbg> x/100wx 0x555555559210 |
可以发现题目还非常好心的把padding标上的标号。但其实我并没有在phase中看到有这个操作。
这里具体做的事情,就是按照标号,取出链表节点.
0x00005555555558a1 <+120>: mov esi,0x0 |
然后再把对应指针存储到rsp+0x20开始的数组
6*4 = 0x18 为什么要空 0x08? 这通常是为了进行对齐
第三部分
0x00005555555558d4 <+171>: mov rbx,QWORD PTR [rsp+0x20] |
最轻松的一部分,可以看出就是按照数组顺序,重新连接了单链表。
第四部分
0x000055555555590e <+229>: mov ebp,0x5 |
这个部分也不难,ebp是for里面的i (i=5 ;i>0;i–),rbx是上一个部分已经存储的第一个节点指针,可以当成head
那么就是进行5次比较,
判断当前节点数据是否小于等于下一节点数据。
综上,四个部分的结果就是按照标号,重新排列了链表,要求最后的链表是递增的。
outcome
6 5 1 2 4 3 |
验证
按照实验要求,我需要使用argv[1]传入输入的内容
建立一个文本整合上述输入即可
❯ ./bomb solution_24300240142_彭宇.txt |