Ikoct的饮冰室

你愿意和我学一辈子二进制吗?

0%

强网杯S9 tradre Writeup

终于审完wp可以放了(

父子进程通过ptrace进行通信, 子进程通过int 3向父进程发送信号, 父进程的处理如下:

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
struct stream
{
stream *next;
_QWORD op_jmp;
void *operate;
_QWORD rip_jmp;
};
unsigned __int64 __fastcall parentdo(unsigned int pid)
{
int stat_loc; // [rsp+18h] [rbp-2A8h] BYREF
int psp; // [rsp+1Ch] [rbp-2A4h]
int is_ripjmp; // [rsp+20h] [rbp-2A0h]
int found; // [rsp+24h] [rbp-29Ch]
int i; // [rsp+28h] [rbp-298h]
int v7; // [rsp+2Ch] [rbp-294h]
int v8; // [rsp+30h] [rbp-290h]
int is_opjmp; // [rsp+34h] [rbp-28Ch]
__int64 n204; // [rsp+38h] [rbp-288h]
user_regs_struct regs; // [rsp+40h] [rbp-280h] BYREF
_QWORD pstack[51]; // [rsp+120h] [rbp-1A0h]
unsigned __int64 v13; // [rsp+2B8h] [rbp-8h]

v13 = __readfsqword(0x28u);
v7 = 0;
psp = 0;
ops = opstream;
wait((__WAIT_STATUS)&stat_loc);
while ( (unsigned __int8)stat_loc == 0x7F )
{
ptrace(PTRACE_GETREGS, pid, 0, &regs);
v8 = ptrace(PTRACE_PEEKTEXT, pid, regs.rip, 0);
n204 = (unsigned __int8)ptrace(PTRACE_PEEKDATA, pid, regs.rip - 1, 0);
if ( n204 != 0xCC )
{
ptrace(PTRACE_KILL, pid, 0, 0);
exit(0);
}
is_ripjmp = 1;
if ( ops->operate )
{
is_opjmp = ((__int64 (__fastcall *)(user_regs_struct *))ops->operate)(&regs);
if ( is_opjmp == 1 )
{
ops = (stream *)ops->op_jmp; // jmp
}
else if ( is_opjmp )
{
switch ( is_opjmp )
{
case 2:
if ( psp <= 0 )
exit(-1);
ops = (stream *)pstack[--psp]; // parent ret
regs.rsp += 8LL;
break;
case 3:
regs.rip = ops->op_jmp;
ops = ops->next;
regs.rsp -= 8LL;
ptrace(PTRACE_POKEDATA, pid, regs.rsp, ops->rip_jmp);// child call
is_ripjmp = 0;
break;
case 4:
if ( psp > 48 )
exit(-1);
pstack[psp++] = ops->next;
regs.rsp -= 8LL; // parent call
ops = (stream *)ops->op_jmp;
break;
case 5:
if ( psp > 48 )
exit(-1);
pstack[psp++] = ops->next;
regs.rsp -= 8LL;
found = 0;
for ( i = 0; i <= 180; ++i )
{
if ( opstream[i].rip_jmp == regs.rip )// call table
{
ops = &opstream[i];
found = 1;
break;
}
}
if ( !found )
exit(-1);
is_ripjmp = 0;
break;
}
}
else
{
ops = ops->next;
}
}
else
{
ops = (stream *)ops->op_jmp;
}
if ( is_ripjmp )
regs.rip = ops->rip_jmp;
ptrace(PTRACE_SETREGS, pid, 0, &regs);
if ( ptrace(PTRACE_CONT, pid, 0, 0) < 0 )
{
perror("Ptrace.");
return __readfsqword(0x28u) ^ v13;
}
wait((__WAIT_STATUS)&stat_loc);
}
return __readfsqword(0x28u) ^ v13;
}

404850初始化了父进程的流水线操作, 父子进程之间没有数据通信, 所以加密过程完全由子线程实现, 所以重点关注父进程注入到子进程的call操作和jmp操作, 通过以下两个条件断点打印出子进程的指令流:

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
# 402200
from ida_dbg import get_reg_val as regs
from idc import get_bytes
from ida_funcs import get_func_name

reg_offsets = {
'r15': 0x0,
'r14': 0x8,
'r13': 0x10,
'r12': 0x18,
'rbp': 0x20,
'rbx': 0x28,
'r11': 0x30,
'r10': 0x38,
'r9': 0x40,
'r8': 0x48,
'rax': 0x50,
'rcx': 0x58,
'rdx': 0x60,
'rsi': 0x68,
'rdi': 0x70,
'orig_rax': 0x78,
'rip': 0x80,
'cs': 0x88,
'eflags': 0x90,
'rsp': 0x98,
'ss': 0xa0,
'fs_base': 0xa8,
'gs_base': 0xb0,
'ds': 0xb8,
'es': 0xc0,
'fs': 0xc8,
'gs': 0xd0,
}
child_regs = regs('rbp') - 0x280

def get_child_reg(reg_name):
return int.from_bytes(get_bytes(child_regs + reg_offsets[reg_name], 8), 'little')

with open("process_log.txt", "a") as log_file:
child_call = get_func_name(regs('rax'))
log_file.write(f"Child call: {child_call}({get_child_reg('rdi'):08x}, {get_child_reg('rsi'):08x}, {get_child_reg('rdx'):08x}, {get_child_reg('rcx'):08x})\n")
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
# 40238D
from ida_dbg import get_reg_val as regs
child_jmp = regs('rax')
from idc import GetDisasm, get_item_size

reg_offsets = {
'r15': 0x0,
'r14': 0x8,
'r13': 0x10,
'r12': 0x18,
'rbp': 0x20,
'rbx': 0x28,
'r11': 0x30,
'r10': 0x38,
'r9': 0x40,
'r8': 0x48,
'rax': 0x50,
'rcx': 0x58,
'rdx': 0x60,
'rsi': 0x68,
'rdi': 0x70,
'orig_rax': 0x78,
'rip': 0x80,
'cs': 0x88,
'eflags': 0x90,
'rsp': 0x98,
'ss': 0xa0,
'fs_base': 0xa8,
'gs_base': 0xb0,
'ds': 0xb8,
'es': 0xc0,
'fs': 0xc8,
'gs': 0xd0,
}
# child_regs = regs('rbp') - 0x280

# def get_child_reg(reg_name):
# return int.from_bytes(get_bytes(child_regs + reg_offsets[reg_name], 8), 'little')

# important_regs = ['rax', 'rbx', 'rcx', 'rdx', 'rsi', 'rdi']

with open("process_log.txt", "a") as log_file:
while not GetDisasm(child_jmp).startswith('int'):
insn = GetDisasm(child_jmp)
log_file.write(f"{child_jmp:08x}\t{insn}\n")
child_jmp += get_item_size(child_jmp)
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
# 402200

from ida_dbg import get_reg_val as regs
child_jmp = regs('rcx')
from idc import GetDisasm, get_item_size

reg_offsets = {
'r15': 0x0,
'r14': 0x8,
'r13': 0x10,
'r12': 0x18,
'rbp': 0x20,
'rbx': 0x28,
'r11': 0x30,
'r10': 0x38,
'r9': 0x40,
'r8': 0x48,
'rax': 0x50,
'rcx': 0x58,
'rdx': 0x60,
'rsi': 0x68,
'rdi': 0x70,
'orig_rax': 0x78,
'rip': 0x80,
'cs': 0x88,
'eflags': 0x90,
'rsp': 0x98,
'ss': 0xa0,
'fs_base': 0xa8,
'gs_base': 0xb0,
'ds': 0xb8,
'es': 0xc0,
'fs': 0xc8,
'gs': 0xd0,
}
# child_regs = regs('rbp') - 0x280

# def get_child_reg(reg_name):
# return int.from_bytes(get_bytes(child_regs + reg_offsets[reg_name], 8), 'little')

# important_regs = ['rax', 'rbx', 'rcx', 'rdx', 'rsi', 'rdi']

with open("process_log.txt", "a") as log_file:
while not GetDisasm(child_jmp).startswith('int'):
insn = GetDisasm(child_jmp)
log_file.write(f"{child_jmp:08x}\t{insn}\n")
child_jmp += get_item_size(child_jmp)

得到日志后让AI分析, 得出结论输入的第一步处理是AES-128, 密钥通过设置种子为0x10000后进行16次rand取最低字节生成, 两块16字节的数据加密完后通过第17次rand产生了最后加密使用的密钥:

QQ_1760873486043

最后的加密是使用16字节固定密钥(在400A12处子进程进入时赋值)以及16次rand生成的16字节密钥进行异或, 后和4048F0中的字节进行对比, 两块16字节的数据分别与4048F0[:16]4048F0[17:17+16]进行对比, 据此写出keygen:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <stdlib.h>
void main(){
srand(0x10000);
for(int i = 0; i < 0x10; i++){
printf("%02x ", rand() & 0xff);
}
puts("");
srand(rand());
for(int i = 0; i < 0x20; i++){
printf("%02x ", rand() & 0xff);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
key = bytes.fromhex(key)
xor_key1 = bytes.fromhex("f8 44 c6 ba b1 e5 0e 3b a2 4b b5 aa 4a 89 c7 a0 19 bd ec 5e de c1 c3 87 75 e6 12 71 61 ea f4 59")
xor_key2 = bytes.fromhex("e2 8b 55 38 69 fa 80 c2 64 4e 7f e7 13 06 14 c5 c0 13 d3 12 6b bd f2 c7 88 44 3e 09 e8 a3 83 30")
enc = b'Congratulations! This is the correct flag!'

encs = [bytearray(enc[:16]), bytearray(enc[17:])]
for i in range(2):
for j in range(0x10):
encs[i][j] ^= xor_key1[i * 0x10 + j] ^ xor_key2[i * 0x10 + j]

encs = encs[0] + encs[1]
for e in encs[:0x20]:
print(f"{e:02x} ", end='')
# https://gchq.github.io/CyberChef/#recipe=AES_Decrypt(%7B'option':'Hex','string':'f4%2070%20bb%20c0%2031%20ca%20ee%205e%20%2058%20b2%2072%20ea%2002%20f3%20ff%20e6%20%20'%7D,%7B'option':'Hex','string':''%7D,'ECB/NoPadding','Hex','Raw',%7B'option':'Hex','string':''%7D,%7B'option':'Hex','string':''%7D)&input=NTkgYTAgZmQgZTUgYWEgN2UgZmEgOGMgYWEgNjQgYmUgMjQgMzYgZTEgYTAgNDQgOGQgYzYgNTYgM2YgOTUgMTUgNDIgNjAgODkgY2EgNDkgNTggZWEgMjYgMDUgMWI&oeol=VT