实验说明:
A “binary bomb” is a program provided to students as an object code file. When run, it prompts the user to type in 6 different strings. If any of these is incorrect, the bomb “explodes,” printing an error message and logging the event on a grading server. Students must “defuse” their own unique bomb by disassembling and reverse engineering the program to determine what the 6 strings should be. The lab teaches students to understand assembly language, and also forces them to learn how to use a debugger. It’s also great fun. A legendary lab among the CMU undergrads.
是个逆向的小游戏,我们使用gdb来做
反汇编一下main函数,可以大概看到有很多的phase,我们需要逐个拆除
phase_1 反汇编可以看到在phase_1里面调用了一个strings_not_equal的函数,猜测如果两个字符串不相等就会爆炸,x64下程序通过rsi传入第二个参数,我们看一下具体的内存值即可。
运行程序,发现可以不被引爆
phase_2 反汇编phase_2
可以看到后面有个jne指令向上跳转,所以肯定是个循环结构,在上面是个read_six_numbers,读入6个数字,所以猜测对每个数字都有要求,并且通过动调可以发现cmp那条指令要求第一个数字为1
读入六个数字,否则就退出程序
0x0000000000400f1a <+30 >: add eax ,eax 0x0000000000400f1c <+32 >: cmp DWORD PTR [rbx ],eax
这个位置动态调试可以发现是后面的数据是否等于前面数字的二倍
那么payload应该就是1 2 4 8 16 32了 可以通过验证
phase_3 0x0000000000400f43 <+0 >: sub rsp ,0x18 0x0000000000400f47 <+4 >: lea rcx ,[rsp +0xc ] 0x0000000000400f4c <+9 >: lea rdx ,[rsp +0x8 ] 0x0000000000400f51 <+14 >: mov esi ,0x4025cf 0x0000000000400f56 <+19 >: mov eax ,0x0 0x0000000000400f5b <+24 >: call 0x400bf0 <__isoc99_sscanf@plt> 0x0000000000400f60 <+29 >: cmp eax ,0x1 # 读入数据要是2 个 0x0000000000400f63 <+32 >: jg 0x400f6a <phase_3+39 > 0x0000000000400f65 <+34 >: call 0x40143a <explode_bomb> 0x0000000000400f6a <+39 >: cmp DWORD PTR [rsp +0x8 ],0x7 # 第一个读入的数据不能超过7 0x0000000000400f6f <+44 >: ja 0x400fad <phase_3+106 > 0x0000000000400f71 <+46 >: mov eax ,DWORD PTR [rsp +0x8 ] 0x0000000000400f75 <+50 >: jmp QWORD PTR [rax *8 +0x402470 ] # 寻址到不同的分支 0x0000000000400f7c <+57 >: mov eax ,0xcf 0x0000000000400f81 <+62 >: jmp 0x400fbe <phase_3+123 > 0x0000000000400f83 <+64 >: mov eax ,0x2c3 0x0000000000400f88 <+69 >: jmp 0x400fbe <phase_3+123 > 0x0000000000400f8a <+71 >: mov eax ,0x100 0x0000000000400f8f <+76 >: jmp 0x400fbe <phase_3+123 > 0x0000000000400f91 <+78 >: mov eax ,0x185 0x0000000000400f96 <+83 >: jmp 0x400fbe <phase_3+123 > 0x0000000000400f98 <+85 >: mov eax ,0xce 0x0000000000400f9d <+90 >: jmp 0x400fbe <phase_3+123 > 0x0000000000400f9f <+92 >: mov eax ,0x2aa 0x0000000000400fa4 <+97 >: jmp 0x400fbe <phase_3+123 > 0x0000000000400fa6 <+99 >: mov eax ,0x147 0x0000000000400fab <+104 >: jmp 0x400fbe <phase_3+123 > 0x0000000000400fad <+106 >: call 0x40143a <explode_bomb> 0x0000000000400fb2 <+111 >: mov eax ,0x0 0x0000000000400fb7 <+116 >: jmp 0x400fbe <phase_3+123 > 0x0000000000400fb9 <+118 >: mov eax ,0x137 0x0000000000400fbe <+123 >: cmp eax ,DWORD PTR [rsp +0xc ] 0x0000000000400fc2 <+127 >: je 0x400fc9 <phase_3+134 > 0x0000000000400fc4 <+129 >: call 0x40143a <explode_bomb> 0x0000000000400fc9 <+134 >: add rsp ,0x18 0x0000000000400fcd <+138 >: ret
目测看起来像个Switch结构,关键点给出注释,中间通过读入的数字到eax里面进行寻址 比如我要输入的第一个数字是7,那么查看地址如下pwndbg> x/wx 0x4024a8 0x4024a8 : 0x00400fa6
可以发现会执行mov eax,0x147
,然后jmp到cmp指令,将sscanf读入的第二个参数和该参数相比较,那么第二个我们输入0x147即可通过验证
Welcome to my fiendish little bomb. You have 6 phases with which to blow yourself up. Have a nice day! Border relations with Canada have never been better. Phase 1 defused. How about the next one? 1 2 4 8 16 32 That's number 2. Keep going! 7 327 Halfway there!
这是截止到目前的程序输出
phase_4 Dump of assembler code for function phase_4: 0x000000000040100c <+0 >: sub rsp ,0x18 0x0000000000401010 <+4 >: lea rcx ,[rsp +0xc ] 0x0000000000401015 <+9 >: lea rdx ,[rsp +0x8 ] 0x000000000040101a <+14 >: mov esi ,0x4025cf 0x000000000040101f <+19 >: mov eax ,0x0 0x0000000000401024 <+24 >: call 0x400bf0 <__isoc99_sscanf@plt> 0x0000000000401029 <+29 >: cmp eax ,0x2 0x000000000040102c <+32 >: jne 0x401035 <phase_4+41 > 0x000000000040102e <+34 >: cmp DWORD PTR [rsp +0x8 ],0xe # 第一个参数需要小于0xe 0x0000000000401033 <+39 >: jbe 0x40103a <phase_4+46 > 0x0000000000401035 <+41 >: call 0x40143a <explode_bomb> 0x000000000040103a <+46 >: mov edx ,0xe 0x000000000040103f <+51 >: mov esi ,0x0 0x0000000000401044 <+56 >: mov edi ,DWORD PTR [rsp +0x8 ] 0x0000000000401048 <+60 >: call 0x400fce <func4> # 调用func4函数继续处理,第一个参数为我们输入的第一个值,第二个参数是0 ,第三个参数是0xe 0x000000000040104d <+65 >: test eax ,eax 0x000000000040104f <+67 >: jne 0x401058 <phase_4+76 > 0x0000000000401051 <+69 >: cmp DWORD PTR [rsp +0xc ],0x0 0x0000000000401056 <+74 >: je 0x40105d <phase_4+81 > # 返回值和第二个参数都必须为0 0x0000000000401058 <+76 >: call 0x40143a <explode_bomb> 0x000000000040105d <+81 >: add rsp ,0x18 0x0000000000401061 <+85 >: ret
可以看到还是常规的首先比较参数个数,需要两个,并且第一个参数需要不能大于0xe
func4函数如下0x0000000000400fce <+0 >: sub rsp ,0x8 0x0000000000400fd2 <+4 >: mov eax ,edx # 0xe 0x0000000000400fd4 <+6 >: sub eax ,esi 0x0000000000400fd6 <+8 >: mov ecx ,eax 0x0000000000400fd8 <+10 >: shr ecx ,0x1f 0x0000000000400fdb <+13 >: add eax ,ecx 0x0000000000400fdd <+15 >: sar eax ,1 0x0000000000400fdf <+17 >: lea ecx ,[rax +rsi *1 ]0x0000000000400fe2 <+20 >: cmp ecx ,edi # 第一轮循环:ecx = (0xe >> 0x1f + 0xe ) >> 1 ,然后和我们输入的第二个参数比较 0x0000000000400fe4 <+22 >: jle 0x400ff2 <func4+36 >0x0000000000400fe6 <+24 >: lea edx ,[rcx -0x1 ]# 第三个参数:[rcx -1 ] 0x0000000000400fe9 <+27 >: call 0x400fce <func4>0x0000000000400fee <+32 >: add eax ,eax 0x0000000000400ff0 <+34 >: jmp 0x401007 <func4+57 >0x0000000000400ff2 <+36 >: mov eax ,0x0 0x0000000000400ff7 <+41 >: cmp ecx ,edi 0x0000000000400ff9 <+43 >: jge 0x401007 <func4+57 >0x0000000000400ffb <+45 >: lea esi ,[rcx +0x1 ] 0x0000000000400ffe <+48 >: call 0x400fce <func4>0x0000000000401003 <+53 >: lea eax ,[rax +rax *1 +0x1 ]0x0000000000401007 <+57 >: add rsp ,0x8 0x000000000040100b <+61 >: ret
一个递归函数,有点绕。。。 借助一下ida - - (我好菜__int64 __fastcall func4 (__int64 a1, __int64 a2, __int64 a3) { signed int v3; __int64 result; v3 = (a3 - a2) / 2 + a2; if ( v3 > a1 ) return 2 * func4(a1, a2, (v3 - 1 )); result = 0LL ; if ( v3 < a1 ) result = 2 * func4(a1, (v3 + 1 ), a3) + 1 ; return result; }
很清晰,不能大于不能小于,必须等于(当然在递归中等于也可以 仔细想想,这不就是二分查找吗(x 所以只要能被二分查找发现的都可以
phase_5 汇编代码如下0x0000000000401062 <+0 >: push rbx 0x0000000000401063 <+1 >: sub rsp ,0x20 0x0000000000401067 <+5 >: mov rbx ,rdi 0x000000000040106a <+8 >: mov rax ,QWORD PTR fs :0x28 0x0000000000401073 <+17 >: mov QWORD PTR [rsp +0x18 ],rax 0x0000000000401078 <+22 >: xor eax ,eax 0x000000000040107a <+24 >: call 0x40131b <string_length>0x000000000040107f <+29 >: cmp eax ,0x6 # 比较字符串长度 0x0000000000401082 <+32 >: je 0x4010d2 <phase_5+112 >0x0000000000401084 <+34 >: call 0x40143a <explode_bomb>0x0000000000401089 <+39 >: jmp 0x4010d2 <phase_5+112 >0x000000000040108b <+41 >: movzx ecx ,BYTE PTR [rbx +rax *1 ]0x000000000040108f <+45 >: mov BYTE PTR [rsp ],cl 0x0000000000401092 <+48 >: mov rdx ,QWORD PTR [rsp ]0x0000000000401096 <+52 >: and edx ,0xf 0x0000000000401099 <+55 >: movzx edx ,BYTE PTR [rdx +0x4024b0 ]0x00000000004010a0 <+62 >: mov BYTE PTR [rsp +rax *1 +0x10 ],dl 0x00000000004010a4 <+66 >: add rax ,0x1 # 每次+1 0x00000000004010a8 <+70 >: cmp rax ,0x6 # 循环6 次 0x00000000004010ac <+74 >: jne 0x40108b <phase_5+41 >0x00000000004010ae <+76 >: mov BYTE PTR [rsp +0x16 ],0x0 0x00000000004010b3 <+81 >: mov esi ,0x40245e 0x00000000004010b8 <+86 >: lea rdi ,[rsp +0x10 ]0x00000000004010bd <+91 >: call 0x401338 <strings_not_equal>0x00000000004010c2 <+96 >: test eax ,eax 0x00000000004010c4 <+98 >: je 0x4010d9 <phase_5+119 >0x00000000004010c6 <+100 >: call 0x40143a <explode_bomb>0x00000000004010cb <+105 >: nop DWORD PTR [rax +rax *1 +0x0 ]0x00000000004010d0 <+110 >: jmp 0x4010d9 <phase_5+119 >0x00000000004010d2 <+112 >: mov eax ,0x0 0x00000000004010d7 <+117 >: jmp 0x40108b <phase_5+41 >0x00000000004010d9 <+119 >: mov rax ,QWORD PTR [rsp +0x18 ]0x00000000004010de <+124 >: xor rax ,QWORD PTR fs :0x28 0x00000000004010e7 <+133 >: je 0x4010ee <phase_5+140 >0x00000000004010e9 <+135 >: call 0x400b30 <__stack_chk_fail@plt>0x00000000004010ee <+140 >: add rsp ,0x20 0x00000000004010f2 <+144 >: pop rbx 0x00000000004010f3 <+145 >: ret
很显然是一个大循环 动调发现有一个类似于代换表的东西,需要在循环中给出索引,然后查表,最后和字符串对比
pwndbg> x/s 0x4024B0 0x4024b0 <array >: "maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?"
pwndbg> x/s 0x40245e 0x40245e : "flyers"
应该是取前面的maduiersnfotvbyl
索引值为[9, 15, 14, 5, 6, 7] 取ascii码值和0xf与后等于索引值即可, “yonefg“
phase_6 部分汇编代码如下
0x0000000000401106 <+18 >: call 0x40145c <read_six_numbers> 0x000000000040110b <+23 >: mov r14 ,rsp 0x000000000040110e <+26 >: mov r12d ,0x0 0x0000000000401114 <+32 >: mov rbp ,r13 # rsp = rbp 都指向我们输入的6 个数字,通过栈传递参数,返回值存入rax 0x0000000000401117 <+35 >: mov eax ,DWORD PTR [r13 +0x0 ] # 输入的第一个数字进入eax 0x000000000040111b <+39 >: sub eax ,0x1 0x000000000040111e <+42 >: cmp eax ,0x5 0x0000000000401121 <+45 >: jbe 0x401128 <phase_6+52 > # -1 后和5 进行比较, 小于等于则跳转,即第一位数字必须小于0x5 0x0000000000401123 <+47 >: call 0x40143a <explode_bomb> 0x0000000000401128 <+52 >: add r12d ,0x1 0x000000000040112c <+56 >: cmp r12d ,0x6 0x0000000000401130 <+60 >: je 0x401153 <phase_6+95 > loop1: # 跳出循环 0x0000000000401132 <+62 >: mov ebx ,r12d # 循环中的第二层循环 0x0000000000401135 <+65 >: movsxd rax ,ebx 0x0000000000401138 <+68 >: mov eax ,DWORD PTR [rsp +rax *4 ] # 索引取出数字 0x000000000040113b <+71 >: cmp DWORD PTR [rbp +0x0 ],eax # 比较取出的数字是否和第一个数字相同,相同则爆炸 0x000000000040113e <+74 >: jne 0x401145 <phase_6+81 > 0x0000000000401140 <+76 >: call 0x40143a <explode_bomb> 0x0000000000401145 <+81 >: add ebx ,0x1 0x0000000000401148 <+84 >: cmp ebx ,0x5 # ebx = 1 -6 ,比较所有的数字 0x000000000040114b <+87 >: jle 0x401135 <phase_6+65 > # 跳出第二层循环 0x000000000040114d <+89 >: add r13 ,0x4 # 动调发现r13 指向第一个数字,+4 后指向第二个数字 0x0000000000401151 <+93 >: jmp 0x401114 <phase_6+32 > # 循环,退出条件为r12d = 6 loop2: 0x0000000000401153 <+95 >: lea rsi ,[rsp +0x18 ] 0x0000000000401158 <+100 >: mov rax ,r14 0x000000000040115b <+103 >: mov ecx ,0x7 # 循环开始 0x0000000000401160 <+108 >: mov edx ,ecx 0x0000000000401162 <+110 >: sub edx ,DWORD PTR [rax ] 0x0000000000401164 <+112 >: mov DWORD PTR [rax ],edx # 数字 = edx ( 0x7 - 数字) 0x0000000000401166 <+114 >: add rax ,0x4 0x000000000040116a <+118 >: cmp rax ,rsi 0x000000000040116d <+121 >: jne 0x401160 <phase_6+108 > # 退出条件:当前数字为0
loop1中主要是比较六个数字中是否有相同的数字
0x000000000040116f <+123 >: mov esi ,0x0 0x0000000000401174 <+128 >: jmp 0x401197 <phase_6+163 > loop3: 0x0000000000401176 <+130 >: mov rdx ,QWORD PTR [rdx +0x8 ] # node节点 0x000000000040117a <+134 >: add eax ,0x1 0x000000000040117d <+137 >: cmp eax ,ecx 0x000000000040117f <+139 >: jne 0x401176 <phase_6+130 > # 循环,一直到eax 和数字相等 0x0000000000401181 <+141 >: jmp 0x401188 <phase_6+148 > 0x0000000000401183 <+143 >: mov edx ,0x6032d0 0x0000000000401188 <+148 >: mov QWORD PTR [rsp +rsi *2 +0x20 ],rdx # 将node节点存入到栈空间中 0x000000000040118d <+153 >: add rsi ,0x4 0x0000000000401191 <+157 >: cmp rsi ,0x18 # 一直到rsi =0x18 ,即遍历完全6 个数字 0x0000000000401195 <+161 >: je 0x4011ab <phase_6+183 > 0x0000000000401197 <+163 >: mov ecx ,DWORD PTR [rsp +rsi *1 ] 0x000000000040119a <+166 >: cmp ecx ,0x1 # 第一个数字 0x000000000040119d <+169 >: jle 0x401183 <phase_6+143 > # 如果小于等于则向上跳 0x000000000040119f <+171 >: mov eax ,0x1 0x00000000004011a4 <+176 >: mov edx ,0x6032d0 # 0x6032d0 -> 0x000000010000014c 0x00000000004011a9 <+181 >: jmp 0x401176 <phase_6+130 > # 向上跳
这一部分也详细写入注释中 经过动调发现实现了将输入的数字减去7,并且将对应的node节点都移动到栈中
0x00000000004011ab <+183 >: mov rbx ,QWORD PTR [rsp +0x20 ]0x00000000004011b0 <+188 >: lea rax ,[rsp +0x28 ]0x00000000004011b5 <+193 >: lea rsi ,[rsp +0x50 ]0x00000000004011ba <+198 >: mov rcx ,rbx # start 0x00000000004011bd <+201 >: mov rdx ,QWORD PTR [rax ]0x00000000004011c0 <+204 >: mov QWORD PTR [rcx +0x8 ],rdx 0x00000000004011c4 <+208 >: add rax ,0x8 0x00000000004011c8 <+212 >: cmp rax ,rsi 0x00000000004011cb <+215 >: je 0x4011d2 <phase_6+222 >0x00000000004011cd <+217 >: mov rcx ,rdx 0x00000000004011d0 <+220 >: jmp 0x4011bd <phase_6+201 ># end 遍历六个node 0x00000000004011d2 <+222 >: mov QWORD PTR [rdx +0x8 ],0x0 0x00000000004011da <+230 >: mov ebp ,0x5 0x00000000004011df <+235 >: mov rax ,QWORD PTR [rbx +0x8 ]0x00000000004011e3 <+239 >: mov eax ,DWORD PTR [rax ]0x00000000004011e5 <+241 >: cmp DWORD PTR [rbx ],eax 0x00000000004011e7 <+243 >: jge 0x4011ee <phase_6+250 ># 如果前一个节点大于后一个节点则不会跳转 0x00000000004011e9 <+245 >: call 0x40143a <explode_bomb>0x00000000004011ee <+250 >: mov rbx ,QWORD PTR [rbx +0x8 ]0x00000000004011f2 <+254 >: sub ebp ,0x1 0x00000000004011f5 <+257 >: jne 0x4011df <phase_6+235 >0x00000000004011f7 <+259 >: add rsp ,0x50 0x00000000004011fb <+263 >: pop rbx 0x00000000004011fc <+264 >: pop rbp 0x00000000004011fd <+265 >: pop r12 0x00000000004011ff <+267 >: pop r13 0x0000000000401201 <+269 >: pop r14 0x0000000000401203 <+271 >: ret
对比了node节点中的数据的大小,我们重启程序,查看原生的node节点情况如下
pwndbg> x/32 gx 0x6032d0 0x6032d0 <node1>: 0x000000010000014c 0x00000000006032e0 0x6032e0 <node2>: 0x00000002000000a8 0x00000000006032f0 0x6032f0 <node3>: 0x000000030000039c 0x0000000000603300 0x603300 <node4>: 0x00000004000002b3 0x0000000000603310 0x603310 <node5>: 0x00000005000001dd 0x0000000000603320 0x603320 <node6>: 0x00000006000001bb 0x0000000000000000
大小顺序为 3 4 5 6 1 2 那么变换后是这些,变换前就是 4 3 2 1 6 5
反馈与总结 通过这个实验复习了一些汇编的知识,很大收获,之前习惯于看c代码,才发现读汇编的能力是如此之弱
objdump -d -m 指令格式 ./binary_file > xxx.asm 反汇编出文件
函数的连续调用的寄存器参数变化,依然是rdi等寄存器传进去
毅力,自制力和专注力