文件来源 :http://files.cnblogs.com/remlostime/bomb.zip
一.解析工具
IDA Pro(64位)
Ubuntu
gdb调试工具
二.分析基本流程
首先用IDA打开,找到main函数,通过main函数的解析,可知大体流程为:
①初始化炸弹
②读取参数后进行判断是否为正确参数,否则引爆炸弹
③一共需要6次判断,即输入6次参数,每一次的判断都有特定的判断方式
三.具体分析
①phase_1
首先来进行第一次的判断,运行该二进制文件后,出现以下提示
可以先输入任意字符测试一下,例如
炸弹引爆,程序结束,下面开始正式分析。
经过IDA分析,可以得到第一步思路,在phase_1这个函数上下断,因为执行完该函数,就出现了第一次判断结果
输入任意字符后,发现在phase_1处断下,disass观察该处反汇编
标红区域为重点分析区域
首先,该函数push了两个参数,第一个是位于0x8049678地址的字符,第二个[ebp+0x8],学过C语言汇编的明白,这是第一个参数所在的地址
接着该函数又call一个函数,即<strings_not_equal>,可以判断这是一个比较函数,如果这两个字符串相等,则eax置零,成功实现跳转,跳到下一个判断函数,否则调用<explode_bomb>,引爆炸弹,程序结束。
于是,关键任务就是找出,0x8049678该处地址所存储的内容是什么,这一点,用IDA进行解决
IDA已经给出答案,即0x8049678处所存的内容为The future will be better tomorrow.
输入程序中进行验证,得到正确结果,进入下一关
===================================================================
②phase_2
进入第二个判断函数,随意输入字符测试
与上述同理,在phase_2函数处下断,进行gdb调试,获得反汇编代码
红色标记处为关键代码段,下面进行分析
首先将参数push到栈中,然后调用<read_six_numbers>,从字义上来看,就是读取6个数字,读取完成之后,将1与[ebp-0x20]进行比较,如果相等,则进行下面进一步的判断,如果不相等,则触发爆炸函数,程序结束。
于是,可以得到结论,即第一个数字为1。
继续往下分析,首先将ebx赋值为2,然后将ebx的值赋给eax,下面关键点来了
lea esi,[ebp-0x20] //将参数的起始地址赋给esi imul eax,dword ptr [esi+ebx*4-0x8] //[esi+ebx*4-0x8]相当于[esi+(ebx*4-0x8)],即这里代表值,而不是实际参数中的数字 cmp eax,dword ptr [esi+ebx*4-0x4] //将参数中的第x个数字与eax相比较 je 0x8048b79 <phase_2+60> //相等则向下跳转 call <explode_bomb> inc ebx //ebx+1 cmp ebx,0x7 //若不相等 jne <phase_2+42(imul)> //跳回imul所在的代码继续执行,即是一个循环
通过一个循环,对所输入参数中的数字进行了判断,因为第一个数字已经确定为1,所以下面比较的是从第二个数字开始(这是容易出错的地方)
通过这段代码,即可逆向分析出结果 1 2 6 24 120 720
===================================================================
③phase_3
接着往下分析,首先输入任意字符,发现炸弹爆炸,查看ida得到下一个判断函数为phase_3,于是在gdb中对该函数下断,输入参数后中断,下面分析反汇编
x08048b86 <+0>: push ebp 0x08048b87 <+1>: mov ebp,esp 0x08048b89 <+3>: sub esp,0x18 => 0x08048b8c <+6>: lea eax,[ebp-0x8] 0x08048b8f <+9>: push eax 0x08048b90 <+10>: lea eax,[ebp-0x4] 0x08048b93 <+13>: push eax 0x08048b94 <+14>: push 0x8049968 0x08048b99 <+19>: push DWORD PTR [ebp+0x8] //传入参数 0x08048b9c <+22>: call 0x8048878 <sscanf@plt> //将输入的字符串转为数据 int类型 0x08048ba1 <+27>: add esp,0x10 0x08048ba4 <+30>: cmp eax,0x1 //如果输入的参数小于2个,跳转到爆炸函数 0x08048ba7 <+33>: jg 0x8048bae <phase_3+40> 0x08048ba9 <+35>: call 0x80493ec <explode_bomb> 0x08048bae <+40>: cmp DWORD PTR [ebp-0x4],0x7 0x08048bb2 <+44>: ja 0x8048c19 <phase_3+147> //如果大于7,跳转到爆炸函数 0x08048bb4 <+46>: mov eax,DWORD PTR [ebp-0x4] 0x08048bb7 <+49>: jmp DWORD PTR [eax*4+0x80496cc] //通过连续比较跳转和具有switch结构特征的跳转表,判断出下面是一个是switch结构 0x08048bbe <+56>: mov eax,0x0 0x08048bc3 <+61>: jmp 0x8048c12 <phase_3+140> //switch(ret)通过返回值判断跳转地址 0x08048bc5 <+63>: mov eax,0x0 0x08048bca <+68>: jmp 0x8048c0d <phase_3+135> 0x08048bcc <+70>: mov eax,0x0 0x08048bd1 <+75>: jmp 0x8048c08 <phase_3+130> 0x08048bd3 <+77>: mov eax,0x0 0x08048bd8 <+82>: jmp 0x8048c03 <phase_3+125> 0x08048bda <+84>: mov eax,0x0 0x08048bdf <+89>: jmp 0x8048bfe <phase_3+120> 0x08048be1 <+91>: mov eax,0x0 0x08048be6 <+96>: jmp 0x8048bf9 <phase_3+115> 0x08048be8 <+98>: mov eax,0x359 0x08048bed <+103>: jmp 0x8048bf4 <phase_3+110> 0x08048bef <+105>: mov eax,0x0 0x08048bf4 <+110>: sub eax,0x1df // 0x08048bf9 <+115>: add eax,0x2bd // 0x08048bfe <+120>: sub eax,0x2db // 0x08048c03 <+125>: add eax,0xf2 // 初始化参数 0x08048c08 <+130>: sub eax,0x86 // 0x08048c0d <+135>: add eax,0x86 // 0x08048c12 <+140>: sub eax,0x19b // 0x08048c17 <+145>: jmp 0x8048c23 <phase_3+157> 0x08048c19 <+147>: call 0x80493ec <explode_bomb> 0x08048c1e <+152>: mov eax,0x0 0x08048c23 <+157>: cmp DWORD PTR [ebp-0x4],0x5 0x08048c27 <+161>: jg 0x8048c2e <phase_3+168> 0x08048c29 <+163>: cmp eax,DWORD PTR [ebp-0x8] 0x08048c2c <+166>: je 0x8048c33 <phase_3+173> 0x08048c2e <+168>: call 0x80493ec <explode_bomb> 0x08048c33 <+173>: leave 0x08048c34 <+174>: ret
sscanf()函数是c语言中将字符转为数据的函数,通过查看0x8049968地址中的值可以得出,传入函数的是两个int类型的参数
然后phase_3函数会判断输入的参数是否大于一个,否则就跳转到爆炸函数结束进程,接下来会判断输入的第一个数据是否为小于7的int型数据,如果是,则跳转到switch结构执行,如果不是,则跳转到爆炸函数。
到了switch结构可以发现,第一个数据可以当作一个retn值,即作为switch跳转表的索引,通过这个值,来跳转到相应的地址接着执行。
这里拿retn = 5进行举例,当返回值为5时,会跳转到0x08048c12,因为前面eax已经被初始化为0,所以sub之后变为-411,接着判断第一个数据是否为5,第二个数据是否为-411,如果这两个数都符合要求,就通过判断函数。
当然,这里也可以用其他值,可以进行分析尝试。
===================================================================
④phase_4
老样子,输入任意字符检测,通过gdb在phase_4中下断,得到反汇编进行进一步分析
0x08048c71 <+0>: push ebp 0x08048c72 <+1>: mov ebp,esp 0x08048c74 <+3>: sub esp,0x1c => 0x08048c77 <+6>: lea eax,[ebp-0x4] //结构体指针 0x08048c7a <+9>: push eax 0x08048c7b <+10>: push 0x804996b 0x08048c80 <+15>: push DWORD PTR [ebp+0x8] 0x08048c83 <+18>: call 0x8048878 <sscanf@plt> 0x08048c88 <+23>: add esp,0x10 0x08048c8b <+26>: cmp eax,0x1 //传入的参数如果不等于1,跳转到爆炸函数,进程结束 0x08048c8e <+29>: jne 0x8048c96 <phase_4+37> 0x08048c90 <+31>: cmp DWORD PTR [ebp-0x4],0x0 //判断参数是否大于0 0x08048c94 <+35>: jg 0x8048c9b <phase_4+42> //大于则跳转 0x08048c96 <+37>: call 0x80493ec <explode_bomb> 0x08048c9b <+42>: push DWORD PTR [ebp-0x4] //将传入的参数作为fun4的参数 0x08048c9e <+45>: call 0x8048c35 <func4> //执行fun4() 0x08048ca3 <+50>: add esp,0x4 0x08048ca6 <+53>: cmp eax,0x90 //如果fun4()的返回值为0x90,则通过判断函数,否则结束进程 0x08048cab <+58>: je 0x8048cb2 <phase_4+65> 0x08048cad <+60>: call 0x80493ec <explode_bomb> 0x08048cb2 <+65>: leave 0x08048cb3 <+66>: ret
通过大体分析可以得知,传入sscanf函数的参数只有1个,如果这个参数大于0,则将这个参数作为fun4()的参数执行fun4(),执行完该函数后,如果返回值为0x90,则成功通过判断函数。
于是,下一步需要知道fun4都做了些什么,在ida中观察得
push ebp .text:08048C36 mov ebp, esp .text:08048C38 push esi .text:08048C39 push ebx .text:08048C3A mov ebx, [ebp+arg_0] //将[ebp+8]这个参数传给ebx .text:08048C3D cmp ebx, 1 //如果参数大于1,继续执行,否则跳出fun4,结束进程 .text:08048C40 jg short loc_8048C49 .text:08048C42 mov esi, 0 .text:08048C47 jmp short loc_8048C67 .text:08048C49 ; --------------------------------------------------------------------------- .text:08048C49 .text:08048C49 loc_8048C49: .text:08048C49 mov esi, 0 .text:08048C4E .text:08048C4E loc_8048C4E: .text:08048C4E sub esp, 0Ch .text:08048C51 lea eax, [ebx-1] //ebx-1后,将新ebx的地址给eax .text:08048C54 push eax .text:08048C55 call func4 //将eax作为参数再次调用fun4,可知是一个递归结构 .text:08048C5A sub ebx, 2 .text:08048C5D add esi, eax .text:08048C5F add esp, 10h .text:08048C62 cmp ebx, 1 .text:08048C65 jg short loc_8048C4E //若ebx值大于1,跳回循环 .text:08048C67 .text:08048C67 loc_8048C67: .text:08048C67 lea eax, [esi+1] .text:08048C6A lea esp, [ebp-8] .text:08048C6D pop ebx .text:08048C6E pop esi .text:08048C6F leave .text:08048C70 retn .text:08048C70 func4 endp
其中含有一个递归结构,最终的目的就是当ebx为1时,该递归结构完毕,继续向下执行,于是,可以根据汇编逆出正向代码
得到 n =11,输入进程,通过判断,向下执行。
===================================================================