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; // r0
int i; // [sp+24h] [bp-4Ch]
int j; // [sp+24h] [bp-4Ch]
int v8; // [sp+34h] [bp-3Ch] BYREF
char v9[8]; // [sp+38h] [bp-38h] BYREF
int v10[3]; // [sp+40h] [bp-30h] BYREF
char v11[34]; // [sp+4Eh] [bp-22h] BYREF

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; // [rsp+20h] [rbp-130h] BYREF
__int64 v2; // [rsp+28h] [rbp-128h] BYREF
__int64 v3[2]; // [rsp+30h] [rbp-120h] BYREF
char s[8]; // [rsp+40h] [rbp-110h] BYREF
unsigned __int64 v5; // [rsp+148h] [rbp-8h]

v5 = __readfsqword(0x28u);
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]; // [rsp+20h] [rbp-1050h] BYREF
unsigned __int64 v4; // [rsp+1068h] [rbp-8h]

v4 = __readfsqword(0x28u);
sub_55EDD8A01493((unsigned int *)a1, -2, a2);
sub_55EDD8A01C1C((__int64)v3, (__int64)a2, 16);
sub_55EDD8A01B6B(v3, (int *)a1, (int *)(a1 + 4));
return __readfsqword(0x28u) ^ v4;
}

这里可以使用hook把源代码直接复制出来(493可以直接复制,C1C和B6B应该是BLOWFISH,也可以用findcrypt插件(edit->plugins->findcrypt)
注意这里的密钥是个SMC,具体可以在init处找到一个叫src串的交叉引用,然后在D3F之前找到一个RC4加密,不过一路动调到D3F处就可以直接获得密钥W3lc0meT0TSCTF!!(雾)

下面就是写解密脚本了,由于式动态flag,先写到这,开摆。