2022RCTF

2022RCTF RE 部分wp

CheckYourKey

逆向签到题,apk没什么好说的,就是调用so库里面的ooxx文件对输入进行检查,直接看so库

image-20221216194242927

在函数栏中直接搜索ooxx,定位到

Java_com_ctf_CheckYourKey_MainActivity_ooxx函数

定位到这里

image-20221216194537837

第227行和第231行的函数分别是base58,base64加密

base58函数

可以看到这里的(138 * a2 / 0x64) + 2 和 div(quot_low + (v14[–v8] << 8), 58),所以猜测是base58加密

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
unsigned __int8 *__fastcall sub_F7DC(__int64 a1, unsigned __int64 a2)
{
unsigned __int64 v2; // x11
__int64 v3; // x10
bool v5; // [xsp+18h] [xbp-78h]
unsigned __int64 v6; // [xsp+30h] [xbp-60h]
div_t v7; // [xsp+38h] [xbp-58h]
unsigned __int64 v8; // [xsp+40h] [xbp-50h]
unsigned __int64 v9; // [xsp+48h] [xbp-48h]
int quot_low; // [xsp+54h] [xbp-3Ch]
unsigned __int64 v11; // [xsp+58h] [xbp-38h]
unsigned __int64 j; // [xsp+60h] [xbp-30h]
unsigned __int64 v13; // [xsp+60h] [xbp-30h]
unsigned __int8 *v14; // [xsp+68h] [xbp-28h]
unsigned __int64 i; // [xsp+78h] [xbp-18h]

for ( i = 0LL; !*(_BYTE *)(a1 + i); ++i )
;
v14 = (unsigned __int8 *)malloc((unsigned int)(138 * a2 / 0x64) + 2);
memset(v14, 0, 138 * a2 / 0x64 + 2);
for ( j = 0LL; j < i; ++j )
{
v2 = j;
v14[v2] = *(_BYTE *)base58_table;
}
v11 = 0LL;
while ( j < a2 )
{
v3 = j++;
quot_low = *(unsigned __int8 *)(a1 + v3);
v9 = 0LL;
v8 = 138 * a2 / 0x64 + 1;
while ( 1 )
{
if ( quot_low || (v5 = 0, v9 < v11) )
v5 = v8 != 0;
if ( !v5 )
break;
v7 = div(quot_low + (v14[--v8] << 8), 58);
v14[v8] = v7.rem;
quot_low = LOBYTE(v7.quot);
++v9;
}
v11 = v9;
}
v13 = i;
v6 = 138 * a2 / 0x64 + 1 - v11 - i;
while ( v13 < i + v11 )
{
v14[v13] = *((_BYTE *)base58_table + v14[v13 + v6]);
++v13;
}
if ( v6 )
v14[v13] = 0;
return v14;
}

image-20221216194931330

可以找到长度为58的表,但是内容不太对,X找交叉引用

image-20221216195017296

找到这里有异或的操作

直接上脚本测试一下,得到了base58的码表,确定这里是base58加密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
byte_41010 = [
0x9E, 0x9D, 0x9C, 0x9B, 0x9A, 0x99, 0x98, 0x97, 0x96, 0xEE,
0xED, 0xEC, 0xEB, 0xEA, 0xE9, 0xE8, 0xE7, 0xE5, 0xE4, 0xE3,
0xE2, 0xE1, 0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8,
0xF7, 0xF6, 0xF5, 0xCE, 0xCD, 0xCC, 0xCB, 0xCA, 0xC9, 0xC8,
0xC7, 0xC6, 0xC5, 0xC4, 0xC2, 0xC1, 0xC0, 0xDF, 0xDE, 0xDD,
0xDC, 0xDB, 0xDA, 0xD9, 0xD8, 0xD7, 0xD6, 0xD5, 0xAF, 0x00,
0x00, 0x00, 0x00, 0x00]
table = ''
for i in range(58):
byte_41010[i] ^= 0xAF
table+=chr(byte_41010[i])
print(table)
#123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz

base64函数

这里的就很容易看出来是base64了

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
__int64 __fastcall sub_13788(__int64 a1, unsigned int a2, __int64 a3)
{
__int64 v3; // x13
__int64 v4; // x14
__int64 v5; // x16
__int64 v6; // x16
__int64 v7; // x13
__int64 v8; // x13
__int64 v9; // x13
__int64 v10; // x13
__int64 v11; // x13
unsigned __int8 v13; // [xsp+2Ah] [xbp-26h]
unsigned __int8 v14; // [xsp+2Bh] [xbp-25h]
unsigned int v15; // [xsp+2Ch] [xbp-24h]
unsigned int v16; // [xsp+2Ch] [xbp-24h]
unsigned int v17; // [xsp+2Ch] [xbp-24h]
unsigned int v18; // [xsp+2Ch] [xbp-24h]
unsigned int i; // [xsp+30h] [xbp-20h]
int v20; // [xsp+34h] [xbp-1Ch]

v20 = 0;
v13 = 0;
v15 = 0;
for ( i = 0; i < a2; ++i )
{
v14 = *(_BYTE *)(a1 + i);
if ( v20 )
{
if ( v20 == 1 )
{
v20 = 2;
v4 = v15++;
*(_BYTE *)(a3 + v4) = base64_table[(16 * (v13 & 3)) | ((int)v14 >> 4) & 0xF];
}
else
{
v20 = 0;
v5 = v15;
v16 = v15 + 1;
*(_BYTE *)(a3 + v5) = base64_table[(4 * (v13 & 0xF)) | ((int)v14 >> 6) & 3];
v6 = v16;
v15 = v16 + 1;
*(_BYTE *)(a3 + v6) = base64_table[v14 & 0x3F];
}
}
else
{
v20 = 1;
v3 = v15++;
*(_BYTE *)(a3 + v3) = base64_table[((int)v14 >> 2) & 0x3F];
}
v13 = v14;
}
if ( v20 == 1 )
{
v7 = v15;
v17 = v15 + 1;
*(_BYTE *)(a3 + v7) = base64_table[16 * (v13 & 3)];
v8 = v17++;
*(_BYTE *)(a3 + v8) = 61;
v9 = v17;
v15 = v17 + 1;
*(_BYTE *)(a3 + v9) = 61;
}
else if ( v20 == 2 )
{
v10 = v15;
v18 = v15 + 1;
*(_BYTE *)(a3 + v10) = base64_table[4 * (v13 & 0xF)];
v11 = v18;
v15 = v18 + 1;
*(_BYTE *)(a3 + v11) = 61;
}
*(_BYTE *)(a3 + v15) = 0;
return v15;
}

image-20221216194847744

可以看出来是换表的base64加密

在该函数的最后找到strcmp函数

image-20221216195438199

可以知道最后的密文就是asc_41120,x交叉引用找到了另外的调用

image-20221216195529903

先异或后解密,但是结果不对,猜测有其他的加密

利用findcrypt找到了AES加密,直接定位到该函数

image-20221216195605066

sub_14FC0函数

该函数的整体逻辑和上面的ooxx函数十分相似,所以直接定位到

image-20221216195822846

整体的加密逻辑就是先AES后base58最后base64

AES的key刚好就是该函数的第二个参数,直接提取出来

1
2
3
4
5
6
7
unsigned char key[] =
{
0x98, 0x90, 0x90, 0x9B, 0x93, 0x8A, 0x9C, 0x94, 0x8C, 0x92,
0x9E, 0x8D, 0x8B, 0x92, 0x9E, 0x91, 0xFF, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00
};

刚才在找asc_41120的交叉引用是定位到了一个函数,在那里同时也找到了key的处理,直接输出得到key

image-20221216200126879

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#define _BYTE unsigned char
int main()
{
unsigned char key[] =
{
0x98, 0x90, 0x90, 0x9B, 0x93, 0x8A, 0x9C, 0x94, 0x8C, 0x92,
0x9E, 0x8D, 0x8B, 0x92, 0x9E, 0x91, 0xFF, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00
};
for (int i = 0; i < 32; i++)
{
printf("%c", (_BYTE)~key[i]);
}
return 0;
}
//goodlucksmartman

最后比较的密文也不一样,但是同样给要先经过异或处理

image-20221216201150277

最后是从NU1L那里学来的python脚本,也可以用cyberchef直接解

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
import base64
import binascii
from Crypto.Cipher import AES
b58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
def base58decode(s): # 传⼊的参数是加密后的字符串
result = 0
for c in s:
result = result * 58 + b58.find(c)
return result
# print(b58decode("56fkoP8KhwCf3v7CEz"))
changed_base64 = "+/EFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789ABCD" #

base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" #
# 标准码表是从(A-Z)+(a-z)+(0-9)+(+/)
enflag = "SVTsfWzSYGPWdYXodVbvbni6doHzSi==" # ⽐较数据
right_enflag = ''
enflag1 = ''
flag = ''
for i in range(len(enflag)):
right_enflag += base[changed_base64.find(enflag[i])]
# print(right_enflag)
enflag1 = base64.b64decode(right_enflag)
print(enflag1)
print(hex(base58decode("A4juLPXCTmefm6mfX8naqB")))
data = binascii.unhexlify(hex(base58decode("A4juLPXCTmefm6mfX8naqB"))[2:].encode("utf8"))
mode = AES.MODE_ECB
key = list(b'goodlucksmartman')
cryptos = AES.new(bytes(key), mode)
flag = cryptos.decrypt(bytes(data))
print(flag)
#flag{rtyhgf!@#$}

HuoWang

一个迷宫题

程序的符号表是被去除了,但是群里大哥看出来是unicorn写的了

使用bandiff恢复一下符号表,顺便搜索了一下各个函数的功能

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int64 v3; // rdx
int result; // eax
int v5; // [rsp+10h] [rbp-150h] BYREF
int i; // [rsp+14h] [rbp-14Ch]
int len; // [rsp+18h] [rbp-148h]
int v8; // [rsp+1Ch] [rbp-144h]
__int64 uc; // [rsp+20h] [rbp-140h] BYREF
__int64 v10[3]; // [rsp+28h] [rbp-138h] BYREF
int v11; // [rsp+40h] [rbp-120h]
char input[264]; // [rsp+50h] [rbp-110h] BYREF
unsigned __int64 v13; // [rsp+158h] [rbp-8h]

v13 = __readfsqword(0x28u);
scanf((__int64)"%s", input);
len = strlen();
v5 = 0xFFFFF;
for ( i = 0; i <= 6; ++i )
qword_19537E0[i] = &test[i];
uc_open(4u, 8, &uc); // uc初始化
// (UC_ARCH_, UC_MODE_ARM, &uc)
uc_mem_map(uc, (__int64)&dword_400000, 0x200000LL, 7u);// 申请内存空间
// (uc, ADDRESS, 大小, UC_PROT_ALL)
uc_mem_map(uc, 0LL, 0x100000LL, 7u);
v10[1] = 0LL;
v10[2] = 0LL;
v11 = 0;
uc_mem_write(uc, (__int64)&qword_400080, (__int64)&loc_145E010, 0x7AA8uLL);// 将指令数据写入到模拟器内存
// (uc, ADDRESS, code, sizeof(code))
v8 = uc_mem_write(uc, (__int64)&loc_407B34, (__int64)input, len);
v8 = uc_reg_write(uc, 30, (__int64)&v5); // 第一个是uc类,
// 第二个就是我们要修改的寄存器常量,
// 第三个参数就是我们要修改的值,这个也是数组地址,和第二个参数一一对应
// 第四个就是要修改的个数
v8 = uc_hook_add(uc, v10, 2, (__int64)sub_401FC5, 0LL, 1LL, 0LL, 0x2BB, (int)argv);// //第一个参数是uc类
// 第二个参数是回调路径
// 第三个参数是hook类型,通过修改这个参数我们可以让不同时候来调用这个hook,
// 第四个参数就是hook函数,我们每次调用hook的时候要执行的函数,
// 第五个参数是hook函数里的user_data,也就是用户数据,
// 第六个参数是代码起始地址,
// 第七个参数是代码终止地址,从起始地址开始到终止地址的代码都会进行hook判断,然后根据hook类型来判断是否调用hook函数,如果起始地址大于终止地址,也就像这个样例一样,那么就会把整个范围认为是整个unicorn运行的代码范围
v8 = uc_emu_start(uc, 0x400119LL, 0x40016ALL, 0LL, 0LL);// 模拟器从指定的地址开始运行,到指定的地址停止运行
v8 = uc_emu_start(uc, 0x40016ALL, &qword_4000D8, 0LL, 0LL);
uc_close(uc, 0x40016ALL, v3);
maze((__int64)asc_18D7180, (__int64)input, len);
if ( check1 && check2 )
{
puts((__int64)"GrandFather Dao, I've made it!(ohhhhh. The flag is RCTF{(md5(your input))}.");
}
else if ( check1 || check2 )
{
puts((__int64)"Mom, I really can't tell the difference!");
}
else
{
puts((__int64)"CTF is fun! Jie Jie Jie");
}
result = 0;
if ( __readfsqword(0x28u) != v13 )
sub_1404EA0();
return result;
}

主要的迷宫还是在maze函数中

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
__int64 __fastcall maze(__int64 a1, __int64 a2, int a3)
{
__int64 result; // rax
int v4; // [rsp+24h] [rbp-Ch]
int v5; // [rsp+28h] [rbp-8h]
unsigned int i; // [rsp+2Ch] [rbp-4h]

v4 = 0;
v5 = 1;
for ( i = 0; ; ++i )
{
result = i;
if ( (int)i >= a3 )
break;
result = *(unsigned __int8 *)((int)i + a2);
if ( (_DWORD)result == 'w' )
{
if ( !v4 )
return result;
result = *(unsigned __int8 *)(a1 + 24LL * v4 - 24 + v5);
if ( (_BYTE)result != ' ' )
return result;
--v4;
}
else
{
if ( *(unsigned __int8 *)((int)i + a2) > 0x77u )
goto LABEL_20;
if ( (_DWORD)result == 's' )
{
result = *(unsigned __int8 *)(a1 + 24 * (v4 + 1LL) + v5);
if ( (_BYTE)result != ' ' )
return result;
++v4;
}
else
{
if ( *(unsigned __int8 *)((int)i + a2) > 0x73u )
goto LABEL_20;
if ( (_DWORD)result == 'a' )
{
if ( !v5 )
return result;
result = *(unsigned __int8 *)(a1 + 24LL * v4 + v5 - 1);
if ( (_BYTE)result != ' ' )
return result;
--v5;
}
else
{
if ( (_DWORD)result != 'd' )
LABEL_20:
sub_13AF360(-1);
result = *(unsigned __int8 *)(a1 + 24LL * v4 + v5 + 1);
if ( (_BYTE)result != ' ' )
return result;
++v5;
}
}
}
}
if ( v4 == 22 && v5 == 21 )
check1 = 1;
return result;
}

其中a1是

image-20221216202743853

可以得知我们要做的就是从左上角走到右下角,wsad分别对应上下左右,需要找到最短路径

主要方法还是遍历破解,来自Arr3stY0u战队的wp(orz)

2022 RCTF writeup by Arr3stY0u (qq.com)

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
from pwn import *
maze_map = [
"0S000000000000000000000",
"01011111011111110111110",
"01010101000101000101010",
"01110101111101011101010",
"01010100000000010100010",
"01010111110111110101110",
"01010000010101000001010",
"01110101110111111111010",
"00010101000101000100010",
"01110101110101011101110",
"01000100010101011100010",
"01110101110101011111010",
"01010101000101000001010",
"01011101111111110111010",
"01000000000001000101010",
"01110111110111111101010",
"01000000000100000000010",
"01110010001101111111010",
"01110100001001000001010",
"01110101101111011111010",
"01010101010000010000010",
"01111111011111011111110",
"000000000000000000000E0"
]
def dfs_solve_maze(map, x, y, walks, path, allpath):
if x < 0 or x >= 23:
return allpath
if y < 0 or y >= 23:
return allpath
if (x, y) in walks:
return allpath
if map[y][x] not in 'S1E':
return allpath
if map[y][x] == 'E':
allpath.add(path)
return allpath
new_walks = walks.copy()
new_walks.add((x, y))
dfs_solve_maze(map, x-1, y, new_walks, path+'a', allpath)
dfs_solve_maze(map, x+1, y, new_walks, path+'d', allpath)
dfs_solve_maze(map, x, y-1, new_walks, path+'w', allpath)
dfs_solve_maze(map, x, y+1, new_walks, path+'s', allpath)
return allpath
allpath = dfs_solve_maze(maze_map, 1, 0, set(), '', set())
for p in allpath:
io=process("./HuoWang")
io.sendline(p)
out=io.recvall()
if b"flag" in out:
print(p)
io.close()

通过dfs遍历所有的情况

image-20221216204341992

然后md5加密得到flag

1
2
3
4
5
6
7
8
import hashlib
a = "sssddwwddssssddddssaassddssaassddddwwwwwwwwddddwwddwwddddssssaassaassaassddddssaassaaaaaassassdddwwddddddssaaaassdddddds"
enc = hashlib.md5()
enc.update(a.encode('utf-8'))
print('flag{',end='')
print(enc.hexdigest(),end='')
print('}')
#flag{e1f9e3d166dcec5ecff3a2c5fbdeab3b}

RTTT

一道有点抽象的rust逆向(rust都抽象)

首先静态分析一下

找到了五个很可疑的数组

image-20221216210957006

image-20221216211008109

看到他们后续的操作是异或,直接上手,发现结果就是

1
2
Welc0me to RCTF 2O22
Congratulations

所以猜测congratulations前面的那个数组大概率是密文

直接在左侧搜索memcmp函数,x找交叉引用定位到最后密文做比较的地方

直接开始动调,F8到输入处开始跟踪,然后发现第一个操作是打乱输入顺序

input:RCTF{abcdefghijklmnopqrstuvwxyz0123456789}

打乱后t4u6f2rysRxvoin}dkh3wacgeF1C{9l58Tz7pbq0jm

看F61d大佬们的wp

RCTF 2022 WriteUp By F61d | CTF导航 (ctfiot.com)

得知第二步加密居然是RC4,我是看了两个多小时没看出来,居然只凭借这点信息就能判断出来是RC4,真厉害

image-20221216214645661

借用一下脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from Crypto.Cipher import ARC4
rc4 = ARC4.new(b'Welc0me to RCTF 2O22')
t1 = [52, 194, 101, 45, 218, 198, 177, 173, 71, 186, 6, 169, 59, 193, 204, 215, 241, 41, 36, 57, 42, 192, 21, 2, 126, 16, 102, 123, 94, 234, 94, 208, 89, 70, 225, 214, 110, 94, 178, 70, 107, 49]
f = rc4.decrypt(bytes(t1)).decode()
print(f)
s1 = 'RCTF{abcdefghijklmnopqrstuvwxyz0123456789}'
#s1 = 'qwertyuiopasdfghjklzxcvbnm1234567890QWERTY'
#s2 = 'n0mWa8v4bq31zflYohd92yispr7wtTjQRe5Exuc6gk'
s2 = 't4u6f2rysRxvoin}dkh3wacgeF1C{9l58Tz7pbq0jm'

dic = {}
for i in range(len(s2)):
dic[i] = s1.index(s2[i])

f1 = ['' for i in range(42)]
for i in range(len(f)):
f1[dic[i]] = f[i]
print(''.join(f1))
#RCTF{03C3E9B2-E37F-2FD6-CD7E-57C91D77DD61}

Web_run

wasm逆向, ez_wasm.2 改为 ez_ca.wasm ,ez_wasm.1 改为 ez_wasm.html

然后直接用jeb打开.wasm

main()

image-20230415131758752

f11()

image-20230415131834660

关键函数主要是f10(), f6(),f9()这三个

f10()

image-20230415142301789

主要是输入data,格式为xxxx/xx/xx xx:xx,解析成为一个整数

要留意的是202211110054这个数

f6()

image-20230415142439751

主要功能就是把f10()返回的result值赋给全局变量

f30()

其中有对0xA30处值的初始化

image-20230415143147404

f9()

分情况对这个整数进行加密

具体是”xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx”

image-20230415132916761

红框部分就是主要的加密过程

f7()

image-20230415142729078

f31()

image-20230415142742289

主要的加密就是f31()的返回值模16,然后按照对应规则转换为16进制

写脚本

由于是复现,所以直接与已知的官方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
#include <iostream>
#include <string>
using namespace std;

int main() {
long long magic = 6364136223846793005;
long long data = 202211110054;

long long A30 = (int)data - 1;
string test = "RCTF{40959ea7-26e0-4c9d-8f4a-62faf14ff392}";
string flag = "RCTF{";
char str[] = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx";

for (int i = 0; i < strlen(str); i++) {
if (str[i] != '4' && str[i] != '-') {
long long res = A30 * magic + 1;
A30 = res;
unsigned int v0 = (unsigned int)(res >> 33);
int num = v0 % 16;
if (str[i] == 'y') {
num = ((num & 3) | 8);
}
if (num >= 0 && num <= 9) {
flag += num + '0';
}
else {
flag += num - 10 + 'a';
}
}
else {
flag += str[i];
}
}
flag += '}';
std::cout << flag << endl;
if (flag == test) {
printf("correct!\n");
}

return 0;
}

2022RCTF
http://example.com/2022/12/16/2022RCTF/
Author
Eutop1a
Posted on
December 16, 2022
Licensed under