美文网首页
CS:APP 二进制炸弹拆解全解析

CS:APP 二进制炸弹拆解全解析

作者: 巫水硫酸铜 | 来源:发表于2018-08-13 16:39 被阅读0次

    花了一段时间做完了暑假作业, 二进制炸弹破解的过程可谓苦尽甘来

    现在把自己写的解析报告放上来, 纯原创, 没有参考任何关于此炸弹的解析

    可能会与你拿到的炸弹有所不同, 本文只提供思路

    第一关 字符串比较

    1.    通过read_line从标准输入流输入测试字符串例如: 1234567

    2.    进入phase_1 函数

    3.    进入strings_not_equal 函数

    4.    进入第一个string_length函数

    5.    根据string_length函数返回值 %eax = 7 推断第二个string_length函数计算的是标准字符串(密码)的长度

    6.    进入第二个stirng_length函数, 根据add 与 jne 指令判断该函数通过遍历字符数组到’\0’来计算长度, 易知 %ebx 存储该数组的首地址

    7.    遍历完成后得到 %eax 值为 41, 表明密码字符串长度为41,

    8.    restart 程序并执行到第6步

    9.    运用 print $ebx 得到起始地址 134521080

    10.    通过 x/41cb (x查看内存值 41表明单位个数c以字符形式显示b表明每次取一个字节长度) 指令 得到密码字符串的内容

    第一关密钥的内容

    第一关密钥:

    For NASA, space is still a high priority.

    第二关 循环

    1.    进入read_line函数, 随便输入几个字符 "asdasfa"

    2.    进入, read_six_numbers函数

    3.    按s继续执行,进入sscanf函数

    4.    继续按s可以看到sscanf函数的定义, 如下

    第二关sscanf函数的定义

    5.    可知应该输入6个数字, 每个数字用空格隔开

    6.    重新进入read_line, 按n后输入数字1 2 3 4 5 6

    7.    继续执行到sscanf后, 易知0x4(%esp)取输入的第一个数字, 即1, 并与1比较,相等即继续执行

    8.    通过观察寄存器值的变化, 可以判断出phase_2 + 54 到 phase_2 + 73为循环过程, 遍历我们刚刚输入的6个数, 整理循环的运行逻辑如下, 以a1 – a6 表示他们.

    start = a1;

    x = a2;

    if (start == 1)

            do {

                    start += start;

                    if ( start != x );

                    BOOM!!!;

                    x = a.next;

            } while ( x != a6 )

    else        BOOM!!!!;

    9.    根据上述算法依次类推, 即可得到应该输入的密码为 1 2 4 8 16 32.

    第二关密钥执行结果

    第二关密钥:

    1 2 4 8 16 32

    第三关 条件/分支

    这一关通得很快, 因为有多解, 碰巧凑了一个, 感觉运气成分比较大 (逃

    1.    同上, 进入read_line函数后随便输入一串字符  "hahaha"

    2.    si 进入sscanf函数, 再按一次s可以调出sscanf函数原型, 如下

    第三关sscanf函数原型

    3.    可知这关密码的格式是 整数空格字符空格整数

    4.    退出重新进入, 输入测试字符串1<空格>a<空格>2

    ( 体现运气的时刻到了)

    5.    通过 cmpl $0x7, 0x4(%esp) <换行> ja blabla 语句可知, 第一个数字需要小于7

    6.    通过 jmp *0x804a160 (, %eax, 4) 语句可知跳转目的语句与第一个数字的值有关

    7.    猜测一共有7种破解密码, 我这里遇到的是第2种(第一个数字为1)

    8.    跳转到phase_3+113, 将第二个整数与0xbe (即190 ) 作比较, 相等即可继续进行, 由此推断 第二个整数是0xbe, 即190

    9.    观察跳转到phase_3+323后需要将%al中的值与字符参数做比较相等才可以完成密码匹配

    10.  通过 print $al 查看寄存器中的值, 发现为113, 查ASCII码表, 发现为字符’q’

    那么, 1 q 190即为解之一

    第三关密钥执行结果

    第三关密钥 ( 之一)

    1 q 190

    第四关 递归调用和栈

    1.    同样, 输入测试字符进入sscanf函数, 输入s指令观察到sscanf函数的原型

    第四关sscanf函数原型

    2.    重新进入, 输入测试字符1 2

    3.    根据phase_4+78的 cmp $0x5, %eax 语句以及 cmpl $0x5, 0x8(%esp) 语句, 可以推断出以第一个数字为参数的fun4函数返回值应该为5, 第二个数字的值应该为5

    4.    进入fun函数,观察%eax, %ebx, %esi, %ecx的变化

    5.    经过初步推断, %ecx存储函数参数值, %eax为返回值, %ebx与%esi分别存储函数中间值, 并赋初始值分别为0和14, %edx作为函数比较的中间值, 其作为中转站与%eax性质相同

    6.    通过分析比较代码, 可以大致推断出原函数的算法如下, 其中以origin作为%esi存储的值, 以sub作为%ebx的值, 以center作为%eax和%edx所存储的值, x为函数参数, 也就是我们输入的第一个数字

    origin = 14;

    center = origin;

    sub = 0;

    fun4 ( int x ) {

            center = ( origin – sub ) / 2 + sub;    //函数的开头计算用于比较的center值( %eax )

            if ( x == center ) return 0;         //对应汇编代码fun4 + 0 ---- fun4 + 33

            else if ( x < center ) {                     

                    center -= 1;                 //汇编代码fun4+ 40

                    origin = center;              //汇编代码push%edx

                    return 2 * fun ( x ) ;           //汇编代码 fun4 +54 add指令

            }                              

            else if ( x > center ) {             

                   center += 1;                 //汇编代码fun4+ 71

                   sub = center;                //汇编代码push %edx

                   return 2 * fun ( x ) + 1;        //汇编代码fun4 + 84

            }

    }

    7.    根据上述代码, 反向递推, 令最后返回的%eax值为5

    5 = 2 * 2 * ( 2 * 0 + 1 ) + 1, 只有10可以满足左侧递推式

    第四关密钥执行结果

    第四关密钥

    10 5

    第五关 指针

    1.    进入read_line函数后进入phase5中的sscanf函数, 按下s得到sscanf的函数原型

    第五关sscanf函数原型

    2.    可知本关依然需要两个整数作为密钥

    3.    重新进入, 输入测试数据1和2并观察函数执行情况

    4.    根据下方的 mov <地址>(, %eax, 4 ) 可以推断出输入的第一个整数作为地址的偏移量

    即整形数组中的下标

    5.    利用 gdb x/70cb <地址> 指令查看程序内部提供整形数组的所有值, 并记录为表格如下

    查看内存中数组的存储情况 数组下标与元素对应情况

    6.    根据后续的 jne $0xf 以及对起计数作用的%edx中的值可知, 该程序进行15次循环后使得%eax的值为15, 即通过15次对地址的重新计算得到最终值15, 下标为5, 按此逻辑写出该程序段的大致算法如下:

    result = input ( 第一个参数);

    counter = 0;

    sum = 0;

    do{

           counter ++;

           result= array [ result ];

           sum+= result;

    while ( result != 15 && counter != 15 );

    7.    按照上述算法, 从15开始递推 15次, 得到第一次result的值, 也就是我们需要输入的一个整数值为5.

    15 6 14 2 1 10 0 8 4 9 13 11 7 3 12 ➡ array [ 5 ] = 12

    8.    又根据 cmp 0x8 ( %esp ), %ecx 可知, 第二个参数需要与sum值相同, 即所有遍历过的值的和, 15 + 6 + ……+ 12 = 115

    第五关密钥执行情况

    第五关密钥

    5 115

    第六关 链表/指针/结构

    1.    进入read_line函数, 根据phase_6 + 26的read_six_numbers函数, 根据第二关已知, 需要输入6个数字.

    2.    重新进入函数, 并输入测试数字组1 2 3 4 5 6

    3.    进入phase函数并跳过read_six_numbers函数, 就来到了本关的第一个循环判断, 如下

    第六关第一个坎

    以array数组表示用户输入的六个数字,该部分代码可用以下算法来描述

    for ( int I = 0; I < = 5 ; I ++ ) {

           if ( array [ I ] <= 6 ) {

                  for ( int m = I + 1; m <=5; m ++ )

                         if( array [ I ] == array [ m ] )

                                BOOM!!!;

           }

           else    BOOM!!!;

    }

    根据该段代码可知用户输入的每个数字都应该小于6并且各数字互不相等

    4.    phase_6+104 ~ phase_6+115是7减去原来每个数后重新存入源地址中, 算法不赘述, 即phase[x] = 7 – phase [7]

    5.    phase_6+124 ~ phase_6+167是通过计算好的新数组, 将一个六结点链表初始地址每次移动(array[x] – 1) 个8字节后得到值依次保存于栈帧中. 原理可用下述公式表示, 在这里观察到 0x8(%edx) 直接就是移动到下一个结点的首地址了, 而每个结点所占空间是12个字节, 推翻上述分析, 移动8个地址后即是下一个结点首地址,从而构成单向链表.

    即此部分是取第array [  x ] 个结点值并依次保存

    第六关第二个坎

    6.    通过 x/18dw 指令观察链表六个结点首元素的值:

    链表node1 - 6

    从上图可以看出:

    链表node1 - 6

    7.    下述代码作为此关的终极武器, 经过寄存器值的查看即跳转条件分析, 只有满足存入栈帧的结点降序排列才可躲避phase_6+219的爆炸函数

    第六关第三个坎

    8.    手动排序, node6 > node4 > node1> node5 > node2 > node3

          整理得最终结果与输入值的对应关系如下

    输入值的变化过程 第六关密钥执行结果

    第六关密钥:

    1 3 6 2 5 4

    隐藏关卡

    1.    进入phase4的defused函数, 观察到一条指令 

    cmpl $0x6, 0x804c3cc

    使用x指令查看地址0x804c3cc中的值, 发现这个值记录的是输入字符串的个数

    也就是在输入6个字符串, 即通过6个关卡后才可以跳转到下方的sscanf函数附近

    2.    猜测defused函数中的sscanf函数记录着有关隐藏关的密码

    3.    继续进行到第六关, 重新进入phase_defused函数, 进入sscanf函数使用 s 指令查看到

    有关隐藏关密钥的格式, 即我们需要在第四关密钥后加一个字符串.

    隐藏关sscanf函数原型

    4.    重新开始, 第四关密钥处输入测试字符串 "10 5 haha" , 进入到第六关后的pahse_defused函数, 进入其中的strings_not_equal函数.

    5.    按照第一关流程, 直接进入第二个string_length函数, 使用

    x/8cb $edx

    指令查看%s, 即第四关密钥后需要输入什么字符串

    进入隐藏关的密钥

    据此写出%s的内容: DrEvil.

    看起来像一个人名, 猜测这个彩蛋密钥应该算类似“炸弹制作者名单”

    6.    重新进入该phase_defused函数, 进行到puts函数, 发现了进入隐藏关的提示语

    已进入隐藏关

    不慌, 继续执行进入secret_phase函数

    7.    read_line函数, 输入一行字符串, "lstnb"

    8.    进入到strtol函数, 该函数是将字符串转化为数字, base参数表明进制, 查看原型:

    隐藏关strtol函数原型

    base = 10, 由此可知将输入数字转化为十进制整数

    9.    根据   cmp $0x3e8, %eax / jbe ~~

    可知输入一个小于等于1000的数字可以避免爆炸

    10.    重新开始, 输入1000, 进入fun7函数, 发现fun7又是一个递归函数……$%!&^%!*#&^%

    fun7函数

    11.    经过一定观察, 发现fun7函数比第四关的fun4函数简单了不少, 长舒一口气

    整理得到fun7函数所描述的算法如下

    middle = array [ 0 ];   //array首地址存储在%edx中, middle存储在middle中

    fun7 ( input ) {          //input存储在%ecx中

           if( input == middle ) return 0;

           else if ( input < middle) middle = *( array + 1 ) return 2 * fun ( input ) ;

           else middle = *(array + 2)return 2 * fun ( input ) + 1;

       }

    12.    根据secretphase函数中的 cmp 0x2 %eax 指令, 可知fun7返回值应为2

    2 = 2 * ( 2 * 0 + 1) 进行两次递归调用, 利用x指令查看array中的部分值

    数组array

    根据递推关系36→ 8 →22 可知需要用户输入的值为22

    隐藏关密钥:

    22


    [完结]

    破解二进制炸弹

    相关文章

      网友评论

          本文标题:CS:APP 二进制炸弹拆解全解析

          本文链接:https://www.haomeiwen.com/subject/ccwybftx.html