0%

KCTF2019 部分题解(未更新完)

看雪2019Q1的题,目前复现了以下题
流浪者
初入好望角
Repwn


流浪者

第一题很简单,IDA打开搜字符串找到sub_401890,发现对输入进行了处理,把0-9a-zA-Z映射到0-64,然后处理完后将数组传入sub_4017F0

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
int __thiscall sub_401890(CWnd *this)
{
struct CString *v1; // ST08_4@1
CWnd *v2; // eax@1
int v3; // eax@1
int result; // eax@2
int v5[26]; // [sp+4Ch] [bp-74h]@7
int i; // [sp+B4h] [bp-Ch]@3
char *Str; // [sp+B8h] [bp-8h]@1
CWnd *v8; // [sp+BCh] [bp-4h]@1

v8 = this;
v1 = (CWnd *)((char *)this + 100);
v2 = CWnd::GetDlgItem(this, 1002);
CWnd::GetWindowTextA(v2, v1);
v3 = sub_401A30((char *)v8 + 100);
Str = CString::GetBuffer((CWnd *)((char *)v8 + 100), v3);
if ( strlen(Str) )
{
for ( i = 0; Str[i]; ++i )
{
if ( Str[i] > 57 || Str[i] < 48 ) // ~ 0-9
{
if ( Str[i] > 122 || Str[i] < 97 ) // ~ a-z
{
if ( Str[i] > 90 || Str[i] < 65 ) // ~ A-Z
sub_4017B0();
else
v5[i] = Str[i] - 29; // A-Z 36-61
}
else
{
v5[i] = Str[i] - 87; // a-z 10-35
}
}
else
{
v5[i] = Str[i] - 48; // 0-9
}
}
result = sub_4017F0((int)v5);
}
else
{
result = CWnd::MessageBoxA(v8, "请输入pass!", 0, 0);
}
return result;
}

再通过查表映射到另一个数组得到Str1,将Str1与"KanXueCTF2019JustForhappy"比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int __cdecl sub_4017F0(int a1)
{
int result; // eax@6
char Str1[28]; // [sp+D8h] [bp-24h]@4
int v3; // [sp+F4h] [bp-8h]@1
int v4; // [sp+F8h] [bp-4h]@1

v4 = 0;
v3 = 0;
while ( *(_DWORD *)(a1 + 4 * v4) < 62 && *(_DWORD *)(a1 + 4 * v4) >= 0 )
{
Str1[v4] = aAbcdefghiabcde[*(_DWORD *)(a1 + 4 * v4)];
++v4;
}
Str1[v4] = 0;
if ( !strcmp(Str1, "KanXueCTF2019JustForhappy") )
result = sub_401770();
else
result = sub_4017B0();
return result;
}

code:

1
2
3
4
5
6
7
8
9
10
11
12
s1='abcdefghiABCDEFGHIJKLMNjklmn0123456789opqrstuvwxyzOPQRSTUVWXYZ'
s2='KanXueCTF2019JustForhappy'
s3=''
for i in range(len(s2)):
a=s1.index(s2[i])
if a<=9 and a>=0:
s3+=chr(a+48)
elif a>=10 and a<=35:
s3+=chr(a+87)
elif a>=36 and a<=61:
s3+=chr(a+29)
print(s3)

flag:

j0rXI4bTeustBiIGHeCF70DDM


初入好望角

是C#写的,用dnSpy打开

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
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

// Token: 0x02000003 RID: 3
internal class a
{
// Token: 0x06000004 RID: 4 RVA: 0x0000209B File Offset: 0x0000029B
private static void a(string[] A_0)
{
Console.WriteLine("Please Input Serial:");
if (global::a.a(Console.ReadLine(), "Kanxue2019") == "4RTlF9Ca2+oqExJwx68FiA==")
{
Console.WriteLine("Congratulations! : )");
Console.ReadLine();
}
}

// Token: 0x06000005 RID: 5 RVA: 0x000020D4 File Offset: 0x000002D4
public static string a(string A_0, string A_1)
{
byte[] bytes = Encoding.UTF8.GetBytes("Kanxue2019CTF-Q1");
byte[] bytes2 = Encoding.UTF8.GetBytes(A_0);
byte[] bytes3 = new PasswordDeriveBytes(A_1, null).GetBytes(32);
ICryptoTransform transform = new RijndaelManaged
{
Mode = CipherMode.CBC
}.CreateEncryptor(bytes3, bytes);
MemoryStream memoryStream = new MemoryStream();
CryptoStream cryptoStream = new CryptoStream(memoryStream, transform, CryptoStreamMode.Write);
cryptoStream.Write(bytes2, 0, bytes2.Length);
cryptoStream.FlushFinalBlock();
byte[] inArray = memoryStream.ToArray();
memoryStream.Close();
cryptoStream.Close();
return Convert.ToBase64String(inArray);
}

// Token: 0x04000003 RID: 3
private const string a = "Kanxue2019CTF-Q1";

// Token: 0x04000004 RID: 4
private const int b = 256;
}

查msdn可知是AES加密,第一个Byte[]是密钥,第二个Byte[]是初始化向量

image

动态调试获得bytes3的值为

\x6D\xDE\xF7\xA4\x3C\x00\x4F\x7D\x69\x83\x04\x4B\x1E\x36\xA9\x34\x59\xF1\x8B\xC8\x37\xC4\x6E\xAF\x32\x11\x32\x73\x41\x63\xA0\xB4

code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from Crypto.Cipher import AES
from base64 import b64encode, b64decode

# 解密后,去掉补足的空格用strip() 去掉
def decrypt(text):
key = b'\x6D\xDE\xF7\xA4\x3C\x00\x4F\x7D\x69\x83\x04\x4B\x1E\x36\xA9\x34\x59\xF1\x8B\xC8\x37\xC4\x6E\xAF\x32\x11\x32\x73\x41\x63\xA0\xB4'
iv = b'Kanxue2019CTF-Q1'
mode = AES.MODE_CBC
cryptos = AES.new(key, mode, iv)
plain_text = cryptos.decrypt(b64decode(text))
return bytes.decode(plain_text).rstrip('\0')


if __name__ == '__main__':
e = b'4RTlF9Ca2+oqExJwx68FiA=='
d = decrypt(e) # 解密
print("解密:", d)

flag:

Kanxue2019Q1CTF


Repwn

根据输出搜索字符串,找到sub_4014C0,主要的部分如下

1
2
3
4
5
6
7
8
9
10
11
12
13
puts("Please Input Your Key_ Now!");
scanf("%s", &v13);
if ( sub_4012F0((int)&v13) )
{
sub_401460(&v13);
system("pause");
result = 0;
}
else
{
puts(v4);
result = 0;
}

进入sub_4012F0

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
signed int __cdecl sub_4012F0(int a1)
{
signed int v1; // ecx@1
signed int v2; // edx@1
signed int result; // eax@4
int v4; // [sp+0h] [bp-38h]@1
int v5; // [sp+4h] [bp-34h]@1
int v6; // [sp+8h] [bp-30h]@1
char v7; // [sp+Ch] [bp-2Ch]@1
int v8; // [sp+10h] [bp-28h]@1
int v9; // [sp+14h] [bp-24h]@1
int v10; // [sp+18h] [bp-20h]@1
int v11; // [sp+1Ch] [bp-1Ch]@1
int v12; // [sp+20h] [bp-18h]@1

v1 = 8;
v2 = 0;
v8 = 'ruoY';
v9 = 'pnI_';
v10 = 'I_tu';
v11 = 'rW_s';
v12 = 'gno';
v4 = '0Y1X';
v5 = 't3Nu';
v6 = 'd00G';
v7 = 0;
while ( *((_BYTE *)&v4 + v2) == *(_BYTE *)(v1 + a1) )
{
++v2;
++v1;
if ( v2 > 11 )
{
result = 1;
if ( *(_BYTE *)(a1 + 20) == 72 ) //'H'
return result;
return 0;
}
}
return 0;
}

逻辑很简单,判断传入的字符串第8个开始是不是匹配'X1Y0uN3tG00dH'

再看sub_401460,里面有个sub_4013B0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int __cdecl sub_401460(char *a1)
{
char Dest; // [sp+8h] [bp-10h]@5

if ( strlen(a1) == 24 )
{
if ( sub_4013B0((int)a1) )
{
a1[20] -= 0x58; // H - F0
a1[21] -= 0x46; // a - 1B
a1[22] -= 3; // C - 40
a1[23] -= 0x6B; // k - 00
strcpy(&Dest, a1);
}
}
else
{
printf("String Length is Wrong");
}
return 0;
}

在里可以看到sub_401460可以看到字符串长度为24,并将字符串传进sub_4013B0后返回1的话对字符串后4位修改,修改后有个溢出。

这里先推后四位(实际上的逆向我们可能先关注sub_4013B0这个函数),那么接下来就修改后4位然后经过计算跳到相应的地址,已知a1[20]为'H'(ascii为0x48),所以0x48-0x58=F0,那么地址就是xxxxxxF0

这里猜测跳转到的地址可能会涉及输出,我们搜索一下'printf',可以看到一个没有被IDA解析为函数的地方也有printf,我们将这个地方手动创建函数,地址为00401BF0,符合我们之前的计算,那么我们将剩余三个字符倒推一下得到了 a(0x1B + 0x46) C(0x40 + 0x03) k(0x00 + 0x6B),所以最后3位为'aCk'

再看sub_4013B0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
signed int __cdecl sub_4013B0(int a1)
{
int v1; // ebx@1
int v2; // ecx@1
int v3; // esi@1
signed int result; // eax@2

sub_401380(a1); //将前8个字符减48
v1 = dword_40802C + 1000 * dword_408020[0] + 100 * dword_408024 + 10 * dword_408028;
v2 = dword_408034 + 10 * dword_408030;
v3 = dword_40803C + 10 * dword_408038;
if ( 2 * (v1 + v2) != 4040 || 3 * v2 / 2 + 100 * v3 != 115 )
goto LABEL_2;
result = 1;
if ( v1 - 110 * v3 != 1900 )
{
printf("Key_Is_Wrong,Please_Input_Again!");
LABEL_2:
result = 0;
}
return result;
}

用z3解方程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from z3 import *
dword = [Int('dword%d'%i) for i in range(8)]
v = [Int('v%d'%i) for i in range(1,4)]
x = Solver()
x.add(v[0]==dword[3]+1000*dword[0]+100*dword[1]+10*dword[2])
x.add(v[1]==dword[5]+10*dword[4])
x.add(v[2]==dword[7]+10*dword[6])
x.add(2 *(v[0] + v[1]) == 4040)
x.add(3 * v[1] / 2 + 100 * v[2] == 115)
x.add(v[0] - 110 * v[2] == 1900)
#这题是有多解的,但是只有全为数字的正确,所以加了下面的限制
for i in range(8):
x.add(dword[i]>=0)
x.add(dword[i]<=9)
x.check()
x.model()

得出前8位'20101001'

将上面的组合一下即得到flag

flag:

20101001X1Y0uN3tG00dHaCk