x86上的二进制炸弹对于反汇编的练习来说还是比较经典的,由于网上有很多该程序的讲解,所以在此我打算写一下arm平台上的二进制拆炸弹这个游戏。
环境的搭建
由于是arm平台的环境,所以需要在linux环境下安装一个模拟器,在此我选择了qemu该模拟器,具体操作如下(该操作对Ubuntu环境有效,其他linux版本可自行查找方法)
sudo apt-get install qemu-user
运行ARM指令集模拟器并运行开启gdbserver和运行bomb_1程序
qemu-arm -g 8009 bomb_1
其中,-g参数是为了添加调试信息,为了使远程gdb调试能够起作用,8009为自定义的端口号。
另外启动一个终端,通过命令远程开启gdb调试器并加载待调试程序。
arm-linux-gdb bomb_1
输命令来连接模拟器中的gdbserver
target remote localhost:8009
辅助工具IDA pro。IDA pro是一款静态分析的反汇编工具,利用它可以查看数据段的具体数据,用起来十分方便。
具体分析
Phase 1
Arm指令
1 | 0000844c <phase_1>: |
反汇编分析
从标重点的三行汇编可以看出,该代码从把输入的字符串为参数1,把8484里存的字符串作为参数2,然后再调用函数strings_not_equal,判断两个函数是否相等。所以关键是找8484处的地址存的字符串。有IDA pro可以看出8484处存的是数据段6483c处存的字符串,所以用ida可以找出该处的字符串。
密码
密码为Let’s begin now!
利用gdb及arm服务器验证该答案正确。
Phase 2
Arm指令
1 | 00008488 <phase_2>: |
反汇编分析
该炸弹的逻辑是for循环。
○1处那两行由函数名可以看出为从终端读取六个数,并将其存入一个数组中,且地址从fp-32开始。○2处那四行是读取数组第一个数判断是否为1,如果是1继续判断,如果不是就会explode_bomb。如果是1 的话就会使i = 1到5,然后判断每一个数值。○3那四行及以上几行的逻辑是取出a[i-1]的值,并将a[i-1]*(i-1),然后存入r2中。○4那两行及以上几行的逻辑是取出a[i]的数据,并将其存入r3中。○5处的逻辑是判断r2和r3的值,如果相等则i+1,继续判断,如果不行等则explode_bomb。
其大致的c语言逻辑如下:
1 | if(a[0] != 1) |
所以a[0] = 1;a[1] = a[2] = a[3] = a[4] = a[5] = 0;
密码
1 0 0 0 0 0
Phase 3
arm指令
1 | 00008538 <phase_3>: |
反汇编分析
○1处将pc+412的数据传入r1,借助IDA pro可以查看pc+412引用的是
00064850处的数据,如图:
所以该处的数据为”%d %c %d”,由此可知输入的数据的形式为int,char,int。
○2处判断第一个参数int是否大于2,如果大于2则进行switch操作,如果不是则explode_bomb。○3处是对输入的第一个int进行switch判断,其伪代码如下:
1 | switch(int c) |
○4-○11是对case 0 –case 7这八种情况的具体判断,其逻辑都是一样的,所以在此只分析一种情况(由于必须要求第一个int大于2,所以分析为3,4,5,6,7这五种情况)。现分析○7,首先将0x6b放入fp-5,然后将fp-12的值(即第三个传入的int的值)与0xfb(十进制为251)进行比较,如果相等则跳转86b4,如果不是的话就会explode_bomb;然后相等的话跳转86b4,我们发现从869c到86cc的逻辑都是先nop,什么都不做,然后再b 86d0
密码
3 k 0xfb
Phase 4
Arm指令
1 | 00008760 <phase_4>: |
反汇编分析
○1处是输入的参数的形式,位于87d0处,利用IDA pro找到了87d0引用了0006485C数据段的数据,为
即要求输入一个数字。○2处判断是否是输入的参数是否大于0,如果大于0则继续判断,如果不是则explode_bomb。○3处将输入的数传入r0中作为参数然后调用func4()。○4首先将传入的参数存入fp-16中,然后判断参数是否小于等于1,如果小于等于1的话就跳到○7处,○7处将返回值赋值为1,然后pop返回值和参数;如果大于1的话就到○5,○5和○6的逻辑就是分别让参数-1和-2,然后再分别调用func4,由此可以看出该bomb的逻辑是一个递归的调用。其c语言的伪代码如下:
1 | func4(int i) |
然后看代码块○8,其将func4函数的返回值与8进行比较,如果等于8就通过,否则explode_bomb。所以通过计算可以得出输入的值为5。
密码
5
Phase 5
Arm指令
1 | 000087d4 <phase_5>: |
反汇编分析
○1中代码首先判断读入的字符串的长度,如果长度等于6则跳到代码块○2中去,如果不等于6则explode_bomb。在代码块○2中,首先将计数变量i置为0,然后跳到代码块○3中去,在代码块○3中先判断i是否小于等于5,如果小于等于5的话就跳入○4中,其中○4○5○6○7为for循环的主体,我将其化成了这4部分。其中在○4的逻辑如下:取出字符串的第i个字符s[i],
然后将s[i]与0xF相与,其中一个char为一个字节,由两个16进制数组成,做相与运算后就只取出char的后4个b,将数值存入r3中。然后到代码块○5中,找到数据pc+96(8888)处的数据,根据IDA pro得出其引用的是0008216C处的数据,数据如图:
,然后根据r3中的数值作为索引取得相应的字符,将得到的字符存入r2中,然后到代码块○6中,其将r2的数据存入(fp-4-11)+i的地址处。代码○7是将i++。所以该段代码的伪代码如下:
1 | for(i = 0;i <= 5;i++) |
其中s为输入的字符串,s1为找到的字符串。
for循环结束了之后,得到长度为6的s1字符串,到了代码块○8中,该逻辑是将s1与(pc-32)即888c处的字符串进行比较,根据IDA pro得到引用了数据段00064860处的数据为:
所以只有当s1为”giants”时,才能通过,即索引顺序为0xF,0x0,0x5,0xB,0xD,0x1,查ascii表得到后4个b分别为这些的十六进制的数的字符分别为opeka(可能有多重情况,只要后4个b分别对应那五个十六进制数的字符即可)。
密码
opeka
Phase 6
指令及逻辑分析:
由于该炸弹的指令及逻辑较长,所以指令分开了几份分别进行分析。
00008890
读入六个数
1 | 8890: e92d4800 push {fp, lr} |
以上的指令的逻辑比较简单,是首先读入六个数字,并将其放入数组中,假设数组为a。
对这六个数的值进行判定
1 | 88b8: e3a03000 mov r3, #0 |
以上的指令为两个for循环的嵌套。
其中○1为(fp-12)处存的变量假设为i,把变量i的值存入r2,然后代码段○2处为取出a[i]的值并将其存入r3中,紧接着代码段○3处判断r3的值是否大于6,如果大于6就explode_bomb。然后进入代码块○4中,在此就进入了第二个for循环,首先初始化(fp-8)处的变量,设为j,j = i+1;然后进入代码段○5处对j的值进行判断,如果大于5就进入代码段○9,其中代码○9是将i增1并进行判断,如果i大于5就跳出for循环,如果小于5则跳至○1再进行一次for循环,对于代码○5处的判断如果j小于等于5的话,则跳入代码○6处,其中逻辑为取出a[i]的值将其放入r2中,然后进入○7中,取出a[j]的值将其放入r3中,然后进入○8,将r2和r3进行比较,如果相等的话就explode_bomb,不行等就进入○5,又进行了一次循环。其伪代码如下:
1 | for(int i = 0;i <= 5;i++) |
根据输入的6个数把链表的6个node进行索引排序
1 | 8978: e59f315c ldr r3, [pc, #348] ; 8adc <phase_6+0x24c> |
该段代码也有一个for循环的嵌套。
首先初始化,将(PC+348)的索引的数存入r3中,由ida pro得出具体数据为
所以将node1的索引传入r3,然后跳入到代码块○2,其逻辑是先取出a[i]的数据至r2,然后取出j至r3,比较这两个数据的大小,如果r2 >r3,就跳到代码段○3,该逻辑是将r3+0x8的数据存入fp-16中,有以上的数据可知该数据为0x20A0,用IDA pro查找到为:
以此类推,根据每个节点的第8个字节处的数据作为下一个节点的地址,直到找到链表的a[i]个元素,就会跳出该内层的for循环,然后进入代码块○4,该逻辑是将该node的地址存入-0x3c+[R11] + 4 * i中,然后进入代码块○5,其逻辑是使变量i增1,然后判断是否大于5,如果小于5再跳入○1中继续循环。
其for循环的伪代码如下:
1 | for(int i = 0;i <=5;i++) |
所以经过上一个for循环的嵌套,从-0x3c+[R11]开始就有了这些节点的地址的索引,然后进入代码段○9,其逻辑是先将fp-12处的变量i初始化为1,目的是进入for循环。进入代码段○6,其目的是将*((-0x3c+[R11]+i-1)+0x8) = (-0x3c+[R11]+i);然后进入代码段○7,其目的是将当前节点设为-0x3c+[R11]+i处的地址所指向的节点,进入代码块○8,使i增1并判断和5的大小关系。
判断链表的节点的数值是否是按从大到小的顺序排列的
1 | 8a70: e51b3010 ldr r3, [fp, #-16] |
首先进入○1进行初始化,将(fp-16)处的地址换为链表的第一个node,并对(fp-12)的变量i初始化为0。然后进入代码块○2,首先获得当前node的地址,并将该node的数据存入r2中,地址存入r3中,然后通过[r3+8]获得下一个node的地址,并将下一个node的地址存入r3中,然后比较r2和r3的值,如果r2小于r3,就explode_bomb,
如果不小于就进入代码块○3,该逻辑是将当前节点(fp-16)的地址改为[r3+8]处的数值,即为下一个节点的地址,然后进入代码○4,对变量i进行增1操作,并判断和4的大小。其伪代码如下:
其中node的数据结构如下:
1 | struct node |
所以根据逻辑,先找出每个节点的数据,然后再根据输入的数进行排序,保证其是从大到小的顺序排列的。根据IDA pro找到每个node所在的地址为:
地址 数据 下个节点地址
0x820AC 0xFD 0x820A0
0x820A0 0x2D5 0x82094
0x82094 0x12D 0x82088
0x82088 0x3E5 0x8207C
0x8207C 0xD4 0x82070
0x82070 0x1B0
所以由大到小排列的话就为4 2 6 3 1 5
密码
4 2 6 3 1 5