starCTF部分题解

StarCTF

RE_NaCl(汇编, XTEA加密, 可逆加密算法)

这道题只看反编译很难看懂, 所以需要汇编 + 调试得到题目的大致逻辑

我们按照逻辑逐步调试

第一轮加密的data[]生成

首先是生成第一轮加密所需的data数组(元素类型为DWORD),

Untitled

然后跳转到生成data数组的代码块

Untitled

data生成完成后会有一个输入字符串分组逆序处理

随后来到第一部分的加密区域

第一部分加密

Untitled

可以看到循环左移的次数都是固定的, 将算法逻辑写下来就是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
输入: abcdefgh

分大组: abcdefgh 八个一组

分小组: abcd efgh

小组逆序: dcba(倒序一组) hgfe(倒序二组)

for i in range(0x2b):注意左移是循环左移
结果1 = (倒序一组 << 1) & (倒序一组 << 8) ^ (倒序一组 << 2) ^ 倒序二组(无左移) ^ (data + 4 * i)

结果2 = (结果1 << 1) & (结果1 << 8) ^ (结果1 << 2) ^ 倒序一组(无左移) ^ (data + 4 * i)

结果3 = (结果2 << 1) & (结果2 << 8) ^ (结果2 << 2) ^ 结果1 ^ (data + 4 * i)

结果4 = (结果3 << 1) & (结果3 << 8) ^ (结果3 << 2) ^ 结果2 ^ (data + 4 * i)

以此类推
.........
进行0x2b

其中的data就是前面生成的

Untitled

随后来到第二部分的加密

第二部分加密

其实就是XTEA加密, 但是当时只照着XXTEA的算法看了一下发现不是就没管了.(主要想着有TEA特征delta值, 但是这道题把delta给改了)

下面是一些分析

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
SFI:0000000008080180 ; START OF FUNCTION CHUNK FOR sub_8080900
SFI:0000000008080180
SFI:0000000008080180 loc_8080180: ; 加密结果高32
SFI:0000000008080180 mov eax, [r15-0Ch] ; 上次加密的结果B562C653
SFI:0000000008080184 shl eax, 4 ; 高密 << 4, 注意这里不是逻辑左移了, 溢出32直接没了
SFI:0000000008080184 ; 密文 << 4
SFI:0000000008080187 mov edx, eax
SFI:0000000008080189 mov eax, [r15-0Ch]
SFI:000000000808018D shr eax, 5 ; 高密 >> 5
SFI:000000000808018D ; 密文 >> 5
SFI:0000000008080190 xor edx, eax ; (高密 << 4) ^ (高密 >> 5)
SFI:0000000008080190 ; (密文 << 4) ^ (密文 >> 5)
SFI:0000000008080192 mov eax, [r15-0Ch]
SFI:0000000008080196 lea ecx, [rdx+rax] ; 高密 + ((高密 << 4) ^ (高密 >> 5))
SFI:0000000008080196 ; 密文 + ((密文 << 4) ^ (密文 >> 5))
SFI:0000000008080199 mov eax, [r15-10h] ; 0
SFI:0000000008080199 ; 10325476
SFI:000000000808019D and eax, 3 ; 0
SFI:000000000808019D ; eax = 2
SFI:00000000080801A0 lea rdx, ds:0[rax*4]
SFI:00000000080801A8 mov rax, [r15-18h] ; 这是一个内存存有000102030405060708090A....注意小端排序
SFI:00000000080801AC add rax, rdx
SFI:00000000080801AF mov eax, eax
SFI:00000000080801B1 mov edx, [r13+rax+0] ; rdx = 03020100
SFI:00000000080801B1 ; rdx = B0A09080
SFI:00000000080801B6 mov eax, [r15-10h] ; 0
SFI:00000000080801B6 ; 10325476
SFI:00000000080801BA add eax, edx ; 03020100
SFI:00000000080801BA ; 1B3C5D7E
SFI:00000000080801BC xor eax, ecx ; 03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5)))
SFI:00000000080801BC ; 1B3C5D7E ^ (密文 + ((密文 << 4) ^ (密文 >> 5)))
SFI:00000000080801BE xchg ax, ax
SFI:00000000080801C0 add [r15-8], eax ; 低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))
SFI:00000000080801C0 ; (低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) + (1B3C5D7E ^ (密文 + ((密文 << 4) ^ (密文 >> 5))))
SFI:00000000080801C4 mov eax, [r15-1Ch] ; 01234567, 小端排序rax = 10325476
SFI:00000000080801C4 ; 同上rax = 10325476
SFI:00000000080801C8 add [r15-10h], eax ;
SFI:00000000080801C8 ; [r15 - 10h] = 2 * 10325476 = 2064A8EC
SFI:00000000080801CC mov eax, [r15-8]
SFI:00000000080801D0 shl eax, 4 ; (低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) << 4
SFI:00000000080801D0 ; ((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) + (1B3C5D7E ^ (密文 + ((密文 << 4) ^ (密文 >> 5))))) << 4
SFI:00000000080801D3 mov edx, eax
SFI:00000000080801D5 mov eax, [r15-8]
SFI:00000000080801D9 shr eax, 5 ; (低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) >> 5
SFI:00000000080801D9 ; ((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) + (1B3C5D7E ^ (密文 + ((密文 << 4) ^ (密文 >> 5))))) >> 5
SFI:00000000080801DC xor edx, eax ; ((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) >> 5) ^ ((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) << 4)
SFI:00000000080801DC ; (((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) + (1B3C5D7E ^ (密文 + ((密文 << 4) ^ (密文 >> 5))))) >> 5) ^ (((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) + (1B3C5D7E ^ (密文 + ((密文 << 4) ^ (密文 >> 5))))) << 4)
SFI:00000000080801DE xchg ax, ax
SFI:00000000080801E0 mov eax, [r15-8] ; 低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))
SFI:00000000080801E0 ; (低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) + (1B3C5D7E ^ (密文 + ((密文 << 4) ^ (密文 >> 5))))
SFI:00000000080801E4 lea ecx, [rdx+rax] ; (低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) +(((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) >> 5) ^ ((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) << 4))
SFI:00000000080801E4 ; ((((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) + (1B3C5D7E ^ (密文 + ((密文 << 4) ^ (密文 >> 5))))) >> 5) ^ (((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) + (1B3C5D7E ^ (密文 + ((密文 << 4) ^ (密文 >> 5))))) << 4)) + ((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) + (1B3C5D7E ^ (密文 + ((密文 << 4) ^ (密文 >> 5)))))
SFI:00000000080801E7 mov eax, [r15-10h] ; 10325476
SFI:00000000080801E7 ; 2064A8EC
SFI:00000000080801EB shr eax, 0Bh ; 2064A
SFI:00000000080801EB ; 40C95
SFI:00000000080801EE mov eax, eax
SFI:00000000080801F0 and eax, 3 ; EAX = 2
SFI:00000000080801F0 ; EAX = 1
SFI:00000000080801F3 lea rdx, ds:0[rax*4] ; RDX = 8
SFI:00000000080801F3 ; RDX = 4
SFI:00000000080801FB mov rax, [r15-18h]
SFI:00000000080801FF nop
SFI:0000000008080200 add rax, rdx ; 数组地址 + 8
SFI:0000000008080200 ; 数组地址 + 4
SFI:0000000008080203 mov eax, eax
SFI:0000000008080205 mov edx, [r13+rax+0] ; 0B0A0908
SFI:0000000008080205 ; 07060504
SFI:000000000808020A mov eax, [r15-10h] ; 10325476
SFI:000000000808020A ; 2064A8EC
SFI:000000000808020E add eax, edx ; 10325476 + 0B0A0908
SFI:000000000808020E ; 2064A8EC + 07060504
SFI:0000000008080210 xor eax, ecx ; ((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) +(((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) >> 5) ^ ((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) << 4))) ^ (10325476 + 0B0A0908)
SFI:0000000008080210 ; (((((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) + (1B3C5D7E ^ (密文 + ((密文 << 4) ^ (密文 >> 5))))) >> 5) ^ (((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) + (1B3C5D7E ^ (密文 + ((密文 << 4) ^ (密文 >> 5))))) << 4)) + ((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) + (1B3C5D7E ^ (密文 + ((密文 << 4) ^ (密文 >> 5)))))) ^ (2064A8EC + 07060504)
SFI:0000000008080212 add [r15-0Ch], eax ; 高密 + (((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) +(((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) >> 5) ^ ((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) << 4))) ^ (10325476 + 0B0A0908))
SFI:0000000008080212 ; (高密 + (((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) +(((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) >> 5) ^ ((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) << 4))) ^ (10325476 + 0B0A0908))) + ((((((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) + (1B3C5D7E ^ (密文 + ((密文 << 4) ^ (密文 >> 5))))) >> 5) ^ (((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) + (1B3C5D7E ^ (密文 + ((密文 << 4) ^ (密文 >> 5))))) << 4)) + ((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) + (1B3C5D7E ^ (密文 + ((密文 << 4) ^ (密文 >> 5)))))) ^ (2064A8EC + 07060504))
SFI:0000000008080216 inc dword ptr [r15-4] ; i++
SFI:000000000808021A nop word ptr [rax+rax+00h]

我们需要抽取其中最关键的特征

  • (高密 << 4)
  • (高密 >> 5)
  • and eax, 3
  • mov eax, [r15-10h] 和 add eax, edx, 传给eax的是10325476, 这个就是delta

综上是可以判断其为XTEA算法, 有没有魔改可以通过调试验证. 这道题是没有魔改过的

最后是关键判断

关键判断

Untitled

查看一下

Untitled

通过调试得到了大体的逻辑, 接下来复现算法

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
#if 1
#define _CRT_SECURE_NO_WARNINGS
#include <stdint.h>
#include <stdio.h>
#include <math.h>

uint32_t rol(uint32_t num, uint32_t n);
void encode(uint32_t n1, uint32_t n2, uint32_t *mid);
void encipher(unsigned int num_rounds, uint32_t* high, uint32_t* low);

int main(void)
{
uint32_t input[8] = { 0x61626364, 0x65666768, 0x61626364, 0x65666768,
0x61626364, 0x65666768, 0x61626364, 0x65666768 };

uint32_t midput[8];

uint32_t i;

for (i = 0; i < 8; i += 2) {
encode(input[i], input[i + 1], midput + i);
//printf("第一部分加密:组%d:0x%x%x\n", i, tmp[i / 2] & 0xffff0000, tmp[i /2] & 0xffff);
}

for (i = 0; i < 4; i++) {
encipher(pow(2, i + 1), midput + (i * 2), midput + ((i * 2) + 1));

printf("0x%08x%08x\n", midput[i * 2], midput[(i * 2) + 1]);
}
}

uint32_t rol(uint32_t num, uint32_t n)
{
//uint32_t tmp = (num >> (32 - n));
//return ((num << n) & tmp);
return ((num << n) | ((num >> (32 - n))));
}

//可逆加密
void encode(uint32_t n1, uint32_t n2, uint32_t *mid)
{
uint32_t data[] = { 0x04050607, 0x00010203, 0x0C0D0E0F, 0x08090A0B,
0xCD3FE81B, 0xD7C45477, 0x9F3E9236, 0x0107F187, 0xF993CB81,
0xBF74166C, 0xDA198427, 0x1A05ABFF, 0x9307E5E4, 0xCB8B0E45,
0x306DF7F5, 0xAD300197, 0xAA86B056, 0x449263BA, 0x3FA4401B,
0x1E41F917, 0xC6CB1E7D, 0x18EB0D7A, 0xD4EC4800, 0xB486F92B,
0x8737F9F3, 0x765E3D25, 0xDB3D3537, 0xEE44552B, 0x11D0C94C,
0x9B605BCB, 0x903B98B3, 0x24C2EEA3, 0x896E10A2, 0x2247F0C0,
0xB84E5CAA, 0x8D2C04F0, 0x3BC7842C, 0x1A50D606, 0x49A1917C,
0x7E1CB50C, 0xFC27B826, 0x5FDDDFBC, 0xDE0FC404, 0xB2B30907 };

uint64_t result, i;

uint32_t xordata[2] = { n2, n1 };//初始化运算中轮换的异或数据

for (i = 0; i <= 0x2b; i++) {
result = ((rol(xordata[1], 1) & rol(xordata[1], 8)) ^ rol(xordata[1], 2)) ^ xordata[0] ^ data[i];
xordata[0] = xordata[1];
xordata[1] = result;
}
printf("0x%x%x\n", xordata[1], xordata[0]);

*mid = xordata[0];//这里是顺序变了
*(mid + 1) = xordata[1];
}

void encipher(unsigned int num_rounds, uint32_t *high, uint32_t *low) {
unsigned int i;

uint32_t key[4] = {0x03020100, 0x07060504, 0x0B0A0908, 0x0F0E0D0C};

uint32_t v0 = *high, v1 = *low;
uint32_t sum = 0, delta = 0x10325476;

for (i = 0; i < num_rounds; i++) {
v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
sum += delta;
v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]);
}

*high = v0; *low = v1;
}

接下来是解密算法

解密算法

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
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdint.h>
#include <math.h>

uint32_t rol(uint32_t num, uint32_t n);
void decipther(unsigned int num_runds, uint32_t* high, uint32_t* low);
void decode(uint32_t* mid);
void re_chr(uint32_t num);

int main(void)
{
uint32_t en_data[8] = {0xFDF5C266, 0x7A328286, 0xCE944004, 0x5DE08ADC, 0xA6E4BD0A, 0x16CAADDC, 0x13CD6F0C, 0x1A75D936};
unsigned int i;

for (i = 0; i < 4; i++) {
decipther(pow(2, i + 1), en_data + (2 * i), en_data + ((2 * i) + 1));
}

for (i = 0; i < 8; i+=2) {
decode(en_data + i);
printf("0x%x%x\n", en_data[i], en_data[i + 1]);
}

for (i = 0; i < 8; i++) {
re_chr(en_data[i]);
}

return 0;
}

uint32_t rol(uint32_t num, uint32_t n)
{
return ((num << n) | ((num >> (32 - n))));
}

void decipther(unsigned int num_rounds, uint32_t* high, uint32_t* low)
{
unsigned int i;

uint32_t key[4] = { 0x03020100, 0x07060504, 0x0B0A0908, 0x0F0E0D0C };

uint32_t v0 = *high, v1 = *low, delta = 0x10325476, sum = delta * num_rounds;
for (i = 0; i < num_rounds; i++) {
v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]);
sum -= delta;
v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
}
*high = v0; *low = v1;
}

void decode(uint32_t* mid)
{
uint32_t result, i;

uint32_t data[] = { 0x04050607, 0x00010203, 0x0C0D0E0F, 0x08090A0B,
0xCD3FE81B, 0xD7C45477, 0x9F3E9236, 0x0107F187, 0xF993CB81,
0xBF74166C, 0xDA198427, 0x1A05ABFF, 0x9307E5E4, 0xCB8B0E45,
0x306DF7F5, 0xAD300197, 0xAA86B056, 0x449263BA, 0x3FA4401B,
0x1E41F917, 0xC6CB1E7D, 0x18EB0D7A, 0xD4EC4800, 0xB486F92B,
0x8737F9F3, 0x765E3D25, 0xDB3D3537, 0xEE44552B, 0x11D0C94C,
0x9B605BCB, 0x903B98B3, 0x24C2EEA3, 0x896E10A2, 0x2247F0C0,
0xB84E5CAA, 0x8D2C04F0, 0x3BC7842C, 0x1A50D606, 0x49A1917C,
0x7E1CB50C, 0xFC27B826, 0x5FDDDFBC, 0xDE0FC404, 0xB2B30907 };

uint32_t xordata[2] = {*mid, *(mid + 1)};

for (i = 0; i <= 0x2b; i++) {
result = ((rol(xordata[0], 1) & rol(xordata[0], 8)) ^ rol(xordata[0], 2)) ^ xordata[1] ^ data[0x2b - i];
xordata[1] = xordata[0];
xordata[0] = result;
}

mid[0] = xordata[1];
mid[1] = xordata[0];
}

void re_chr(uint32_t num)
{
for (int i = 24; i >= 0; i -= 8)
printf("%c", (num & (0xff << i)) >> i);
}

input

得到了inputmM7pJIobsCTQPO6R0g-L8kFExhYuivBN

flag

CTF{mM7pJIobsCTQPO6R0g-L8kFExhYuivBN}

RE_SimpleFileSystem(可逆加密算法, 文件系统)

txt文档初始化后可以看到有很多的inode(译成中文为索引节点, 用于存放档案及目录的基本信息, 包含时间, 档名, 使用者及其群组, 只不过这道题简化成了编号)

使用IDA分析

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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
__int64 __fastcall main(int a1, char **a2, char **a3)
{
int v4; // eax
int *v5; // rax
char *v6; // rax
unsigned int v7; // eax
unsigned int v8; // eax
int i; // [rsp+18h] [rbp-1038h]
int j; // [rsp+1Ch] [rbp-1034h]
int v11; // [rsp+20h] [rbp-1030h]
int v12; // [rsp+24h] [rbp-102Ch]
unsigned int v13; // [rsp+28h] [rbp-1028h]
unsigned int v14; // [rsp+28h] [rbp-1028h]
int v15; // [rsp+28h] [rbp-1028h]
unsigned int v16; // [rsp+28h] [rbp-1028h]
unsigned int v17; // [rsp+28h] [rbp-1028h]
unsigned int v18; // [rsp+28h] [rbp-1028h]
unsigned int v19; // [rsp+28h] [rbp-1028h]
unsigned int v20; // [rsp+28h] [rbp-1028h]
int r_num1; // [rsp+2Ch] [rbp-1024h]
int r_num2; // [rsp+30h] [rbp-1020h]
time_t timer; // [rsp+38h] [rbp-1018h] BYREF
char s[16]; // [rsp+40h] [rbp-1010h] BYREF
char s1[1024]; // [rsp+440h] [rbp-C10h] BYREF
char nptr[1024]; // [rsp+840h] [rbp-810h] BYREF
char v27[1032]; // [rsp+C40h] [rbp-410h] BYREF
unsigned __int64 v28; // [rsp+1048h] [rbp-8h]

v28 = __readfsqword(0x28u);
if ( a1 != 3 ) // 如果参数不是三个则提示输入的格式
{
printf("use: %s <diskfile> <nblocks>\n", *a2);
return 1LL;
}
v4 = atoi(a2[2]);

if ( !(unsigned int)sub_55A3FD5D9BEC(a2[1], v4) )// 打开文件image.flag
{
v5 = __errno_location();
v6 = strerror(*v5);
printf("couldn't initialize %s: %s\n", a2[1], v6);
return 1LL;
}

v7 = sub_55A3FD5D9C9E();
printf("opened emulated disk image %s with %d blocks\n", a2[1], v7);
while ( 1 ) // shell的while循环
{
printf(" simplefs> "); // 打印输入提示符
fflush(stdout);
if ( !fgets(s, 1024, stdin) ) // 输入指令
break;
if ( s[0] != '\n' ) // 如果有输入内容
{
s[strlen(s) - 1] = 0; // 把结尾的'\n'改为'\0'
v11 = __isoc99_sscanf(s, "%s %s %s", s1, nptr, v27);// 输入三个参数(也可以不输入这么多, 这个只是最大值), 从0号开始检验
if ( v11 ) // 其中v11是成功录入的变量数量, 根据这个判断输入是否有效
{
if ( !strcmp(s1, "format") )
{
if ( v11 == 1 )
{
if ( (unsigned int)sub_55A3FD5D8357() )// 这里应该是为该系统分配磁盘, 在这里初始化了
puts("disk formatted."); // 磁盘格式化
else
puts("format failed!");
}
else
{
puts("use: format");
}
}
else if ( !strcmp(s1, "mount") ) // 挂载: 是指由操作系统使一个存储设备(诸如硬盘、CD-ROM或共享资源)上的计算机文件和目录可供用户通过计算机的文件系统访问的一个过程
{
if ( v11 == 1 )
{
if ( (unsigned int)sub_55A3FD5D87C2() )// 这里设置了inode_index为1
puts("disk mounted.");
else
puts("mount failed!");
}
else
{
puts("use: mount");
}
}
else if ( !strcmp(s1, "debug") )
{
if ( v11 == 1 )
sub_55A3FD5D84BB();
else
puts("use: debug");
}
else if ( !strcmp(s1, "getsize") )
{
if ( v11 == 2 )
{
v13 = atoi(nptr);
v12 = sub_55A3FD5D9014(v13);
if ( v12 < 0 )
puts("getsize failed!");
else
printf("inode %d has size %d\n", v13, (unsigned int)v12);
}
else
{
puts("use: getsize <inumber>");
}
}
else if ( !strcmp(s1, "create") )
{
if ( v11 == 1 )
sub_55A3FD5D816A();
else
puts("use: create");
}
else if ( !strcmp(s1, "delete") )
{
if ( v11 == 2 )
{
v14 = atoi(nptr);
if ( (unsigned int)sub_55A3FD5D8DD1(v14) )
printf("inode %d deleted.\n", v14);
else
puts("delete failed!");
}
else
{
puts("use: delete <inumber>");
}
}
else if ( !strcmp(s1, "cat") )
{
if ( v11 == 2 )
{
v15 = atoi(nptr);
if ( !(unsigned int)sub_55A3FD5D8017(v15, "/dev/stdout") )
puts("cat failed!");
}
else
{
puts("use: cat <inumber>");
}
}
else if ( !strcmp(s1, "captureflag") ) // 这个应该是个无用指令
{
puts("Are you kidding?");
}
else if ( !strcmp(s1, "plantflag") ) // 可疑代码
{
v8 = time(&timer); // 记录现在的时间戳到v8和timer中
srand(v8); // 随机数播种
r_num1 = rand() % 100; // 100以内的随机数
r_num2 = rand() % 100; // 同上
for ( i = 0; i < r_num1; ++i ) // 循环随机的次数
{
v16 = sub_55A3FD5D816A();
if ( (unsigned int)sub_55A3FD5D7E16("flag", v16, 2) )
printf("copied file %s to inode %d\n", nptr, v16);
else
puts("copy failed!");
}

v17 = sub_55A3FD5D816A();
if ( (unsigned int)sub_55A3FD5D7E16("flag", v17, 1) )
printf("plant flag to inode %d!\n", v17);
else
puts("copy failed!");

for ( j = 0; j < r_num2; ++j )
{
v18 = sub_55A3FD5D816A();
if ( (unsigned int)sub_55A3FD5D7E16("flag", v18, 2) )
printf("copied file %s to inode %d\n", nptr, v18);
else
puts("copy failed!");
}
}
else if ( !strcmp(s1, "copyin") )
{
if ( v11 == 3 )
{
v19 = atoi(v27);
if ( !(unsigned int)sub_55A3FD5D7E16(nptr, v19, 0) )
goto LABEL_69;
printf("copied file %s to inode %d\n", nptr, v19);
}
else
{
puts("use: copyin <filename> <inumber>");
}
}
else if ( !strcmp(s1, "copyout") )
{
if ( v11 == 3 )
{
v20 = atoi(nptr);
if ( (unsigned int)sub_55A3FD5D8017(v20, v27) )
printf("copied inode %d to file %s\n", v20, v27);
else
LABEL_69:
puts("copy failed!");
}
else
{
puts("use: copyout <inumber> <filename>");
}
}
else if ( !strcmp(s1, "help") )
{
puts("Commands are:");
puts(" format");
puts(" mount");
puts(" debug");
puts(" create");
puts(" delete <inode>");
puts(" cat <inode>");
puts(" captureflag");
puts(" plantflag");
puts(" copyin <file> <inode>");
puts(" copyout <inode> <file>");
puts(" help");
puts(" quit");
puts(" exit");
}
else
{
if ( !strcmp(s1, "quit") || !strcmp(s1, "exit") )
break;
printf("unknown command: %s\n", s1);
puts("type 'help' for a list of commands.");
}
}
}
}
puts("closing emulated disk.");
sub_55A3FD5D9E6B();
return 0LL;
}

可以看到跟shell一样, 我们要确定大致的分析方向, 知道哪个指令是比较关键

可以看到前两个指令都是初始化, 后面的debug, create, delete, cat, copyin, copyout, help, quit, exit都是关于文件操作的指令, 不重要. 所以排除法得到了两个关键的指令captureflag 和 plantflag. 而通过分析法相captureflag是一个无用指令, 所以我们可以锁定关键的指令就是plantflag.

同时我们通过调试发现所有索引上的数据都是通过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
else if ( !strcmp(s1, "plantflag") )    // 可疑代码
{
v8 = time(&timer); // 记录现在的时间戳到v8和timer中
srand(v8); // 随机数播种
r_num1 = rand() % 100; // 100以内的随机数
r_num2 = rand() % 100; // 同上
for ( i = 0; i < r_num1; ++i ) // 循环随机的次数
{
v16 = sub_55A3FD5D816A();
if ( (unsigned int)sub_55A3FD5D7E16("flag", v16, 2) )
printf("copied file %s to inode %d\n", nptr, v16);
else
puts("copy failed!");
}

v17 = sub_55A3FD5D816A();
if ( (unsigned int)sub_55A3FD5D7E16("flag", v17, 1) )
printf("plant flag to inode %d!\n", v17);
else
puts("copy failed!");

for ( j = 0; j < r_num2; ++j )
{
v18 = sub_55A3FD5D816A();
if ( (unsigned int)sub_55A3FD5D7E16("flag", v18, 2) )
printf("copied file %s to inode %d\n", nptr, v18);
else
puts("copy failed!");
}
}

而查看其代码内部可以发现两个可疑函数sub_55A3FD5D816A()sub_55A3FD5D7E16().

经过调试以后我们在sub_55A3FD5D7E16()中发现一个加密函数sub_55A3FD5D81B2((__int64)str, len);

Untitled

跟进查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
__int64 __fastcall sub_55A3FD5D81B2(__int64 a1, int a2)
{
int i; // [rsp+10h] [rbp-10h]
int xordata; // [rsp+14h] [rbp-Ch]
_BYTE *str2; // [rsp+18h] [rbp-8h]

xordata = sub_55A3FD5D90A3();
for ( i = 0; i < a2; ++i )
{
str2 = (_BYTE *)(i + a1);
*str2 = (*str2 >> 1) | (*str2 << 7);
*str2 ^= xordata;
*str2 = ((unsigned __int8)*str2 >> 2) | (*str2 << 6);
*str2 ^= BYTE1(xordata);
*str2 = ((unsigned __int8)*str2 >> 3) | (32 * *str2);
*str2 ^= BYTE2(xordata);
*str2 = ((unsigned __int8)*str2 >> 4) | (16 * *str2);
*str2 ^= HIBYTE(xordata);
*str2 = (*str2 >> 5) | (8 * *str2); // str2 << 3
}
return 0LL;
}

我们在这个函数下下断点

可以看到加密的过程

Untitled

经过加密后的数据

Untitled

我们知道flag的前四个字符就是*CTF, 所以可以通过这四个字符加密过后的数据在image文件中查找并得到加密后的flag

我们在HexEditor中查找0x00, 0xd2, 0xfc, 0xd8

Untitled

将数据dump下来并解密

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
#if 1
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdint.h>
#include <string.h>

void decode(uint8_t* output, unsigned int n);

int main(void)
{
uint8_t output[] = { 0x00, 0xd2, 0xfc, 0xd8, 0xa2, 0xda, 0xba, 0x9e, 0x9c, 0x26,
0xf8, 0xf6, 0xb4, 0xce, 0x3c, 0xcc,0x96, 0x88, 0x98, 0x34,
0x82, 0xde, 0x80, 0x36, 0x8a, 0xd8, 0xc0, 0xf0, 0x38, 0xae,
0x40 };
decode(output, sizeof(output));

return 0;
}

void decode(uint8_t* output, unsigned int n)
{
uint32_t xordata[4] = { 0xDEEDBEEF, 0xDEEDBE, 0xDEED, 0xDE};
uint8_t tmp;

for (int i = 0; i < n; i++) {
tmp = *(output + i);
tmp = (tmp << 5) | (tmp >> 3);
tmp ^= xordata[3];
tmp = (tmp << 4) | (tmp >> 4);
tmp ^= xordata[2];
tmp = (tmp << 3) | (tmp >> 5);
tmp ^= xordata[1];
tmp = (tmp << 2) | (tmp >> 6);
tmp ^= xordata[0];
tmp = (tmp << 1) | (tmp >> 7);
printf("%c", tmp);
}
printf("\n");
}
#endif

即可得到flag

flag

CTF{Gwed9VQpM4Lanf0kEj1oFJR6}

RE_Jump(setjump和longjump打乱逻辑顺序, Burrows-Wheeler压缩算法)

关于setjump和longjump更加详细的介绍可以看CSAPP第八章的最靠后的部分, 里面有讲到setjump和longjump

进入IDA分析可以发现是去符号表了, 但是还是可以分析的.

我们同通过字符串查找找到了核心函数

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
__int64 __fastcall sub_40293E(__int64 a1, __int64 a2)
{
int v2; // edx
int v3; // ecx
int v4; // r8d
int v5; // r9d
int v7; // edx
int v8; // ecx
int v9; // r8d
int v10; // r9d
__int64 v11; // rdx
int v12; // edx
int v13; // ecx
int v14; // r8d
int v15; // r9d
int v16; // edx
int v17; // ecx
int v18; // r8d
int v19; // r9d
__int64 v20; // rdx
int v21; // ecx
int v22; // r8d
int v23; // r9d
__int64 v24; // rdx
int v25; // edx
int v26; // ecx
int v27; // r8d
int v28; // r9d
int v29; // edx
int v30; // ecx
int v31; // r8d
int v32; // r9d
char v33; // [rsp+0h] [rbp-10h]
char v34; // [rsp+0h] [rbp-10h]
char v35; // [rsp+0h] [rbp-10h]
char v36; // [rsp+0h] [rbp-10h]
char v37; // [rsp+0h] [rbp-10h]
int v38; // [rsp+Ch] [rbp-4h]
int v39; // [rsp+Ch] [rbp-4h]
int v40; // [rsp+Ch] [rbp-4h]
int v41; // [rsp+Ch] [rbp-4h]

sub_419570(&input); // scanf
v38 = setjmp(&status, a2, v2, v3, v4, v5, v33);
if ( !v38 )
sub_402689(); // (0)
if ( v38 == 1 )
return 1LL;
input23_addr = mb_malloc(len + 1, 1uLL);
if ( !setjmp(&status, 1LL, v7, v8, v9, v10, v34) )// (1) 运行到了这里随后向下继续运行到了下一个setjmp
longjmp(&off_4C9460);
qword_4C9408 = sub_427140(8LL * len, 1LL, v11);

v39 = setjmp(&status, 1LL, v12, v13, v14, v15, v35);// (2) 设下坐标setjmp, 初始化了1
// (4) 在为input_change申请完内存空间后又回到了这里
if ( !v39 )
longjmp(&off_4C9460);
if ( v39 <= len )
longjmp(&off_4C9460);
sub_401F62(qword_4C9408, 1uLL);
v40 = setjmp(&status, 1LL, v16, v17, v18, v19, v36);
if ( !v40 )
sub_402826(&status, 1LL, v20, v21, v22, v23);// ******
if ( v40 <= len )
longjmp(&off_4C9540);
sub_427730(qword_4C9408, 1LL, v20);
sub_427730(input23_addr, 1LL, v24); // 真正的变换在这里
v41 = setjmp(&status, 1LL, v25, v26, v27, v28, v37);
if ( !v41 )
longjmp(&off_4C9540);
if ( v41 == 1 )
sub_411920("*CTF{%s}\n", &input, v29, v30, v31, v32);// 其实看到这里的时候就不应该把关注的重点放在input上面了, 因为如果对其变换的话这里的打印就不是我们输入的明文了
return 0LL;
}

setjmp会将当前的上下文保存在status中, 而longjmp会跳回最近一次的setjmp并将其参数作为setjmp的返回值.

所以看起来这个函数中只有if语句, 其实是有很多的for语句的.

还需要注意的是longjmp不能F8步过, 相当于把直接跳过了for循环的所有语句了, 只能F7步入然后跳转到相应的地方才能进行分析

最后得到了其加密算法是Burrows-Wheeler, 是单纯的换序算法.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
s = '\03jmGn_=uaSZLvN4wFxE6R+p\02D2qV1CBTck'
lst = [[0]] * 34
#定义最后一列
for i in range(34):
lst[i] = s[i]

tmp = [[0]] * 34

for i in range(34):
tmp[i] = s[i]

for k in range(34):
lst.sort()
for i in range(34):
lst[i] = tmp[i] + lst[i]
#最后是按行打印
print(lst)
文章作者: LamのCrow
文章链接: http://example.com/2022/04/23/StarCTF(汇编, XTEA加密, 可逆加密算法) d2c4611ee19c445694e9c66ea5eb4eb6/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 LamのCrow