【CTF】看雪CTF第一题 流浪者
下载题目,从图标初步判断是一个MFC程序,其它也没什么好看的,直接拖入虚拟机双击运行看下显示效果。
双击后程序显示一个输入框,提示输入passwod,并验证。
首先什么都不输入,直接验证,程序弹窗输出提示字符串:“请输入pass!”。
而随意输入pass,则弹窗输出提示字符串:“错了! 加油!”,并且程序退出。
从以上信息我们猜测:
1、如果pass正确,程序应该会和失败时一样弹窗,并且提示相关字符串,我们可以从搜索错误或者正确时提示的字符串入手,也可以从弹窗API入手。
2、既然程序需要用户输入,那我们就从输入控件,或者获取控件字符串的API入手。
3、假如程序是MFC程序,并且用户输入后需要点击按钮验证,我们可以从OD搜索MFC按钮事件代码入手。
4、输入pass错误后,程序会退出,搜索退出函数或API下断,然后代码回溯。
有了以上4个破解思路,我们就可以打开IDA开始分析了。
待IDA分析完代码后,按快捷键Shift+F12查看字符串。
双击可疑字符串,查看代码交叉引用。
F5查看伪代码。
1 int __thiscall sub_401890(CWnd *this) 2 { 3 struct CString *v1; 4 CWnd *v2; 5 int v3; 6 int result; 7 int v5[26]; 8 int i; 9 char *Str; 10 CWnd *v8; 11 12 v8 = this; 13 v1 = (CWnd *)((char *)this + 100); 14 v2 = CWnd::GetDlgItem(this, 1002); 15 CWnd::GetWindowTextA(v2, v1); 16 v3 = sub_401A30((char *)v8 + 100); 17 Str = CString::GetBuffer((CWnd *)((char *)v8 + 100), v3); 18 if ( strlen(Str) ) 19 { 20 for ( i = 0; Str[i]; ++i ) 21 { 22 if ( Str[i] > 57 || Str[i] < 48 ) 23 { 24 if ( Str[i] > 122 || Str[i] < 97 ) 25 { 26 if ( Str[i] > 90 || Str[i] < 65 ) 27 sub_4017B0(); 28 else 29 v5[i] = Str[i] - 29; 30 } 31 else 32 { 33 v5[i] = Str[i] - 87; 34 } 35 } 36 else 37 { 38 v5[i] = Str[i] - 48; 39 } 40 } 41 result = sub_4017F0(v5); 42 } 43 else 44 { 45 result = CWnd::MessageBoxA(v8, "请输入pass!", 0, 0); 46 } 47 return result; 48 }
第15、17行代码将用户输入的pass保存到Str。
第18行代码使用strlen函数检测pass长度,如果为空,则第45行代码弹窗提示重新输入。
第22、24、26行代码通过循环比较来判断pass每个字符是否在0-9,a-z,A-Z范围之间。通过IDA快捷键“R”可以将比较数值转换为字符显示。
第29、33、38行代码则对不同范围内的字符进行减法操作:
0-9:Str[i] -= 48
a-z:Str[i] -= 87
A-Z:Str[i] -= 29
第41行程序将处理后的pass交由“sub_4017F0”函数进行验证处理。
1 int __cdecl sub_4017F0(int a1) 2 { 3 int result; 4 char Str1[28]; 5 int v3; 6 int v4; 7 8 v4 = 0; 9 v3 = 0; 10 while ( *(_DWORD *)(a1 + 4 * v4) < 62 && *(_DWORD *)(a1 + 4 * v4) >= 0 ) 11 { 12 Str1[v4] = aAbcdefghiabcde[*(_DWORD *)(a1 + 4 * v4)]; 13 ++v4; 14 } 15 Str1[v4] = 0; 16 if ( !strcmp(Str1, "KanXueCTF2019JustForhappy") ) 17 result = sub_401770(); 18 else 19 result = sub_4017B0(); 20 return result; 21 }
第10行代码循环遍历pass字符,判断pass[i]是否>=0并且<62。
第12行代码如果以上条件满足,程序将pass[i]的值当做下标索引来获取“aAbcdefghiabcde”字符数组对应索引位置的字符,保存到Str1。
第16行代码使用“strcmp”函数比较Str1是否等于“KanXueCTF2019JustForhappy”字符串,如果等于则调用第17行函数,否则调用第19行函数。
第17行代码:pass正确!
第19行代码:pass错误!
假设:
字符串1 = "KanXueCTF2019JustForhappy"。
字符串2 = "abcdefghiABCDEFGHIJKLMNjklmn0123456789opqrstuvwxyzOPQR"。
通过以上分析我们知道,正确的pass在经过一系列减法操作后,最终应该等于字符串1。
而字符串1是通过字符串2得到的。所以通过反向思维,我们首先获取字符串1每个字符在字符串2中的下标索引。
然后根据每一个索引值的范围加上不同的数,最终就是输入时正确的pass!
1 void deCode() 2 { 3 char* key1 = "KanXueCTF2019JustForhappy"; 4 char* key2 = "abcdefghiABCDEFGHIJKLMNjklmn0123456789opqrstuvwxyzOPQRSTUVWXYZ"; 5 6 for (int i = 0; i < strlen(key1); i++) 7 { 8 char* s = strchr(key2, key1[i]); 9 char c = s - key2; 10 if ((c + 48 >= '0') && (c + 48 <= '9')) 11 { 12 printf("%c", c + 48); 13 } 14 else if ((c + 87 >= 'a') && (c + 87 <= 'z')) 15 { 16 printf("%c", c + 87); 17 } 18 else if ((c+29 >= 'A') && (c+29 <= 'Z')) 19 { 20 printf("%c", c + 29); 21 } 22 } 23 printf("\n"); 24 }