CSAPP 二进制炸弹实验

本文详细介绍了二进制炸弹的拆解过程,通过分析炸弹的各阶段代码,使用GDB和OBJDUMP工具,逐步揭示了每关的破解技巧和原理。从汇编语言基础到GDB调试,再到具体关卡的策略,全面覆盖了二进制炸弹的拆解流程。

实验简介

二进制炸弹是一个作为目标代码提供的程序。运行时提示用户输入6个不同的字符串,如其中一个字符串不正确,炸弹会引爆并打印一条错误信息。需要通过反汇编确定输入的6个字符串,从而拆除炸弹。

知识点

  • 汇编语言基础
  • GDB和OBJDUMP工具的使用

实验环境

Centos7 x86_64

获取二进制炸弹

首先从CSAPP官网获取二进制炸弹bomb.tar: http://csapp.cs.cmu.edu/3e/labs.html

在linux下执行tar xvf bomb.tar,得到二进制炸弹的文件,文件列表如下:

|-- bomb		# 二进制炸弹,x86-64位
|-- bomb.c		# 主程序,逻辑是接受用户输入的6个字符串,并判断每个字符串是否正确。如果正确,调用phase_defused进入下一关,否则调用explode_bomb引爆炸弹

第一关

1. 首先理解main函数执行过程

反汇编炸弹,使用objdump -d bomb > bomb.txt命令,内容如下:

0000000000400da0 <main>:
  400da0:   53                      push   %rbx
  ......
  400e19:   e8 84 05 00 00          callq  4013a2 <initialize_bomb>
  400e1e:   bf 38 23 40 00          mov    $0x402338,%edi
  400e23:   e8 e8 fc ff ff          callq  400b10 <puts@plt>
  400e28:   bf 78 23 40 00          mov    $0x402378,%edi
  400e2d:   e8 de fc ff ff          callq  400b10 <puts@plt>
  400e32:   e8 67 06 00 00          callq  40149e <read_line> # 接受用户输入的字符串,保存到%rax寄存器
  400e37:   48 89 c7                mov    %rax,%rdi		  # 用户输入的字符串保存在$rdi寄存器,并作为phase_1函数的第一个入参传递
  400e3a:   e8 a1 00 00 00          callq  400ee0 <phase_1>	  # 调用phase_1(), 执行第一关代码
  400e3f:   e8 80 07 00 00          callq  4015c4 <phase_defused>

查看0x400e32处的代码: 主程序调用read_line接收用户输入的字符串,保存到$rax,并且将该字符串作为函数入参传递给phase_1,执行第一阶段的代码。

这里需要理解x86-64的过程调用规则:(关于过程调用,可参考《CSAPP原书第三版》3.7小节 —— 过程)

  • 函数调用中,利用%rax寄存器保存返回值。
  • 关于参数传递 , 如果函数参数不超过6个,会依次通过%rdi, %rsi, %rdx, %rcx, %r8, %r9传递; 如超过6个参数,超出的参数利用栈传递。
  • %rbx, %rbp, %r12~%15被划分为被调用者保存寄存器;其余的寄存器,除了栈指针%rsp,都被划分为调用者保存寄存器
2. 理解phase_1执行过程

查看phase_1的代码,如下:

0000000000400ee0 <phase_1>:
  400ee0:   48 83 ec 08             sub    $0x8,%rsp
  400ee4:   be 00 24 40 00          mov    $0x402400,%esi 			  # $esi作为strings_not_equal函数的第二个参数传递
  400ee9:   e8 4a 04 00 00          callq  401338 <strings_not_equal> # 调用strings_not_equal函数,比较用户输入的字符串($rdi)和$esi处的字符串是否相等
  400eee:   85 c0                   test   %eax,%eax 				  # 判断%eax的值是否为0
  400ef0:   74 05                   je     400ef7 <phase_1+0x17> 	  # 如%eax等于0,跳转到400ef7,正常退出
  400ef2:   e8 43 05 00 00          callq  40143a <explode_bomb> 	  # 如%eax不等于0,炸弹爆炸
  400ef7:   48 83 c4 08             add    $0x8,%rsp
  400efb:   c3                      retq

phase_1执行过程如下:

  • 首先通过callq指令调用strings_not_equal函数。根据过程调用的规则,可以确定这个函数接受2个参数。第一个参数是%rdi, 上面分析过,是我们拆弹时输入的字符串;第二个参数是$esi,值为0x402400。顾名思义,strings_not_equal函数用来判断两个字符串是否相等。
  • 接着用test指令判断函数的返回值是否为0。如果为0进行跳转并返回,调用phase_defused拆除炸弹,否则通过callq指令调用explode_bomb,引爆炸弹。

因此,拆弹的关键在于,确认0x402400地址处的字符串是什么。只需要在0x400ee9处打个gdb断点就可以了。

3. gdb调试炸弹

执行gdb bomb, 设置断点b *0x400ee9, 执行run。 此时程序会要求用户输入字符串,先随便输一个字符串使程序运行到我们设置的断点 0x400ee9处,如下:

# gdb bomb		
(gdb) b *0x400ee9							# 设置断点, b相当于break
Breakpoint 1 at 0x400ee9
(gdb) r										# 运行程序, r相当于run
Starting program: /home/pc/CSAPP/bomb/bomb
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
123											# 随便输入一个字符串,让程序运行到断点0x400ee9
Breakpoint 1, 0x0000000000400ee9 in phase_1 ()
(gdb) p (char *)$rdi						# $rdi保存用户输入字符串,为123,与x/s $rdi指令等价
$2 = 0x603780 <input_strings> "123"
(gdb) x/s $esi								# 查看esi处字符串,这就是第一关的答案
$1 = 0x402400 "Border relations with Canada have never been better."

查看%esi处字符串, 即第一关的答案,如下:

Border relations with Canada have never been better.

quit指令退出gdb, 新建一个文件answer,将答案写入该文件。重新执行bomb程序,指定answer文件名作为参数,如下:

# ./bomb answer
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Phase 1 defused. How about the next one?

显示Phase 1 defused, 表示第一关已成功通过。

几个调试汇编代码相关的GDB指令

使用disassemble指令查看汇编代码,箭头表示下一步即将执行的汇编指令。

si指令用于单步执行汇编代码,相当于stepi

ni指令以函数调用为单位进行单步执行, 相当于nexti

(gdb) disassemble
Dump of assembler code for function phase_1:
   0x0000000000400ee0 <+0>:     sub    $0x8,%rsp
   0x0000000000400ee4 <+4>:     mov    $0x402400,%esi
=> 0x0000000000400ee9 <+9>:     callq  0x401338 <strings_not_equal>
   0x0000000000400eee <+14>:    test   %eax,%eax
   0x0000000000400ef0 <+16>:    je     0x400ef7 <phase_1+23>
   0x0000000000400ef2 <+18>:    callq  0x40143a <explode_bomb>
   0x0000000000400ef7 <+23>:    add    $0x8,%rsp
   0x0000000000400efb <+27>:    retq
End of assembler dump.

更多用法参考gdb手册或《CSAPP 原书第3版》3.10.2小节 —— 使用GDB调试器

拆弹小技巧
  • 查看bomb符号表或者直接查看汇编代码,会发现有个explode_bomb符号,该函数用来引爆炸弹。

  • 调试时可以对该函数设置断点,在拆弹失败时暂停运行,不让其爆炸,便于调试。可使用 b explode_bomb指令设置断点

第二关

先贴出第二关phase_2的部分汇编代码:

0000000000400efc <phase_2>:
  400efc:   55                      push   %rbp
  400efd:   53                      push   %rbx
  400efe:   48 83 ec 28             sub    $0x28,%rsp
  400f02:   48 89 e6                mov    %rsp,%rsi	# %rsi作为read_six_numbers的第二个入参传递, $rsp既是入参也是出参;第一个入参为$rdi, 即用户输入的字符串
  400f05:   e8 52 05 00 00          callq  40145c <read_six_numbers> # 读六个数字
  ......

phase_2程序先调用read_six_numbers。该函数接受两个参数,第一个参数为$rdi, 即我们输入的字符串;第二个参数为$rsp, $rsp既是入参也是出参,用于保存read_six_numbers函数解析$rdi后得到的6个整数。想得到这个结论,需要分析read_six_numbers代码,如下:

(gdb) disassemble read_six_numbers
Dump of assembler code for function read_six_numbers:
   0x000000000040145c <+0>:     sub    $0x18,%rsp
   0x0000000000401460 <+4>:     mov    %rsi,%rdx		# sscanf函数的第1个可变参数,第3个参数,通过%rdx传递
   0x0000000000401463 <+7>:     lea    0x4(%rsi),%rcx	# sscanf函数的第2个可变参数,第4个参数, 通过%rcx传递
   0x0000000000401467 <+11>:    lea    0x14(%rsi),%rax	# sscanf函数的第6个可变参数,第8个参数,通过栈传递
   0x000000000040146b <+15>:    mov    %rax,0x8(%rsp)
   0x0000000000401470 <+20>:    lea    0x10(%rsi),%rax  # sscanf函数的第5个可变参数,第7个参数,通过栈传递
   0x0000000000401474 <+24>:    mov    %rax,(%rsp)
   0x0000000000401478 <+28>:    lea    0xc(%rsi),%r9	# sscanf函数的第4个可变参数,第6个参数, 通过%r9传递
   0x000000000040147c <+32>:    lea    0x8(%rsi),%r8	# sscanf函数的第3个可变参数,第5个参数, 通过%r8传递
   0x0000000000401480 <+36>:    mov    $0x4025c3,%esi	# sscanf函数的第2个参数,通过%esi传递,0x4025c3地址的格式化字符串为"%d %d %d %d %d %d",表示输入字符串应该为6个整数
   0x0000000000401485 <+41>:    mov    $0x0,%eax
   0x000000000040148a <+46>:    callq  0x400bf0 <__isoc99_sscanf@plt>
   0x000000000040148f <+51>:    cmp    $0x5,%eax		# sscanf读入的可变参数需大于5个,否则爆炸
   0x0000000000401492 <+54>:    jg     0x401499 <read_six_numbers+61>
   0x0000000000401494 <+56>:    callq  0x40143a <explode_bomb>
   0x0000000000401499 <+61>:    add    $0x18,%rsp
   0x000000000040149d <+65>:    retq

查看0x400f02, 0x401463 ~ 0x40147c处的代码,我们可以把调用者phase_2中的$rsp看作一个一维数组的首地址,该数组的长度为6,内容依次为$rsp, $rsp + 4, $rsp + 8, $rsp + 12, $rsp + 16, $rsp + 20,用于 保存sscanf函数执行后生成的6个整数。

炸弹用sscanf读取并解析用户输入字符串

注意到0x40148a处调用sscanf函数,作用是从用户输入的字符串%rdi中解析出6个整数,保存到调用者phase_2中的$rsp

C语言中sscanf函数原型如下:

int sscanf(const char *str, const char *format, ...);

对照汇编代码, $rdi相当于sscanf的参数str;查看0x401480代码,$esi相当于sscanf的参数format, 用gdb查看0x4025c3处的字符串,为"%d %d %d %d %d %d", 说明sscanf中的可变参数个数为6,且都是指向int类型的地址。

(gdb) x/s 0x4025c3
0x4025c3:       "%d %d %d %d %d %d"
</
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

pcj_888

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值