首页
社区
课程
招聘
[原创] KCTF 2023 第五题 wp - 98k
2023-9-11 23:52 7688

[原创] KCTF 2023 第五题 wp - 98k

2023-9-11 23:52
7688

程序主逻辑没多少,但是加入了大量无关代码,单个函数体积膨胀非常大,这对正常逻辑分析造成了一定阻碍,没办法直接阅读 ida 生成的代码。不过加入的这些逻辑与主要逻辑无关,只要找 F5 中的关键变量(从函数参数或函数返回值入手)的交叉引用,将所有相关的语句手动组合起来即可得到判断逻辑。

大致逻辑恢复如下(后面的注释是该语句在二进制程序中对应的地址):

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
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
int has_debugger() { // 0x402220
    // patch
    return 0;
    // ...
}
 
int check1(char* buf, size_t size) { // 0x455F80
    unsigned int crc = CRC32(buf, size - 4); // 0x457690
    return crc == *(unsigned int*) (buf + size - 4); // 0x457C13
}
 
int check2(void* buf, size_t size, unsigned short** buf1, unsigned short** buf2) {
    if (size >= 0x10) { // 0x45942B
        *buf1 = buf; // 0x45AF4E
        if ((*buf1)[2] && (*buf1)[2] <= size - 16) { // 0x45B836
            *buf2 = (unsigned short *)((unsigned char*) buf + (*buf1)[2] + 8); // 0x45CA22
            if ((*buf2)[2] && (*buf2)[2] <= size - 16 - (*buf1)[2]) { // 0x45D3A3
                return 1;
            } else {
                *buf2 = NULL;
            }
        } else {
            *buf1 = NULL;
        }
    }
    return 0;
}
 
int check_inverse(unsigned long long x, unsigned long long y, unsigned long long z) {
    return x * y % z == 1;
}
 
int check3(unsigned int x, unsigned int y) { // 0x474170
    unsigned long long p = 0x346F8717, a = 32069; // 0x47546A
    if (check_inverse(a, x, p)) { // 0x477A1C
        p = 0x729969FF;
        a = 55057; // 0x478965
        if (check_inverse(a, y, p)) { // 0x47A972
            return 1;
        }
    }
    return 0;
}
 
unsigned int* alloc_map(size_t n) { // 0x4D2250
    return (unsigned int*) malloc(4 * n * n); // 0x4D34CD
}
 
void set_value(unsigned int* map, size_t n, unsigned int y, unsigned int x, unsigned int value) { // 0x4D45A0
    map[n * y + x] = value;
}
 
unsigned int get_value(unsigned int* map, size_t n, unsigned int y, unsigned int x) { // 0x4D6450
    return map[n * y + x];
}
 
void create_map(size_t n, unsigned int** map) { // 0x4D76B0
    if (n % 4) return;
    unsigned int* _map = alloc_map(n); // 0x4D82D0
    int value = 1; // 0x4D8B3F
    for (int y = 0; y < n; y++) {
        for (int x = 0; x < n; x++) {
            set_value(_map, n, y, x, value); // 0x4D956F
            value++; // 0x4D986A
        }
    }
    size_t y1 = 0; // 0x4DA51B
    size_t y2 = n - 1; // 0x4DA9DB
    while (y1 < y2) { // 0x4DB041
        for (int x = 0; x < n; x++) { // 0x4DB3E7
            if (x % 4 == 0 || x % 4 == 3) { // 0x4DB913
                unsigned int a = get_value(_map, n, y1, x); // 0x4DC2A8
                unsigned int b = get_value(_map, n, y2, x); // 0x4DCD5B
                set_value(_map, n, y1, x, b); // 0x4DCD7D
                set_value(_map, n, y2, x, a); // 0x4DD05C
            }
        }
        y1++; // 0x4DDB3A
        y2--; // 0x4DE105
    }
    *map = _map; // 0x4DF1E8
}
 
void shuffle_map(unsigned int* map, size_t n, unsigned int seed) { // 0x4DF410
    srand(seed); // 0x4DF75D
    for (int y1 = 0; y1 < n; y1++) { // 0x4DFF3B
        for (int x1 = 0; x1 < n; x1++) { // 0x4E017C
            unsigned int v1 = get_value(map, n, y1, x1); // 0x4E04A6
            unsigned int y2 = rand() % n; // 0x4E09C4
            unsigned int x2 = rand() % n; // 0x4E0CA2
            unsigned int v2 = get_value(map, n, y2, x2); // 0x4E1197
            set_value(map, n, y1, x1, v2);
            set_value(map, n, y2, x2, v1); // 0x4E12EC
        }
    }
}
 
void trans(unsigned int seed, char* buf, size_t size) { // 0x4E1620
    unsigned int* map;
    size_t n = 16; // 0x4E1B9B
    create_map(n, &map); // 0x4E21C3
    shuffle_map(map, n, seed); // 0x4E27BD
    int x = 0; // 0x4E4442
    int y = 0; // 0x4E4452
    int i = 0; // 0x4E445E
    while (i < size) { // 0x4E4823
        buf[i] ^= get_value(map, n, y, x); // 0x4E492B
        if (i % n == 0) { // 0x4E4BDE
            y++; // 0x4E4F92
            x = 0; // 0x4E5402
        }
        x++; // 0x4E641B
        i++; // 0x4E68C7
    }
}
 
char* alloc_buf() { // 0x423810
    return (char*) malloc(200); //
}
 
unsigned long long get_values1(char** buf, size_t* size) {
    *size = 74;
    char* _buf = (char*) malloc(74);
    char __buf[] = {
        // ...
    };
    memcpy(_buf, __buf, 74);
    *buf = _buf; // 004A79EA;
    return 0xAAC82A5B;
}
 
void buf_set_values(char* buf, unsigned long long value) { // 0x426150
    memset(buf, 0, 200); // ???
    *(unsigned long long*) buf = value;
}
 
int buf_len(char* buf) { // 0x421970
    int i;
    for (i = 199; i >= 0; i--) {
        if (buf[i]) break;
    }
    return i + 1;
}
 
int buf_empty(char* x) {
    for (int i = 0; i < 200; i++) {
        if (x[i]) {
            return 0;
        }
    }
    return 1;
}
 
void buf_add(char* x, char* y, char* z) {
    for (int i = 0; i < 200; i++) {
        z[i] = x[i] + y[i];
    }
}
 
void buf_mul(char *x, char *y, char *z) { // 0x43C130
    memset(z, 0, 200);
    if (buf_empty(x) || buf_empty(y)) { // 0x43DFC5
        return ;
    }
    int x_len = buf_len(x); // 0x43E86F
    int y_len = buf_len(y); // 0x43EDAD
    char* tmp = alloc_buf(); // 0x43F271
    for (int x_index = 0; x_index < x_len; x_index++) { // 0x43F658
        int y_index = 0; // 0x43F8A4
        int value = 0; // 0x43FD65
        memset(tmp, 0, 200);
        while (y_index < y_len) { // 0x440589
            int _value = value + (unsigned char) y[y_index] * (unsigned char) x[x_index]; // 0x440ACE
            tmp[x_index + y_index] = _value; // 0x441393
            value = _value & 0xff00; // 0x4419D8
            value >>= 8; // 0x442168
            ++y_index; // 0x44057D
        }
        if (x_index + y_index < 200) { // 0x442E85
            tmp[x_index + y_index] = value; // 0x44324F
        }
        buf_add(tmp, z, z); // 0x443B53
    }
}
 
void buf_cpy(char* x, char* y) { // 0x429980
    memcpy(x, y, 200);
}
 
void buf_divmod(char* x, char* y, char* z, char* w) { // 0x444F30
    /*
    memset(w, 0, 200);
    if (buf_empty(y)) { // 0x447D10
        return;
    }
    memset(w); // 0x447116
    buf_cpy(w, x); // 0x449797
    v582 = buf_len(w) - buf_len(y); // 0x449A5A
    /**/
}
 
void buf_mod(char* x, char* y, char* z) { // 0x44EA60
    // ...
    buf_divmod(x, y, tmp, z);
}
 
int check_buf(char* x, char* y, char* z) {
    if (buf_len(x) + buf_len(y) > 200) { // 0x4C6123
        return 0;
    }
    char* tmp1 = alloc_buf(); // 0x4C6FF1
    char* tmp2 = alloc_buf(); // 0x4C783E
    buf_mul(x, y, tmp1); // 0x4C9B3F
    buf_mod(tmp1, z, tmp2); // 0x4CA6CB
    // return tmp2 == 1;
    return 1;
}
 
 
int check4(char* buf1, size_t len1, char* buf2, size_t len2) {
    char* dst = alloc_buf(); // 0x461262
    char* tmp = alloc_buf(); // 0x461ACB
    char* out = alloc_buf(); // 0x462284
    char* dst1, dst2;
    size_t size1, size2;
    unsigned long long value1 = get_values1(&dst1, &size1)// 0x462B2D
    buf_set_values(dst, value1); // 0x4632D6
    memcpy(tmp, buf1, len1); // 0x463BD4
    memcpy(out, dst1, size1); // 0x46473E
    if (check_buf(dst, tmp, out)) { // 0x468066
        // 0x46D156
        // get_value2 -> 0x8395475F, buf2, dst2 // 0x4C2285
        // check_buf(dst, tmp, out); // 0x470F73
        return 1;
    }
    return 0;
}
 
int check(char* input) { // sub_47B430
    char* output;
    size_t length;
    if (b64decode(input, &output, &length)) { // 0x47D609
        if (has_debugger() == 1) { // 0x47E402
            if ( (*output & 1) != 0 )
                *output += rand() % 3 + 1;
            else
                *output += rand() % 4 + 4;
        }
        /*
            char a[16] = {
                33, -124, 16, 66, 8, 33, -124, 16,
                66, -56, 81, -121, -61, 0x80, -44, 61
            }
            for (int i = 0; i < (length >> 1); i++) { // 0x47E8A7
                output[i] ^= a[i % 16];
            }
            for (int i = 0; i < length; i++) { // 0x47F146
                output[i] ^= 0x63;
            }
            for (int i = length - 1; i < >= 0; i--) { // 0x47F146
                output[i] ^= 0xa3;
            }
            for (int i = 0; i < length; i++) {
                output[i] ^= 0xa3;
            }
            for (int i = 0; i < length; i++) {
                output[i] ^= 0x63;
            }
            for (int i = 0; i < (length >> 1); i++) { // 0x47FD46
                output[i] ^= a[i % 16];
            }
        /**/
 
        unsigned short* buf1;
        unsigned short* buf2;
        if (check1(output, output_length)) { // 0x480414
            if (check2(output, output_length - 4, &buf1, &buf2)) { // 0x482D0E
                if (check3(*(unsigned int*) buf1, *(unsigned int*) buf2)) { // 0x48564B
                    trans(*(unsigned int*) buf1, (char*)((unsigned char*) buf1 + 8), buf1[2]); // 0x48792E
                    trans(*(unsigned int*) buf2, (char*)((unsigned char*) buf2 + 8), buf2[2]); // 0x488229
                    if (check4((char*)((unsigned char*) buf1 + 8), buf1[2], (char*)((unsigned char*) buf2 + 8), buf2[2])) { // 0x489AFF
                        return 1;
                    }
                }
            }
        }
    }
    return 0;
}

提取出来后发现有一些跟主变量相关的逻辑也是无效逻辑(如重复异或一个值两次),直接忽略这些即可。还有一个函数用于反调试(检测调试器是否存在),直接 patch 为 return 0

程序主要逻辑:

  1. 将输入 base64 解码;

  2. 检查解码后数据的后 4 个字节对应的数值是前面的数据的 CRC32 校验;

  3. 解析前面的数据,得到两块数据,其具体结构体形式如下:

1
2
3
4
5
6
struct A {
    int seed; // 0~4
    short length; // 4~6
    // char padding[2]; // 6~8;
    char data[length]; // 8~8+length
};
  1. 验证两个 seed 的值,判断的形式是 unsigned __int64 运算中的 seed * y % z == 1

  2. 将两块数据中的 data 以 seed 作为随机数种子解密;

  3. 验证两段解密后的 data 的值,判断的形式是 char[200] 表示的大数的运算 data * Y % Z == 1

4 和 6 中的运算的两组 (y, z) 和 (Y, Z) 都是直接给出来的,就只是个有限域求逆的问题。反向求解输入就行。

先把两组 seed 、解密后的 data 值求出来,再将两者组合得到加密的 data ,再重构结构体、组合得到数据并求 CRC32 附在其后,最后 base64 。

但是此题由于几个地方没完全限制正确导致出现多解。导致多解的原因:

  1. 结构体中数据的对其导致多出来 2 字节的填充,这两组 2 字节可以为任意值;可行的解决方案:结构体定义中定义按 1 字节对齐,或者将 length 设置为 int 类型;

  2. 大数的结构为 char[200] ,但是实际计算中的 Z 只有 74 字节,也就是解密后的 data 有效字节数为 74 ,那么可以在解密的 data 后添加 0~126 字节的 0 (即高位补 0 ,增加长度但是不改变数值。 126 是因为数据结构 char[200] 是堆上分配的,过长将覆盖后面的数据导致程序崩溃。实际测试最长可增加 129 字节的 0 );可行的解决方案:增加对 length 的判断,并且还需要增加一条限制 data < Z 避免出现 data + kZ 的情况;

  3. 解析上述第 3 步中的两个数据结构时判断两块数据的长度不大于而不是等于输入的长度,所以这部分输入后面可以有任意长的填充;可行的解决方案:改成等于;

  4. base64 解析时遇到 '=' 直接停止,后面的数据可以为任意值;可行的解决方案:限制输入长度,对第一个 '=' 后面的数据进行有效性判断或者预期输入中直接去掉填充的 '=' 。

题目整体的判断逻辑设计得挺好的,就是逆向的时候看到限制条件太松一定会产生非常多解的,之前的两道逆向题弄出心理阴影了,我都担心最后会出现一个哈希判断。虽然现在还鞭尸不太好,不过还是想多说一下,逆向题里用哈希限制输入的唯一性完全没必要,题目设计时限制条件处理好就不会产生多解。当然也可能会在自己意想不到的地方出现多解,不过公布题解后出题人也能学到一些新的东西,以后的题目中把限制条件做得更精确,大家也可以开心做题。而明明知道限制条件不够强会导致多解的情况下,硬给题目加一个哈希判断就是在耍流氓,加的这个条件达到了出题人限制输入唯一的目的,但是这条件对做题方没有任何帮助,这就是把本来应该由出题人干的活强行塞给做题方来干。当然也不排除某些情况下题目限制条件虽然会导致多解但是没办法再加限制条件了,或者说再加任何条件都会导致题目难度大量下降,如果这时的解不多那就可以加上哈希的条件。当然,我的建议是题目中给出目标哈希值,但是只对其至多两个字节(即 4 个十六进制数值,碰撞概率 1/65536 )做判断。这样能大概率达到限制解唯一的情况,并且对做题方也很友好。

最后给出逆向脚本以及两组多解:

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
#!/usr/bin/env python3
 
from Crypto.Util.number import inverse, long_to_bytes
import base64
import zlib
 
# check4
p1 = int.from_bytes(bytes.fromhex('03 8D B6 BB BD 8A DB 73 AC 57 2C 60 51 99 E6 EC AE 56 98 E1 D4 56 BA 9F 90 2B F6 50 9F 1D 23 91 8E 28 AF B9 A4 C5 06 C7 7D AA 28 8E BE 40 12 33 7E 76 17 96 CE 3B 48 2E 83 2E 2C 79 A4 E3 41 0D A3 D1 8E 58 C0 AB D6 B8 C1 D3'), 'little')
x1 = 0xAAC82A5B
p2 = int.from_bytes(bytes.fromhex('05 B3 44 98 B0 EF 11 F7 EB 3B 97 8C A2 FB 6D 6A 59 17 C6 A1 98 8B 9C 21 D4 89 06 EC CF 8D E7 74 30 83 37 88 B4 6C 7A 2E 3D 91 50 5E 25 D1 8F EF 12 78 5B AF 3D BA B8 EC 5F 3D FC 7B 37 5B 2D 72 5C EC 12 2C 59 57 AF 41 B4 B5'), 'little')
x2 = 0x8395475F
 
y1 = inverse(x1, p1)
y2 = inverse(x2, p2)
 
# extra_size1 = 203 - 74
extra_size1 = 0
extra_size2 = 0
buf1 = long_to_bytes(y1)[::-1] + b'\x00' * extra_size1
buf2 = long_to_bytes(y2)[::-1] + b'\x00' * extra_size2
len1 = len(buf1)
len2 = len(buf2)
 
# check3
seed1 = inverse(32069, 0x346F8717)
seed2 = inverse(55057, 0x729969FF)
 
_seed = 1
def srand(seed):
    global _seed
    _seed = seed
 
def rand():
    global _seed
    _seed = 214013 * _seed + 2531011 & 0xffffffff
    return (_seed >> 16) & 0x7fff
 
def trans(seed, buf):
    _map = list(range(1, 257))
    for y in range(8):
        for x in range(16):
            if x % 4 == 0 or x % 4 == 3:
                _map[16 * y + x], _map[16 * (15 - y) + x] = _map[16 * (15 - y) + x], _map[16 * y + x]
    srand(seed)
    for y1 in range(16):
        for x1 in range(16):
            y2 = rand() % 16
            x2 = rand() % 16
            _map[16 * y1 + x1], _map[16 * y2 + x2] = _map[16 * y2 + x2], _map[16 * y1 + x1]
    x = 0
    y = 0
    out = []
    for i in range(len(buf)):
        out.append(buf[i] ^ _map[16 * (y + x // 16) + x % 16] & 0xff)
        if i % 16 == 0:
            y += 1
            x = 0
        x += 1
    return bytes(out)
 
buf1 = trans(seed1, buf1)
buf2 = trans(seed2, buf2)
 
# check2
rand_bytes1 = b'\x00\x00' # 2 random bytes
rand_bytes2 = b'\x00\x00' # 2 random bytes
flag = b''
flag += seed1.to_bytes(4, 'little')
flag += len1.to_bytes(2, 'little') + rand_bytes1
flag += buf1
 
flag += seed2.to_bytes(4, 'little')
flag += len2.to_bytes(2, 'little') + rand_bytes2
flag += buf2
 
# flag += b'A' * 8 # can be any bytes
 
# check1
flag += (zlib.crc32(flag) & 0xffffffff).to_bytes(4, 'little')
 
print(base64.b64encode(flag))
 
 
# solution 1 (standard): KmJTMUoAAAD1UMTRG+iFRaF+30mmANMCEqF3YPgU8FTyUvX6yePJFbEZXZ8tUvO70stWkJgshd+8oMQQITLP8l5HQPPJxxF0gCxZp/r5ja68Up2kckNKAAAAVESbyOlxOm8t5prM6tsrtV9VApTX9NiH1WHIWMEtdKohjkV2bQeZORqMYX9eP7AP1JlenlB3EAchhYJhoiP7R3NzbOYCcnXnqewe7SYM
# solution 2 (extra_size1 = 1): KmJTMUsAAAD1UMTRG+iFRaF+30mmANMCEqF3YPgU8FTyUvX6yePJFbEZXZ8tUvO70stWkJgshd+8oMQQITLP8l5HQPPJxxF0gCxZp/r5ja68UtmdpHJDSgAAAFREm8jpcTpvLeaazOrbK7VfVQKU1/TYh9VhyFjBLXSqIY5Fdm0HmTkajGF/Xj+wD9SZXp5QdxAHIYWCYaIj+0dzc2zmAnJ156nsu9Cu3g==
# solution 3 (rand_bytes1 = b'\x00\x01', rand_bytes2 = b'\x00\x02'): KmJTMUoAAAH1UMTRG+iFRaF+30mmANMCEqF3YPgU8FTyUvX6yePJFbEZXZ8tUvO70stWkJgshd+8oMQQITLP8l5HQPPJxxF0gCxZp/r5ja68Up2kckNKAAACVESbyOlxOm8t5prM6tsrtV9VApTX9NiH1WHIWMEtdKohjkV2bQeZORqMYX9eP7AP1JlenlB3EAchhYJhoiP7R3NzbOYCcnXnqewivEZe

阿里云助力开发者!2核2G 3M带宽不限流量!6.18限时价,开 发者可享99元/年,续费同价!

收藏
点赞2
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回