• [转组第3天] | 黑盒测试


    2018-04-26

    DDCTF(re2):Reverseme.elf 参照夜影大佬的wp

      这个题目的代码清晰透明,没有各种花指令,混淆等操作,按夜影大佬说法,是个硬核分析题。

      

      重点说一下数据结构的定义:程序在上图第一个红框里进行结构体的初始化,在new(v4)中也有部分初始化,不过重点是在genstruct这个构造函数中。

      嗯,从夜影大佬那里学的分析数据结构很重要,虽然代码结构明显,但是后面各种指针乱飞,看的晕晕乎乎,分析题目如果有比较重要的结构体,特别是存储输入字符串相关的结构体,最好要分析透彻再继续。

    偏移 类型 长度 备注
    a1 sth_p qword 0x100(sth)  
    a1+8 char_table_0_p qword 0x100(char_table_0) byte(char_table_0)
    a1+16 input(char) byte 0x64  
    a1+272 rand%50      
    a1+280 char_table_0_p-sth_p qword   *(a1+8) - *a1
    a1+288+8 char_table_2 dword 9  
    a1+408 char_table_1 byte 255  
    a1+672 func_addr qword   临时变量,指向func_table中某值
    a1+672+8 func_table qword 255 表内9个指定位置函数会被修改

      关于后面提到的*(a1+664),*(a1+665)等的初始化,均在new(v4)中实现。

      genstruct(v4)初始化了上述结构体,接下来进行输入,然后到达check函数,看一下check函数。

      

      写成python伪代码:

    1 for i in range(len(input)):
    2     *(a1+664) = input[i+1]
    3     for j in range(8):
    4         if (f[input[i]] == (a1+408)[(a1+288+8)[j]]):
    5             *(a1+672) = (a1+672+8)[(a1+288+8)[j]]
    6             call *(a1+672)(a1)

      注意:将IDA的结构体写成高级语言的数组时,尽量将4*i,8*i等偏移卸掉,因为显然4,8是数组的元素大小,将其卸掉写成(addr)[i]的形式更容易理解,也更容易和上面分析出来的结构体对应。举个例子:

    *(a1 + *(a1 + 4 * (j + 72) + 8) + 408)  --> (a1+408)[(a1+288+8)[j]])
    把固定偏移72乘出来
    *(a1 + *(a1 + 288 + 8 + 4 * j) + 408)
    可以看出a1+288+8是上述结构体中的偏移,存储char_table_2,且该表元素类型是dword,4字节对应4*j.改写如下
    *(a1 + (a1 + 288 + 8)[j] + 408)
    再观察发现a1+408也是上述结构体中的偏移,存储char_table_1,且该表元素类型是byte,1字节正好对应。改写如下
    (a1 + 408)[(a1 + 288 + 8)[j]]
    如果还觉得不好理解还可以直接替换成char_table等自己定义的别名如下,不过不建议这么做,因为或许其他地方还会有各种指针偏移出现,过于抽象高级反而不好跟前面分析出的结构体对应。
    char_table_1[char_table_2[j]]

      嗯,这是一种好的分析习惯,分析结构体,因数替换。

      接着看上面check代码,实际上就是令Input[i]作为下标取数组f的值,然后遍历char_table_1中的9个值,如有相等的则取func_addr中对应的函数来调用。可以定位到那9个函数。逐个反编译:

     1     func_0:
     2         if(*(a1+288)<*(a1+292)):
     3             *(a1+665) = char_table_0[*(a1+288)] 
     4     func_1:
     5         if(*(a1+288)<*(a1+292)&& *(a1+665)):
     6             char_table_0[*(a1+288)]  = *(a1+665)
     7     func_2:
     8         if(*(a1+288)<*(a1+292)):
     9             *(a1+665) = *(a1+665)+*(a1+664)-33
    10     func_3:
    11         *(a1+665) = *(a1+665)-(*(a1+664)-33)
    12         if(*(a1+288)<*(a1+292) && *(a1+665) == 0):
    13             *(a1+665)++;
    14     func_4:
    15         if(*(a1+288)<*(a1+292)):
    16             *(a1+288)++;
    17     check_func:
    18         *(a1+664) == 's'
    19         s = char_table_0[*(a1+288) +i] len=20
    20         if(check(s))->sucess
    21     func_6:
    22         if(*(a1+288)>0):
    23             *(a1+288)--;
    24     func_7:
    25         if(*(a1+288)<*(a1+292)&&*(a1+664)<=0x59):
    26             char_table_0[*(a1+288)] = input[*(a1+288)+*(a1+664)-48]-49
    27     func_8:
    28         for(i=0;*(a1+664)>i;++i)
    29             *(a1+288)++;
    30         if(*(a1+664)<=0x69)
    31             char_table_0[*(a1+288)] = input[*(a1+288)+*(a1+664)-48]-49

      其中用到的变量一共有4个

    *(a1+664) = [next]
    *(a1+292) = 255
    *(a1+288) = index 0
    *(a1+665) = m(临时变量) 0

      再优化一下func:

     1 index = 0,range = 255,m = 0 ,[next]
     2 func_0:
     3     if(index<255):
     4         m = char_table_0[index]
     5 func_1:
     6     if(index<255&& m):
     7         char_table_0[index]  = m
     8 func_2:
     9     if(index<255):
    10         m = m+[next]-33
    11 func_3:
    12     m = m-([next]-33)
    13     if(index<255 && m == 0):
    14         m++;
    15 func_4:
    16     if(index<255):
    17         index++;
    18 func_6:
    19     if(index>0):
    20         index--;
    21 func_7:
    22     if(index<255&&[next]<=0x59):
    23         char_table_0[index] = input[index+[next]-48]-49

      在check_func中会输出s,s是从char_table_0中以index为起点取的0x20个值。如果s满足三个方程则通过校验,返回成功

      而实际上那三个方程是不需要逆的—题目中明示了只要输出“Binggo”即可得到flag。仔细读题很重要!

      因此目标显然是在char_table_0中获得Binggo的字符串,将其dump出来输出了一下发现并字符顺序并没有合适的,甚至上述5个字母都不齐以及一个最关键的问题,check_func中取了0x20个值赋给s,这显然不符合”Binggo”的要求,因此第七个字符必须给上’’使其截断才行。

      分析其余8个函数,发现0和1可以交换char_table_0中的字符的位置,2、3和7、8则可以修改char_table_0中字符的值,4和6则是用来移动下标的,最后check_func加’s’来结束并输出。

      在构造输入之前,先要找到函数对应的输入值,IDA动态调试断在函数调用处调用idc脚本即可得到对应值:IDC脚本很重要!

     1 #include<idc.idc>
     2 static main()
     3 {
     4     auto i, j, v14, p, q;
     5     for(i=0;i<=8;i++)
     6     {
     7         p = Byte(0xc1e440+288+8+4*i); 
     8 
     9         v14 = Dword(0xc1e440+672+8+8*p);
    10         
    11         for(j=0;j<255;j++)
    12         {
    13             if(Byte(0x603900+j)==Byte(0xc1e440+408+p))
    14             {
    15                 q = j;
    16                 break;
    17             }
    18             //Message("Not Found : %x", Byte(0x603700+p));
    19         }
    20         Message("%x	%c	%x
    ",q , q, v14);
    21     }
    22     Message("finish
    ");
    23 }
    24 
    25 //得到输出
    26     24  $   400dc1  
    27     38  8   400e7a  
    28     43  C   400f3a  
    29     74  t   401064 
    30     30  0   4011c9  
    31     45  E   40133d  
    32     75  u   4012f3  
    33     23  #   4014b9  
    34     3b  ;   400cf1

      得到这9个输入字符即可开始构造了 ,由于函数功能很多样,因此构造方法很多,在此仅表述我的构造方法:思路也参考夜影大大

      思路:由于输入buffer有限,因此不适合向右移动指针太多来找寻合适的字符。所以我就原地变换—毕竟将一个字符变成另一个字符满打满算也只要4个输入,移动指针可就轻而易举几十上百了。

      

      |func|
    $ 0 m = 0x50 t 3 m = m - ([next]-33) = 0x50 - (0x2f-33) = 0x42 --->'B' / 8 1 char_table_0[0] = 0x42('B') 0 4 index++; $ 0 m = 0x61 C 2 m = m + [next]-33 = 0x61 + 0x29 -33 = 0x69 --->'i' ) 8 1 char_table_0[1] = 0x69('i') 0 4 index++; $ 0 m = 0x46 C 2 m = m + [next]-33 = 0x46 + 0x49 -33 = 0x6e --->'n' I 8 1 char_table_0[2] = 0x6e('n') 0 4 index++; $ 0 m = 0x30 C 2 m = m + [next]-33 = 0x30 + 0x58 -33 = 0x67 --->'g' X 8 1 char_table_0[3] = 0x67('g') 0 4 index++; $ 0 m = 0x21 C 2 m = m + [next]-33 = 0x21 + 0x67 -33 = 0x67 --->'g' g 8 1 char_table_0[4] = 0x67('g') 0 4 index++; $ 0 m = 0x26 C 2 m = m + [next]-33 = 0x26 + 0x6a -33 = 0x6f --->'o' j 8 1 char_table_0[5] = 0x6f('o') 0 4 index++; //index=6 1 # 7 char_table_0[6] = input[6+72-48]-49 = input[30]-49 = 49-49 =0 --->'' H u u u u u u index归0 Es 触发check_func

    最终str:$t/80$C)80$CI80$CX80$Cg80$Cj801#HuuuuuuEs

      在linux上运行测试:

      

      提交给服务器即可获得flag。

      总结:

        好的分析习惯:分析结构体,因数替换。

        仔细读题很重要!

        IDC脚本很重要!

       明天预计:

        DDCTF re3,

        了解了解android安全怎么搞?菜哭

  • 相关阅读:
    [转]我在Facebook学到的10个经验
    [转]MPlayer快捷键&参数设置>系统开销最少的影音播放器
    [转]国外程序员推荐:每个程序员都应读的书
    Linux运维:CentOS6和7的区别
    将数组中指定的前N位移动到数组的最后面
    DataReader和DataSet区别
    求数组中和最大的子数组与始末下标
    使用XPathExpression类对XML文件进行排序
    配置WebSite的IIS时遇到的问题与解决方法
    已知一个整数N,求另外一个整数M,使得M本身 + M各个位上的数 = N
  • 原文地址:https://www.cnblogs.com/nww-570/p/8954214.html
Copyright © 2020-2023  润新知