介绍

本题是xctf攻防世界中Reverse的进阶区的题Newbie_calculations

题目来源: Hack-you-2014

This challenge is awesome! Soooo cool! 😃
用溢出来实现加减乘操作

不识庐山真面目–分析

运行程序,打印提示 Your flag is: 。但是输入不了。

1
2
3
4
~/Downloads 
$ file 3f1303e5fac1477b8156ae54c76c3b7b.exe
3f1303e5fac1477b8156ae54c76c3b7b.exe: PE32 executable (console) Intel 80386, for
MS Windows

看标题就知道这题没做出来了 😦

但是最后想通了以后觉得这题太帅了!

初步分析

用IDA进行静态分析。在main函数中,首先将v120[32]这个数组都初始化为1,然后调用puts函数打印了Your flag is: 。然后是调用了一堆的函数。可以观察到这些函数对v120[32]的每个元素进行了处理。下标为偶数时,调用sub_401100函数进行处理;下标为奇数时,调用sub_401000函数进行处理

在main函数的末尾,可以看到flag的开头为CTF{ ,末尾是}\n 。二者之间,通过一个for循环,得到flag的主要内容。flag大括号中的内容是v120[32]这个数组里的内容。

用ollydbg动态调试,发现打印出字符串Your flag is: 以后,在调用0037110函数(根据IDA中的汇编代码进行判断)的时候一直在运行中,猜测可能遇到了死循环。

现在来看看0037110函数,看看为什么会一直在运行中。

在第一个循环while(a2)中,a2初始值是从main传进来的1000000000: v3 = sub_401100(v120, 1000000000); ,并且a2在循环体中一直在自减。当a2减到0的时候,循环就会推出了,所以这个循环不会是造成程序一直在循环中运行出不来的原因。

在第二个循环while(v4)中,v4的初始值为-1,并且在循环体里自减。v4的值永远不可能减到0,因此这里就是造成程序一直没响应的原因,因为它一直在这个循环里出不来了。哈哈罪魁祸首找到了!

前面提到,最后的flag其实就是数组v120的内容,在sub_401100函数中,a1,也就是数组v120的偶数位置的元素,最后是由v8这个进行赋值的。并且v8是在while(a2)这个循环中通过sub_401000函数(也是处理v120奇数位置元素的函数)进行处理的,在while(v4)中没有对v8进行修改。也就是说,while(v4)这个循环,完全不影响v120数组中偶数位置元素的值,我们可以在ollydbg动态调试的时候把它nop掉。

现在来看一下sub_401000函数。看到这个函数整个人都不好了。这个函数里两个循环,全是死循环。其中如果不让第一个循环陷入死循环,则a2必须≤0才行。这也太坑了… 做不下去了:(

寻求wp的帮助

没办法,求助网上的writeup吧。下面为writeup的内容:

直接运行,发现提示为Your flag:flag直接输出,但一直没有输出,猜测为有大量耗时操作。拖进IDA进行分析,发现程序的主流程全部在一个函数中。其中调用了许多相同的函数,经过分析为简单的加,减,乘,附加上大量循环的耗时操作,将函数改写为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221

// all flag chars are set to 1for ( i = 0; i < 32; ++i )
flag_chars[i] = 1;

flag_chars[32] = 0; // null terminator

print_text("Your flag is:");

var0 = mul_ab(flag_chars, 1000000000);
var1 = sub_ab(var0, 999999950);
mul_ab(var1, 2);
// 100 = d

var2 = add_ab(&flag_chars[1], 5000000);
var3 = sub_ab(var2, 6666666);
var4 = add_ab(var3, 1666666);
var5 = add_ab(var4, 45);
var6 = mul_ab(var5, 2);
add_ab(var6, 5);
// 97 = a

var7 = mul_ab(&flag_chars[2], 1000000000);
var8 = sub_ab(var7, 999999950);
var9 = mul_ab(var8, 2);
add_ab(var9, 2);
// 102 = f

var10 = add_ab(&flag_chars[3], 55);
var11 = sub_ab(var10, 3);
var12 = add_ab(var11, 4);
sub_ab(var12, 1);
// 56 = 8

var13 = mul_ab(&flag_chars[4], 100000000);
var14 = sub_ab(var13, 99999950);
var15 = mul_ab(var14, 2);
add_ab(var15, 2);
// 102 = f

var16 = sub_ab(&flag_chars[5], 1);
var17 = mul_ab(var16, 1000000000);
var18 = add_ab(var17, 55);
sub_ab(var18, 3);
// 52 = 4

var19 = mul_ab(&flag_chars[6], 1000000);
var20 = sub_ab(var19, 999975);
mul_ab(var20, 4);
// 100 = d

var21 = add_ab(&flag_chars[7], 55);
var22 = sub_ab(var21, 33);
var23 = add_ab(var22, 44);
sub_ab(var23, 11);
// 56 = 8

var24 = mul_ab(&flag_chars[8], 10);
var25 = sub_ab(var24, 5);
var26 = mul_ab(var25, 8);
add_ab(var26, 9);
// 49 = 1

var27 = add_ab(&flag_chars[9], 0);
var28 = sub_ab(var27, 0);
var29 = add_ab(var28, 11);
var30 = sub_ab(var29, 11);
add_ab(var30, 53);
// 54 = 6

var31 = add_ab(&flag_chars[10], 49);
var32 = sub_ab(var31, 2);
var33 = add_ab(var32, 4);
sub_ab(var33, 2);
// 50 = 2

var34 = mul_ab(&flag_chars[11], 1000000);
var35 = sub_ab(var34, 999999);
var36 = mul_ab(var35, 4);
add_ab(var36, 50);
// 54 = 6

var37 = add_ab(&flag_chars[12], 1);
var38 = add_ab(var37, 1);
var39 = add_ab(var38, 1);
var40 = add_ab(var39, 1);
var41 = add_ab(var40, 1);
var42 = add_ab(var41, 1);
var43 = add_ab(var42, 10);
add_ab(var43, 32);
// 49 = 1

var44 = mul_ab(&flag_chars[13], 10);
var45 = sub_ab(var44, 5);
var46 = mul_ab(var45, 8);
var47 = add_ab(var46, 9);
add_ab(var47, 48);
// 97 = a

var48 = sub_ab(&flag_chars[14], 1);
var49 = mul_ab(var48, 4000000000);
var50 = add_ab(var49, 55);
sub_ab(var50, 3);
// 52 = 4

var51 = add_ab(&flag_chars[15], 1);
var52 = add_ab(var51, 2);
var53 = add_ab(var52, 3);
var54 = add_ab(var53, 4);
var55 = add_ab(var54, 5);
var56 = add_ab(var55, 6);
var57 = add_ab(var56, 7);
add_ab(var57, 20);
// 49 = 1

var58 = mul_ab(&flag_chars[16], 10);
var59 = sub_ab(var58, 5);
var60 = mul_ab(var59, 8);
var61 = add_ab(var60, 9);
add_ab(var61, 48);
// 97 = a

var62 = add_ab(&flag_chars[17], 7);
var63 = add_ab(var62, 6);
var64 = add_ab(var63, 5);
var65 = add_ab(var64, 4);
var66 = add_ab(var65, 3);
var67 = add_ab(var66, 2);
var68 = add_ab(var67, 1);
add_ab(var68, 20);
// 49 = 1

var69 = add_ab(&flag_chars[18], 7);
var70 = add_ab(var69, 2);
var71 = add_ab(var70, 4);
var72 = add_ab(var71, 3);
var73 = add_ab(var72, 6);
var74 = add_ab(var73, 5);
var75 = add_ab(var74, 1);
add_ab(var75, 20);
// 49 = 1

var76 = mul_ab(&flag_chars[19], 1000000);
var77 = sub_ab(var76, 999999);
var78 = mul_ab(var77, 4);
var79 = add_ab(var78, 50);
sub_ab(var79, 1);
// 53 = 5

var80 = sub_ab(&flag_chars[20], 1);
var81 = mul_ab(var80, -294967296);
var82 = add_ab(var81, 49);
sub_ab(var82, 1);
// 48 = 0

var83 = sub_ab(&flag_chars[21], 1);
var84 = mul_ab(var83, 1000000000);
var85 = add_ab(var84, 54);
var86 = sub_ab(var85, 1);
var87 = add_ab(var86, 1000000000);
sub_ab(var87, 1000000000);
// 53 = 5

var88 = add_ab(&flag_chars[22], 49);
var89 = sub_ab(var88, 1);
var90 = add_ab(var89, 2);
sub_ab(var90, 1);
// 50 = 2

var91 = mul_ab(&flag_chars[23], 10);
var92 = sub_ab(var91, 5);
var93 = mul_ab(var92, 8);
var94 = add_ab(var93, 9);
add_ab(var94, 48);
// 97 = a

var95 = add_ab(&flag_chars[24], 1);
var96 = add_ab(var95, 3);
var97 = add_ab(var96, 3);
var98 = add_ab(var97, 3);
var99 = add_ab(var98, 6);
var100 = add_ab(var99, 6);
var101 = add_ab(var100, 6);
add_ab(var101, 20);
// 49 = 1

var102 = add_ab(&flag_chars[25], 55);
var103 = sub_ab(var102, 33);
var104 = add_ab(var103, 44);
var105 = sub_ab(var104, 11);
add_ab(var105, 42);
// 98 = b

add_ab(&flag_chars[26], flag_chars[25]); // flag_chars[25] = 98// 99 = c

add_ab(&flag_chars[27], flag_chars[12]); // flag_chars[12] = 49// 50 = 2

var106 = flag_chars[27]; // 50
var107 = sub_ab(&flag_chars[28], 1);
var108 = add_ab(var107, var106);
sub_ab(var108, 1);
// 49 = 1

var109 = flag_chars[23]; // 97
var110 = sub_ab(&flag_chars[29], 1);
var111 = mul_ab(var110, 1000000);
add_ab(var111, var109);
// 97 = a

var112 = flag_chars[27]; // 50
var113 = add_ab(&flag_chars[30], 1);
mul_ab(var113, var112);
// 100 = d

add_ab(&flag_chars[31], flag_chars[30]); // flag_chars[30] = 100// 101 = e

print_func("CTF{");

for ( j = 0; j < 32; ++j )
print_func("%c"); // print flag

print_func("}\n");

flag为:CTF{daf8f4d816261a41a115052a1bc21ade}

只缘身在此山中–整数溢出

现在来看看这道题是怎么做到通过溢出来实现这些操作的。

sub_401100函数(乘法)

首先还是sub_401100这个函数。这个函数还是照着我们之前分析的,最后a1只会受到v8的影响。而v8会受到sub_401000函数的影响,并sub_401000函数被调用的次数为a2。

从3.2的分析来看,sub_401000函数是一个加法函数,因此,sub_401000函数中调用这个加法函数a2次,相当于让v8作为保存结果的临时变量,每次都让v8 = v8 + a1,最终有a2个a1相加,即v8=a2×a1。

sub_401000函数(加法)

那我们还是要先看一下sub_401000函数。

  1. 首先,对于循环的条件,两个循环只有分别当v4和v5为0的时候,才会退出。
    • v4的类型为int,我们知道int是4个字节,1个字节是8位,所以总的数量是232=42949672962^{32}=4294967296 ,范围是-2147483648 ~ +2147483647。v4的初始值为-a2,并且从 sub_401100函数的调用以及main函数中的调用来看,这个a2都是整数,也就是v4是一个负数。
    • v5的类型为signed int,有符号的int,范围和int一样,也是-2147483648 ~ +2147483647 。v5的初始值为-1。
  2. 那么,对第一个循环来说,当v4从-a2一直往下减,当减到-2147483648时,会发生什么?
    -2147483648在计算机中,用补码的方式进行存储。当v4为-2147483648,并继续往下减的时候,就会发生溢出。变成2147483647,然后v4会继续往下减,直到为0。
1
2
3
4
5
6
7
8
#include <stdio.h>
int main(void)
{
    int x = -2147483648;
    x = x - 1;
    printf("%d\n", x); // 2147483647
    return 0;
}

在这个循环过程中,a1自减的次数是v4从a2214748364821474836470-a2 \rightarrow -2147483648 \rightarrow 2147483647 \rightarrow 0需要注意的是,从-2147483648到2147483647的这个溢出,也是一次while循环的过程。

同理,在while(v5)这个循环中,a1自增的次数是v5从1214748364821474836470-1 \rightarrow -2147483648 \rightarrow 2147483647 \rightarrow 0

在这两个循环中,a1自增的次数比a1自减的次数多了a2-1次。也就是a1自增了a2-1。并且,在return之前,a1又自增了一次,也就是说,a1在这个函数中自增了a2次,即 a1 = a1 + a2 。因此,这个函数的作用可以简单理解为将两个参数相加,并将结果保存到a1中。

太神奇了!用溢出来实现加法!所谓的加法,比如a+5,其实就是a + 5×1

sub_401220函数(减法)

接下来回到main函数,再看一下sub_401220函数。

函数中有两个循环,v3的类型为int,范围是-2147483648 ~ +2147483647,初始值为-a2。

v4的类型为signed int,范围和int一样,初始值为-1。

在while(v3)这个循环中,a1自增的次数是a2214748364821474836470-a2 \rightarrow -2147483648 \rightarrow 2147483647 \rightarrow 0需要注意的是,从-2147483648到2147483647的这个溢出,也是一次while循环的过程。

在while(v4)这个循环中,a1自增的次数是1214748364821474836470-1 \rightarrow -2147483648 \rightarrow 2147483647 \rightarrow 0

同时注意到在return之前,a1又自增了一次。则a1在这个函数中,自增的情况为:

a1 = a1 + ( -a2 - (-2147483648) ) + 1 + (2147483647-0) + ( -1 - (-2147483648)) + 1 + (2147483647-0) + 1

= a1 - a2 + 2147483648 + 2147483647 + 2147483648 + 2147483647 + 2

= a1 - a2

1
2
3
4
5
6
7
8
#include <stdio.h>
int main(void)
{
    int x = 21;
    x = x - 5 + 2147483648 +  2147483647  + 2147483648 +  2147483647  +2 ;
    printf("%d\n", x);  // 16
    return 0;
}

所以,sub_401220函数是一个减法函数,其作用可以简化为:a1 = a1 - a2

总结

做Rev需要要对整数溢出有一个清楚的了解。以及,pwn的一些知识需要学一下,包括之前做Rev时遇到的数组溢出覆盖的问题以及这道题,都涉及到漏洞利用的知识了。