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 --->'