一道逆向题引起的一系列爆破尝试

[Adagio for Sunmmer Wind]()

0x01 前言

  • 题目下载链接:

下载链接

0x02 主要函数部分

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
int __fastcall main(__int64 a1, char **a2, char **a3)
{
size_t v3; // rax
int v5; // [rsp+1Ch] [rbp-104h]
int v6; // [rsp+20h] [rbp-100h]
int i; // [rsp+24h] [rbp-FCh]
unsigned int seed; // [rsp+28h] [rbp-F8h]
unsigned int v9; // [rsp+2Ch] [rbp-F4h]
char v10; // [rsp+30h] [rbp-F0h]
char v11[16]; // [rsp+90h] [rbp-90h]
char v12[32]; // [rsp+A0h] [rbp-80h]
char s; // [rsp+C0h] [rbp-60h]
char s1[40]; // [rsp+E0h] [rbp-40h]
unsigned __int64 v15; // [rsp+108h] [rbp-18h]

v15 = __readfsqword(0x28u);
seed = 0;
puts("Welcome to Pub Zorro!!");
printf("Straight to the point. How many drinks you want?", a2);
__isoc99_scanf("%d", &v5);
if ( v5 <= 0 )
{
printf("You are too drunk!! Get Out!!");
exit(-1);
}
printf("OK. I need details of all the drinks. Give me %d drink ids:", (unsigned int)v5);
for ( i = 0; i < v5; ++i )
{
__isoc99_scanf("%d", &v6);
if ( v6 <= 16 || v6 > 0xFFFF )
{
puts("Invalid Drink Id.");
printf("Get Out!!");
exit(-1);
}
seed ^= v6;
}
i = seed;
v9 = 0;
while ( i )
{
++v9;
i &= i - 1;
}
if ( v9 != 10 )
{
puts("Looks like its a dangerous combination of drinks right there.");
puts("Get Out, you will get yourself killed");
exit(-1);
}
srand(seed);
MD5_Init(&v10);
for ( i = 0; i <= 29; ++i )
{
v9 = rand() % 1000;
sprintf(&s, "%d", v9);
v3 = strlen(&s);
MD5_Update(&v10, &s, v3);
v12[i] = v9 ^ LOBYTE(dword_6020C0[i]);
}
v12[i] = 0;
MD5_Final(v11, &v10);
for ( i = 0; i <= 15; ++i )
sprintf(&s1[2 * i], "%02x", (unsigned __int8)v11[i]);
if ( strcmp(s1, "5eba99aff105c9ff6a1a913e343fec67") )
{
puts("Try different mix, This mix is too sloppy");
exit(-1);
}
return printf("\nYou choose right mix and here is your reward: The flag is nullcon{%s}\n", v12);
}

0x03 分析

  • 通过输入饮料的瓶数饮料的ID 来影响seed的值,从而影响MD5的值以及v12数组的值。
  • MD5值与题目所给的MD5相同时,则v12数组里面的值也就是正确的flag。

0x04 第一次爆破尝试

  • 爆破思路, 爆破饮料的瓶数和饮料的ID。
  • 但是这样会比较麻烦, 当只要一瓶饮料的时候,爆破程序比较好写,当要两瓶及以上的饮料的时候,爆破程序写起来会复杂一些。
  • 比较幸运的是,这道题目正确的输入就是需要输入一瓶饮料。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# -*- coding: UTF-8 -*-
from ctypes import *
import hashlib
libsystem = cdll.LoadLibrary('libc.so.6')

for id in range(17, 0xffff + 1):
seed = 0
seed ^= id
v9 = 0
i = seed
while i > 0:
v9 += 1
i &= (i - 1)
if v9 == 10:
libsystem.srand(seed)
h = hashlib.md5()
for i in range(30):
ran = libsystem.rand()
ran = ran % 1000
h.update("%d" % ran)
if h.hexdigest() == '5eba99aff105c9ff6a1a913e343fec67':
print 'num = 1 and id = %d' % (id)
raw_input()
  • 需要注意的是 0 ^ x = x,所以 seed == id
  • 这样跑出来(需要在Linux下跑)的结果是,饮料瓶数输入1,ID输入59306
  • 运行程序,这么输入进去就能得到flag

0x05 第二次爆破尝试

  • 也可以直接爆破seed

  • 脚本如下:

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
# -*- coding: UTF-8 -*-
from ctypes import *
import hashlib
libsystem = cdll.LoadLibrary('libc.so.6')
# Extracted by hand
encryption_key = [
0x03C8, 0x0032, 0x02CE, 0x0302, 0x007F,
0x01B8, 0x037E, 0x0188, 0x0349, 0x027F,
0x005E, 0x0234, 0x0354, 0x01A3, 0x0096,
0x0340, 0x0128, 0x02FC, 0x0300, 0x028E,
0x0126, 0x001B, 0x032A, 0x02F5, 0x015F,
0x0368, 0x01EB, 0x0079, 0x011D, 0x024E
]

need_md5 = '5eba99aff105c9ff6a1a913e343fec67'

for seed in range(0xffff+1):
i = seed
v9 = 0
while(i):
v9+=1
i &= (i-1)
if v9 == 10:
libsystem.srand(seed)
h = hashlib.md5()
flag = ''
for a in range(30):
ran = libsystem.rand()%1000
h.update("%d"%ran)
flag += chr((ran^encryption_key[a])&0xff)
if h.hexdigest() == '5eba99aff105c9ff6a1a913e343fec67':
print 'nullcon{'+ flag+'}'

0x06 第三次爆破尝试

  • 这次的方法是借鉴的网上的WP, 这次利用的是python的subprocess模块

  • 但是这个写这个脚本好像已经是确定了瓶数一定为1 ,这很玄学…

  • 脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# -*- coding: UTF-8 -*-
import subprocess

L = []
for seed in range(0xffff+1):
i = seed
v9 = 0
while(i):
v9+=1
i &= (i-1)
if v9 == 10:
L.append(seed)

for i in L:
p = subprocess.Popen('./zorro_bin',stdin = subprocess.PIPE,stdout= subprocess.PIPE)
out = p.communicate('1\n%s\n' % i)[0]
if "nullcon{" in out:
print out
break

0x07 第四次爆破尝试

  • 这是第三种爆破方法的变形,利用的是pwntools,原理和三差不多,前提依然是要先知道饮料只要1瓶,脚本如下:
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
# -*- coding: UTF-8 -*-
import subprocess

L = []
for seed in range(0xffff+1):
i = seed
v9 = 0
while(i):
v9+=1
i &= (i-1)
if v9 == 10:
L.append(seed)

for i in L:
p = subprocess.Popen('./zorro_bin',stdin = subprocess.PIPE,stdout= subprocess.PIPE)
out = p.communicate('1\n%s\n' % i)[0]
if "nullcon{" in out:
print out

for i in L:
p = process('./zorro_bin')
p.recvuntil('Straight to the point. How many drinks you want?')
p.sendline(1)
p.recvuntil('OK. I need details of all the drinks. Give me 1 drink ids:')
p.sendline(i)
str = p.recv()
if 'nullcon{' in str:
print str
break

0x08 总结

  • 这道题解出来还是很玄学的,为什么瓶数一定是1,确实很令人费解。
  • 通过这道题目,也加强了自己写爆破脚本的能力。