32位程序調用64位函數————開源代碼rewolf-wow64ext學習筆記


rewolf-wow64ext的目的就是讓運行在Wow64環境中的x86應用程序可以直接調用x64下ntdll.dll中的Native API。
 
學習中可以得到幾個結論
  1. 在X64環境下的進程,32位程序,映射了兩個地址空間,一個32位,一個64位。而且這兩種工作模式是可以切換的的。
  2. WOW64進程中的R12寄存器指向其64位的TEB結構(線程環境塊)。
  3. 我們可以將進程的32位模式改為64位模式,然后來調用64位的函數。

 

進程的32位模式改為64位模式的具體代碼
主要方法是 借助retf將CS寄存器的值設置為0x33。

在CPU中,CS的全拼為"Code Segment",翻譯為"代碼段寄存器",對應於內存中的存放代碼的內存區域,用來存放內存代碼段區域的入口地址(段基址)。

在CPU執行指令時,通過代碼段寄存器(CS,Code Segment)和指令指針寄存器(IP,Instruction Pointer)來確定要執行的下一條指令的內存地址。

至於為什么要將CS寄存器的值設置為0x33。因為我們所需CPU的解碼模式需要是64位模式,而當前模式是32位。64位CPU是通過GDT表中CS段所對應的表項中L標志位來確定當前解碼模式的。

看雪論壇上這篇關於Wow64的原理的文章https://bbs.pediy.com/thread-221236.htm 介紹了這一機制

#pragma once
#define EMIT(a) __asm __emit (a)
#define X64_Start_with_CS(_cs) \
    { \
    EMIT(0x6A) EMIT(_cs)                         /*1  push   _cs         壓入我們傳入的cs     */ \
    EMIT(0xE8) EMIT(0) EMIT(0) EMIT(0) EMIT(0)                 /*2  call   $+5         壓入eip,jmp到3       */ \
    EMIT(0x83) EMIT(4) EMIT(0x24) EMIT(5)                    /*3  add    dword [esp], 5   跳過接下來五個字節 得到下下條指令的地址      */ \
    EMIT(0xCB)                                     /*4  retf    先彈出堆棧中的IP/EIP,然后彈出CS */ \
    }
/*
00EE169E 6A 33                push        33h 
00EE16A0 E8 00 00 00 00       call        main+25h (0EE16A5h) 
00EE16A5 83 04 24 05          add         dword ptr [esp],5 
00EE16A9 CB                   retf //當代碼執行完這一句以后 eip的值為00EE16AA cs=33h
00EE16AA E8 00 00 00 00       call        main+2Fh (0EE16AFh) 
00EE16AF C7 44 24 04 23 00 00 00 mov         dword ptr [esp+4],23h 
00EE16B7 83 04 24 0D          add         dword ptr [esp],0Dh 
00EE16BB CB                   retf 

*/
#define X64_End_with_CS(_cs) \
    { 
    EMIT(0xE8) EMIT(0) EMIT(0) EMIT(0) EMIT(0)                     /*  call   $+5                   */ \
    EMIT(0xC7) EMIT(0x44) EMIT(0x24) EMIT(4) EMIT(_cs) 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                         */ \
    }
#define X64_Start() X64_Start_with_CS(0x33)
#define X64_End() X64_End_with_CS(0x23)

 

 ret,iret和retf的區別  ,call $+5的意思

ret:也可以叫做近返回,即段內返回。處理器從堆棧中彈出IP或者EIP,然后根據當前的CS:IP跳轉到新的執行地址。如果之前壓棧的還有其余的參數,則這些參數也會被彈出。
retf:也叫遠返回,從一個段返回到另一個段。先彈出堆棧中的IP/EIP,然后彈出CS,有之前壓棧的參數也會彈出。(近跳轉與遠跳轉的區別就在於CS是否壓棧。)
iret:用於從中斷返回,會彈出IP/EIP,然后CS,以及一些標志。然后從CS:IP執行。


$表示當前地址,call $+5表示,調用用 本地址+5字節后的 子程序

 

 
我們首先要自己定義一個拷貝內存的函數。
void getMem64(void* dstMem, DWORD64 srcMem, size_t sz)
{
    if ((nullptr == dstMem) || (0 == srcMem) || (0 == sz))
        return;
    reg64 _src = { srcMem };
    __asm
    {
        X64_Start();
        ;// below code is compiled as x86 inline asm, but it is executed as x64 code
        ;// that's why it need sometimes REX_W() macro, right column contains detailed
        ;// transcription how it will be interpreted by CPU
        push   edi                  ;// push     rdi
        push   esi                  ;// push     rsi
                                    ;//
        mov    edi, dstMem          ;// mov      edi, dword ptr [dstMem]        ; high part of RDI is zeroed
  REX_W     mov    esi, _src.dw[0]      ;// mov      rsi, qword ptr [_src]
        mov    ecx, sz              ;// mov      ecx, dword ptr [sz]            ; high part of RCX is zeroed
                                    ;//
        mov    eax, ecx             ;// mov      eax, ecx
        and    eax, 3               ;// and      eax, 3  最后2位
        shr    ecx, 2               ;// shr      ecx, 2  除4 因為接下來 每次MOVSD 傳送四個字節.
                                    ;//
        rep    movsd                ;// rep movs dword ptr [rdi], dword ptr [rsi]
                                    ;//
        test   eax, eax             ;// test     eax, eax 比較是否為空
        je     _move_0              ;// je       _move_0
        cmp    eax, 1               ;// cmp      eax, 1 是否為一
        je     _move_1              ;// je       _move_1
                                    ;//
        movsw                       ;// movs     word ptr [rdi], word ptr [rsi]
        cmp    eax, 2               ;// cmp      eax, 2
        je     _move_0              ;// je       _move_0
                                    ;//
_move_1:                            ;//
        movsb                       ;// movs     byte ptr [rdi], byte ptr [rsi]
                                    ;//
_move_0:                            ;// 還原后返回
        pop    esi                  ;// pop      rsi   
        pop    edi                  ;// pop      rdi
        X64_End();
    }
}

 

現在我們可以去查找目標函數地址

思想是 目標函數從動態庫(ntdll)中獲得,我們需要從LDR中匹配動態庫,LDR可以在PEB中找到,PEB可以在TEB中找到,TEB可以通過R12寄存器獲得。
故 R12-TEB-PEB-LDR-NTDLL。
 
獲得R12
#define _R12 12
#define X64_Push(r) EMIT(0x48 | ((r) >> 3)) EMIT(0x50 | ((r) & 7))
#define X64_Pop(r) EMIT(0x48 | ((r) >> 3)) EMIT(0x58 | ((r) & 7))

獲得TEB
DWORD64 getTEB64()
{
    reg64 reg;
    reg.v = 0;
   
    X64_Start();
    // R12 register should always contain pointer to TEB64 in WoW64 processes
    X64_Push(_R12);
    // below pop will pop QWORD from stack, as we're in x64 mode now
    __asm pop reg.dw[0]
    X64_End();
    return reg.v;
}
獲得PEB
PEB64 peb64;
getMem64(&peb64, teb64.ProcessEnvironmentBlock, sizeof(PEB64));

獲得LDR
PEB_LDR_DATA64 ldr;
getMem64(&ldr, peb64.Ldr, sizeof(PEB_LDR_DATA64));

獲得ntdll基地址
   wchar_t lpModuleName=L"ntdll.dll"
    DWORD64 LastEntry = peb64.Ldr + offsetof(PEB_LDR_DATA64, InLoadOrderModuleList);
    LDR_DATA_TABLE_ENTRY64 head;
    head.InLoadOrderLinks.Flink = ldr.InLoadOrderModuleList.Flink;

do
    {
        getMem64(&head, head.InLoadOrderLinks.Flink, sizeof(LDR_DATA_TABLE_ENTRY64));
        wchar_t* tempBuf = (wchar_t*)malloc(head.BaseDllName.MaximumLength);
        if (nullptr == tempBuf)
            return 0;
        WATCH(tempBuf);
        getMem64(tempBuf, head.BaseDllName.Buffer, head.BaseDllName.MaximumLength);
        if (0 == _wcsicmp(lpModuleName, tempBuf))
            return head.DllBase;
    }
    while (head.InLoadOrderLinks.Flink != LastEntry);
 
找到模塊基地址以后,我們就可以通過PE文件結構去獲得我們需要的函數了
思想是 得到目標函數地址,需要利用函數名找到對應函數的相對偏移RVA,那就需要找到IMAGE_EXPORT_DIRECTORY結構體下的 AddressOfNames,AddressOfNameOrdinals,AddressOfFunctions 三個成員。
故 IMAGE_DOS_HEADER---IMAGE_NT_HEADERS64---IMAGE_DATA_DIRECTORY---IMAGE_EXPORT_DIRECTORY-AddressOfNames,AddressOfNameOrdinals,AddressOfFunctions---目標函數RVA
DWORD64 getLdrGetProcedureAddress()
{
    DWORD64 modBase = getNTDLL64();
       if (0 == modBase)
              return 0;
   
    IMAGE_DOS_HEADER idh;
    getMem64(&idh, modBase, sizeof(idh));
    IMAGE_NT_HEADERS64 inh;
    getMem64(&inh, modBase + idh.e_lfanew, sizeof(IMAGE_NT_HEADERS64));
   
    IMAGE_DATA_DIRECTORY & idd = inh.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
   
    if (0 == idd.VirtualAddress)
        return 0;
    IMAGE_EXPORT_DIRECTORY ied;
    getMem64(&ied, modBase + idd.VirtualAddress, sizeof(ied));
    DWORD* rvaTable = (DWORD*)malloc(sizeof(DWORD)*ied.NumberOfFunctions);
    if (nullptr == rvaTable)
        return 0;
    WATCH(rvaTable);
    getMem64(rvaTable, modBase + ied.AddressOfFunctions, sizeof(DWORD)*ied.NumberOfFunctions);
   
    WORD* ordTable = (WORD*)malloc(sizeof(WORD)*ied.NumberOfFunctions);
    if (nullptr == ordTable)
        return 0;
    WATCH(ordTable);
    getMem64(ordTable, modBase + ied.AddressOfNameOrdinals, sizeof(WORD)*ied.NumberOfFunctions);
    DWORD* nameTable = (DWORD*)malloc(sizeof(DWORD)*ied.NumberOfNames);
    if (nullptr == nameTable)
        return 0;
    WATCH(nameTable);
    getMem64(nameTable, modBase + ied.AddressOfNames, sizeof(DWORD)*ied.NumberOfNames);
    // lazy search, there is no need to use binsearch for just one function
    for (DWORD i = 0; i < ied.NumberOfFunctions; i++)
    {
        if (!cmpMem64("LdrGetProcedureAddress", modBase + nameTable[i], sizeof("LdrGetProcedureAddress")))
            continue;
        else
            return modBase + rvaTable[ordTable[i]];
    }
    return 0;
}

 

 

注意!我們得到函數地址以后,不能直接調用 
需要通過X64Call()來執行。 X64調用約定前4個參數通過rcx,rdx,r8,r9來傳遞,之后通過堆棧傳遞。
#pragma warning(push)
#pragma warning(disable : 4409)
DWORD64 __cdecl X64Call(DWORD64 func, int argC, ...)
{
       if (!g_isWow64)
              return 0;
    va_list args;//VA_LIST 是在C語言中解決變參問題的一組宏,所在頭文件:#include <stdarg.h>,用於獲取不確定個數的參數。
    va_start(args, argC);//VA_START宏,獲取可變參數列表的第一個參數的地址(ap是類型為va_list的指針,v是可變參數最左邊的參數):
    reg64 _rcx = { (argC > 0) ? argC--, va_arg(args, DWORD64) : 0 };
    reg64 _rdx = { (argC > 0) ? argC--, va_arg(args, DWORD64) : 0 };//VA_ARG宏,獲取可變參數的當前參數,返回指定類型並將指針指向下一參數(t參數描述了當前參數的類型):
    reg64 _r8 = { (argC > 0) ? argC--, va_arg(args, DWORD64) : 0 };
    reg64 _r9 = { (argC > 0) ? argC--, va_arg(args, DWORD64) : 0 };
    reg64 _rax = { 0 };
    reg64 restArgs = { (DWORD64)&va_arg(args, DWORD64) };//剩余成員用堆棧傳遞
   
    // conversion to QWORD for easier use in inline assembly
    reg64 _argC = { (DWORD64)argC };
    DWORD back_esp = 0;
       WORD back_fs = 0;
    __asm
    {
        ;// reset FS segment, to properly handle RFG
        mov    back_fs, fs
        mov    eax, 0x2B
        mov    fs, ax
        ;// keep original esp in back_esp variable
        mov    back_esp, esp
       
        ;// align esp to 0x10, without aligned stack some syscalls may return errors !
        ;// (actually, for syscalls it is sufficient to align to 8, but SSE opcodes
        ;// requires 0x10 alignment), it will be further adjusted according to the
        ;// number of arguments above 4
              ;//對齊
        and    esp, 0xFFFFFFF0
        X64_Start();
        ;// below code is compiled as x86 inline asm, but it is executed as x64 code
        ;// that's why it need sometimes REX_W() macro, right column contains detailed
        ;// transcription how it will be interpreted by CPU
        ;// fill first four arguments
  REX_W     mov    ecx, _rcx.dw[0]                          ;// mov     rcx, qword ptr [_rcx]
  REX_W     mov    edx, _rdx.dw[0]                          ;// mov     rdx, qword ptr [_rdx]
        push   _r8.v                                    ;// push    qword ptr [_r8]
        X64_Pop(_R8);                                   ;// pop     r8
        push   _r9.v                                    ;// push    qword ptr [_r9]
        X64_Pop(_R9);                                   ;// pop     r9
                                                        ;//
  REX_W     mov    eax, _argC.dw[0]                         ;// mov     rax, qword ptr [_argC]
                                                        ;//
        ;// final stack adjustment, according to the    ;//
        ;// number of arguments above 4                 ;//
        test   al, 1                                    ;// test    al, 1
        jnz    _no_adjust                               ;// jnz     _no_adjust
        sub    esp, 8                                   ;// sub     rsp, 8  對齊對齊對齊!假如有單個參數 需要對齊!!!
_no_adjust:                                             ;//
                                                        ;//
        push   edi                                      ;// push    rdi
  REX_W    mov    edi, restArgs.dw[0]                      ;// mov     rdi, qword ptr [restArgs]
                                                        ;//
        ;// put rest of arguments on the stack          ;//
  REX_W     test   eax, eax                                 ;// test    rax, rax
        jz     _ls_e                                    ;// je      _ls_e
  REX_W     ea    edi, dword ptr [edi + 8*eax - 8]         ;// lea     rdi, [rdi + rax*8 - 8]取最后一個的地址
                                                        ;//
_ls:                                                    ;//
  REX_W     test   eax, eax                                 ;// test    rax, rax 判斷是否為0
        jz     _ls_e                                    ;// je      _ls_e
        push   dword ptr [edi]                          ;// push    qword ptr [rdi]
  REX_W    sub    edi, 8                                   ;// sub     rdi, 8
  REX_W     sub    eax, 1                                   ;// sub     rax, 1
        jmp    _ls                                      ;// jmp     _ls
_ls_e:                                                  ;//
                                                        ;//
        ;// create stack space for spilling registers   ;//
  REX_W     sub    esp, 0x20                                ;// sub     rsp, 20h
                                                        ;//
        call   func                                     ;// call    qword ptr [func]
                                                        ;//
        ;// cleanup stack                               ;//
  REX_W     mov    ecx, _argC.dw[0]                         ;// mov     rcx, qword ptr [_argC]
  REX_W     lea    esp, dword ptr [esp + 8*ecx + 0x20]      ;// lea     rsp, [rsp + rcx*8 + 20h]
                                                        ;//
        pop    edi                                      ;// pop     rdi
                                                        ;//
        // set return value                             ;//
  REX_W     mov    _rax.dw[0], eax                          ;// mov     qword ptr [_rax], rax
        X64_End();
        mov    ax, ds
        mov    ss, ax
        mov    esp, back_esp
        ;// restore FS segment
        mov    ax, back_fs
        mov    fs, ax
    }
    return _rax.v;
}

 

一個例子

DWORD64 __cdecl GetProcAddress64(DWORD64 hModule, char* funcName)
{
    static DWORD64 _LdrGetProcedureAddress = 0;
    if (0 == _LdrGetProcedureAddress)
    {
        _LdrGetProcedureAddress = getLdrGetProcedureAddress();//從ntdll中獲得的目標函數地址
        if (0 == _LdrGetProcedureAddress)
            return 0;
    }
    _UNICODE_STRING_T<DWORD64> fName = { 0 };
    fName.Buffer = (DWORD64)funcName;
    fName.Length = (WORD)strlen(funcName);
    fName.MaximumLength = fName.Length + 1;
    DWORD64 funcRet = 0;
    X64Call(_LdrGetProcedureAddress, 4, (DWORD64)hModule, (DWORD64)&fName, (DWORD64)0, (DWORD64)&funcRet);
    return funcRet;
}

 最近在學反調試,其中就用到了這個方法,等學習完以后會將例子放上來

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM