happy mota 先玩游戏,玩两局发现魔王很难打,通过npc对话发现flag被分成四段,并且玩到第11层的时候对话中有提示:11-19层的墙壁有点奇怪 ,发现第一串flag是TSCTF{enj
。
有了玩游戏的基础,发现flag都隐藏在NPC对话中,用pyinstxtractor.py反编译main.exe得到main.py,但是main函数中的字符串经过decode后发现都是操作相关,并不涉及于NPC的对话,于是先扒文件,在\happy_mota\main\scripts
目录下找到人物对话源代码,发现三段可疑代码:
1 2 3 4 5 s = b'' f2 = self.parameter['2wsxdr5' ] for i in range (len (f2)): s += bytes ([f2[i] ^ i ^ 0xC8 ]) self.conversation_control.print_word("商人L3m0nade" , "爽快!我这儿捡了个字符串:\"" + s.decode() + '\"你看有没有用.' ,"npc_2" )
1 2 3 4 5 s = b'' f3 = self.parameter['3edcft6' ] for i in range (len (f3)): s += bytes ([f3[i] ^ i ^ 0xB4 ]) self.parameter['answer3' ] = s.decode()
1 2 3 4 5 s = b'' f4 = self.parameter['4rfvgy7' ] for i in range (len (f4)): s += bytes ([(f4[i] ^ (len (f4) - i) ^ 0xA9 )]) self.parameter['answer4' ] = s.decode()
一开始以为f2,f3,f4就是2wsxdr5,3edcft6,4rfvgy7,搞了很久,最后才发现应该只是类似于C++STL库中的map映射,翻了翻人物对话发现找不到这个映射,于是回到main函数:
1 2 3 4 5 6 '2wsxdr5' : b'\xf8\xb0\x95\xfc\x84\x88' ,'3edcft6' : b'\xeb\xe7\x85\xe1\xd5\xc3\x87\xd6\x85\xdc\xd3\xda\x9e' ,'4rfvgy7' : b'\xee\x97\xd4\xcc\xe7\x91\xf7\xd4\x92\xdc\xe3\xc5\xcb\xcf\x8a\xd5' ,'answer2' : '' ,'answer3' : '' ,'answer4' : '' ,
然后写exp得到后三段flag:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <iostream> using namespace std;char s2[6 ];char s3[13 ];char s4[16 ];int f2[6 ] = {0xf8 ,0xb0 ,0x95 ,0xfc ,0x84 ,0x88 };int f3[13 ] = {0xeb ,0xe7 ,0x85 ,0xe1 ,0xd5 ,0xc3 ,0x87 ,0xd6 ,0x85 ,0xdc ,0xd3 ,0xda ,0x9e };int f4[16 ] = {0xee ,0x97 ,0xd4 ,0xcc ,0xe7 ,0x91 ,0xf7 ,0xd4 ,0x92 ,0xdc ,0xe3 ,0xc5 ,0xcb ,0xcf ,0x8a ,0xd5 };int main () { for (int i=0 ;i<6 ;i++)s2[i] = f2[i] ^ i ^ 0xC8 ,cout<<s2[i]; for (int i=0 ;i<13 ;i++)s3[i] = f3[i] ^ i ^ 0xB4 ,cout<<s3[i]; for (int i=0 ;i<16 ;i++)s4[i] = f4[i] ^ (16 - i) ^ 0xA9 ,cout<<s4[i]; }
最后合并一下得到完整flag:TSCTF{enj0y_7HE_R3Ver5e9ame&W1shB3Tt3rLife!}
Patternlock 安卓逆向,check函数是native方法,想到逆向SO文件,然后尝试分析SO文件,通过交叉引用找到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 jint JNI_OnLoad (JavaVM *vm, void *reserved) { int v2; int i; int j; int v8; char v9[8 ]; int v10[3 ]; char v11[34 ]; ptrace(PTRACE_TRACEME, 0 , 0 , 0 ); if ( sub_1AF0(vm, &v8, 65542 ) ) return -1 ; strcpy (v9, "cig`o" ); strcpy (v11, "(Mhbrd)kigm$_y|f~v):N" ); for ( i = 0 ; i <= 4 ; ++i ) v9[i] ^= i; for ( j = 0 ; j <= 20 ; ++j ) v11[j] ^= j; v10[0 ] = (int )v9; v10[1 ] = (int )v11; v10[2 ] = (int )sub_179C; if ( !sub_1B24(v8, "com/crackme/tsctf/TsUtil" , v10, 1 ) ) return -1 ; v2 = sub_159C(); sub_1670(v2); return 65542 ; }
发现可以和java逆出来的check函数对的上,但是比赛的时候由于这里读不懂直接寄了 后来发现可以变成 当场去世
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 #include <bits/stdc++.h> using namespace std;char v9[] = "cig`o" ;char v11[] = "(Mhbrd)kigm$_y|f~v):N" ;char a[] = "\r<6\x12)G^VfIDjDX" ;char TSCTF[] = "TSCTF2022!!!!!" ;char key[50 ];char inputbytes[50 ];int cmp[40 ] = {97 , 14 , 20 , 35 , 10 , 68 , 11 , 86 , 55 , 91 , 4 , 42 , 4 , 76 , 107 , 89 , 68 , 32 , 95 , 77 , 15 , 6 , 55 , 9 , 86 , 47 , 87 , 26 , 109 , 86 , 68 , 116 , 11 , 19 , 11 , 5 , 54 , 12 , 87 , 122 };int main () { int v12[3 ]; for (int i = 0 ; i <= 4 ; ++i) v9[i] ^= i; for (int j = 0 ; j <= 20 ; ++j) v11[j] ^= j; cout<<v9<<endl<<v11<<endl; for (int i=0 ;i<=13 ;i++) key[i] = TSCTF[i] ^ a[i]; cout<<key<<endl; for (int i=0 ;i<40 ;i++) inputbytes[i] = key[i%strlen (key)] ^ cmp[i]; cout<<inputbytes<<endl; }
happy_string 虚拟机连内网后从远端接收一个base64传输的文件,接收后,先拖入IDA分析,查字符串发现关机键字符串没有交叉引用
进入main函数发现有一个数据块需要恢复,猜测D3F开始为关键函数,一般启用动态调试即可自动恢复。
但是前面有ptrace反调试,需要先patch掉,尝试修改 == -1为 != -1(把jnz改成jz),这里需要注意:未启用动态调试的时候patch的结果不会带入启用动调后的程序,也就是说需要在该语句前下断点,在动调的同时patch汇编语句。
然后一路F8到D3F处,F7步入函数,会出现一个恢复code的弹窗
按快捷键P即可创建函数得到关键代码。
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 __int64 sub_55EDD8A00D3F () { __int64 v1; __int64 v2; __int64 v3[2 ]; char s[8 ]; unsigned __int64 v5; v5 = __readfsqword(0x28 u); puts ("PLease input an interesting string!\x00Wrong!\x00Right!" ); fgets(s, 256 , stdin ); if ( s[strlen (s) - 1 ] == 10 ) s[strlen (s) - 1 ] = 0 ; if ( strlen (s) != 8 ) exit (0 ); v1 = *(_QWORD *)s; v2 = qword_55EDD8C040B8; qmemcpy(v3, "W3lc0meT0TSCTF!!" , sizeof (v3)); sub_55EDD8A013B5(&v1, v3); sub_55EDD8A00F31(&v2, v3); if ( v2 != v1 ) { puts ("Wrong!\x00Right!" ); exit (0 ); } puts (&::s[43 ]); return 0LL ; }
代码逻辑就是把输入的flagV1通过sub_55EDD8A013B5(一个改了delta的tea)加密,再把v2通过sub_55EDD8A00F31加密。
对于对v2的加密函数
1 2 3 4 5 6 7 8 9 10 11 unsigned __int64 __fastcall sub_55EDD8A00F31 (__int64 a1, _DWORD *a2) { _DWORD v3[1042 ]; unsigned __int64 v4; v4 = __readfsqword(0x28 u); sub_55EDD8A01493((unsigned int *)a1, -2 , a2); sub_55EDD8A01C1C((__int64)v3, (__int64)a2, 16 ); sub_55EDD8A01B6B(v3, (int *)a1, (int *)(a1 + 4 )); return __readfsqword(0x28 u) ^ v4; }
这里可以使用hook把源代码直接复制出来(493可以直接复制,C1C和B6B应该是BLOWFISH,也可以用findcrypt插件(edit->plugins->findcrypt) 注意这里的密钥是个SMC,具体可以在init处找到一个叫src串的交叉引用,然后在D3F之前找到一个RC4加密,不过一路动调到D3F处就可以直接获得密钥W3lc0meT0TSCTF!!(雾)
下面就是写解密脚本了,由于式动态flag,先写到这,开摆。