0x00 题目分析

父进程开启一个子进程并接收子进程的异常信息,根据这些信息来执行程序流

由于这个开启的子进程是它本身,所以子进程的代码实际上是可以在程序中找到的(不过加了密,需要dump下来解密先)

但实际上没有必要去看子进程的解密结果,只要直接调父进程就可以就可以得到程序的执行流程了

0x01 解

无壳,直接拖IDA

符号加载还算比较完全,基本可以看懂在干什么,这个A10就是主函数

因为opcode不能直接获取,所以这题动调才能搞清楚流程

注意限制了输入长度

之后单步调试可以发现到了这个地方

关于ReadProcessMemory可以看微软的文档

查看ProcessInformation可以发现这个ReadProcessMemory的目标就是与题目同名的一个程序

之后根据lpBaseAddress可以找到opcode的位置,大小是0xD6

解密只是一个简单的异或,可以写个脚本解析一下

1
2
3
4
5
6
7
8
9
with open("./opcode_enc",'rb') as f:
opcode = bytearray(f.read())
print(opcode)
opcode_dec = b""
for i in range(len(opcode)):
opcode[i] ^= 0x44
print(opcode)
with open('opcode_deced', 'wb') as fi:
fi.write(opcode)

不过有一说一解析出来没啥大用,因为可以通过动调的方式来看清楚程序流

继续上面的动调流程,这里可以看到先把输入与0x7D进行异或

之后是一个移位运算

再之后会到这个函数里面

进去会发现是这样的(这里改了下变量名方便查看)

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
int __cdecl sub_E22420(int entered, int entered_len, int key, int key_len)
{
int result; // eax
char tmp; // [esp+D3h] [ebp-155h]
char tmp1; // [esp+D3h] [ebp-155h]
int index; // [esp+F4h] [ebp-134h]
int i; // [esp+100h] [ebp-128h]
int j; // [esp+100h] [ebp-128h]
int k; // [esp+100h] [ebp-128h]
unsigned __int8 index1; // [esp+10Fh] [ebp-119h]
unsigned __int8 index2; // [esp+11Bh] [ebp-10Dh]
char table[260]; // [esp+124h] [ebp-104h]

__CheckForDebuggerJustMyCode(&unk_E2D0AC);
for ( i = 0; i < 256; ++i )
table[i] = i;
index = 0;
for ( j = 0; j < 256; ++j )
{
index = (*(unsigned __int8 *)(key + j % key_len) + index + (unsigned __int8)table[j]) % 256;
tmp = table[j];
table[j] = table[index];
table[index] = tmp;
}
index2 = 0;
index1 = 0;
for ( k = 0; ; ++k )
{
result = k;
if ( k >= entered_len )
break;
index1 += table[++index2];
tmp1 = table[index2];
table[index2] = table[index1];
table[index1] = tmp1;
*(_BYTE *)(k + entered) ^= table[((unsigned __int8)table[index1] + (unsigned __int8)table[index2]) % 256];
}
return result;
}

首先获取一张0~255的表,然后将表根据key进行置换,置换后进入一个循环,循环内先进行表的置换,再将置换结果与输入进行异或

这是个RC4

RC4解密比较重要的就是获取与明文异或的值,这个可以使用明文和密文来获取,即将密文与明文逐位异或即可获取进行异或的key

注意这里的明文是经过了上面的所有运算之后的结果,并非是一开始的输入

所以整个小脚本来处理

1
2
3
4
5
6
7
8
9
10
enc = bytearray(0)
plain = bytearray(0)
with open('./enc','rb') as f:
enc = bytearray(f.read())
with open ('./plain','rb') as f:
plain = bytearray(f.read())
for i in range(len(enc)):
plain[i] ^= enc[i]
print(plain.hex())
#4b8aa9e199284d4b22b7bd8d5a49350063b6c56b6194a503

之后再继续动调,发现已经到了比较了

因此把密文dump下来之后就可以解密了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import struct
enc = bytearray(0)
with open("./encry","rb") as f:
enc = bytearray(f.read())
enc = bytearray(struct.unpack("i"*25,enc))
key = bytes.fromhex("4b8aa9e199284d4b22b7bd8d5a49350063b6c56b6194a503")
for i in range(len(key)):
enc[i]^=key[i]
print(enc)
for i in range(len(enc)):
enc[i] = (enc[i] >> 6) | (enc[i] << 2) & 0xff
for i in range(len(enc)):
enc[i] ^= 0x7D
print(bytes(enc))
# b'RCTF{a_baby_debug_bloker}'