二进制炸弹 关键是GDB调试的学习使用,以及汇编训练

受不了了,指令级别调试:

  1. info register:显示寄存器信息
  2. i b  显示断点信息。(删除delete,禁用disable 方法自己上网查)
  3. break  *add   :设置地址级别断点 ,add可以是0x8048b86这样的东西
  4. 那么比如现在有两个断点,

 

    输入r,运行到第一个断点,那么怎么才能继续运行到第二个断点呢?

      Continue,输入c就可以了

  5.比如二进制炸弹某一关输入失败了,怎么回到上一关的断点处继续执行呢?

      我想这是没有办法做到的。。。因为程序执行过程中相当丰富的信息无处存储。冯诺依曼机的局限性。

  6.下一个问题,如何查看 诸如-2c(%ebx)这样的寄存器的值。

      我目前的想法是,ib显示ebx值,x /1xg addr 的方式来查看内存值就是了。

 

   真正的方法:

  7. gdb获取反汇编代码:

      现在会的:断点到某个函数处,disas

      如果这个反汇编代码中还有函数,那么继续disa func,其中func是函数名。

  8.怎么在调试的时候运行一行代码或者运行一行汇编语言代码来单步调试呢?

   s:执行一条源程序代码

   si:执行一条汇编语言代码

  9.怎么使用GDB调试带有argc以及argv的程序捏?

     首先我们需要了解一下argc以及argv是什么:

   比如正常执行一个可执行程序:./a.out   -i    item1

   这就有了三个命令行参数,分别为argv[0],argv[1],argv[2]。显然,argv[0]总是可执行程序名

   而且此时argc等于3,显然argc总是大于等于1,因为包含第一个。

   在gdb当中,上述指令可以通过如下命令来完成:

   gdb a.out

   (gdb)     set  args   -i     item1

   (gdb)     r

     使用方法显然。

  10.如果print (char*)add显示不完怎么办?

  

 

   show print elements可以显示能显示多少字符串。set print elements能设置输出的字符串的最大长度。

二进制炸弹

  6个phase,phase函数源代码没有展示,需要反汇编得出。之后在solution.txt当中写出六个字符串

  首先给出一个输入错误使得二进制炸弹爆炸的例子:

  在运行之后,输入了一个hello,然后就是显然错误的输入,使得二进制炸弹爆炸了。

  $ gdb bomb

  (gdb) break phase_1

  (gdb) run

   然后就开始解决第一个二进制炸弹问题。

   首先输入

  (gdb) disas

  可以查看phase_1()函数的汇编代码,如下图:

Phase_1

显然我们需要从这张图来得到输入字符串

 

  程序的开头总是%ebp,为什么呢,

  因为%esp是堆栈指针,不能对%esp操作,所以用%ebp来操作栈指针。

  首先,程序调用需要传参。假设一开始esp的位置为X,执行完Call(调用phase_1()函数)之后,esp=X-4,因为要保存程序入口地址。

  之后,push %ebp,保存原来的ebp,esp=X-8。

  为什么要再将esp-8呢?这是为了之后的string_not_equal函数传递参数。0x4(%esp)保存的是参数2,(%esp)保存的是参数1。

   结构大概如下:

      上一个参数(输入的串)            add:  X==0x8(%ebp)  //ebp是之后的值

      phase_1()之前的地址              add:X-4

      ebp之前的值                       add:X-8

      参数2的地址                      add: X-12==0x4(%esp)

      参数1的地址                      add:  X-16==(%esp)

      string_not_equal之前的地址

 

  显然答案在0x8049938

  (gdb) print (char*) 0x8049938

 

 

  测试一下发现可行。

Phase_2

 

  这是循环语句。里面的跳转很经典

  Cmp %eax ,%edx

  Je   <phase_2+60>

  如果相等就跳转

 

  Cmpl $0x5 ,-0x4(%ebp)

  Jle <phase_2+33>  

  如果后面的小于等于前面的就跳转

 

  最终发现是一个公差为5的等差数列,首项任意

 

Phase_3

 

 

 

  Compl 0x1,ssss

  Jg  <phase_3+67> //如果$0x1大于后面的就跳转

 

  Cmpl $0x7 ,-0x14(%ebp)

  Ja <phase_3+146>

  如果前面的大于等于后面的就不跳转

 

  第一个参数小于等于7

  后面的是经典的跳转表指令:

 

  Mov 0x8049968(,%edx,4),%eax

  Jmp *%eax

 

  显然跳转表就在0x8049968

  需要根据这个打印跳转表备用:

 

  所以说跳转的地址会取决于第一个数字x。

  可是后面的又要求第一个数字大于等于5

 

  所以第一个数字取6, 跳转地址为0x8048c74

  第二个数字为-0x32c,为-812

 

  一开始写错的原因是编址问题,0x8049968的实际值是右边的0x8048c46

 

  所以第一个数字取6, 跳转地址为0x08048c6d

  6 -259

   

  发现不行,下面开始这样调试:

  设置如下四个断点:

 

  而且在phase_3之前需要输入我觉得正确的输入,就是6  -259

  

  在第一个断点中,输入i r调试。

  

  发现是esp和ebp之间相差0x28,第一个断点获取信息有限。

  输入c进入第二个断点。

  因为esp=0xffffd120,我们查看上面一个单元0xffffd124,发现正好是0x08049962

   

  这样调试太慢了,我们从ebp到esp全都显示

  

  接着进入关键的第三个断点:

  因为switch的跳转指令就在这里,可以看到eax=0x08048c6d

  

  也就是说跳转到的地方我们分析的是没有问题的。

  同时分析其他的内存

  

  Ebp-0x4(也就是0xffffd144)的地方是0x00000002,保存的是sscanf的返回值,也就是需要输入的参数值。原程序需要这个值大于1。

  在-0x14(%ebp),也就是0xffffd134,我们发现这个值是0x00000006,也就是说我们输入的6正确地输入了。-0xc(%ebp),也就是0xffffd13c存储的是一样的值6

  那么现在问题就是,我们输入的第二个参数,-259在哪里?目测就是0xffffefd。写个程序测试一下:

  

  

  发现就是输入到了0xffffd138当中。也就是-0x10(%ebp)

  到了这里也没错呀。

 

  我们继续进入下一个断点0x8048c8d。这条命令是cmp %eax,-0x8(%ebp),比较的是-0x10(%ebp)和-0x8(%ebp)

  尴尬,炸弹爆炸了。

     我们设置断点为前一个,0x8048c8a,再测试一遍。

  发现炸弹还是爆炸了!!!

  那么就是再之前出问题了,也就是0x08048c85,cmp $0x5,%eax这句。

  还是爆炸了?

 

  那么试一试0x08048c6d,也就是switch跳转之后的地址

       还是炸了!!!卧槽

 

  猜测可能不是炸了的原因,是因为断点之后需要再设置一个断点。我们这样设置两个断点:

  

  卧槽还是炸了

  目前已知只能跳到c44的位置。

  上面的猜测错误了

  

  只有这一个断点是可以运行的。

  我们测试一下

  

  发现-0x10(%ebp)和-0x8(%ebp)都变成了-259了呀。

  到这里还是没错的

  

  这里也可以

  

  再到下一条

  也可以运行,eax和5进行比较

  

  假如这里能够成功:

 

 

 

  发现到不了c8a这里!!!找到问题了。

  …乌鱼子,就这样过了?

 

 

 

 

  测试之后发现需要小于等于5

Phase_4

 

  大概我猜只用输入一个数

  %eax为0时不会跳转,会爆炸。所以说需要%eax > = 1 。

  传递到func4中,使得输出为0x37=55

 

  这里调用了func4这个函数,那么怎么看func4这个函数的代码呢?

 

  解析一下func4功能:

  发现是一个斐波那契数列。

  这个func4还是能很直观地向我们展示递归函数的。

   

  F(9)==55 答案显然

Phase_5

   

  输入长度为6的字符串

  可以看到下面有6个循环

  我发现可能有一个问题我没有搞得很懂。那就是,add 0x8(%ebp),%eax这句话,显然,根据我对于堆栈的认识,0x8(%ebp)显然是和输入的六位字符串相关的。字符串是以字符指针开头,有偏移量的一个数组,那么这个0x8(%ebp)这个位置存储的是整个字符串,还是字符串开头的字符指针呢?

  我傻逼啦!!!显然是指针啊,c语言不传值,传递的是指针!!!

  所以这里也不是问题了,这里依次取出输入字符串的各个字符,用movzbl和movsbl的方法传递到eax,注意一个字符刚好1byte,因此用al。

  and $0xf ,%eax 显然就是要取低16位来作为偏移值,因此我们把0x804a5c0附近的内存打印出来:

  

   哇偶,那么,0x804a5c0应该就是一个字符数组的初始地址的指针(我看前面刚好有16个英文字母),这个指针加上前面面的低16位就对应一个字符,

   

  六个循环之后可以知道,判断0x804998b的串和-0xf(%ebp)的串是否相等。

   

 

  第一个字符s在串中的下标为1,满足要求的字母有a q

  第二个字符a在串中的下标为5,e,u

  第三个字符i在串中的下标为0,p

  第四个字符n在串中的下标为11,k

  第五个字符t在串中的下标为13,m

  第六个字符s在串中的下标为1,有a,q

 

  aepkma

phase_6

 

  Atoi函数是把字符串转化为整形的函数。

 

 

  可以看出来这个fun6很长啊

  好像说func6的返回值和输入没有关系,那不就断点获取一波。

 

  注意c语言传指针,不传递值

 

  这个就是返回值

  但是这个返回值,还有6次ret=*(ret+8)的转换,我们不妨直接获取最后一次的eax值

   

  简直秒杀,输入就是0x27c的十进制值636

  最后torch 一个solution.txt,把六行全放进去,./bomb solution.txt即可。

Phase_defused

  隐藏关捏。

 

  可以看到,调用secret_phase是有条件的

  我们在第108行的phase_defused()也就是最后一个phase_defused()暂停一下。

  Jne之前的条件,是只有到第六关才会出现。

  

  继续往下看。

  

  这个0x39等于57

  

  说明sscanf输入的是数字和串

  在判断字符串是否相等的函数前面有:

  

  最终是要像这样,在第四关后面加上这个东西:

 

  在炸弹处设置断点调试:

 

 

   Fun7:

  

  首先,输入需要小于等于0x3e9=1001

  Func7输入第二个数字

 

  要求fun7返回值等于4

  继续分析fun7

 

  可见左节点0x0804a6e4 ,右节点0x0804a6d8

 

 

  需要返回4的话,

  只能是从root开始,向左,向左,向右。

  X=7

 

posted @ 2021-11-13 22:22  TheDa  阅读(964)  评论(0编辑  收藏  举报