二进制炸弹(第二次实验)

本文详细介绍了如何通过汇编语言、调试器和逆向工程来拆除一个包含六个阶段的二进制炸弹程序。每个阶段涉及不同的技术,如字符串比较、循环、条件分支、递归、指针和链表。通过分析阶段一到六的代码,揭示了解除炸弹所需的输入字符串。此外,还揭示了一个隐藏阶段,要求在阶段四后面附加特定字符串,以触发额外的解谜环节。
摘要由CSDN通过智能技术生成

实验目的

本实验通过要求你使用课程所学知识拆除一个“binary bombs”来增强对程序的机器级表示、汇编语言、调试器和逆向工程等方面原理与技能的掌握。 一个“binary bombs”(二进制炸弹,下文将简称为炸弹)是一个Linux可执行程序,包含了6个阶段(或层次、关卡)。炸弹运行的每个阶段要求你输入一个特定字符串,你的输入符合程序预期的输入,该阶段的炸弹就被拆除引信即解除了,否则炸弹“爆炸”打印输出 “BOOM!!!”。
实验的目标是拆除尽可能多的炸弹层次。
每个炸弹阶段考察了机器级程序语言的一个不同方面,难度逐级递增:
阶段1:字符串比较
阶段2:循环
阶段3:条件/分支
阶段4:递归调用和栈
阶段5:指针
阶段6:链表/指针/结构
另外还有一个隐藏阶段,只有当你在第4阶段的解后附加一特定字符串后才会出现。
为完成二进制炸弹拆除任务,你需要使用gdb调试器和objdump来反汇编炸弹的可执行文件并单步跟踪调试每一阶段的机器代码,从中理解每一汇编语言代码的行为或作用,进而设法推断拆除炸弹所需的目标字符串。比如在每一阶段的开始代码前和引爆炸弹的函数前设置断点。

实验分析

为了使分析清晰明了,每个阶段都有等价的c语言代码,隐藏关留到最后分析。
在开始之前,首先介绍两个库函数:
1.int sscanf(char *input,char *format,arg3,arg4….);这个函数和scanf功能相似,只是从input里读数据,而不是从stdin里读数据,返回成功读取数据的个数
2.long int strtol(const char *nptr,char **endptr,int base,int group);这个函数以base为进制将nptr字符串转化为对应的数值并返回

阶段一

08048b20 <phase_1>:
...
 8048b2c:       push   $0x80497c0                  /pattern/
 8048b31:       push   %eax                         /input/
 8048b32:       call   8049030 <strings_not_equal>
...

由上面的代码可以发现,调用了strings_not_equal比较input和pattern,用gdb查看0x80497c处的值:

(gdb) x/20x 0x80497c0
0x80497c0:  0x6c627550  0x73206369  0x6b616570  0x20676e69
0x80497d0:0x76207369    0x20797265  0x79736165  0x6425002e
...(省略)

根据字节顺序对照ASCII码表可知pattern为”Public speaking is easy.”,故阶段一的正确输入就是pattern。

阶段二

08048b48 <phase_2>:
 8048b4b:               sub    $0x20,%esp
 8048b4e:               push   %esi
 8048b4f:               push   %ebx
 8048b50:               mov    0x8(%ebp),%edx               /edx=input/  
 8048b53:               add    $0xfffffff8,%esp
 8048b56:               lea    -0x18(%ebp),%eax             /&a[0]/
 8048b59:               push   %eax
 8048b5a:               push   %edx                         /input/
 8048b5b:               call   8048fd8 <read_six_numbers>

这里我们可以看到,代码分配了int a[6]的存储空间,并调用了

read_six_numbers(input,a):
08048fd8 <read_six_numbers>:
 8048fdb:       sub    $0x8,%esp
 8048fde:    08048b48 <phase_2>:
   mov    0x8(%ebp),%ecx                                    /input/
 8048fe1:       mov    0xc(%ebp),%edx                       /a/
 8048fe4:       lea    0x14(%edx),%eax                      /a+5/
 8048fe7:       push   %eax
 8048fe8:       lea    0x10(%edx),%eax                      /a+4/
 8048feb:       push   %eax
 8048fec:       lea    0xc(%edx),%eax                           /a+3/
 8048fef:       push   %eax
 8048ff0:       lea    0x8(%edx),%eax                           /a+2/
 8048ff3:       push   %eax
 8048ff4:       lea    0x4(%edx),%eax                           /a+1/
 8048ff7:       push   %eax
 8048ff8:       push   %edx                                 /a/
 8048ff9:       push   $0x8049b1b                              /format/
 8048ffe:       push   %ecx                                 /input/
 8048fff:       call   8048860 <sscanf@plt>

这里可以清楚地看到read_six_numbers将input,format,&a[0],&a[1]…&a[5]保存在esp指向的栈里面,接着调用sscanf,从input里读入数据存入数组a里。使用gdb查看format值:

(gdb) x/20x 0x8049b1b
0x8049b1b:  0x25206425  0x64252064  0x20642520  0x25206425
0x8049b2b:  0x61420064  0x6f682064  0x28207473
...(省略)

通过ASCII码表得知,format确实是”%d %d %d %d %d %d”,接着:

08048fd8 <read_six_numbers>:
 8049004:       add    $0x20,%esp
 8049007:       cmp    $0x5,%eax               /sscanf从input中读取数字个数/ 
 804900a:       jg     8049011 <read_six_numbers+0x39>
 804900c:       call   80494fc <explode_bomb>       /if eax<=5,explode/
 8049011:       mov    %ebp,%esp
 8049013:       pop    %ebp    
 8049014:       ret

当sscanf返回值<=5时,引爆,否则返回phase_2。继续分析phase_2:

08048b48 <phase_2>:
8048b63:               cmpl   $0x1,-0x18(%ebp)             /a[0]/
 8048b67:               je     8048b6e <phase_2+0x26>
 8048b69:               call   80494fc <explode_bomb>           /if (a[0]!=1/
 8048b6e:               mov    $0x1,%ebx                   /if (a[0]==1)/
 8048b73:               lea    -0x18(%ebp),%esi             /esi=&a[0]/
 .loop
 8048b76:               lea    0x1(%ebx),%eax               /eax=ebx-1/ 
 8048b79:               imul   -0x4(%esi,%ebx,4),%eax           /eax*=a[ebx]/
 8048b7e:               cmp    %eax,(%esi,%ebx,4)
 8048b81:               je     8048b88<phase_2+0x40>
 8048b83:               call   80494fc <explode_bomb>           /eax!=a[ebx]/
 8048b88:               inc    %ebx
 8048b89:               cmp    $0x5,%ebx
 8048b8c:               jle    8048b76 <phase_2+0x2e>
 goto .loop

这段代码首先比较a[0]和1,如果不相等,爆炸,否则进入for循环,比较每个数组元素的值,整个阶段二的等价C语言代码为:

void read_six_numbers(char *input,int a[])
{
    Int eax=sscanf(input,"%d %d %d %d %d %d",
a,a+1,a+2,a+3,a+4,a+5);
    if (eax<=5)
        explode_bomb();
}
void phase_2(char *input)
{
    int a[6];
    read_six_numbers(input,a);
    if (a[0]!=1)
        explode_bomb();
    for (int ebx=1;ebx<=5;ebx++)
        if (a[ebx]!=(ebx+1)*a[ebx-1])
            explode_bomb();
}

由此可以知道阶段二的字符串必须以”1 2 6 24 120 720 ”为开头,由于read_six_numbers仅限定了读入数据个数不小于6,因此正确答案不唯一

阶段三

08048b98 <phase_3>:
  8048ba5:       lea    -0x4(%ebp),%eax
 8048ba8:       push   %eax                         /&c/
 8048ba9:       lea    -0x5(%ebp),%eax
 8048bac:       push   %eax                         /&b/
 8048bad:       lea    -0xc(%ebp),%eax
 8048bb0:       push   %eax                         /&a
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值