James Hoi's Blog

TSCTF 2022 校内赛 逆向部分

Word count: 2.2kReading time: 12 min
2022/04/24 Share

感觉没人打比赛啊…都是自己人

happy_mota

main.exe可以得到一些信息,根据script文件夹下的解密运行一次得到三块flag

image-20220424033856293

根据提示看出11到19层的地图形状为字符,其中少了一层,根据后半flag猜测为Enjoy

image-20220424034203991image-20220424034218700

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
print("TSCTF{Enj",end="")

s = b''
f2 = b'\xf8\xb0\x95\xfc\x84\x88'
for i in range(len(f2)):
s += bytes([f2[i] ^ i ^ 0xC8])
print(s.decode(),end="")

s = b''
f3 = b'\xeb\xe7\x85\xe1\xd5\xc3\x87\xd6\x85\xdc\xd3\xda\x9e'
for i in range(len(f3)):
s += bytes([f3[i] ^ i ^ 0xB4])
print(s.decode(),end="")

s = b''
f4 = b'\xee\x97\xd4\xcc\xe7\x91\xf7\xd4\x92\xdc\xe3\xc5\xcb\xcf\x8a\xd5'
for i in range(len(f4)):
s += bytes([(f4[i] ^ (len(f4) - i) ^ 0xA9)])
print(s.decode(),end="")

PatternLock

java绕了dex半天还是一样的,native JNI_Load异或后可以看出,大致猜是一个hook(没细看),异或一下即可。

image-20220424034820662

1
2
3
4
5
6
7
8
9
10
11
12
13
14
v9 = list('cig`o')
v11 = list('(Mhbrd)kigm$_y|f~v):N')
for i in range(5): v9[i] = chr(ord(v9[i])^i)
for i in range(21): v11[i] = chr(ord(v11[i])^i)
print("".join(v9))
print("".join(v11))

data = [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]
cipher = list("TSCTF2022!!!!!")
enc = "\r<6\x12)G^VfIDjDX"
cipher = [ord(enc[i])^ord(cipher[i]) for i in range(len(cipher))]

for i in range(len(data)):
print(chr(data[i]^cipher[i%len(cipher)]),end="")

happy_string

题目是linux下的smc,前面ptrace反调试,过掉就行。

image-20220424035554644

明显Tea,主程序用v2比较,而v2就算经过加密后也是不变的,用v2加密后的字节解密v1

image-20220424035614646

findcrypto显示blowfish,参考相关源码,没有sub_557DBE6B8493这一项

image-20220424035742234

sub_557DBE6B8B6B是blowfish_decrypt,而输入的key为W3lc0meT0TSCTF!!,对前面的v2进行加密。由于题目是动态flag,所以还是需要还原一次v2的加密过程,其中sub_557DBE6B8493直接复制粘贴伪代码即可。

my_blowfish脚本,库是https://github.com/Rupan/blowfish

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#include <stdio.h>
#include "blowfish.h"

typedef unsigned int _DWORD;
typedef unsigned char _BYTE;

__int64 __fastcall sub_55640047F493(unsigned int* a1, int a2, _DWORD* a3)
{
unsigned int* v3; // rax
unsigned int* v4; // rax
__int64 result; // rax
unsigned int* v6; // rax
int v7; // [rsp+Ch] [rbp-3Ch]
unsigned int v8; // [rsp+24h] [rbp-24h]
unsigned int v9; // [rsp+24h] [rbp-24h]
unsigned int v10; // [rsp+28h] [rbp-20h]
unsigned int v11; // [rsp+28h] [rbp-20h]
unsigned int v12; // [rsp+28h] [rbp-20h]
unsigned int v13; // [rsp+2Ch] [rbp-1Ch]
unsigned int v14; // [rsp+2Ch] [rbp-1Ch]
unsigned int j; // [rsp+30h] [rbp-18h]
int i; // [rsp+30h] [rbp-18h]
int v17; // [rsp+34h] [rbp-14h]
int v18; // [rsp+34h] [rbp-14h]
unsigned int v19; // [rsp+38h] [rbp-10h]
unsigned int v20; // [rsp+38h] [rbp-10h]
int v21; // [rsp+38h] [rbp-10h]
unsigned int v22; // [rsp+38h] [rbp-10h]
unsigned int v23; // [rsp+3Ch] [rbp-Ch]
unsigned int v24; // [rsp+3Ch] [rbp-Ch]
unsigned int v25; // [rsp+3Ch] [rbp-Ch]
unsigned int v26; // [rsp+3Ch] [rbp-Ch]
unsigned int v27; // [rsp+40h] [rbp-8h]
unsigned int v28; // [rsp+40h] [rbp-8h]
unsigned int v29; // [rsp+40h] [rbp-8h]
unsigned int v30; // [rsp+40h] [rbp-8h]
int v31; // [rsp+44h] [rbp-4h]
unsigned int v32; // [rsp+44h] [rbp-4h]

if (a2 <= 1)
{
if (a2 < -1)
{
v21 = -1091581186;
v25 = (0xBEEFCAFE >> (4 * a3[3])) ^ (0xBEEFCAFE >> (6 * a3[2])) ^ (-1091581186 << (2 * a3[1])) ^ (0xBEEFCAFE >> (2 * *a3)) ^ 0xF77E57F0;
v29 = (0xBEEFCAFE >> (4 * *((_BYTE*)a3 + 12))) ^ (v25 >> (6 * a3[2])) ^ (-1091581186 << (2 * a3[1])) ^ (v25 >> (2 * *a3)) ^ (8 * v25);
v7 = -a2;
v18 = 52 / -a2 + 6;
v14 = -559038737 * v18;
v9 = *a1;
do
{
v22 = v21 ^ 0xDEADBEEF;
v26 = v25 ^ 0xDEADBEEF;
v30 = v29 ^ 0xDEADBEEF;
v32 = (v14 >> 2) & 3;
for (i = v7 - 1; i; --i)
{
v11 = a1[i - 1];
v26 |= v22;
v30 = v26;
v22 = v26;
v6 = &a1[i];
*v6 -= ((v9 ^ v14) + (v11 ^ a3[v32 ^ i & 3])) ^ (((4 * v9) ^ (v11 >> 5)) + ((v9 >> 3) ^ (16 * v11)));
v9 = *v6;
}
v12 = a1[v7 - 1];
*a1 -= (((4 * v9) ^ (v12 >> 5)) + ((v9 >> 3) ^ (16 * v12))) ^ ((v9 ^ v14) + (v12 ^ a3[v32]));
result = *a1;
v9 = *a1;
v21 = v22 + 559038737;
v25 = v26 + 559038737;
v29 = v30 + 559038737;
v14 += 559038737;
--v18;
} while (v18);
}
}
else
{
v19 = -889274641;
v23 = (-889274641 << (4 * a3[3])) ^ (-889274641 << (6 * a3[2])) ^ (0xCAFEBEEF >> (2 * a3[1])) ^ (-889274641 << (2 * *a3)) ^ 0x195FD7DD;
v27 = (-889274641 << (4 * *((_BYTE*)a3 + 12))) ^ (v23 << (6 * a3[2])) ^ (0xCAFEBEEF >> (2 * a3[1])) ^ (v23 << (2 * *a3)) ^ (v23 >> 3);
v17 = 52 / a2 + 6;
v13 = 0;
v10 = a1[a2 - 1];
do
{
v13 -= 559038737;
v20 = v19 - 559038737;
v24 = v23 - 559038737;
v28 = v27 - 559038737;
v31 = (v13 >> 2) & 3;
for (j = 0; j < a2 - 1; ++j)
{
v8 = a1[j + 1];
v24 |= v20;
v28 = v24;
v20 = v24;
v3 = &a1[j];
*v3 += ((v8 ^ v13) + (v10 ^ a3[v31 ^ j & 3])) ^ (((4 * v8) ^ (v10 >> 5)) + ((v8 >> 3) ^ (16 * v10)));
v10 = *v3;
}
v19 = v20 ^ 0xDEADBEEF;
v23 = v24 ^ 0xDEADBEEF;
v27 = v28 ^ 0xDEADBEEF;
v4 = &a1[a2 - 1];
*v4 += ((*a1 ^ v13) + (v10 ^ a3[v31 ^ j & 3])) ^ (((4 * *a1) ^ (v10 >> 5)) + ((*a1 >> 3) ^ (16 * v10)));
result = *v4;
v10 = result;
--v17;
} while (v17);
}
return result;
}

void main(void) {
uint32_t L[] = { 0x5E430F45, 0x704A90BA};
scanf_s("%x %x",&L[0],&L[1]);
BLOWFISH_CTX ctx;

sub_55640047F493(&L, 0xFFFFFFFE, (_DWORD*)"W3lc0meT0TSCTF!!");
Blowfish_Init (&ctx, (uint8_t *)"W3lc0meT0TSCTF!!", 16);
Blowfish_Decrypt(&ctx, &L[0], &L[1]);
printf("%08lX %08lX\n", (long unsigned int)L[0], (long unsigned int)L[1]);
}

动态flag,相关脚本

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
'''
src = [ord(s) for s in "frYVQ)VtT5cctvhM"]
text = "13m0nAde i5 mes5ing arounD saf3 and s0und.Please d0 not tell Ver 0v0! I will show you the Key to decrypt sth!"
for i in range(16):
src[i] ^= ord(text[5*i])
src = "".join([chr(s) for s in src])
print(src)
'''

import base64
from pwn import *

conn = remote('10.7.2.136',45530)

def byteArr2intArr(data,order='little'):
return [int.from_bytes(data[i:i+4],byteorder=order) for i in range(0,len(data),4)]

def intArr2byteArr(data,order='little'):
out = b''
for s in data: out += s.to_bytes(4,order)
return out

def encrypt(v, k):
v0 = v[0]; v1 = v[1]
k0 = k[0]; k1 = k[1]; k2 = k[2]; k3 = k[3]
x = 0
delta = 0x21524111
for i in range(32):
x -= delta; x = x & 0xFFFFFFFF
v0 += ((v1 << 4) + k0) ^ (v1 + x) ^ ((v1 >> 5) + k1); v0 = v0 & 0xFFFFFFFF
v1 += ((v0 << 4) + k2) ^ (v0 + x) ^ ((v0 >> 5) + k3); v1 = v1 & 0xFFFFFFFF
v[0] = v0
v[1] = v1
return v

def decrypt(v, k):
v0 = v[0]; v1 = v[1]
k0 = k[0]; k1 = k[1]; k2 = k[2]; k3 = k[3]
x = 0xD5B7DDE0
delta = 0x21524111
for i in range(32):
v1 -= ((v0 << 4) + k2) ^ (v0 + x) ^ ((v0 >> 5) + k3); v1 = v1 & 0xFFFFFFFF
v0 -= ((v1 << 4) + k0) ^ (v1 + x) ^ ((v1 >> 5) + k1); v0 = v0 & 0xFFFFFFFF
x += delta; x = x & 0xFFFFFFFF
v[0] = v0
v[1] = v1
return v

def solve_once(i):
elf_name = 'tmp%d.elf'%i
feedback = conn.recvline()
filedata = base64.b64decode(feedback.decode())
with open(elf_name,"wb") as f: f.write(filedata)
conn.recvuntil(b'>NOW? Show me Your Answer!\n')

e=ELF('./%s'%elf_name)
tmp = e.read(0x2040B8,8)
elf_key = hex(int.from_bytes(tmp,'little'))
ret = subprocess.Popen(
"my_blowfish.exe",
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
stderr=subprocess.PIPE,
)
ret.stdin.write(("%s %s\n"%(elf_key[10:],elf_key[2:10])).encode())
ret.stdin.flush()
result = ret.stdout.readline().decode()
blow_key = eval("0x%s%s"%(result[9:17],result[:8]))
enc = byteArr2intArr(blow_key.to_bytes(8,byteorder='little'))
key = byteArr2intArr(b'W3lc0meT0TSCTF!!')
flag = intArr2byteArr(decrypt(enc,key))
conn.sendline(flag)

conn.recvuntil(b'>Ready to recv(Y|N)?\n', drop=True)
conn.sendline(b'Y')
solve_once(1)
conn.recvuntil(b">GREAT!!! very interesting,Here's Your Half flag:\"TSCTF{ug1y&13OriNg_En\" But I still want one more!\n")
solve_once(2)
conn.interactive()
# TSCTF{ug1y&13OriNg_EncD3c_Ich0OseEmu1t@v@}

digdig

go语言,一堆digdig_tree函数,正确会输出right,前面main_convert将输入的字符串转为二进制,每个digdig_tree函数会判断一个比特,从而跳转完成二叉树跳转。写个脚本建立二叉树,再找到遍历找到输出right的地方,利用输出right的节点的父节点向上,再判断每一位应当是0或1即可还原flag。

idapython脚本

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import ida_name
import idc
import idautils
import ida_kernwin
import ida_idp
import ida_funcs
import json

file = open("graph.dot","w")
file.write("digraph R {\n")
end = None

class Node(object):
def __init__(self,elem,parent):
self.addr = elem
self.parent = parent
self.l = None
self.r = None

def get_sub_fun(addr):
global all_fun
dism_addr = list(idautils.FuncItems(addr))
xref_froms = []
right = True
for ea in dism_addr:
asm = idc.GetDisasm(ea)
if "wrong" in asm and "lea" in asm: right &= False
if ida_idp.is_call_insn(ea) is False: continue
else:
callee = get_first_fcref_from(ea)
if callee != addr and callee not in all_fun:
name = ida_funcs.get_func_name(callee).replace(".","_")
root_name = ida_funcs.get_func_name(addr).replace(".","_")
if "Exit" in name: xref_froms.append(int(right))
elif name == "fmt_Fprintln": xref_froms.append(int(right))
elif "digdig_tree" in name and "wrong" not in name:
xref_froms.append(callee)
file.write(" %s->%s\n"%(root_name,name))
elif "wrong" in name: pass
elif "runtime" in name: pass
else: print(name)

xref_froms = list(set(xref_froms))
return xref_froms

def loop_graph(n):
global all_fun
fun = get_sub_fun(n.addr)
if len(fun)>2:
n.l = Node(fun[0],n); n.r = Node(fun[1],n)
elif len(fun)==2:
n.l = Node(fun[0],n); n.r = Node(fun[1],n)
elif len(fun)==1: n.l = Node(fun[0],n)
all_fun.extend(fun)
all_fun = list(set(all_fun))

if n.l != None and n.l != 0 and n.l != 1: loop_graph(n.l)
if n.r != None and n.r != 0 and n.l != 1: loop_graph(n.r)
return n

def preorderTraversal(root: Node):
res = []
def helper(node):
if node == None: return
if node.addr == 1: res.append(node)
else:
helper(node.l)
helper(node.r)
helper(root)
return res

flag_bit = ""
def get_parent(n: Node,child_name):
global flag_bit
if n == None: return
dism_addr = list(idautils.FuncItems(n.addr))
name = ida_funcs.get_func_name(n.addr).replace(".","_")
if child_name != "fmt_Fprintln":
for ea in dism_addr:
if ida_idp.is_call_insn(ea) is False: continue
else:
callee = get_first_fcref_from(ea)
tmp = ida_funcs.get_func_name(callee).replace(".","_")
if "wrong" in tmp: continue
if "digdig_tree" in tmp:
flag_bit = str(int(tmp==child_name))+flag_bit
break
get_parent(n.parent,name)


all_fun = []
addr = 0x49B7E0 # digdig_tree.AztyfJahWAIYmgn
n = loop_graph(Node(addr,None))
res = preorderTraversal(n)
get_parent(res[0].parent,"fmt_Fprintln")
flag = [chr(int(flag_bit[i:i+8],2)) for i in range(0,len(flag_bit),8)]
flag = "".join(flag)
file.write("}\n")
file.close()
CATALOG
  1. 1. happy_mota
  2. 2. PatternLock
  3. 3. happy_string
  4. 4. digdig