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

Posted nww-570

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[转组第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”的要求,因此第七个字符必须给上’\\0’使其截断才行。

  分析其余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\\t%c\\t%x\\n",q , q, v14);
21     }
22     Message("finish\\n");
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 --->\\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安全怎么搞?菜哭












以上是关于[转组第3天] | 黑盒测试的主要内容,如果未能解决你的问题,请参考以下文章

[转组第5天] | 天枢分享Reverse入门

[转组第9天] | 数组和指针的寻址

[转组第10天] | Android6.0.0_r1源码编译和POC程序的编译

[转组第8天] | 变量在内存中的位置和访问方式

[转组第2天] | baby_mips和android xss的调研

黑盒测试实践(小组测试)第5天