天堂之门技术

记录一下天堂之门技术。还在补充

天堂之门技术

0x00 简介

天堂之门技术(Heaven’s Gate)是一种病毒反检测技术。该技术基于32位应用程序的基础上,向64位进程注入恶意代码,从而隐藏API调用,使其更加难以被检测到。

在最近的羊城杯Re题OddCode中,就出现了天堂之门技术的身影。再加上大量混淆,无效指令和跳转,使得这道题的难度变得十分大。(不过万能的地球人👴还是把它给gay出来了,salute)

之后我在CTFShow上面翻的时候,发现其“月饼杯”的第一个Re题,EasyTea,也利用了这个技术。不过由于这个题目本身难度不大,且题目有暗示加密方式,所以也有不少人解出来了。

0x01 流程

理论基础

微软在Windows平台上嵌入了一个名叫WoW64(Windows 32 on Windows 64)的翻译层,用于在win-x64平台上跑x86程序。

这个Wow,基本上将每一个32位的PE程序当作一个本地独立的64位进程(native standalone 64bit process)托管在里面,并将每一个32位的系统中断翻译成64位的系统调用。

换句话说:在64位系统中,32位的应用程序运行在一个WoW64的子系统中,这个子系统为32位程序提供了一个类似沙箱的运行环境。事实上这个32位程序运行的环境也是一个64位的运行环境,系统在创建32位进程时,首先创建一个64位的进程,然后再在其中创建一个32位的子环境。32位程序所调用的系统函数,最终都要通过64位的动态链接库而实现。

恶意代码注入流程

  1. 从32位运行模式转入64位模式
  2. 从PEB64结构中获取64位NTDLL模块
  3. 从NTDLL中获取64位函数
  4. 向64位进程中完成注入

注释:PEB是Process Environment Block的缩写,其中存储了进程用户态重要的数据结构。

主要的核心技术就在于实现32位至64位的一个转换。

而就这仅仅第一步,32位跳转至64位模式,就可以实现反调试;IDA,甚至Windbg都无法继续调试。

0x02 32至64

Windows判别位的方式,是根据cs段寄存器的。

在32位程序中,cs的值是0x23;

在64位程序中,cs的值是0x33。

所以只要修改cs的值,就能实现切换。而实现这个切换,使用的是ljmp长跳转指令(在Intel语法中也可以叫做jmp far ptr)。

其实我一开始有点懵逼,因为这个jmp far指令我也就在16位8086CPU里面见过,通过cs配合ip实现长转移,但是现在32位和64位的jmp指令的索引空间已经够大了,再加上Windows推荐的平坦模式(flat mode),直接将整个4gb内存空间看成一个段使用,csss等段寄存器全部不需要使用;而且cs的职责也发生了变化,变成了段选择子。

16位8086下jmp far ptr长跳转的表现

看个16位8086的例子:

1
2
3
4
5
6
7
8
9
10
assume cs:code
code segment
start: mov ax, 9527h
jmp far ptr L
db 32768 dup(0)
mov ax, 2222h
L: mov ax, 4c00h
int 21h
code ends
end start

中间搞了一个32768字节的空数据,是因为16位下,jmp near ptr只能在-32768~32767内有效这么长的跳转,只能由jmp far实现,通过对csip的共同修改实现段间跳转。

编译出来的指令是:

1
2
B82795	    mov		ax, 9527
EA0880860B jmp 0B86:8008

天堂之门技术中的实现

EasyTea

我选择直接使用最近做出来的EasyTea进行讲解。

IDA32反汇编,一看

1

会发现,这条指令,IDA的反汇编器都没法正常反汇编出来。

且不难发现从jmp far开始后面的指令,都变得邪门了起来,一大堆反汇编错误,因为变成64位指令了,而IDA32仍然把它当成32位的看。

于是乎我们选择使用高质量开源反汇编器capstone来帮我们解决这个问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from capstone import *
from capstone.x86_const import *

uc32 = Cs(CS_ARCH_X86, CS_MODE_32)
uc64 = Cs(CS_ARCH_X86, CS_MODE_64)

f = open('EasyTea.exe', 'rb')
file = f.read()
f.close()

code32 = file[0x11A0:0x1255]

code32_insn = list(uc32.disasm(code32, 0x400000))

for i in code32_insn:
print("addr:{:7}|size:{:2}|{:12}{}".format(hex(i.address), i.size, i.mnemonic, i.op_str))

得到

2

Capstone反汇编的就很对了,ljmp声明是长跳转,同时0x33:0x401258分别对应着对cseip的修改。

cs改成0x33,也就是Windows对64位的描述,从而实现了模式切换。

补充

11

这些是EasyTea.exe加载时的dll。可以发现调用了wow64cpu.dll

0x03 64至32

跳转进64位模式后,还可以选择重新回到32位。

实现方式就是以下代码

4

首先call了下一个指令,也就是相当于将下一个指令的地址推进栈中。

向栈中添加了一个0x23,用于retf时修改cs的,这样能重新回到32位

然后add 0xD,调整刚才推入的地址,至retf的后面(数一下即可得知,从mov开始至retf,字节码的长度正好是0xD)

retf长返回,首先从栈中弹出ip值,然后弹出cs值,cs:ip共同作用实现返回。

0x04 Intel开发手册中的描述

简简单单翻了一下Intel Developer Manual,找到了jmp指令的详细描述。

Opcode Instruction Op/En 64-Bit Mode Compat/Leg Mode Description
EA cd JMP ptr16:16 S Inv. Valid Jump far, absolute, address given in operand
EA cp JMP ptr16:32 S Inv. Valid Jump far, absolute, address given in operand

编码模式:

5

S模式,也就意思是段+绝对地址配合进行跳转。且已经声明了是far jmp长跳转了。

比如刚刚的ljmp 0x33:0x401258,编码是

1
EA 58 12 40 00 33 00

能发现

58 12 40 00就是地址0x401258,33 00就是cs的0x33。

进一步解释

简单翻译了一下手册中对保护模式中长跳转的描述(注意不是8086的实模式了!)

当处理器在保护模式下运行时,JMP指令可以用来执行以下三种类型的长跳转。

  1. 远跳到一个符合或不符合要求的代码段。(A far jump to a conforming or non-conforming code segment.)
  2. 通过一个调用门的远跳(A far jump through a call gate.)
  3. 一个任务转换(A task switch)

(JMP指令不能用于执行权限级之间的远跳。)

后面的看不懂了,等我看懂了再补充 :p

0x05 WoW Hell: Rebuilding Heavens Gate

这是HITB2021 SecConf中针对此技术的一个二次开发,阐明了一个全新的,通过天堂之门技术绕过杀软API Hook的攻击方法。

概要

  • 32-bit Hell & Userland HIPS Design
  • 通过逆向工程分析Wow64的设计思路
    1. WOW64 进程初始化
    2. Path to The Heaven
    3. The Paradise Translator
  • The 32-bit Hell v.s. 64-bit Heaven
  • wowGrail: Rebuild the Heaven’s Gate
  • wowInjector: One Gadget to Take Over the Wall

What “The Hell”

Host-based Instruction Prevention System(HIPS)

6

这展示了在没有被hooked的情况下,wow64对一个API进行的翻译操作。

杀毒软件通常会hook一些API,使得在病毒想干恶俗之事的时候被及时发现

7

这张图片,32位的API被反病毒软件Hook了。

这个Heaven's Gate技术,就是假设64位的API没有被反病毒软件Hook住,那么就可以通过直接执行64位的系统调用API来绕过病毒检测机制。

8

总体思想

  1. 跳转至64位模式
  2. 通过gs段选择子(gs:0x30)获取环境结构体(PEB64)
  3. 通过PEB64下的Ldr枚举所有加载的64位模块
  4. 定位NtDll64的基址(imageBase)
  5. 得到从DLL中获取导出APIntdll!LdrGetProcedureAddress
  6. BOOM! We got the key of Heaven’s Gate!

补充:PEB64结构体

64位的PEB结构体一直没有被微软公开,这个是网上人们通过逆向工程获得的PEB64结构体信息。

其中位移0x18就有我们需要的Ldr

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

//
// PEB64 structure - TODO: comb more through http://terminus.rewolf.pl/terminus/structures/ntdll/_PEB_x64.html and add OS delineations and Windows 10 updates
//
// The structure represented here is a work-in-progress as only members thru offset 0x320 are listed; the actual sizes per OS are:
// 0x0358 XP/WS03
// 0x0368 Vista
// 0x037C Windows 7
// 0x0388 Windows 8
// 0x07A0 Windows 10
//
struct PEB64
{
union
{
struct
{
BYTE InheritedAddressSpace; //0x000
BYTE ReadImageFileExecOptions; //0x001
BYTE BeingDebugged; //0x002
BYTE _SYSTEM_DEPENDENT_01; //0x003
} flags;
QWORD dummyalign;
} dword0;
QWORD Mutant; //0x0008
QWORD ImageBaseAddress; //0x0010
PTR64 Ldr; //0x0018
PTR64 ProcessParameters; //0x0020 / pointer to RTL_USER_PROCESS_PARAMETERS64
QWORD SubSystemData; //0x0028
QWORD ProcessHeap; //0x0030
QWORD FastPebLock; //0x0038
QWORD _SYSTEM_DEPENDENT_02; //0x0040
QWORD _SYSTEM_DEPENDENT_03; //0x0048
QWORD _SYSTEM_DEPENDENT_04; //0x0050
union
{
QWORD KernelCallbackTable; //0x0058
QWORD UserSharedInfoPtr; //0x0058
};
DWORD SystemReserved; //0x0060
DWORD _SYSTEM_DEPENDENT_05; //0x0064
QWORD _SYSTEM_DEPENDENT_06; //0x0068
QWORD TlsExpansionCounter; //0x0070
QWORD TlsBitmap; //0x0078
DWORD TlsBitmapBits[2]; //0x0080
QWORD ReadOnlySharedMemoryBase; //0x0088
QWORD _SYSTEM_DEPENDENT_07; //0x0090
QWORD ReadOnlyStaticServerData; //0x0098
QWORD AnsiCodePageData; //0x00A0
QWORD OemCodePageData; //0x00A8
QWORD UnicodeCaseTableData; //0x00B0
DWORD NumberOfProcessors; //0x00B8
union
{
DWORD NtGlobalFlag; //0x00BC
DWORD dummy02; //0x00BC
};
LARGE_INTEGER CriticalSectionTimeout; //0x00C0
QWORD HeapSegmentReserve; //0x00C8
QWORD HeapSegmentCommit; //0x00D0
QWORD HeapDeCommitTotalFreeThreshold; //0x00D8
QWORD HeapDeCommitFreeBlockThreshold; //0x00E0
DWORD NumberOfHeaps; //0x00E8
DWORD MaximumNumberOfHeaps; //0x00EC
QWORD ProcessHeaps; //0x00F0
QWORD GdiSharedHandleTable; //0x00F8
QWORD ProcessStarterHelper; //0x0100
QWORD GdiDCAttributeList; //0x0108
QWORD LoaderLock; //0x0110
DWORD OSMajorVersion; //0x0118
DWORD OSMinorVersion; //0x011C
WORD OSBuildNumber; //0x0120
WORD OSCSDVersion; //0x0122
DWORD OSPlatformId; //0x0124
DWORD ImageSubsystem; //0x0128
DWORD ImageSubsystemMajorVersion; //0x012C
QWORD ImageSubsystemMinorVersion; //0x0130
union
{
QWORD ImageProcessAffinityMask; //0x0138
QWORD ActiveProcessAffinityMask; //0x0138
};
QWORD GdiHandleBuffer[30]; //0x0140
QWORD PostProcessInitRoutine; //0x0230
QWORD TlsExpansionBitmap; //0x0238
DWORD TlsExpansionBitmapBits[32]; //0x0240
QWORD SessionId; //0x02C0
ULARGE_INTEGER AppCompatFlags; //0x02C8
ULARGE_INTEGER AppCompatFlagsUser; //0x02D0
QWORD pShimData; //0x02D8
QWORD AppCompatInfo; //0x02E0
UNICODE_STRING64 CSDVersion; //0x02E8
QWORD ActivationContextData; //0x02F8
QWORD ProcessAssemblyStorageMap; //0x0300
QWORD SystemDefaultActivationContextData; //0x0308
QWORD SystemAssemblyStorageMap; //0x0310
QWORD MinimumStackCommit; //0x0318

}; //struct PEB64

参考

2011 — Mixing x86 with x64 code

2012 — Knockin’ on Heaven’s Gate

2012 — KERNEL: Creation of Thread Environment Block (TEB)

2015 — WoW64 and So Can You Bypassing EMET

2018 — WoW64 Internals

2020 — WoW64 Subsystem Internals and Hooking Techniques

虽然但是,这写前人的文章里面的技术都不太稳定,难以实现。

有一个原因便是Windows不同版本中dll的内存分布差异:

9

在Win7中,比较容易,因为ntdll在4GB内存段之内;而8.1以上版本则在4GB外地址空间,发生了内存布局的改变,这就会让事情变得麻烦。

Wow64 Process Init

32-bit Program Managed in 64-bit Process

BTCpuSimulate()

这是每一个wow64进程的入口函数。

10

这个函数比较简单,就是在一个无限循环中调用另外一个函数RunSimulatedCode

12

这是我从自己的Windows系统中的system32/wow64cpu.dll中反编译出来的结果。

13

这是RunSimulatedCode函数的开头

r12指向gs:0x30,也就是TEB64结构体

r15指向一个表,名叫TurboThunk Table

r13指向TEB64结构体中的一个成员,WoW64 Thread Context

补充:TEB64结构体

又是一个没公开的结构体 :(

Terminus Project - _TEB64 - x64 view (rewolf.pl)

查一下表,发现偏移0x1488是

1
_TEB64.TlsSlots+8

指向了TlsSlots数组的第二个元素

14

这是TurboThunk表,这是一序列的函数指针。我们需要注意的就是头尾两个元素,TurboDispatchJumpAddressEndCpupReturnFromSimulatedCode

CpupReturnFromSimulatedCode这个函数是最重要的,它是第一个将32位代码拷贝到64位的函数。

TurboDispatchJumpAddressEnd也是一个拷贝函数。

NtAPI Trampoline

这里我们研究32位向64位的一个转变。

15

可以发现,这个Wow64Transition里面,使用了ljmp 0x33:wow64cpu.dll+6009这个代码,也就是刚刚阐述的长跳转代码,从而实现改变cs,进而变成64位模式。

于是ljmp下面的代码都是用64位反汇编器查看的。

上面我们刚讲到,r15指向了一个TurboThunkDispatch表,这里就根据r15和一个偏移进行jmp,进入表内函数中。这个jmp正好是跳到了最后一个函数,CpupReturnFromSimulatedCode中。

16

CpupReturnFromSimulatedCode的开头,先尽可能地为线程体做快照,将大部分信息存储在r13指向的结构体内(刚刚的TlsSlots数组中)。

然后他会跳转到TurboDispatchJumpAddressEnd函数中。

17

这边调用了Wow64SystemServiceEx函数,实现一个对32位系统调用的模拟(或者叫翻译,把32位系统调用翻译成64位)。

18

结束后,它会从刚刚保存的信息中恢复原来的寄存器状态。

Heaven’s Translator

将x86系统调用转化成x64的

19

这张截图显示了ntdllZwOpenProcess函数的流程。

其中eax获得的0xBE,实际上是一个16位大小的结构体。

1
2
3
4
typedef struct _WOW64_SYSTEM_SERVICE{
USHORT SystemCallNumber : 12;
USHORT ServiceTableIndex: 4;
} WOW64_SYSTEM_SERVICE, *PWOW64_SYSTEM_SERVICE;

一开始我先去System32文件夹下找到了ntdll,但是反编译出来的结果不一样。

20

事实上,这个应该是给64位exe直接提供服务的64位的dll,而我们需要的dll,是为32为程序提供翻译的,所以应该在SysWoW64下找ntdll。两个同名的dll实际上服务对象不一样,位数也不同。

21

这个才是我们需要的,不过发现结构体数值发生了一些变化。小问题,暂时不用管。

22

可以发现这个函数的第一个参数确实就是_WOW64_SYSTEM_SERVICE,再加上下面有(syscall >> 12) & 3这样的掩码,即可证实。

右边的列表,是在wow64cpu.dll中的函数指针表。这些函数都是用来将32位系统中断翻译成64位系统中断的。

23

这个是直接看表执行翻译函数。

OverView

25

最后一个h阶段是RestoreStatus,就是病毒注入完后恢复状态。

Recap

26

A New Path to the Heaven

根据刚刚的分析,我们可以总结出这个函数调用流程。

27

鉴于wow64层的稳定性,我们可以尝试根据以上流程,自己直接实现对wow64SystemServiceEx的一个调用,从而实现绕过。

wowGrail

一个PoC代码,用于证实其绕过的有效性。

28

https://github.com/aaaddress1/wowGrail

wowInjector

WOW64 Thread Snapshot

One Gadget to Take Over the 32-bit Hell

想想刚刚的流程,会发现32位向64位之间的转换前,会将上下文保存。返回到32位的时候,又会重新弹出信息。如果我们能够修改这个信息,就能实现最后返回的时候跳转到我们想要的恶意代码上。

29

这个是一个内核函数,是用于创建PEB和TEB结构体的。

只要我们想办法找到一个块,那么剩下3各块都可以获取到。

https://github.com/aaaddress1/wowInjector

0x06 Heaven’s Gate原作者文章

0x07 实现

尝试自己实现一个有天堂之门技术的代码

暂时只准备使用32位与64位之间的切换,实现反调试与混淆。

而关于网上能找到的一堆什么32位注入64位进程,以及Ntdll API获取,总体来说都是病毒技术,没有必要搞那么复杂。

代码实现

Visual Studio 2019上简单实现了一下代码。

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
#include <stdio.h>
#include <Windows.h>

//#define X64Enter() \
// __asm{ \
// _emit(0x6A) _emit(0x33) /* push _cs */ \
// _emit(0xE8) _emit(0x00) _emit(0x00) _emit(0x00) _emit(0x00) /* call $+5 */ \
// _emit(0x83) _emit(0x04) _emit(0x24) _emit(0x05) /* add dword [esp], 5 */ \
// _emit(0xCB) /* retf */ \
// }

//#define X64Exit() \
// __asm{ \
// _emit(0xE8) _emit(0) _emit(0) _emit(0) _emit(0) /* call $+5 */ \
// _emit(0xC7) _emit(0x44) _emit(0x24) _emit(4) _emit(0x23) _emit(0) _emit(0) _emit(0) /* mov dword [rsp + 4], _cs */ \
// _emit(0x83) _emit(4) _emit(0x24) _emit(0xD) /* add dword [rsp], 0xD */ \
// _emit(0xCB) /* retf */ \
// }
//

int main(void) {
//char Input[100] = { 0 };
DWORD dwInt;
puts("Enter Your Input:");
scanf_s("%d", &dwInt);

__asm {
lea ecx, dwInt
}
// X64Enter
__asm {
nop;
_emit 0x6A;
_emit 0x33;
_emit 0xE8;
_emit 0x00;
_emit 0x00;
_emit 0x00;
_emit 0x00;
_emit 0x83;
_emit 0x04;
_emit 0x24;
_emit 0x05;
_emit 0xCB;
}

// x64 mov eax, [ecx]
__asm {
_emit 0x67
_emit 0x8B
_emit 0x01
}

// x64: rol eax, 3
__asm {
_emit 0xC1
_emit 0xC0
_emit 0x03
}


// x64: mov [ecx], eax
_asm {
_emit 0x67
_emit 0x89
_emit 0x01
}

// X64Exit
__asm {
_emit 0xE8;
_emit 0x00;
_emit 0x00;
_emit 0x00;
_emit 0x00;
_emit 0xC7;
_emit 0x44;
_emit 0x24;
_emit 0x04;
_emit 0x23;
_emit 0x00;
_emit 0x00;
_emit 0x00;
_emit 0x83;
_emit 0x04;
_emit 0x24;
_emit 0x0D;
_emit 0xCB;
}
printf("%d\n", dwInt);
puts("AAA");

return 0;
}

解析

1
2
3
4
5
6
7
8
//char Input[100] = { 0 };
DWORD dwInt;
puts("Enter Your Input:");
scanf_s("%d", &dwInt);

__asm {
lea ecx, dwInt
}

输入一个数字,然后将数字所在地址存在ecx寄存器中。

其他寄存器似乎不太好用,因为可能会在进入x64层的时候发生修改。

当然其他寄存器也没怎么仔细试验过,有兴趣的可以每一个寄存器都试试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//  X64Enter
__asm {
nop;
_emit 0x6A;
_emit 0x33;
_emit 0xE8;
_emit 0x00;
_emit 0x00;
_emit 0x00;
_emit 0x00;
_emit 0x83;
_emit 0x04;
_emit 0x24;
_emit 0x05;
_emit 0xCB;
}

也就是

1
2
3
4
push 0x33
call $+5
add dword [esp], 5
retf

进入x64模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// x64 mov eax, [ecx]
__asm {
_emit 0x67
_emit 0x8B
_emit 0x01
}

// x64: rol eax, 3
__asm {
_emit 0xC1
_emit 0xC0
_emit 0x03
}


// x64: mov [ecx], eax
_asm {
_emit 0x67
_emit 0x89
_emit 0x01
}

这里是我实现的x64代码,由于是个demo,所以每个代码都是用keystone慢慢编译出来的。

具体来嗦就是一个循环左移3位。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// X64Exit
__asm {
_emit 0xE8;
_emit 0x00;
_emit 0x00;
_emit 0x00;
_emit 0x00;
_emit 0xC7;
_emit 0x44;
_emit 0x24;
_emit 0x04;
_emit 0x23;
_emit 0x00;
_emit 0x00;
_emit 0x00;
_emit 0x83;
_emit 0x04;
_emit 0x24;
_emit 0x0D;
_emit 0xCB;
}
printf("%d\n", dwInt);
puts("AAA");

退出x64模式,回到x86。

31

改进

直接在进入x64后调用x64代码感觉太直球了,我觉得可以call一个函数,在里面搞些操作之后再回来。

编译环境:32位主程序由VisualStudio2019,在x86+Debug版本下编译;

x64函数代码由Mingw在x64+Debug版本下编译。

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

// 单纯用于下断点而有的nop


//#define X64Enter() \
// __asm{ \
// _emit(0x6A) _emit(0x33) /* push _cs */ \
// _emit(0xE8) _emit(0x00) _emit(0x00) _emit(0x00) _emit(0x00) /* call $+5 */ \
// _emit(0x83) _emit(0x04) _emit(0x24) _emit(0x05) /* add dword [esp], 5 */ \
// _emit(0xCB) /* retf */ \
// }

//#define X64Exit() \
// __asm{ \
// _emit(0xE8) _emit(0) _emit(0) _emit(0) _emit(0) /* call $+5 */ \
// _emit(0xC7) _emit(0x44) _emit(0x24) _emit(4) _emit(0x23) _emit(0) _emit(0) _emit(0) /* mov dword [rsp + 4], _cs */ \
// _emit(0x83) _emit(4) _emit(0x24) _emit(0xD) /* add dword [rsp], 0xD */ \
// _emit(0xCB) /* retf */ \
// }
//

__declspec(naked) void func(char* Input) {
__asm {
_emit 0x55
_emit 0x48
_emit 0x89
_emit 0xe5
_emit 0x48
_emit 0x83
_emit 0xec
_emit 0x10
_emit 0x48
_emit 0x89
_emit 0x4d
_emit 0x10
_emit 0xc7
_emit 0x45
_emit 0xfc
_emit 0x00
_emit 0x00
_emit 0x00
_emit 0x00
_emit 0xeb
_emit 0x26
_emit 0x8b
_emit 0x45
_emit 0xfc
_emit 0x48
_emit 0x98
_emit 0x48
_emit 0x8b
_emit 0x55
_emit 0x10
_emit 0x48
_emit 0x01
_emit 0xd0
_emit 0x0f
_emit 0xb6
_emit 0x08
_emit 0x8b
_emit 0x45
_emit 0xfc
_emit 0x48
_emit 0x98
_emit 0x48
_emit 0x8b
_emit 0x55
_emit 0x10
_emit 0x48
_emit 0x01
_emit 0xd0
_emit 0x83
_emit 0xf1
_emit 0x55
_emit 0x89
_emit 0xca
_emit 0x88
_emit 0x10
_emit 0x83
_emit 0x45
_emit 0xfc
_emit 0x01
_emit 0x83
_emit 0x7d
_emit 0xfc
_emit 0x0f
_emit 0x7e
_emit 0xd4
_emit 0x90
_emit 0x48
_emit 0x83
_emit 0xc4
_emit 0x10
_emit 0x5d
_emit 0xc3
}
}



int main(void) {
char Input[100] = { 0 };
puts("Enter Your Input:");
scanf("%16s", Input);

__asm {
lea ecx, Input
}
// X64Enter
__asm {
nop;
_emit 0x6A;
_emit 0x33;
_emit 0xE8;
_emit 0x00;
_emit 0x00;
_emit 0x00;
_emit 0x00;
_emit 0x83;
_emit 0x04;
_emit 0x24;
_emit 0x05;
_emit 0xCB;
}

// 用rcx传参
__asm {

call func
}

// X64Exit
__asm {
_emit 0xE8;
_emit 0x00;
_emit 0x00;
_emit 0x00;
_emit 0x00;
_emit 0xC7;
_emit 0x44;
_emit 0x24;
_emit 0x04;
_emit 0x23;
_emit 0x00;
_emit 0x00;
_emit 0x00;
_emit 0x83;
_emit 0x04;
_emit 0x24;
_emit 0x0D;
_emit 0xCB;
}

//__asm {
// _emit 0x6A _emit 0x33 /* push _cs */
// _emit 0xE8 _emit 0x00 _emit 0x00 _emit 0x00 _emit 0x00 /* call $+5 */
// _emit 0x83 _emit 0x04 _emit 0x24 _emit 0x05 /* add dword [esp], 5 */
// _emit 0xCB /* retf */
//}
//
//__asm {
// _emit 0xE8 _emit 0x00 _emit 0x00 _emit 0x00 _emit 0x00 /* call $+5 */
// _emit 0xC7 _emit 0x44 _emit 0x24 _emit 0x04 _emit 0x23 _emit 0x00 _emit 0x00 _emit 0x00 /* mov dword [rsp + 4], _cs */
// _emit 0x83 _emit 0x04 _emit 0x24 _emit 0x0D /* add dword [rsp], 0xD */
// _emit 0xCB /* retf */
//}

printf("%s\n", Input);
puts("AAA");

return 0;
}

分析

1
2
3
4
5
6
7
8
9
10
__asm {
lea ecx, Input
}
....
进入x64
....
// 用rcx传参
__asm {
call func
}

在x64模式中调用func函数。

至于为什么能直接写call,是的编译器用x86的代码来编译,是因为64和86下的call指令字节码是一样的。

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
__declspec(naked) void func(char* Input) {
__asm {
_emit 0x55
_emit 0x48
_emit 0x89
_emit 0xe5
_emit 0x48
_emit 0x83
_emit 0xec
_emit 0x10
_emit 0x48
_emit 0x89
_emit 0x4d
_emit 0x10
_emit 0xc7
_emit 0x45
_emit 0xfc
_emit 0x00
_emit 0x00
_emit 0x00
_emit 0x00
_emit 0xeb
_emit 0x26
_emit 0x8b
_emit 0x45
_emit 0xfc
_emit 0x48
_emit 0x98
_emit 0x48
_emit 0x8b
_emit 0x55
_emit 0x10
_emit 0x48
_emit 0x01
_emit 0xd0
_emit 0x0f
_emit 0xb6
_emit 0x08
_emit 0x8b
_emit 0x45
_emit 0xfc
_emit 0x48
_emit 0x98
_emit 0x48
_emit 0x8b
_emit 0x55
_emit 0x10
_emit 0x48
_emit 0x01
_emit 0xd0
_emit 0x83
_emit 0xf1
_emit 0x55
_emit 0x89
_emit 0xca
_emit 0x88
_emit 0x10
_emit 0x83
_emit 0x45
_emit 0xfc
_emit 0x01
_emit 0x83
_emit 0x7d
_emit 0xfc
_emit 0x0f
_emit 0x7e
_emit 0xd4
_emit 0x90
_emit 0x48
_emit 0x83
_emit 0xc4
_emit 0x10
_emit 0x5d
_emit 0xc3
}
}

这个func使用__declspec(naked)来取消编译器默认的函数头和函数尾,单纯的只包含了你的代码。

于是乎,单独写一份func.c文件,然后mingw64模式下编译出纯净的代码,然后将其字节码拷贝进来。

又因为x64模式下的fastcall,正好第一个传参是rcx,与刚刚传进去的相对应(这个fastcall的寄存器传递顺序与不同编译器有关系,mingw可能是先rcx吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>

// 撸过来的就是这个func字节码
void func(char Input[]){
for(int i=0; i<16; i++){
Input[i] ^= 0x55;
}
return;
}

int main(void){
char Input[16] = {'A'};
func(Input);

puts(Input);

return 0;
}

31

成功调用,并返回。

0xFF 参考

主要是参考最新HITB2021 SecConf对此技术的重构

https://conference.hitb.org/hitbsecconf2021ams/sessions/wow-hell-rebuilding-heavens-gate/

https://github.com/aaaddress1/wowGrail

https://conference.hitb.org/hitbsecconf2021ams/sessions/wow-hell-rebuilding-heavens-gate/

https://github.com/aphage/wow64-gate-hook

https://www.calder-systems.com/articles/web/209983.html

ret和retf - 知乎 (zhihu.com)

https://blog.csdn.net/weixin_41890599/article/details/99556319

BYTE* / Undocumented 64-bit PEB and TEB Structures (bytepointer.com)