zoukankan      html  css  js  c++  java
  • 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;
    }

     最近在学反调试,其中就用到了这个方法,等学习完以后会将例子放上来

  • 相关阅读:
    The Fifth Week Lucklyzpp
    The Fourth Week Lucklyzpp
    The Third Week Lucklyzpp
    The Second Week lucklyzpp
    快10年没怎么过来了,一切如常
    男女诗篇
    ubuntu安装mysql
    ubuntu配置tomcat和jdk
    ubuntu常用操作命令以及它的通道模式简解
    Ubuntu操作异常汇总
  • 原文地址:https://www.cnblogs.com/Crisczy/p/8231775.html
Copyright © 2011-2022 走看看