西湖论剑 Dual Personality 复现

前置知识

本题主要涉及到了Heaven’s Gate的一种技术,在32位WoW64进程中执行64位代码的过程,以及直接调用64位下win32的API函数

其主要过程便是修改CS的值。将其切换到64位模式,然后使用jmp far来进行远跳转到达64位汇编代码处

CS(代码段选择器)在32位下值为 0x23 ,64位为 0x33

通常情况下一个 0x33 以及 jmp far 是我们判断的主要依据

此处引用一下 Sheng-Hao Ma 大佬的PPT图片来展示其对应的调用过程

ZwOpenProcess函数的调用为例:

  1. a.exe首先调用32位ntdll.dll(以下简称ntdll32)中的ZwOpenProcess函数
  2. ntdll32调用wow64cpu.dll中的X86SwitchTo64BitMode,顾名思义,调用该函数后进程从32位模式切换到64位模式
  3. wow64.dll将32位的系统调用转化为64位
  4. 调用64位ntdll.dllZwOpenProcess函数
  5. 切换到内核态(Ring0)执行系统调用

学习参考

天堂之门 (Heaven’s Gate) C语言实现-软件逆向-看雪论坛

汇编里看Wow64的原理(浅谈32位程序是怎样在windows 64上运行的?)-软件逆向-看雪论坛

Knockin’ on Heaven’s Gate – Dynamic Processor Mode Switching | RCE.co

逆向分析

我们加载后可以看到main函数处一片的红,无法被ida进行识别

在下面我们可以注意到输入后的一个函数

我们进入分析该函数,可以观察到明显的0x33特征,通过调试分析我们也可以看到对应修改后的数据

修改后有如下jmp far

我们对我们的输入下一个硬件断点,当出现对应的读入或者写的时候便断下来

此时我们进入了第一部分,通过观察汇编我们不难看出来对应功能是每四位一组,加上 operator_data,之后在于其进行异或处理

需要注意的是在下面存在有对operator_data的反调试,当检测到调试时便会得到 operator_data 的值为 0xDEADBEEF,不过影响不是很大,我们可以直接手动解密得到 operator_data 应该为 0x3CA7259D

通过 ds:60 进行访问PEB表,随后[eax+2]拿到调试状态的值

之后我们一步步进行调试,我们可以发现又对输入进行了如下变换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int __stdcall sub_401200(int a1, int a2)
{
void *retaddr[2]; // [esp+4h] [ebp+4h]

if ( byte_40705C )
{
*(_DWORD *)(a2 - 3) = __ROL4__(*(_DWORD *)(a2 - 1), 32);
*(_DWORD *)(a2 - 6 + 8) = __ROL4__(*(_DWORD *)(a2 - 4 + 8), 32);
*(_DWORD *)(a2 - 9 + 16) = __ROL4__(*(_DWORD *)(a2 - 7 + 16), 32);
*(_DWORD *)(a2 - 12 + 24) = __ROL4__(*(_DWORD *)(a2 - 10 + 24), 32);
}
else
{
*(_DWORD *)(a2 - 3) = __ROL4__(*(_DWORD *)(a2 - 1), 12);
*(_DWORD *)(a2 - 6 + 8) = __ROL4__(*(_DWORD *)(a2 - 4 + 8), 34);
*(_DWORD *)(a2 - 9 + 16) = __ROL4__(*(_DWORD *)(a2 - 7 + 16), 56);
*(_DWORD *)(a2 - 12 + 24) = __ROL4__(*(_DWORD *)(a2 - 10 + 24), 14);
}
return MK_FP(retaddr[0], retaddr[0])(a1, a2);
}

之后还经过了如下加密:

最后我们大致可以将主函数修复成如下模样,相对来说会好看一点…

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
int v4; // eax
char Buf2[44]; // [esp+D0h] [ebp-70h] BYREF
int v7; // [esp+FCh] [ebp-44h]
int v8; // [esp+100h] [ebp-40h]
int v9; // [esp+104h] [ebp-3Ch]
int v10; // [esp+108h] [ebp-38h]
int v11; // [esp+10Ch] [ebp-34h]
int v12; // [esp+110h] [ebp-30h]
int v13; // [esp+114h] [ebp-2Ch]
int v14; // [esp+118h] [ebp-28h]
char v15; // [esp+11Ch] [ebp-24h]
int i; // [esp+128h] [ebp-18h]
int *v17; // [esp+134h] [ebp-Ch]

sub_401000();
scanf("%99s", input);
v3 = decode_0(7u, (int)sub_4011D0);
if ( v3 )
v3 = operator_data;
operator_data = v3 - 0x21524111;
v17 = input;
for ( i = 0; i < 8; ++i )
{
v17[i] += operator_data;
operator_data ^= v17[i];
}
MK_FP(*((_WORD *)&byte_40700C + 2), byte_40700C)(input, 0);
decode_0(7u, (int)sub_401290); // 这里还有一个加密
while ( ::i != 33 )
{
v4 = ::i - 1;
*(_BYTE *)(::i - 1 + 4223072) ^= LOBYTE(dword_407014[(::i - 4) % 4u]); // 此处就是输入进行异或处理
::i = v4 - 1;
}
v7 = 0;
v8 = 0;
v9 = 0;
v10 = 0;
v11 = 0;
v12 = 0;
v13 = 0;
v14 = 0;
v15 = 0;
Buf2[0] = -86;
Buf2[1] = 79;
Buf2[2] = 15;
Buf2[3] = -30;
Buf2[4] = -28;
Buf2[5] = 65;
Buf2[6] = -103;
Buf2[7] = 84;
Buf2[8] = 44;
Buf2[9] = 43;
Buf2[10] = -124;
Buf2[11] = 126;
Buf2[12] = -68;
Buf2[13] = -113;
Buf2[14] = -117;
Buf2[15] = 120;
Buf2[16] = -45;
Buf2[17] = 115;
Buf2[18] = -120;
Buf2[19] = 94;
Buf2[20] = -82;
Buf2[21] = 71;
Buf2[22] = -123;
Buf2[23] = 112;
Buf2[24] = 49;
Buf2[25] = -77;
Buf2[26] = 9;
Buf2[27] = -50;
Buf2[28] = 19;
Buf2[29] = -11;
Buf2[30] = 13;
Buf2[31] = -54;
Buf2[32] = 0;
if ( !memcmp(input, Buf2, 0x20u) )
{
puts("Right, flag is DASCTF{your input}");
exit(0);
}
puts("Wrong flag");
return 0;
}

根据上面的逻辑我们可以写出解密过程:

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
def ror(x,n):
return ((x>>n) | (x<<(64-n)&0xffffffffffffffff))&0xffffffffffffffff

enc = [0xE20F4FAA, 0x549941E4, 0x7E842B2C, 0x788B8FBC, 0x5E8873D3, 0x708547AE, 0xCE09B331, 0xCA0DF513]

# 将加密过程逆置解密
# part 3
# key3 = [0x04, 0x77, 0x82, 0x4A] # 使用这个key时需要对 enc 修改为 byte
key3 = 0x4A827704
for i in range(8):
enc[i] ^= key3

# part 2
key2 = [0xc,0x22,0x38,0xe]
for i in range(0,8,2):
tmp=(enc[i+1]<<32 | enc[i])
tmp=ror(tmp,key2[i//2])
enc[i]=tmp&0xffffffff
enc[i+1]=tmp>>32

# part 1
key1 = 0x3CA7259D
for i in range(8):
tmp=enc[i]
enc[i]=(enc[i]-key1+0x100000000)%0x100000000 # 加上 0x100000000 防止Python数据类型溢出
key1^=tmp

for i in enc:
print(int.to_bytes(i,4,'little').decode(),end='')

# 6cc1e44811647d38a15017e389b3f704

后记

第一次写这种题目,很多地方不是很熟悉,文章必然存在有许多的不足之处,还请师傅们多多指教。


西湖论剑 Dual Personality 复现
https://equinox-shame.github.io/2023/02/27/西湖论剑 Dual Personality 复现/
作者
梓曰
发布于
2023年2月27日
许可协议