看雪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 ; CWnd *v2; int v3; int result; int v5[26 ]; int i; char *Str; CWnd *v8; 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 ) { if ( Str[i] > 122 || Str[i] < 97 ) { if ( Str[i] > 90 || Str[i] < 65 ) sub_4017B0(); else v5[i] = Str[i] - 29 ; } else { v5[i] = Str[i] - 87 ; } } else { v5[i] = Str[i] - 48 ; } } 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; char Str1[28 ]; int v3; int v4; 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[]是初始化向量
动态调试获得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 AESfrom base64 import b64encode, b64decodedef 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
根据输出搜索字符串,找到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; signed int v2; signed int result; int v4; int v5; int v6; char v7; int v8; int v9; int v10; int v11; int v12; 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 ) 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; if ( strlen (a1) == 24 ) { if ( sub_4013B0((int )a1) ) { a1[20 ] -= 0x58 ; a1[21 ] -= 0x46 ; a1[22 ] -= 3 ; a1[23 ] -= 0x6B ; 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; int v2; int v3; signed int result; sub_401380(a1); 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