0x01反调试

拿到Ntdll的模块句柄,从句柄中查找ZwSetInformationThread的地址,然后调用这个函数去检查当前线程是否处于Debug模式下

1
2
3
4
ModuleHandleA = GetModuleHandleA("Ntdll");
ZwSetInformationThread = GetProcAddress(ModuleHandleA, "ZwSetInformationThread");
CurrentThread = GetCurrentThread();
((void (__stdcall *)(HANDLE, _DWORD, _DWORD, _DWORD))ZwSetInformationThread)(CurrentThread, 17, 0, 0);

关于ZwSetInformationThread的类型:

1
2
3
4
5
6
typedef NTSTATUS(NTAPI* pZwSetInformationThread)(
IN HANDLE ThreadHandle, // 线程对象句柄
IN THREAD_INFO_CLASS ThreadInformaitonClass, // 线程信息类型
IN PVOID ThreadInformation, // 线程信息指针
IN ULONG ThreadInformationLength // 线程信息大小
);

重点查看这个线程信息类型,是一个枚举类型,根据传入参数调用特定函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
typedef enum _THREADINFOCLASS {
ThreadBasicInformation,
ThreadTimes,
ThreadPriority,
ThreadBasePriority,
ThreadAffinityMask,
ThreadImpersonationToken,
ThreadDescriptorTableEntry,
ThreadEnableAlignmentFaultFixup,
ThreadEventPair,
ThreadQuerySetWin32StartAddress,
ThreadZeroTlsCell,
ThreadPerformanceCount,
ThreadAmILastThread,
ThreadIdealProcessor,
ThreadPriorityBoost,
ThreadSetTlsArrayAddress,
ThreadIsIoPending,
ThreadHideFromDebugger
}THREAD_INFO_CLASS;

这里传入17,刚好调用ThreadHideFromDebugger,检测到调试就会将该线程强制分离出调试器,造成反调试。修改方法直接将push 的17 patch成0就好了。

3661847bc96f92448810106b4337ebb

修改后便能正常调试。

0x02 走迷宫

一开始往下翻就发现是一个迷宫,中间有一大段加密,看不懂先不管。反正关键肯定是走迷宫,迷宫地图是在运行中生成的,这时候处理完反调试就能跑出地图了。

跑到进行走迷宫逻辑前:

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
  while ( 1 )
{
switch ( v36 )
{
case 'a':
--v39;
break;
case 'd':
++v39;
break;
case 's':
++v37;
v40 += 16;
break;
case 'w':
--v37;
v40 -= 16;
break;
default:
break;
}
if ( dword_10053A8[v40 + v39] == 1 )
break;
v36 = v25[++v38];
if ( !v36 )
{
if ( v37 == 4 && v39 == 9 && v38 < 17 )
{
printf("Congratulations! Here is your flag: RCTF{%s}", (char)ArgList);
return 0;
}
break;
}
}
}
printf("Oh no. Dont eat me!!!!", v47);
return 0;

查看这个dword_10053A8数组,是一个int256大小的数组,点进去把数组内容shiftE出来,弄到python处理一下,有1024个01数字,但是int类型4个一组打印出来。

打印出来后发现只有01数,没有明显终点起点,于是再查看一下代码,发现v37记录着上下,v39记录左右移动。当v37 == 4 ,v39 == 9 ,且移动次数小于17次时,到达终点,于是终点坐标是(9 , 4)

d2c3ca38cdeb9b329aaf100dec290c4

25e10c4ea79eddebac9c60867166c87

再看成v39和v37的初始值,知道起点坐标为(5 , A),假设终点为$,起点为#

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
127
map = [    1,   0,   0,   0,   1,   0,   0,   0,   1,   0,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0]
map[4 * (16 * 0xA + 0x5)] = '#'
map[4 * (16 * 4 + 9)] = '$'
for i in range(16):
for j in range(16):
print(map[4 * (16 * i + j)],end = "")
print()
"""
1111111111111111
1000000000111111
1011111110111111
1011111110111111
101111000$000111
1011110111110111
1011110111110111
1011110000110111
1011111110110111
1011111110110111
10000#0000110111
1111101111110111
1111100000000111
1111111111111111
1111111111111111
1111111111111111
"""

读图法,得到迷宫路径为ddddwwwaaawwwddd

到这里可以合理猜测,输入的flag经过某种算法加密后,得到迷宫路径的正确答案,接下来就分析一下中间的算法是什么东西。

0x03 BLOWFISH

解出迷宫后再回来看看中间那里,猜测是什么算法(迫真猜测),用FindCrypt插件看看(下载地址

image-20240501114439626

去查查BLOWFISH是什么东西(摘抄)。

BLOWFISH加密解析

blowfish加密算法是一种对称的分组加密算法,对Plaintext进行分组,每组长度为64bit,而该加密的密钥为变长密钥,32bit-448bit都可以当作密钥进行加解密处理

密钥可以定义为Key数组,该数组的长度为[1, 14]

存在两个数组pBox和sBox,pBox为18个32位子密钥组成,sBox为4*256个32位子密钥组成,一般情况下,是用Π的小数点的16进制

BLOWFISH加密过程

  1. 传入明文和密钥K,密钥K的长度为32bit-448bit,密钥K数组的单位为32bit,所以得到K = [K1, K2, ... , Kn],其中1≤n≤14
  2. 初始化子密钥,子密钥分为1个pBox和4个sBox,pBox的长度为18,sBox的长度为256,一般情况下,该子密钥的初始化为Π的16进制
  3. 子密钥预处理pBox:这里密钥数组K的长度最大只有14,所以存在轮换使用密钥的情况,假设密钥数组K的长度为14,首先是pBox与密钥数组K进行异或:
    • pBox[0] ^ K[0]
    • pBox[1] ^ K[1]
    • pBox[13] ^ K[13]
    • pBox[14] ^ K[0]
  4. pBox变换:产生一个64bit全0的数据,然后调用BlowFish的主加密函数进行加密,得到一个64bit的数据替换pBox的数据,总共进行9轮,得到一个新的pBox
  5. sBox变换:此时沿用pBox中产生的leftpart和rightpart,再次通过BlowFish进行主加密函数进行加密,每次将处理后的leftpart和rightpart进行替换,替换得到新的sBox
  6. Plaintext加密:首先是对明文进行填充,若不足8个字节则填充至8个字节,填充为8的整数倍,然后分为前4字节与后4字节传入主加密函数进行加密,在主加密函数中进行加密得到最终的密文

主加密函数,总共为8轮加密,每轮都会从pBox中拿取64bit的数据分成2*32bit异或给leftpart和rightpart,然后再分别异或从sBox中查表得到的值

分析程序

1
2
3
4
5
6
7
8
9
10
srand(0xDEADBEEF);
for ( i = 0; i < 8; ++i )
key[i] = rand();
key[0] = 0;
v57[0] = 0x4853494668736966i64;
word_10057A9 = 6671;
word_10057AB = 13569;
byte_10057AD = 58;
byte_10057AE = 59;
byte_10057AF = 32;

这段代码用一个srand指定时间戳,然后用rand生成伪随机数来产生密钥,但是在调试后发现实际上的密钥并非如此,查了地址才发现,6671,13569等值紧随其后对生成的key进行了覆盖,把实际的密钥转化为16进制dump出来。

1
00 0F 1A 01 35 3A 3B 20

cyberchef网站的BLOWFISHFrom_Base64(‘HNO4klm6ij9n%2BJ2hyf0gzA8uvwDEq3X1Q7ZKeFrWcVTts/MRGYbdxSo%3DILaUpPBC5’,false,false/disabled)&input=ZGRkZHd3d2FhYXd3d2RkZA)加密

image-20240501130401853

包裹flag提交,发现错误,后来查资料才知道会填充多余的8位空格。删去最后8个字节得到答案

flag{db824ef8605c5235b4bbacfa2ff8e087}