CSAPP实验2:Bomb Lab笔记

您所在的位置:网站首页 rsp和rbp CSAPP实验2:Bomb Lab笔记

CSAPP实验2:Bomb Lab笔记

2023-12-02 17:53| 来源: 网络整理| 查看: 265

实验简介准备 汇编复习反汇编GDB Phase 1Phase 2Phase 3Phase 4Phase 5Phase 6

实验简介

​ Bomb LAB 目的是熟悉汇编。

​ 一共有7关,六个常规关卡和一个隐藏关卡,每次我们需要输入正确的拆弹密码才能进入下一关,而具体的拆弹密码藏在汇编代码中。实验中的bomb实际上是一个程序的二进制文件,该程序由一系列phase组成,每个phase需要我们输入一个字符串,然后该程序会进行校验,如果输入的字符串不满足拆弹要求,那么就会打印BOOM!!!

​ 完成整个实验的思路是通过objdump对bomb进行反编译(objdump -d bomb > bomb.txt),获取所有的汇编代码。提取每个阶段对应的代码并借助gdb进行分析,逐一拆弹。

Github地址:Bomb Lab

准备 汇编复习 类型语法例子备注常量符号$ 开头$-42, $0x15213一定要注意十进制还是十六进制寄存器符号 % 开头%esi, %rax可能存的是值或者地址内存地址括号括起来(%rbx), 0x1c(%rax), 0x4(%rcx, %rdi, 0x1)括号实际上是去寻址的意思

一些汇编语句与实际命令的转换:

指令效果mov %rbx, %rdxrdx = rbxadd (%rdx), %r8r8 += value at rdxmul $3, %r8r8 *= 3sub $1, %r8r8--lea (%rdx, %rbx, 2), %rdxrdx = rdx + rbx*2

比较与跳转是拆弹的关键,基本所有的字符判断就是通过比较来实现的,比方说 cmp b,a 会计算 a-b 的值,test b, a 会计算 a&b,注意运算符的顺序。例如

cmpl %r9, %r10 jg 8675309

等同于 if %r10 > %r9, jump to 8675309

各种不同的跳转:

指令效果指令效果jmpAlways jumpjaJump if above(unsigned >)je/jzJump if eq / zerojaeJump if above / equaljne/jnzJump if !eq / !zerojbJump if below(unsigned = 0x15213,则跳转到 0xdeadeef

cmp %rax, %rdi jae 15213b

如果 %rdi 的无符号值大于等于 %rax,则跳转到 0x15213b

test %r8, %r8 jnz (%rsi)

如果 %r8 & %r8 不为零,那么跳转到 %rsi 存着的地址中。

x86-64寄存器规则: 默认函数的第一个参数是%rdi 第二个参数%rsi 第三个参数%rdx

反汇编 # 检查符号表 # 然后可以寻找跟 bomb 有关的内容 objdump -t bomb | less # 反编译 # 搜索 explode_bomb objdump -d bomb > bomb.txt # 显示所有字符 strings bomb | less GDB gdb bomb help # 获取帮助 break explode_bomb # 设置断点 break phase_1 run # 开始运行 disas # 反汇编 info registers # 查看寄存器内容 print $rsp # 打印指定寄存器 stepi # (单步跟踪进入)执行一行代码,如果函数调用,则进入该函数 可以使用s简化 n # (单步跟踪) 执行一行代码,如果函数调用,则一并执行 x/4wd $rsp # 检查寄存器或某个地址,查看内存地址里面的内容(常用)

用 ctl+c 可以退出,每次进入都要设置断点(保险起见),炸弹会用 sscanf 来读取字符串,了解清楚到底需要输入什么。

Phase 1 Dump of assembler code for function phase_1: 0x0000000000400ee0 : sub $0x8,%rsp 0x0000000000400ee4 : mov $0x402400,%esi 0x0000000000400ee9 : callq 0x401338 0x0000000000400eee : test %eax,%eax 0x0000000000400ef0 : je 0x400ef7 0x0000000000400ef2 : callq 0x40143a 0x0000000000400ef7 : add $0x8,%rsp 0x0000000000400efb : retq

先 gdb bomb,然后设置断点 break explode_bomb 和 break phase_1

这段代码还是挺好理解的,保存Stack pointer,将$0x402400传给%esi,调用位于0x401338的strings_not_equal函数,比较%eax是否为0,不为零则调用explode_bomb函数,为零则返回设置断点 phase_1和explode_bomb,输入命令r运行会在断点处停下,此时随便输入一个字符串用于测试“abcd”,然后disas查看反汇编代码:=>箭号为当前运行的位置

mark

查看寄存器内容 info register,eax就是rax的低位用print $eax 打印出来,是一个地址用x/s $eax,查看出地址里的内容,发现是输入字符串

mark

用stepi 逐步执行,执行完 mov 之后,把地址中的内容传到%esi中,用print查看!得到字符串,这就是第一关的答案。退出后新建一个文本 touch sol.txt,方便之后输入

mark

mark

Phase 2

这次我们有了第一关的答案,进入gdb后设置好断点,和命令参数。

mark

试运行,在phase_1停住,然后continue,答案正确,触发 phase_2的断点,这次输入abc

mark

反汇编Phase2部分的代码

(gdb) disas Dump of assembler code for function phase_2: => 0x0000000000400efc : push %rbp 0x0000000000400efd : push %rbx 0x0000000000400efe : sub $0x28,%rsp 0x0000000000400f02 : mov %rsp,%rsi 0x0000000000400f05 : callq 0x40145c #读取6个数字 0x0000000000400f0a : cmpl $0x1,(%rsp) #第一个数字和1比较,不相等则爆炸 0x0000000000400f0e : je 0x400f30 #相等,跳转到 0x0000000000400f10 : callq 0x40143a 0x0000000000400f15 : jmp 0x400f30 0x0000000000400f17 : mov -0x4(%rbx),%eax #将(%rbx)前一个数字存到%eax 0x0000000000400f1a : add %eax,%eax #%eax数字加倍 0x0000000000400f1c : cmp %eax,(%rbx) #%eax和(%rbx)比较,=(%rbx)则跳过爆炸 0x0000000000400f1e : je 0x400f25 0x0000000000400f20 : callq 0x40143a 0x0000000000400f25 : add $0x4,%rbx #(%rbx)地址+4,下一个数字 0x0000000000400f29 : cmp %rbp,%rbx #比较%rbp和%rbx,循环是否结束 0x0000000000400f2c : jne 0x400f17 0x0000000000400f2e : jmp 0x400f3c 0x0000000000400f30 : lea 0x4(%rsp),%rbx #指向第2个数字,%rbx保存第2个数字地址 0x0000000000400f35 : lea 0x18(%rsp),%rbp #0x18 = 0x0 + 4 bit * 6 个数字 0x0000000000400f3a : jmp 0x400f17 0x0000000000400f3c : add $0x28,%rsp 0x0000000000400f40 : pop %rbx 0x0000000000400f41 : pop %rbp 0x0000000000400f42 : retq End of assembler dump.

根据Phase1,很敏感的会发现movl $0x4025c3, %esi这行。通过之前一样的方法,得到0x4025c3内存里的字符串

(gdb) x/s $esi 0x4025c3: "%d %d %d %d %d %d

再根据bomb[0x40148a] : callq 0x400bf0 ; symbol stub for: __isoc99_sscanf这句,猜一下,立马就能联想到scanf("%d %d %d %d %d %d",a,b,c,d,e,f);,也就是说,输入的格式已经确定了。

解读出循环中,从1开始,是一个等比数列,公比为2。1 2 4 8 16 32

mark

Phase 3 Dump of assembler code for function phase_3: => 0x0000000000400f43 : sub $0x18,%rsp 0x0000000000400f47 : lea 0xc(%rsp),%rcx 0x0000000000400f4c : lea 0x8(%rsp),%rdx 0x0000000000400f51 : mov $0x4025cf,%esi 0x0000000000400f56 : mov $0x0,%eax 0x0000000000400f5b : callq 0x400bf0 # 调用函数sscanf 0x0000000000400f60 : cmp $0x1,%eax # 说明%eax>1,即输入参数个数>1,跳过爆炸 0x0000000000400f63 : jg 0x400f6a 0x0000000000400f65 : callq 0x40143a 0x0000000000400f6a : cmpl $0x7,0x8(%rsp) # 说明第一个数0x8(%rsp) 0x000000000040100c : sub $0x18,%rsp 0x0000000000401010 : lea 0xc(%rsp),%rcx 0x0000000000401015 : lea 0x8(%rsp),%rdx 0x000000000040101a : mov $0x4025cf,%esi //%d %d 0x000000000040101f : mov $0x0,%eax 0x0000000000401024 : callq 0x400bf0 0x0000000000401029 : cmp $0x2,%eax // 参数数量为2 0x000000000040102c : jne 0x401035 0x000000000040102e : cmpl $0xe,0x8(%rsp) // 第一个参数0xe,拆弹失败。否则跳转到0x40103a执行

0x000000000040103a : mov $0xe,%edx //14 0x000000000040103f : mov $0x0,%esi //0 0x0000000000401044 : mov 0x8(%rsp),%edi //a1

这三条指令用来设置func4的参数,根据x86-64寄存器使用规范,第1,2,3,个参数分别存储在寄存器%edi,%esi,%edx中 在查看func4对应的代码之前,先观察执行callq 400fce 指令之后phase_4的操作:test %eax,%eax指令检查%eax的值是否等于0,如果不等于0,则会引爆炸弹,否则执行指令cmpl $0x0,0xc(%rsp),该指令将输入的第二个数与0做比较,如果相等,那么phase_4正常退出,拆弹成功。因此,phase_4的第二个输入值即为0。经过以上的分析,可以意识到phase_4的核心目标在于要让func4执行后,%eax的值等于0,这取决于输入的第一个数。接着需要分析func4执行的操作,其对应代码如下所示。

反汇编func4

0000000000400fce : 400fce: 48 83 ec 08 sub $0x8,%rsp //x = %edi y = %esi z = %edx 400fd2: 89 d0 mov %edx,%eax // 400fd4: 29 f0 sub %esi,%eax //t = z-y t = %eax 400fd6: 89 c1 mov %eax,%ecx // 400fd8: c1 e9 1f shr $0x1f,%ecx //k=t>>31 t = %ecx 400fdb: 01 c8 add %ecx,%eax //t = t+k 400fdd: d1 f8 sar %eax //t>>1 400fdf: 8d 0c 30 lea (%rax,%rsi,1),%ecx 400fe2: 39 f9 cmp %edi,%ecx 400fe4: 7e 0c jle 400ff2 400fe6: 8d 51 ff lea -0x1(%rcx),%edx 400fe9: e8 e0 ff ff ff callq 400fce 400fee: 01 c0 add %eax,%eax 400ff0: eb 15 jmp 401007 400ff2:(+0x24) b8 00 00 00 00 mov $0x0,%eax 400ff7: 39 f9 cmp %edi,%ecx 400ff9: 7d 0c jge 401007 400ffb: 8d 71 01 lea 0x1(%rcx),%esi 400ffe: e8 cb ff ff ff callq 400fce 401003: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax 401007:(+0x39) 48 83 c4 08 add $0x8,%rsp 40100b: c3 retq

在分析func4之前,不要忘了传递到func4的三个参数分别存储于寄存器%edi、%esi和%edx,其值分别为x(输入的第一个数)、0和14。在0x400fe9处执行了指令callq 400fce ,因此func4很可能是个递归函数,我们将func4翻译成等价的C代码,如下所示。

void func4(int x, int y, int z) { int t = z - y; int k = t >> 31; t = (t + k) >> 1; k = t + y; if(k = x) { return; }else { y = k + 1; func4(x, y, z); } }else { z = k - 1; func4(x, y, z); } }

func4的目的是要让函数退出后%eax的值为0,而在0x400ff2处mov $0x0,%eax显示的将%eax的值设置为0,该指令对应于C代码中的t = 0。并且,func4执行递归的退出条件为k == x,其中x对应于输入的第一个数,而k则可以通过一系列计算得到,由于y = 0且z = 14,易知k = 7,因此输入的第一个数即为7。将字符串7 0作为phase_4的输入,拆弹成功,如下图所示。

Phase 5 Dump of assembler code for function phase_5: => 0x0000000000401062 : push %rbx 0x0000000000401063 : sub $0x20,%rsp 0x0000000000401067 : mov %rdi,%rbx //把字符串起始地址保存在%rbx中 0x000000000040106a : mov %fs:0x28,%rax 0x0000000000401073 : mov %rax,0x18(%rsp) 0x0000000000401078 : xor %eax,%eax //%eax清零 0x000000000040107a : callq 0x40131b 0x000000000040107f : cmp $0x6,%eax //字符串输入长度=6 0x0000000000401082 : je 0x4010d2 0x0000000000401084 : callq 0x40143a 0x0000000000401089 : jmp 0x4010d2 0x000000000040108b : movzbl (%rbx,%rax,1),%ecx // 0x000000000040108f : mov %cl,(%rsp) 0x0000000000401092 : mov (%rsp),%rdx 0x0000000000401096 : and $0xf,%edx 0x0000000000401099 : movzbl 0x4024b0(%rdx),%edx 0x00000000004010a0 : mov %dl,0x10(%rsp,%rax,1) 0x00000000004010a4 : add $0x1,%rax 0x00000000004010a8 : cmp $0x6,%rax 0x00000000004010ac : jne 0x40108b 0x00000000004010ae : movb $0x0,0x16(%rsp) 0x00000000004010b3 : mov $0x40245e,%esi //字符串 flyers 0x00000000004010b8 : lea 0x10(%rsp),%rdi 0x00000000004010bd : callq 0x401338 0x00000000004010c2 : test %eax,%eax 0x00000000004010c4 : je 0x4010d9 0x00000000004010c6 : callq 0x40143a 0x00000000004010cb : nopl 0x0(%rax,%rax,1) 0x00000000004010d0 : jmp 0x4010d9 0x00000000004010d2 : mov $0x0,%eax 0x00000000004010d7 : jmp 0x40108b 0x00000000004010d9 : mov 0x18(%rsp),%rax 0x00000000004010de : xor %fs:0x28,%rax 0x00000000004010e7 : je 0x4010ee 0x00000000004010e9 : callq 0x400b30 0x00000000004010ee : add $0x20,%rsp 0x00000000004010f2 : pop %rbx 0x00000000004010f3 : retq End of assembler dump.

根据x86-64寄存器使用规范,%rdi寄存器存储的是第一个参数的值,由于输入的是字符串,因此%rdi存储的应该是输入字符串的起始地址。0x401067处的指令mov %rdi,%rbx将字符串起始地址保存在%rbx中,即%rbx为基址寄存器。指令xor %eax,%eax的作用是将%eax清零,接着调用string_length函数获取输入字符串的长度,并将长度值(返回值)存储于%eax。指令cmp $0x6,%eax将string_length的返回值与常数6作比较,若不相等则会引爆炸弹,由此可以得知,phase_5的输入字符串长度应该等于6。

(gdb) x/s 0x40245e 0x40245e: "flyers"

待比较的字符串为flyers,且长度也为6。所以,接下来的关键任务是需要对循环操作进行分析,理解该循环操作对输入字符串做了哪些操作。提取循环操作的代码,如下所示。

40108b: 0f b6 0c 03 movzbl (%rbx,%rax,1),%ecx 40108f: 88 0c 24 mov %cl,(%rsp) 401092: 48 8b 14 24 mov (%rsp),%rdx 401096: 83 e2 0f and $0xf,%edx 401099: 0f b6 92 b0 24 40 00 movzbl 0x4024b0(%rdx),%edx 4010a0: 88 54 04 10 mov %dl,0x10(%rsp,%rax,1) 4010a4: 48 83 c0 01 add $0x1,%rax 4010a8: 48 83 f8 06 cmp $0x6,%rax 4010ac: 75 dd jne 40108b

由于%rbx存储的是输入字符串的起始地址,%rax初始化为0,其作用等价于下标,因此movzbl (%rbx,%rax,1),%ecx指令的作用是将字符串的第%rax个字符存储于%ecx,movzbl意味做了零扩展。接着,mov %cl,(%rsp)指令取%ecx的低8位,即一个字符的大小,通过内存间接存储至%rdx中。and $0xf,%edx指令将%edx的值与常数0xf进行位与,由指令movzbl 0x4024b0(%rdx),%edx可知,位与后的值将会作为偏移量,以0x4024b0为基址,将偏移后的值存储至%edx。最后,指令mov %dl,0x10(%rsp,%rax,1)以%edx低8位的值作为新的字符,对原有字符进行替换。综上,phase_5遍历输入字符串的每个字符,将字符的低4位作为偏移量,以0x4024b0为起始地址,将新地址对应的字符替换原有字符,最终得到flyers字符串。打印0x4024b0处的内容,如下图所示。

mark

例如,如果要得到字符f,那么偏移量应为9,二进制表示为1001,通过查找ASCII表,可知字符i的ASCII编码为01101001,满足要求。(或者字符y(01111001)所以解不唯一)剩余5个字符采用同样的策略可以依次求得,最终,phase_5的输入字符串的一个解为ionefg。

Phase 6

phase_6的代码很长

Dump of assembler code for function phase_6: => 0x00000000004010f4 : push %r14 0x00000000004010f6 : push %r13 0x00000000004010f8 : push %r12 0x00000000004010fa : push %rbp 0x00000000004010fb : push %rbx 0x00000000004010fc : sub $0x50,%rsp 0x0000000000401100 : mov %rsp,%r13 0x0000000000401103 : mov %rsp,%rsi 0x0000000000401106 : callq 0x40145c 0x000000000040110b : mov %rsp,%r14 # %r14存储数组起始地址 0x000000000040110e : mov $0x0,%r12d # 将%r12d初始化为0 #################### Section 1:确认数组中所有的元素小于等于6且不存在重复值 ################### 0x0000000000401114 : mov %r13,%rbp # %r13和%rbp存储数组某个元素的地址,并不是第1个元素,意识到这点需要结合0x40114d处的指令 0x0000000000401117 : mov 0x0(%r13),%eax 0x000000000040111b : sub $0x1,%eax # 将%eax的值减1 0x000000000040111e : cmp $0x5,%eax # 将%eax的值与常数5做比较 0x0000000000401121 : jbe 0x401128 0x0000000000401123 : callq 0x40143a 0x0000000000401128 : add $0x1,%r12d # 如果%eax的值小于等于5,%r12d加1 0x000000000040112c : cmp $0x6,%r12d # 将%r12d与常数6做比较 0x0000000000401130 : je 0x401153 0x0000000000401132 : mov %r12d,%ebx # %ebx起了数组下标的作用 # 用于判断数组6个数是否存在重复值,若存在,引爆炸弹 0x0000000000401135 : movslq %ebx,%rax # 将数组下标存储至%rax 0x0000000000401138 : mov (%rsp,%rax,4),%eax # 将下一个数存储至%eax 0x000000000040113b : cmp %eax,0x0(%rbp) # 将第1个数与%eax的值(当前数)做比较 0x000000000040113e : jne 0x401145 # 若相等,引爆炸弹 0x0000000000401140 : callq 0x40143a 0x0000000000401145 : add $0x1,%ebx # 数组下标加1 0x0000000000401148 : cmp $0x5,%ebx # 判断数组下标是否越界(


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3