前言

掌握一道题目的最好办法就是由做题人变成出题人()

本文所说的题目是长城杯某区逆向Time_Machine,题目反编译纯gs,所以本文主要讲解笔者出的题目

:笔者出的题目准备给iscc擂台的,但是没轮上(感觉擂台被控了,怎么说hh)

出题思路

额,不算是思路,站在前人的肩膀上罢了。
出这道题目的初心是因为比赛的时候用心的做了这道题写了出来感觉收获颇多,但当时只局限于写了出来,并不能全面理解,所以有了这么一个想法。
俗话说,实践出真知,熟能生巧,自己去亲手实操将题目敲一遍写一遍,对一个知识点理解的才到位,记忆也更加的深刻。
主要加密算法是使用 SuperFastHash 算法对flag逐字节进行哈希处理,然后通过改变环境变量来进入不同的分支,分支里面对内存进行复写和触发异常来实现父子进程的交互,子进程是一大坨代码块,通过以下指令块控制父进程调试子进程实现加密。

1
2
3
4
5
movabs r11,%d
xor r11,0x1337
ror r11,13
movabs r13,1 #0或1
ud2

程序分析

程序是64位的exe
ida64分析
拖入ida64后映入眼帘,有一个小的主函数,用于检查环境变量的值,ix221iscc221如果为 null,则继续执行sub_401C38函数,否则调用函数sub_40195C函数
也就是相当于一个父进检测
检验的方式是通过一个环境变量,过掉的方式很简单,jz指令出下断点,然后改zf标志位即可过掉
1

sub_401C38-输入函数

进入sub_401C38函数,它打印,然后接受用户输入,然后将内存中的一个区域复制到中间内存,分配一个新的区域,然后将中间内存复制到新分配的区域。(这边的内存就是父进程里面开的子进程并且调试)
这个区域是一个 shell 代码,最后调用这个 shell 代码,用作用户输入作为参数。也就是说输入函数是通过子进程进行调用的,并且VirtualAlloc(0x22A6ui64), — 开辟的shellcode的长度0x22a6.

debugger

重新在main分支的地方下断点,过掉jz,然后手动进子进程分支,看看结尾出函数指针在干嘛
2
f7进入shellcode
3
这儿有一大堆代码块,调一下,发现此块块执行以下操作:

将用户输入的第一个字节加载到r12b寄存器中
将一个奇怪的值与 0x1337 进行异或
将结果右移 0xD 位
将r13寄存器设置为0
执行ud2定义的“未定义”操作码会使程序崩溃!
也就是说
从输入的flag里取一个字节,也没发现有什么校验逻辑,但注意到ud2指令(0F 0B),运行到该指令会触发异常
由于父进程在调试子进程,所以分析父进程的操作逻辑。

sub_40195C-父进程

设置环境变量ix221iscc221=1,然后开一个子进程并调试,子进程进输入分支()
222
当调试出现异常时去看输出Correct Flag :)\n的函数
superhash算法逐字节加密
333
断点调试一下

发现了0f 0b
检查这两个字节是否为0F 0B,很巧,刚好是子进程中的ud2,很明显这里在捕获子进程在ud2指令出错.
6
接下来就是看加密flag逻辑,可以看到
取r12b,进行加密操作后结果与r11d校验,最后检查r13是否为1,两个校验都通过,计数器+1,计数器等于0x18时输出正确(flag长度为0x18)

sub_401584 - enc function

解法一:梭哈!

因为是一个一个字节的输入,中间只会case 1
直接把1字节的256种结果全部算出来,后面梭哈就好
7
调回子进程的shellcode
8
现在就知道子进程在干嘛了,先从flag取一个字节到r12b,再计算r11d,给r13赋值,前面分析父进程知道只有在r13==1时才进行校验,用idapython解析一下,在r13==1时查表r11d得到r12b,即得到flag的一个字节
exp

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
import idc

m = {}
for c in range(256):
l1 = 1
l2 = ((c + l1) << 10) ^ (c + l1)
l1 = (l2 >> 1) + l2
l3 = (((8 * l1) ^ l1) >> 5) + ((8 * l1) ^ l1)
l3 &= 0xFFFFFFFF
l4 = (((16 * l3) ^ l3) >> 17) + ((16 * l3) ^ l3)
l4 &= 0xFFFFFFFF
r = (l4 << 25) ^ l4
r &= 0xFFFFFFFF
r = (r >> 6) + r
r &= 0xFFFFFFFF
print(hex(c), hex(r))
m[r] = c

start = 0x1E0000
end = start + 0x22a6 #VirtualAlloc

ea = start
while (ea < end):
# mov r12b, [rcx+i]
ins_len = idc.create_insn(ea)
ins = idc.generate_disasm_line(ea, 0)
if (ins == "retn"):
break
ea += ins_len
# mov r11, k
ins_len = idc.create_insn(ea)
ins = idc.generate_disasm_line(ea, 0)
k1 = idc.print_operand(ea, 1)
k1 = int("0x"+k1[:-1], 16)
ea += ins_len
# xor r11, k
ins_len = idc.create_insn(ea)
ins = idc.generate_disasm_line(ea, 0)
k2 = idc.print_operand(ea, 1)
k2 = int("0x"+k2[:-1], 16)
ea += ins_len
# ror r11, k
ins_len = idc.create_insn(ea)
ins = idc.generate_disasm_line(ea, 0)
k3 = idc.print_operand(ea, 1)
k3 = int("0x"+k3[:-1], 16)
ea += ins_len
r11 = (((k1 ^ k2) >> k3) | ((k1 ^ k2) << (64 - k3))) & 0xFFFFFFFFFFFFFFFF
# mov r13, 0/1
ins_len = idc.create_insn(ea)
ins = idc.generate_disasm_line(ea, 0)
r13 = idc.print_operand(ea, 1)
r13 = int(r13)
ea += ins_len
# ud2
ins_len = idc.create_insn(ea)
ins = idc.generate_disasm_line(ea, 0)
ea += ins_len
if (r13 == 1):
print(chr(m[r11]), end='')

得解
9

解法二:正常解法

这个加密算法是使用 SuperFastHash 算法对输入的每个字符进行散列,然后与一些硬编码的散列进行比较。
加密源码

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
uint32_t hash(char *a1)
{
uint32_t v2;
uint32_t v3;
uint32_t v4;
uint32_t v5;
uint32_t v6;
uint32_t v7;
uint32_t v8;
uint32_t v9;
uint32_t v10;
uint32_t v11;
uint32_t i;
unsigned char* v13;
v13 = a1;
v4 = 1;
v2 = v4 & 3;
for (i = v4 >> 2; i > 0; --i)
{
v5 = (v13[1] << 8) + *v13 + v4;
v3 = v5 ^ (((v13[3] << 8) + v13[2]) << 11);
v13 += 4;
v4 = (((v5 << 16) ^ v3) >> 11) + ((v5 << 16) ^ v3);
}
switch (v2)
{
case 2:
v8 = (v13[1] << 8) + *v13 + v4;
v4 = (((v8 << 11) ^ v8) >> 17) ^ (v8 << 11) ^ v8;
break;
case 3:
v6 = (v13[1] << 8) + *v13 + v4;
v7 = (v13[2] << 18) ^ (v6 << 16) ^ v6;
v4 = (v7 >> 11) + v7;
break;
case 1:
v9 = ((*v13 + v4) << 10) ^ (*v13 + v4);
v4 = (v9 >> 1) + v9;
break;
}
v10 = (((8 * v4) ^ v4) >> 5) + ((8 * v4) ^ v4);
v11 = (((16 * v10) ^ v10) >> 17) + ((16 * v10) ^ v10);
return (((v11 << 25) ^ v11) >> 6) + ((v11 << 25) ^ v11);
}

常规解题方法步骤
1、提取shellcode代码块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
with open("instructions.txt") as f:
lines = f.readlines()

blocks = []
block = []

for line in lines:
line = line.strip()
if line:
block.append(line)
if line == "ud2":
blocks.append(block)
block = []

with open("1.txt", "w") as out_file:
for b in blocks:
if "mov r13, 1" in b[4]:
out_file.write("\n".join(b) + "\n")

2、筛选出正确的代码块,得到正确密文的hash

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
def extract_ciphers(filename):
try:
with open(filename) as f:
lines = f.readlines()
except FileNotFoundError:
print("Error: File not found")
return []

ciphers = []
for i in range(0, len(lines), 6):
block = lines[i:i+6]

# 检查块的长度是否足够
if len(block) < 2:
print("Error: Block does not have enough lines")
continue

# 提取第二行中的十六进制数值
line = block[1].strip()
start_index = line.find("r11, ") + len("r11, ")
end_index = line.find("h", start_index)
if start_index != -1 and end_index != -1:
cipher_str = line[start_index:end_index]
try:
cipher_value = int(cipher_str, 16)
cipher_value ^= 0x1337
res = ror(cipher_value, 0xd, 64)
ciphers.append(res)
except ValueError:
print("Error: Invalid hex value")

return ciphers

def ror(val, r_bits, max_bits):
return ((val & (2**max_bits-1)) >> r_bits%max_bits) | \
(val << (max_bits-(r_bits%max_bits)) & (2**max_bits-1))

def main():
ciphers = extract_ciphers("clean_diassembly.txt")
if ciphers:
print("Ciphers:", ciphers)

if __name__ == "__main__":
main()

3、hash解密

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

uint32_t hash(char* a1)
{
uint32_t v2;
uint32_t v3;
uint32_t v4;
uint32_t v5;
uint32_t v6;
uint32_t v7;
uint32_t v8;
uint32_t v9;
uint32_t v10;
uint32_t v11;
uint32_t i;
char* v13;
v13 = a1;
v4 = 1;
v2 = v4 & 3;
for (i = v4 >> 2; i > 0; --i)
{
v5 = (v13[1] << 8) + *v13 + v4;
v3 = v5 ^ (((v13[3] << 8) + v13[2]) << 11);
v13 += 4;
v4 = (((v5 << 16) ^ v3) >> 11) + ((v5 << 16) ^ v3);
}
switch (v2)
{
case 2:
v8 = (v13[1] << 8) + *v13 + v4;
v4 = (((v8 << 11) ^ v8) >> 17) ^ (v8 << 11) ^ v8;
break;
case 3:
v6 = (v13[1] << 8) + *v13 + v4;
v7 = (v13[2] << 18) ^ (v6 << 16) ^ v6;
v4 = (v7 >> 11) + v7;
break;
case 1:
v9 = ((*v13 + v4) << 10) ^ (*v13 + v4);
v4 = (v9 >> 1) + v9;
break;
}
v10 = (((8 * v4) ^ v4) >> 5) + ((8 * v4) ^ v4);
v11 = (((16 * v10) ^ v10) >> 17) + ((16 * v10) ^ v10);
return (((v11 << 25) ^ v11) >> 6) + ((v11 << 25) ^ v11);
}

int main()
{
uint32_t flag_hashes[] = { 3250317036, 2059931180, 831843374, 831843374, 2137638942, 291415938, 3060405360, 51373921, 291415938, 1891737825, 2577271396, 2682404089, 1319470528, 291415938, 2577271396, 2376513170, 291415938, 2685652659, 1867828354, 1457933662, 260209567, 2240464916, 3927678806, 2884595695 };
char val[2] = { 0 };
uint32_t all_hashes[256] = { 0 };
for (int i = 0; i < 256; i++)
{
val[0] = i;
all_hashes[i] = hash(val);
}
for (int i = 0; i < 39; i++)
{
for (int j = 0; j < 256; j++)
{
if (flag_hashes[i] == all_hashes[j])
{
printf("%c", j);
break;
}
}
}
return 1;
}

结语

希望本文能让大家对于父子进程的理解更好一点点,哈哈哈
还是那句话,掌握一道题目的最好办法就是由做题人变成出题人。

附录

如有想要实验的,附源码一份

如有需要,细节联系我

题目源码

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
#include <cstdio>
#include <cstdlib>
#include <cstdio>
#include <cstdlib>
#include <windows.h>
#include <unordered_map>
#include <assert.h>

int counter = 0;
#if !defined (Get16Bits)
#define Get16Bits(d) ((((UINT32)(((const UINT8*)(d))[1])) << 8)\
+(UINT32)(((const UINT8*)(d))[0]) )
#endif

SIZE_T StringLengthA(LPCSTR String)
{
LPCSTR String2;

for (String2 = String; *String2; ++String2);

return (String2 - String);
}

UINT32 HashStringSuperFastHashA(PCHAR String)
{
INT Length = StringLengthA(String);
//printf("[+] Len=%d\n", Length);
UINT32 Hash = Length;
INT Tmp = 0;

INT Rem = Length & 3;
Length >>= 2;

for (; Length > 0; Length--)
{
Hash += Get16Bits(String);
Tmp = (Get16Bits(String + 2) << 11) ^ Hash;
Hash = (Hash << 16) ^ Tmp;
#pragma warning( push )
#pragma warning( disable : 6305)
String += 2 * sizeof(UINT16);
#pragma warning( pop )
Hash += Hash >> 11;
}

switch (Rem)
{
case 3:
{
Hash += Get16Bits(String);
Hash ^= Hash << 16;
Hash ^= ((UCHAR)String[sizeof(UINT16)]) << 18;
Hash += Hash >> 11;
break;
}
case 2:
{
Hash += Get16Bits(String);
Hash ^= Hash << 11;
Hash ^= Hash >> 17;
break;
}
case 1:
{
Hash += (UCHAR)*String;
Hash ^= Hash << 10;
Hash += Hash >> 1;
}
}

Hash ^= Hash << 3;
Hash += Hash >> 5;
Hash ^= Hash << 4;
Hash += Hash >> 17;
Hash ^= Hash << 25;
Hash += Hash >> 6;

return Hash;
}
static void HandleException(
const DEBUG_EVENT& ev,
HANDLE ph,
HANDLE th) {

CONTEXT ctx{};
ctx.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER;
BOOL ret = GetThreadContext(th, &ctx);
assert(ret);

BYTE buf[2]{};
BYTE nops[] = { 0x90,0x90 };
BYTE ret_nop[] = { 0xc3,0x90 };
SIZE_T actually_read = 0;
ret = ReadProcessMemory(
ph, (void*)ctx.Rip, buf, 2, &actually_read);
assert(ret);
char str_x[2] = { 0 };
if (buf[0] == 0x0f && buf[1] == 0x0b) {
//printf("UD2 Found\n");
str_x[0] = ctx.R12 % 256;
UINT32 hash = HashStringSuperFastHashA(str_x);
//printf("%d , %p , %c\n", ctx.R13, ctx.R11, str_x[0]);
if (hash == ctx.R11 && ctx.R13 == 1)
{
//printf("%c,%d\n", ctx.R12 % 256, counter);
counter++;
}
if (counter == 24)
{
printf("Correct Flag :)\n");
ret = WriteProcessMemory(
ph, (void*)ctx.Rip, ret_nop, 2, &actually_read);
SetThreadContext(th, &ctx);
}
if (counter != 24)
{
ret = WriteProcessMemory(
ph, (void*)ctx.Rip, nops, 2, &actually_read);
SetThreadContext(th, &ctx);
}

}

}

int Debugger(char* file_name) {
//puts("Debugger");
_putenv("ix221iscc221=1");

STARTUPINFO si{};
si.cb = sizeof(si);
PROCESS_INFORMATION pi{};
BOOL ret = CreateProcess(
file_name, NULL,
NULL, NULL,
FALSE,
DEBUG_PROCESS,
NULL,
NULL,
&si, &pi);

DEBUG_EVENT ev;
std::unordered_map<DWORD, HANDLE> threads;

threads[pi.dwThreadId] = pi.hThread;

bool the_end = false;
while (!the_end) {

ret = WaitForDebugEvent(&ev, INFINITE);
if (!ret) {
printf("Weird failed: %u\n", GetLastError());
return 1;
}

switch (ev.dwDebugEventCode) {
case EXCEPTION_DEBUG_EVENT:

HandleException(ev, pi.hProcess, threads[ev.dwThreadId]);
break;

case EXIT_PROCESS_DEBUG_EVENT:
the_end = true;
break;

case CREATE_THREAD_DEBUG_EVENT:
threads[ev.dwThreadId] = ev.u.CreateThread.hThread;
break;

case EXIT_THREAD_DEBUG_EVENT:
CloseHandle(threads[ev.dwThreadId]);
threads.erase(ev.dwThreadId);
break;

case CREATE_PROCESS_DEBUG_EVENT:
CloseHandle(ev.u.CreateProcessInfo.hFile);
CloseHandle(ev.u.CreateProcessInfo.hProcess);
CloseHandle(ev.u.CreateProcessInfo.hThread);
break;

case LOAD_DLL_DEBUG_EVENT:
case UNLOAD_DLL_DEBUG_EVENT:
break;

default:
printf("Weird: %u\n", ev.dwDebugEventCode);
break;
}

ContinueDebugEvent(
ev.dwProcessId, ev.dwThreadId, DBG_CONTINUE);
}



CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);


return 0;
}


int Debuggee() {

unsigned char input[25] = { 0 };
printf("Enter The Flag:\n");
scanf("%s", &input);
unsigned char data[] =
{ 0x44...... }; // shellcode data块 省略
// ;
long size = sizeof(data);
PVOID image;
image = VirtualAlloc(NULL, size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
memcpy(image, data, size);
void (*foo)(unsigned char*) = (void(*)(unsigned char*))image;
foo(input);
return 0;
}

int main(int argc, char** argv) {

if (getenv("ix221iscc221") == nullptr) {
return Debugger(argv[0]);
}
else {
return Debuggee();
}
}