HITCON 2022 RE Checker 复现

HITCON 2022 Checker

这道题拿到两个程序,一个驱动程序,一个用户的可执行程序。

首先对用户程序进行查壳,发现为无壳 64 位可执行文件。

拖入 IDA 分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int __cdecl main(int argc, const char **argv, const char **envp)
{
HANDLE FileW; // rax
char *v4; // rcx
char OutBuffer[4]; // [rsp+40h] [rbp-18h] BYREF
DWORD BytesReturned; // [rsp+44h] [rbp-14h] BYREF

FileW = CreateFileW(L"\\\\.\\hitcon_checker", 0xC0000000, 0, 0i64, 2u, 4u, 0i64);
qword_140003620 = (__int64)FileW;
if ( FileW == (HANDLE)-1i64 )
{
sub_140001010("driver not found\n");
exit(0);
}
OutBuffer[0] = 0;
DeviceIoControl(FileW, 0x222080u, 0i64, 0, OutBuffer, 1u, &BytesReturned, 0i64);
v4 = "correct\n";
if ( !OutBuffer[0] )
v4 = "wrong\n";
sub_140001010(v4);
system("pause");
return 0;
}

可以发现用户程序的逻辑比较简单,就是用 CreateFileW 启动驱动,打开符号连接,如果成功的话,则通过函数 DeviceIoControl 来将控制代码(IRPI/O 请求数据包))发送到我们的驱动程序,来使驱动执行相应的操作。

接下来我们分析驱动程序:

我们进入DriveEntry 函数唯一调用的函数中查看:

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
__int64 __fastcall sub_140001B50(struct _DRIVER_OBJECT *a1)
{
unsigned int inited; // edi
_BYTE *DriverSection; // rcx
PHYSICAL_ADDRESS PhysicalAddress; // rax
PHYSICAL_ADDRESS v5; // rax
KIRQL v6; // al

a1->DriverUnload = (PDRIVER_UNLOAD)sub_140001000;
inited = init_Device(a1); // 创建设备对象,创建符号链接
a1->MajorFunction[0] = (PDRIVER_DISPATCH)IRP_func;// 填充IRP函数
a1->MajorFunction[2] = (PDRIVER_DISPATCH)IRP_func;
a1->MajorFunction[3] = (PDRIVER_DISPATCH)IRP_func;
a1->MajorFunction[4] = (PDRIVER_DISPATCH)IRP_func;
DriverSection = a1->DriverSection;
a1->MajorFunction[14] = (PDRIVER_DISPATCH)IRP_func;
DriverSection[104] |= 0x20u;
sub_140001040();
PhysicalAddress = MmGetPhysicalAddress((char *)sub_140001490 + 7024);// 0x140003000
addr_140003000_qword_140013170 = (__int64)MmMapIoSpace(PhysicalAddress, 0x1000ui64, MmNonCached);// 0x140003000
addr_140003030_qword_140013178 = addr_140003000_qword_140013170 + 48;// 0x140003000 + 48
v5 = MmGetPhysicalAddress((char *)sub_140001490 - 96);// 0x140001430
addr_140001430_qword_140013188 = (__int64)MmMapIoSpace(v5, 0x1000ui64, MmNonCached);// 0x140001430
addr_140001b30_qword_140013180 = addr_140001430_qword_140013188 + 1792;// 0x140001b30
v6 = sub_140001490(); // 绕过保护
*(_BYTE *)addr_140001b30_qword_140013180 ^= *(_BYTE *)addr_140001430_qword_140013188;
*(_BYTE *)(addr_140001b30_qword_140013180 + 1) ^= *(_BYTE *)(addr_140001430_qword_140013188 + 1);
*(_BYTE *)(addr_140001b30_qword_140013180 + 2) ^= *(_BYTE *)(addr_140001430_qword_140013188 + 2);
*(_BYTE *)(addr_140001b30_qword_140013180 + 3) ^= *(_BYTE *)(addr_140001430_qword_140013188 + 3);
*(_BYTE *)(addr_140001b30_qword_140013180 + 4) ^= *(_BYTE *)(addr_140001430_qword_140013188 + 4);
*(_BYTE *)(addr_140001b30_qword_140013180 + 5) ^= *(_BYTE *)(addr_140001430_qword_140013188 + 5);
*(_BYTE *)(addr_140001b30_qword_140013180 + 6) ^= *(_BYTE *)(addr_140001430_qword_140013188 + 6);
*(_BYTE *)(addr_140001b30_qword_140013180 + 7) ^= *(_BYTE *)(addr_140001430_qword_140013188 + 7);
*(_BYTE *)(addr_140001b30_qword_140013180 + 8) ^= *(_BYTE *)(addr_140001430_qword_140013188 + 8);
*(_BYTE *)(addr_140001b30_qword_140013180 + 9) ^= *(_BYTE *)(addr_140001430_qword_140013188 + 9);
*(_BYTE *)(addr_140001b30_qword_140013180 + 10) ^= *(_BYTE *)(addr_140001430_qword_140013188 + 10);
*(_BYTE *)(addr_140001b30_qword_140013180 + 11) ^= *(_BYTE *)(addr_140001430_qword_140013188 + 11);
*(_BYTE *)(addr_140001b30_qword_140013180 + 12) ^= *(_BYTE *)(addr_140001430_qword_140013188 + 12);
*(_BYTE *)(addr_140001b30_qword_140013180 + 13) ^= *(_BYTE *)(addr_140001430_qword_140013188 + 13);
*(_BYTE *)(addr_140001b30_qword_140013180 + 14) ^= *(_BYTE *)(addr_140001430_qword_140013188 + 14);
*(_BYTE *)(addr_140001b30_qword_140013180 + 15) ^= *(_BYTE *)(addr_140001430_qword_140013188 + 15);
*(_BYTE *)addr_140001b30_qword_140013180 ^= *(_BYTE *)(addr_140001430_qword_140013188 + 16);
*(_BYTE *)(addr_140001b30_qword_140013180 + 1) ^= *(_BYTE *)(addr_140001430_qword_140013188 + 17);
*(_BYTE *)(addr_140001b30_qword_140013180 + 2) ^= *(_BYTE *)(addr_140001430_qword_140013188 + 18);
*(_BYTE *)(addr_140001b30_qword_140013180 + 3) ^= *(_BYTE *)(addr_140001430_qword_140013188 + 19);
*(_BYTE *)(addr_140001b30_qword_140013180 + 4) ^= *(_BYTE *)(addr_140001430_qword_140013188 + 20);
*(_BYTE *)(addr_140001b30_qword_140013180 + 5) ^= *(_BYTE *)(addr_140001430_qword_140013188 + 21);
*(_BYTE *)(addr_140001b30_qword_140013180 + 6) ^= *(_BYTE *)(addr_140001430_qword_140013188 + 22);
*(_BYTE *)(addr_140001b30_qword_140013180 + 7) ^= *(_BYTE *)(addr_140001430_qword_140013188 + 23);
*(_BYTE *)(addr_140001b30_qword_140013180 + 8) ^= *(_BYTE *)(addr_140001430_qword_140013188 + 24);
*(_BYTE *)(addr_140001b30_qword_140013180 + 9) ^= *(_BYTE *)(addr_140001430_qword_140013188 + 25);
*(_BYTE *)(addr_140001b30_qword_140013180 + 10) ^= *(_BYTE *)(addr_140001430_qword_140013188 + 26);
*(_BYTE *)(addr_140001b30_qword_140013180 + 11) ^= *(_BYTE *)(addr_140001430_qword_140013188 + 27);
*(_BYTE *)(addr_140001b30_qword_140013180 + 12) ^= *(_BYTE *)(addr_140001430_qword_140013188 + 28);
*(_BYTE *)(addr_140001b30_qword_140013180 + 13) ^= *(_BYTE *)(addr_140001430_qword_140013188 + 29);
*(_BYTE *)(addr_140001b30_qword_140013180 + 14) ^= *(_BYTE *)(addr_140001430_qword_140013188 + 30);
*(_BYTE *)(addr_140001b30_qword_140013180 + 15) ^= *(_BYTE *)(addr_140001430_qword_140013188 + 31);
sub_1400014B0(v6);
return inited;
}

我们可以看到在前半部分,程序在创建驱动对象和符号连接,然后将 DRIVER_OBJECT 结构体中的 MajorFunction 成员指向开发者创建好的“派遣函数”或者叫“派遣例程”(DispatchRoutine),用于处理用户的 IRP 请求。

接下来,程序首先进行了一系列取地址的操作(在注释中已经对处理完的地址进行了标注)。接下里有一大串的逐字节异或操作。可以看到是对 0x140001b30 地址处的函数的前16字节进行两轮异或,异或的值为 0x140001430 地址处的前 32 字节的数据。所以这里的操作是改变一个函数的代码。

既然这样我们就按照它的逻辑去将这里的数据异或一遍,然后将数据在 IDA 中找个空白处 patch 上去,然后按快捷键 C 键将其转化成代码,按 P 键将其定义为函数(如果可以的话,查看伪代码看它的逻辑会更舒服一点)

1
2
3
4
5
6
7
8
.rdata:0000000140002190                 mov     [rcx], dh
.rdata:0000000140002192 and [rbx], dl
.rdata:0000000140002194 push rbp
.rdata:0000000140002195 mov ah, 4Fh ; 'O'
.rdata:0000000140002197 rep sbb [rdi+5Bh], cl
.rdata:000000014000219C mov al, 29h ; ')'
.rdata:000000014000219E sahf
.rdata:000000014000219F mov dword ptr [rax], 0C3C12Ah

我们发现这个函数可执行性非常差,没什么逻辑可言。

我们回想用户程序中在启动驱动后向驱动发送了 IRP 请求,我们就去派遣函数中查看驱动是如何处理程序的 IRP 请求的。

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
__int64 __fastcall IRP_func(_DEVICE_OBJECT *a1, __int64 a2)
{
ULONG Length; // esi
PIO_STACK_LOCATION CurrentIrpStackLocation; // rax
char v7; // cl
__int64 v8; // rax
int v9; // ecx

Length = 0;
CurrentIrpStackLocation = IoGetCurrentIrpStackLocation((PIRP)a2);
if ( a1 != DeviceObject )
return 0xC0000001i64;
if ( CurrentIrpStackLocation->MajorFunction )
{
if ( CurrentIrpStackLocation->MajorFunction == 14 )
{
Length = CurrentIrpStackLocation->Parameters.Read.Length;
switch ( CurrentIrpStackLocation->Parameters.Read.ByteOffset.LowPart )
{
case 0x222000u:
sub_1400014D0(0);
byte_140013190[0] = 1;
break;
case 0x222010u:
sub_1400014D0(0x20u);
byte_140013190[1] = 1;
break;
case 0x222020u:
sub_1400014D0(0x40u);
byte_140013190[2] = 1;
break;
case 0x222030u:
sub_1400014D0(0x60u);
byte_140013190[3] = 1;
break;
case 0x222040u:
sub_1400014D0(0x80u);
byte_140013190[4] = 1;
break;
case 0x222050u:
sub_1400014D0(0xA0u);
byte_140013190[5] = 1;
break;
case 0x222060u:
sub_1400014D0(0xC0u);
byte_140013190[6] = 1;
break;
case 0x222070u:
sub_1400014D0(0xE0u);
byte_140013190[7] = 1;
break;
case 0x222080u:
if ( !Length )
goto LABEL_15;
v7 = 1;
v8 = 0i64;
while ( byte_140013190[v8] )
{
if ( ++v8 >= 8 )
goto LABEL_21;
}
v7 = 0;
LABEL_21:
if ( v7 )
{
v9 = *(_DWORD *)&byte_140003000 - 'ctih';
if ( *(_DWORD *)&byte_140003000 == 'ctih' )
v9 = unk_140003004 - 'no';
**(_BYTE **)(a2 + 24) = v9 == 0;
}
else
{
LABEL_15:
**(_BYTE **)(a2 + 24) = 0;
}
break;
default:
break;
}
}
}
else
{
byte_140003170[(_QWORD)PsGetCurrentProcessId()] = 1;
}
*(_QWORD *)(a2 + 56) = Length;
*(_DWORD *)(a2 + 48) = 0;
IofCompleteRequest((PIRP)a2, 0);
return 0i64;
}

可以看到这里有一个 switch 结构根据用户传递不同的控制代码而执行不同的操作。那我们来看一看用户程序传入的 0x222080 会使得驱动执行了何种操作。

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
        case 0x222080u:
if ( !Length )
goto LABEL_15;
v7 = 1;
v8 = 0i64;
while ( byte_140013190[v8] )
{
if ( ++v8 >= 8 )
goto LABEL_21;
}
v7 = 0;
LABEL_21:
if ( v7 )
{
v9 = *(_DWORD *)&byte_140003000 - 'ctih';
if ( *(_DWORD *)&byte_140003000 == 'ctih' )
v9 = unk_140003004 - 'no';
**(_BYTE **)(a2 + 24) = v9 == 0;
}
else
{
LABEL_15:
**(_BYTE **)(a2 + 24) = 0;
}
break;
default:
break;
}
}
}
else
{
byte_140003170[(_QWORD)PsGetCurrentProcessId()] = 1;
}
*(_QWORD *)(a2 + 56) = Length;
*(_DWORD *)(a2 + 48) = 0;
IofCompleteRequest((PIRP)a2, 0);
return 0i64;
}

这里可以看到有验证 flag 格式的代码,flag 产生的起始地址就是验证格式的 0x140003000。而得以进入 flag 格式验证步骤的条件则是:

1
2
3
4
5
while( byte_140013190[v8] )
{
if( ++v8 >= 8 )
goto LABEL_21;
}

说明数组的前 8 位都得有值才可以进入flag 格式验证的步骤。而这个数组的前八个元素会在 switch 结构中进行赋值。根据传入的不同的控制代码进入不同的 case,向相同的函数 sub_1400014D0 传入不同的参数,然后给数组的某一个元素赋值 1 。

我们进入函数 sub_1400014D0 查看:

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
void __fastcall sub_1400014D0(unsigned int a1)
{
__int64 v1; // rdi
KIRQL v2; // si
char *v3; // rbx
__int64 v4; // rbx
__int64 v5; // rbx
__int64 v6; // rbx
__int64 v7; // rbx
__int64 v8; // rbx
__int64 v9; // rbx
__int64 v10; // rbx
__int64 v11; // rbx
__int64 v12; // rbx
__int64 v13; // rbx
__int64 v14; // rbx
__int64 v15; // rbx
__int64 v16; // rbx
__int64 v17; // rbx
__int64 v18; // rbx
__int64 v19; // rbx
__int64 v20; // rbx
__int64 v21; // rbx
__int64 v22; // rbx
__int64 v23; // rbx
__int64 v24; // rbx
__int64 v25; // rbx
__int64 v26; // rbx
__int64 v27; // rbx
__int64 v28; // rbx
__int64 v29; // rbx
__int64 v30; // rbx
__int64 v31; // rbx
__int64 v32; // rbx
__int64 v33; // rbx
__int64 v34; // rbx
__int64 v35; // rbx
__int64 v36; // rbx
__int64 v37; // rbx
__int64 v38; // rbx
__int64 v39; // rbx
__int64 v40; // rbx
__int64 v41; // rbx
__int64 v42; // rbx
__int64 v43; // rbx
__int64 v44; // rbx
__int64 v45; // rbx

v1 = a1;
v2 = sub_140001490();
*(_BYTE *)addr_140001b30_qword_140013180 ^= *(_BYTE *)(v1 + addr_140003030_qword_140013178);// 0x140001b30和0x140003030
*(_BYTE *)(addr_140001b30_qword_140013180 + 1) ^= *(_BYTE *)((unsigned int)(v1 + 1) + addr_140003030_qword_140013178);
*(_BYTE *)(addr_140001b30_qword_140013180 + 2) ^= *(_BYTE *)((unsigned int)(v1 + 2) + addr_140003030_qword_140013178);
*(_BYTE *)(addr_140001b30_qword_140013180 + 3) ^= *(_BYTE *)((unsigned int)(v1 + 3) + addr_140003030_qword_140013178);
*(_BYTE *)(addr_140001b30_qword_140013180 + 4) ^= *(_BYTE *)((unsigned int)(v1 + 4) + addr_140003030_qword_140013178);
*(_BYTE *)(addr_140001b30_qword_140013180 + 5) ^= *(_BYTE *)((unsigned int)(v1 + 5) + addr_140003030_qword_140013178);
*(_BYTE *)(addr_140001b30_qword_140013180 + 6) ^= *(_BYTE *)((unsigned int)(v1 + 6) + addr_140003030_qword_140013178);
*(_BYTE *)(addr_140001b30_qword_140013180 + 7) ^= *(_BYTE *)((unsigned int)(v1 + 7) + addr_140003030_qword_140013178);
*(_BYTE *)(addr_140001b30_qword_140013180 + 8) ^= *(_BYTE *)((unsigned int)(v1 + 8) + addr_140003030_qword_140013178);
*(_BYTE *)(addr_140001b30_qword_140013180 + 9) ^= *(_BYTE *)((unsigned int)(v1 + 9) + addr_140003030_qword_140013178);
*(_BYTE *)(addr_140001b30_qword_140013180 + 10) ^= *(_BYTE *)((unsigned int)(v1 + 10) + addr_140003030_qword_140013178);
*(_BYTE *)(addr_140001b30_qword_140013180 + 11) ^= *(_BYTE *)((unsigned int)(v1 + 11) + addr_140003030_qword_140013178);
*(_BYTE *)(addr_140001b30_qword_140013180 + 12) ^= *(_BYTE *)((unsigned int)(v1 + 12) + addr_140003030_qword_140013178);
*(_BYTE *)(addr_140001b30_qword_140013180 + 13) ^= *(_BYTE *)((unsigned int)(v1 + 13) + addr_140003030_qword_140013178);
*(_BYTE *)(addr_140001b30_qword_140013180 + 14) ^= *(_BYTE *)((unsigned int)(v1 + 14) + addr_140003030_qword_140013178);
*(_BYTE *)(addr_140001b30_qword_140013180 + 15) ^= *(_BYTE *)((unsigned int)(v1 + 15) + addr_140003030_qword_140013178);
v3 = (char *)addr_140003000_qword_140013170;
*v3 = sub_140001B30(*(_BYTE *)addr_140003000_qword_140013170);
v4 = addr_140003000_qword_140013170;
*(_BYTE *)(v4 + 1) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 1));
v5 = addr_140003000_qword_140013170;
*(_BYTE *)(v5 + 2) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 2));
v6 = addr_140003000_qword_140013170;
*(_BYTE *)(v6 + 3) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 3));
v7 = addr_140003000_qword_140013170;
*(_BYTE *)(v7 + 4) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 4));
v8 = addr_140003000_qword_140013170;
*(_BYTE *)(v8 + 5) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 5));
v9 = addr_140003000_qword_140013170;
*(_BYTE *)(v9 + 6) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 6));
v10 = addr_140003000_qword_140013170;
*(_BYTE *)(v10 + 7) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 7));
v11 = addr_140003000_qword_140013170;
*(_BYTE *)(v11 + 8) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 8));
v12 = addr_140003000_qword_140013170;
*(_BYTE *)(v12 + 9) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 9));
v13 = addr_140003000_qword_140013170;
*(_BYTE *)(v13 + 10) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 10));
v14 = addr_140003000_qword_140013170;
*(_BYTE *)(v14 + 11) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 11));
v15 = addr_140003000_qword_140013170;
*(_BYTE *)(v15 + 12) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 12));
v16 = addr_140003000_qword_140013170;
*(_BYTE *)(v16 + 13) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 13));
v17 = addr_140003000_qword_140013170;
*(_BYTE *)(v17 + 14) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 14));
v18 = addr_140003000_qword_140013170;
*(_BYTE *)(v18 + 15) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 15));
v19 = addr_140003000_qword_140013170;
*(_BYTE *)(v19 + 16) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 16));
v20 = addr_140003000_qword_140013170;
*(_BYTE *)(v20 + 17) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 17));
v21 = addr_140003000_qword_140013170;
*(_BYTE *)(v21 + 18) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 18));
v22 = addr_140003000_qword_140013170;
*(_BYTE *)(v22 + 19) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 19));
v23 = addr_140003000_qword_140013170;
*(_BYTE *)(v23 + 20) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 20));
v24 = addr_140003000_qword_140013170;
*(_BYTE *)(v24 + 21) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 21));
v25 = addr_140003000_qword_140013170;
*(_BYTE *)(v25 + 22) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 22));
v26 = addr_140003000_qword_140013170;
*(_BYTE *)(v26 + 23) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 23));
v27 = addr_140003000_qword_140013170;
*(_BYTE *)(v27 + 24) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 24));
v28 = addr_140003000_qword_140013170;
*(_BYTE *)(v28 + 25) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 25));
v29 = addr_140003000_qword_140013170;
*(_BYTE *)(v29 + 26) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 26));
v30 = addr_140003000_qword_140013170;
*(_BYTE *)(v30 + 27) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 27));
v31 = addr_140003000_qword_140013170;
*(_BYTE *)(v31 + 28) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 28));
v32 = addr_140003000_qword_140013170;
*(_BYTE *)(v32 + 29) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 29));
v33 = addr_140003000_qword_140013170;
*(_BYTE *)(v33 + 30) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 30));
v34 = addr_140003000_qword_140013170;
*(_BYTE *)(v34 + 31) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 31));
v35 = addr_140003000_qword_140013170;
*(_BYTE *)(v35 + 32) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 32));
v36 = addr_140003000_qword_140013170;
*(_BYTE *)(v36 + 33) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 33));
v37 = addr_140003000_qword_140013170;
*(_BYTE *)(v37 + 34) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 34));
v38 = addr_140003000_qword_140013170;
*(_BYTE *)(v38 + 35) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 35));
v39 = addr_140003000_qword_140013170;
*(_BYTE *)(v39 + 36) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 36));
v40 = addr_140003000_qword_140013170;
*(_BYTE *)(v40 + 37) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 37));
v41 = addr_140003000_qword_140013170;
*(_BYTE *)(v41 + 38) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 38));
v42 = addr_140003000_qword_140013170;
*(_BYTE *)(v42 + 39) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 39));
v43 = addr_140003000_qword_140013170;
*(_BYTE *)(v43 + 40) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 40));
v44 = addr_140003000_qword_140013170;
*(_BYTE *)(v44 + 41) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 41));
v45 = addr_140003000_qword_140013170;
*(_BYTE *)(v45 + 42) = sub_140001B30(*(_BYTE *)(addr_140003000_qword_140013170 + 42));
*(_BYTE *)addr_140001b30_qword_140013180 ^= *(_BYTE *)((unsigned int)(v1 + 16) + addr_140003030_qword_140013178);
*(_BYTE *)(addr_140001b30_qword_140013180 + 1) ^= *(_BYTE *)((unsigned int)(v1 + 17) + addr_140003030_qword_140013178);
*(_BYTE *)(addr_140001b30_qword_140013180 + 2) ^= *(_BYTE *)((unsigned int)(v1 + 18) + addr_140003030_qword_140013178);
*(_BYTE *)(addr_140001b30_qword_140013180 + 3) ^= *(_BYTE *)((unsigned int)(v1 + 19) + addr_140003030_qword_140013178);
*(_BYTE *)(addr_140001b30_qword_140013180 + 4) ^= *(_BYTE *)((unsigned int)(v1 + 20) + addr_140003030_qword_140013178);
*(_BYTE *)(addr_140001b30_qword_140013180 + 5) ^= *(_BYTE *)((unsigned int)(v1 + 21) + addr_140003030_qword_140013178);
*(_BYTE *)(addr_140001b30_qword_140013180 + 6) ^= *(_BYTE *)((unsigned int)(v1 + 22) + addr_140003030_qword_140013178);
*(_BYTE *)(addr_140001b30_qword_140013180 + 7) ^= *(_BYTE *)((unsigned int)(v1 + 23) + addr_140003030_qword_140013178);
*(_BYTE *)(addr_140001b30_qword_140013180 + 8) ^= *(_BYTE *)((unsigned int)(v1 + 24) + addr_140003030_qword_140013178);
*(_BYTE *)(addr_140001b30_qword_140013180 + 9) ^= *(_BYTE *)((unsigned int)(v1 + 25) + addr_140003030_qword_140013178);
*(_BYTE *)(addr_140001b30_qword_140013180 + 10) ^= *(_BYTE *)((unsigned int)(v1 + 26) + addr_140003030_qword_140013178);
*(_BYTE *)(addr_140001b30_qword_140013180 + 11) ^= *(_BYTE *)((unsigned int)(v1 + 27) + addr_140003030_qword_140013178);
*(_BYTE *)(addr_140001b30_qword_140013180 + 12) ^= *(_BYTE *)((unsigned int)(v1 + 28) + addr_140003030_qword_140013178);
*(_BYTE *)(addr_140001b30_qword_140013180 + 13) ^= *(_BYTE *)((unsigned int)(v1 + 29) + addr_140003030_qword_140013178);
*(_BYTE *)(addr_140001b30_qword_140013180 + 14) ^= *(_BYTE *)((unsigned int)(v1 + 30) + addr_140003030_qword_140013178);
*(_BYTE *)(addr_140001b30_qword_140013180 + 15) ^= *(_BYTE *)((unsigned int)(v1 + 31) + addr_140003030_qword_140013178);
sub_1400014B0(v2);
}

函数比较长,我们可以将其拆成三部分来看。

首先是对 0x140001b30 地址处函数的前 16 字节的异或,异或的值是以 0x140003030 为首地址在加上传入的参数(实际上就是偏移量)后的地址开始取16字节异或。也就是又对函数的内容进行了改变

接下来对 43 字节的数据进行修改,修改的方式是将 0x140003000 为首地址开始的 43 字节,逐字节带入我们通过异或修改过的函数 sub_140001B30 中,然后重新赋值。

最后再让 0x140001b30 地址处函数的前 16 字节再次异或,异或的值是第一部分异或值的后16字节。也就是 0x140003030 加上偏移后的值取32个字节参与整个函数的异或过程。

所以我们现在整理一下逻辑:

​ 想要验证 flag 就需要将另外的 8 个case 全部执行一遍,然后 0x140003000 的前 43 个字节就是flag。

所以想要得到 flag 就需要确定这些 case 的执行顺序去执行对应的操作。所以我们尝试每一次反汇编的结果的可读性和可执行性,来尝试得到唯一的且正确的顺序。

我们通过 python 的 capstone 库编写反汇编脚本来初步观察代码的可读性,如果觉得可读性尚可我们就将其 patch 到 IDA 中,通过上面用到过的方法去看它反编译的伪代码。然后按照驱动程序的逻辑去完善我们的 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
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
from capstone import *

def init_func(func, init_data):
for i in range(16):
func[i] ^= init_data[i]
for i in range(16):
func[i] ^= init_data[i+16]
return func

def xor_and_disasm(offset):
global origin_func
global xor_data
global addr_140001430_arr
global md
for i in range(16):
origin_func[i] ^= xor_data[i+offset]
for i in origin_func: # 打印异或后内容便于 idapython 脚本 patch
print(hex(i)+',',end='')
print("")
for i in md.disasm(bytes(origin_func), 0): # 打印反汇编结果,初步观察代码的可读性和可执行性
print('%s\t%s'%(i.mnemonic, i.op_str))

def xor_after_call(offset):
global origin_func
global xor_data
for i in range(16):
origin_func[i] ^= xor_data[offset + 16 + i]

def order_1_offset_0xe0(arr_byte):
return (( 8 * arr_byte ) | (arr_byte >> 5)) & 0xff # 控制有效位

def order_2_offset_0x40(arr_byte):
return (arr_byte ^ 0x26) & 0xff

def order_3_offset_0xc0(arr_byte):
return ((16 * arr_byte) | (arr_byte >> 4)) & 0xff

def order_4_offset_0x00(arr_byte):
return (arr_byte + 55) & 0xff

def order_5_offset_0x20(arr_byte):
return (arr_byte + 123) & 0xff

def order_6_offset_0x80(arr_byte):
return ((arr_byte << 7) | (arr_byte >> 1)) & 0xff

def order_7_offset_0x60(arr_byte):
return (173 * arr_byte) & 0xff

def order_8_offset_0xa0(arr_byte):
return ((4 * arr_byte) | (arr_byte >> 6)) & 0xff


origin_func = [0x80, 0xe9, 0x22, 0x80, 0xf1, 0xad, 0xf, 0xb6, 0xc1, 0x6b, 0xc8, 0x11, 0xb8, 0x9e, 0x0, 0x0, 0x0, 0x2a, 0xc1, 0xc3]
xor_data = [0x19, 0xBC, 0x8F, 0x82, 0xD0, 0x2C, 0x61, 0x34, 0xC0, 0x9F, 0xF6, 0x50, 0xD5, 0xFB, 0x0C, 0x6E, 0xD0, 0xEB, 0xE5, 0xE3, 0xCE, 0xB5, 0x4C, 0xCA, 0x45, 0xAA, 0x11, 0xB2, 0x3E, 0x62, 0x6F, 0x7D, 0xD0, 0xEB, 0xA9, 0xE3, 0xB2, 0x2F, 0x06, 0x47, 0x7C, 0x28, 0xC5, 0xDE, 0xDE, 0x1A, 0x4E, 0xD6, 0xD8, 0x2D, 0x93, 0x4F, 0x82, 0x65, 0x64, 0xFD, 0x08, 0x62, 0x4B, 0x87, 0x7E, 0x52, 0x47, 0x30, 0xB7, 0xBA, 0xD0, 0x39, 0x68, 0x53, 0x50, 0xAB, 0x20, 0xD5, 0xCA, 0x84, 0x26, 0x71, 0x6F, 0x91, 0x1B, 0x36, 0x46, 0x11, 0xA5, 0xF1, 0x4E, 0x58, 0x6C, 0x74, 0xD4, 0x9C, 0x15, 0xE2, 0x28, 0xD5, 0xD9, 0x0F, 0x3D, 0x83, 0xF3, 0xFC, 0xD1, 0x13, 0x1A, 0x62, 0x12, 0x40, 0xAA, 0xEA, 0xCD, 0xCB, 0xE1, 0xC6, 0x08, 0x81, 0x98, 0xF6, 0x68, 0x88, 0xBE, 0x23, 0xB5, 0x9E, 0x55, 0xB9, 0xE2, 0x7D, 0x5A, 0xDA, 0x39, 0x07, 0xF0, 0x2E, 0x32, 0x20, 0x59, 0x56, 0x4C, 0xB4, 0x8F, 0x3E, 0x07, 0x61, 0xD9, 0x0F, 0x2D, 0x61, 0xF1, 0x91, 0x33, 0x14, 0xCB, 0x49, 0x68, 0xFE, 0x1F, 0xD4, 0x8A, 0xFE, 0xE1, 0xC6, 0x18, 0x63, 0x9A, 0x9B, 0x8A, 0x8A, 0x7F, 0x08, 0xC3, 0xE8, 0xE1, 0xEC, 0x0B, 0x8F, 0x3B, 0x00, 0x94, 0xA5, 0x11, 0xE7, 0x47, 0x66, 0xC4, 0x9F, 0x98, 0x18, 0x70, 0xF0, 0x30, 0xF6, 0x94, 0x71, 0xB1, 0x95, 0xD1, 0xF0, 0x6F, 0xB7, 0xD9, 0x3D, 0x05, 0x9E, 0xC1, 0x53, 0x33, 0x76, 0x9B, 0x4B, 0x69, 0xCA, 0xDE, 0xFD, 0x7D, 0x67, 0xB8, 0x29, 0x2B, 0xC7, 0xC5, 0x84, 0x2C, 0xD1, 0x87, 0x87, 0xF1, 0x98, 0x97, 0x74, 0xAD, 0x4B, 0x32, 0xF0, 0x4A, 0x51, 0x72, 0xEA, 0x09, 0xF7, 0x38, 0xFD, 0x27, 0xBD, 0x1C, 0x52, 0x71, 0x43, 0x95, 0x9C, 0x1A, 0x86, 0xF2, 0xC0, 0xF9, 0xF8]
addr_140003000_arr = [0x63, 0x60, 0xA5, 0xB9, 0xFF, 0xFC, 0x30, 0x0A, 0x48, 0xBB, 0xFE, 0xFE, 0x32, 0x2C, 0x0A, 0xD6, 0xE6, 0xFE, 0xFE, 0x32, 0x2C, 0x0A, 0xD6, 0xBB, 0x4A, 0x4A, 0x32, 0x2C, 0xFC, 0xFF, 0x0A, 0xFD, 0xBB, 0xFE, 0x2C, 0xB9, 0x63, 0xD6, 0xB9, 0x62, 0xD6, 0x0A, 0x4F]
addr_140001430_arr = [64, 83, 72, 131, 236, 32, 72, 139, 5, 59, 12, 0, 0, 72, 139, 218, 72, 139, 74, 16, 72, 57, 8, 117, 55, 72, 139, 74, 8, 255, 21, 29, 12, 0, 0, 72, 141, 13, 22, 29, 0, 0, 128, 60, 8, 0, 116, 32, 139, 3, 131, 248, 1, 116, 5, 131, 248, 2, 117, 20, 72, 139, 75, 32, 139, 65, 4, 131, 224, 1, 132, 192, 116, 6, 199, 1, 0, 0, 0, 0, 51, 192, 72, 131, 196, 32, 91]
md = Cs(CS_ARCH_X86, CS_MODE_64)

origin_func = init_func(origin_func, addr_140001430_arr) # 驱动被启动后会先执行 DriveEntry 里的函数,对 0x140001b30 地址处的函数进行第一次异或

xor_and_disasm(0xe0) # 通过不断查看反汇编结果并将数据 patch 到 ida 空白处,验证结果的可读性和可执行性,用此方法试出函数执行顺序
for i in range(43):
addr_140003000_arr[i] = order_1_offset_0xe0(addr_140003000_arr[i])
xor_after_call(0xe0)

xor_and_disasm(0x40)
for i in range(43):
addr_140003000_arr[i] = order_2_offset_0x40(addr_140003000_arr[i])
xor_after_call(0x40)

xor_and_disasm(0xc0)
for i in range(43):
addr_140003000_arr[i] = order_3_offset_0xc0(addr_140003000_arr[i])
xor_after_call(0xc0)

xor_and_disasm(0x00)
for i in range(43):
addr_140003000_arr[i] = order_4_offset_0x00(addr_140003000_arr[i])
xor_after_call(0x00)

xor_and_disasm(0x20)
for i in range(43):
addr_140003000_arr[i] = order_5_offset_0x20(addr_140003000_arr[i])
xor_after_call(0x20)

xor_and_disasm(0x80)
for i in range(43):
addr_140003000_arr[i] = order_6_offset_0x80(addr_140003000_arr[i])
xor_after_call(0x80)

xor_and_disasm(0x60)
for i in range(43):
addr_140003000_arr[i] = order_7_offset_0x60(addr_140003000_arr[i])
xor_after_call(0x60)

xor_and_disasm(0xa0)
for i in range(43):
addr_140003000_arr[i] = order_8_offset_0xa0(addr_140003000_arr[i])
xor_after_call(0xa0)


for i in range(43): # 查看最终结果
addr_140003000_arr[i] &= 0xff
print(chr(addr_140003000_arr[i]), end='')

下面是我们得到每一次传入 case 后异或改变后的函数的伪代码:

第一次偏移为 0xe0

1
2
3
4
5
6
7
8
__int64 __fastcall sub_140002190(unsigned __int8 a1)
{
__int64 result; // rax

result = a1 >> 5;
LOBYTE(result) = (8 * a1) | (a1 >> 5);
return result;
}

第二次偏移为 0x40

1
2
3
4
__int64 __fastcall sub_140002190(unsigned __int8 a1)
{
return a1 ^ 0x26u;
}

第三次偏移为 0xc0

1
2
3
4
5
6
7
8
__int64 __fastcall sub_140002190(unsigned __int8 a1)
{
__int64 result; // rax

result = a1 >> 4;
LOBYTE(result) = (16 * a1) | (a1 >> 4);
return result;
}

第四次偏移为 0x00

1
2
3
4
__int64 __fastcall sub_140002190(int a1)
{
return (unsigned int)(a1 + 55);
}

第五次偏移为 0x20

1
2
3
4
__int64 __fastcall sub_140002190(int a1)
{
return (unsigned int)(a1 + 123);
}

第六次偏移为 0x80

1
2
3
4
5
6
7
8
__int64 __fastcall sub_140002190(unsigned __int8 a1)
{
__int64 result; // rax

result = a1 >> 1;
LOBYTE(result) = (a1 << 7) | (a1 >> 1);
return result;
}

第七次偏移为 0x60

1
2
3
4
__int64 __fastcall sub_140002190(unsigned __int8 a1)
{
return 173 * (unsigned int)a1;
}

第八次偏移为 0xa0

1
2
3
4
5
6
7
8
__int64 __fastcall sub_140002190(unsigned __int8 a1)
{
__int64 result; // rax

result = a1 >> 6;
LOBYTE(result) = (4 * a1) | (a1 >> 6);
return result;
}

得到 flag

hitcon{r3ally_re4lly_rea11y_normal_checker}

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!

扫一扫,分享到微信

微信分享二维码
  • Copyrights © 2022-2023 Syclover.Kama
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信