Snoozy

1.Sleep-inducing; tedious.

64bitアプリケーションのPEBからImageBaseを取得してみる

64bitアプリケーションのPEBからImageBaseを取得してみる

Windowsにおいて各プロセスはエグゼクティブプロセス(Executive Process:EPROCESS)構造体によって表現される。 この構造体にはプロセスに関する多くの情報が保持されており、たとえば、複数のスレッドを持つプロセスであれば各スレッド情報を持つエグゼクティブスレッド(Executive Thread:ETHREAD)構造体へのポインタなどがある。 ただしEPROCESS構造体とその関連構造体の多くはシステムのアドレス空間上に存在する。 ではユーザーモードのコードからプロセス情報を得るにはどうすればよいだろうか。 じつはプロセス環境ブロック(Process Enciroment Block:PEB)という構造体がユーザーモードアドレス空間に用意されており、ここからプロセスに関する種々のデータを取得することができる。

f:id:snoozekvn:20191223211905j:plain

今回は64bitプロセスのPEBからImageBaseを取得してみる。 ImageBaseとはその名の通りイメージファイルがロードされるアドレスであり、例えばPEファイルならImageBaseからの2バイトはMZがくるといった具合だ。 このImageBase+RVAでロードされたPEファイルの各種データにアクセスすることができる。

WinDbgで確認してみる

まずはWinDbgでnotepad.exeのPEBからImageBaseを確認してみる。 次のコマンドでPEB構造体のフォーマットを出力できる。

0:007> dt _peb
ntdll!_PEB
   +0x000 InheritedAddressSpace : UChar
   +0x001 ReadImageFileExecOptions : UChar
   +0x002 BeingDebugged    : UChar
   +0x003 BitField         : UChar
   +0x003 ImageUsesLargePages : Pos 0, 1 Bit
   +0x003 IsProtectedProcess : Pos 1, 1 Bit
   +0x003 IsImageDynamicallyRelocated : Pos 2, 1 Bit
   +0x003 SkipPatchingUser32Forwarders : Pos 3, 1 Bit
   +0x003 IsPackagedProcess : Pos 4, 1 Bit
   +0x003 IsAppContainer   : Pos 5, 1 Bit
   +0x003 IsProtectedProcessLight : Pos 6, 1 Bit
   +0x003 IsLongPathAwareProcess : Pos 7, 1 Bit
   +0x004 Padding0         : [4] UChar
   +0x008 Mutant           : Ptr64 Void
   +0x010 ImageBaseAddress : Ptr64 Void
// 省略

オフセット0x10のImageBaseAddressが求めたいImageBaseだ。

これはただPEB構造体の構造を出力しているだけでnotepad.exeのPEBではない。 notepad.exeのPEBを出力するにはPEBのアドレスを指定する必要がある。 次のようにすればよい。

0:007> r $peb
$peb=000000b5755da000
0:007> dt _peb @$peb
ntdll!_PEB
   +0x000 InheritedAddressSpace : 0 ''
   +0x001 ReadImageFileExecOptions : 0 ''
   +0x002 BeingDebugged    : 0x1 ''
   +0x003 BitField         : 0x84 ''
   +0x003 ImageUsesLargePages : 0y0
   +0x003 IsProtectedProcess : 0y0
   +0x003 IsImageDynamicallyRelocated : 0y1
   +0x003 SkipPatchingUser32Forwarders : 0y0
   +0x003 IsPackagedProcess : 0y0
   +0x003 IsAppContainer   : 0y0
   +0x003 IsProtectedProcessLight : 0y0
   +0x003 IsLongPathAwareProcess : 0y1
   +0x004 Padding0         : [4]  ""
   +0x008 Mutant           : 0xffffffff`ffffffff Void
   +0x010 ImageBaseAddress : 0x00007ff7`8dec0000 Void
// 省略

求めたいImageBaseはPEB構造体のImageBaseAddressから0x00007ff7`8dec0000であることがわかる。

dcコマンドでこのアドレスの中身を見てみる。

0:007> dc 0x00007ff7`8dec0000
00007ff7`8dec0000  00905a4d 00000003 00000004 0000ffff  MZ..............
00007ff7`8dec0010  000000b8 00000000 00000040 00000000  ........@.......
00007ff7`8dec0020  00000000 00000000 00000000 00000000  ................
00007ff7`8dec0030  00000000 00000000 00000000 000000f8  ................
00007ff7`8dec0040  0eba1f0e cd09b400 4c01b821 685421cd  ........!..L.!Th
00007ff7`8dec0050  70207369 72676f72 63206d61 6f6e6e61  is program canno
00007ff7`8dec0060  65622074 6e757220 206e6920 20534f44  t be run in DOS 
00007ff7`8dec0070  65646f6d 0a0d0d2e 00000024 00000000  mode....$.......

確かにImageBaseが取得できているようだ。

ちなみに!pebコマンドを使えば一発で求まる。

0:007> !peb
PEB at 000000b5755da000
    InheritedAddressSpace:    No
    ReadImageFileExecOptions: No
    BeingDebugged:            Yes
    ImageBaseAddress:         00007ff78dec0000
// 省略   

コードで確認してみる

次にコードでnotepad.exeのPEBからImageBaseを求めてみる。

#include <windows.h>
#include <winternl.h> // PROCESS_BASIC_INFORMATION

#include <iostream>
#include <cstdlib> // pause()
#include <sstream> // std::stringstream
#include<stdio.h>

std::stringstream ss;

typedef NTSTATUS(WINAPI *fpNtQueryInformationProcess)(
    HANDLE ProcessHandle,
    DWORD ProcessInformationClass,
    PVOID ProcessInformation,
    ULONG ProcessInformationLength,
    PULONG ReturnLength);


PROCESS_BASIC_INFORMATION* FindRemotePEB(HANDLE hProcess){


    HMODULE hNTDLL = LoadLibraryA("ntdll");
    fpNtQueryInformationProcess NtQueryInformationProcess = (fpNtQueryInformationProcess)GetProcAddress(
        hNTDLL,
        "NtQueryInformationProcess");

    // find remote PEB
    PROCESS_BASIC_INFORMATION *pBasicInfo = new PROCESS_BASIC_INFORMATION();

    // get PROCESS_BASIC_INFORMATION
    NtQueryInformationProcess(
        hProcess,
        0,
        pBasicInfo,
        sizeof(PROCESS_BASIC_INFORMATION),
        NULL);

    // debug
    std::cout << "PEBase: " << pBasicInfo->PebBaseAddress << std::endl;

    return (PROCESS_BASIC_INFORMATION*)pBasicInfo;
}

DWORD64 ReadRemoteImageBase(HANDLE hProcess, PROCESS_BASIC_INFORMATION *pBasicInfo){

    // get ImageBase offset address from the PEB 
    DWORD64 pebImageBaseOffset = (DWORD64)pBasicInfo->PebBaseAddress + 0x10 ;

    // debug
    ss << std::hex << pebImageBaseOffset;
    std::cout << "pebImageBaseOffset: " << ss.str() << std::endl;

    // get ImageBase
    DWORD64 ImageBase = 0;
    SIZE_T ReadSize = 8;
    SIZE_T bytesRead = NULL;
    ReadProcessMemory(hProcess, (LPCVOID)pebImageBaseOffset, &ImageBase, ReadSize, &bytesRead);

    return ImageBase;
}

int main()
{
    // open 64bit notepad.exe
    std::cout << "Creating process\r\n";

    LPSTARTUPINFOA pStartupInfo = new STARTUPINFOA();
    LPPROCESS_INFORMATION pProcessInfo = new PROCESS_INFORMATION();

    CreateProcessA(
        0,
        "notepad.exe",
        0,
        0,
        0,
        0, 
        0,
        0,
        pStartupInfo,
        pProcessInfo);

    PROCESS_BASIC_INFORMATION *pBasicInfo = (PROCESS_BASIC_INFORMATION*)FindRemotePEB(pProcessInfo->hProcess);
    DWORD64 ImageBase = ReadRemoteImageBase(pProcessInfo->hProcess, pBasicInfo);
 
    // debug
    printf("ProcessId:%d\n", pProcessInfo->dwProcessId);
    printf("ImageBase:%p\n", ImageBase);

    system("PAUSE");
    TerminateProcess(pProcessInfo->hProcess, NULL);
}

プロセスハンドルからNtQueryInformationProcessでPROCESS_BASIC_INFORMATIONを取得する。 その中にPEBへのポインタがあるので、0x10加算したアドレスをReadProcessMemoryで読み取ればそれがImageBaseのアドレスというわけだ。

f:id:snoozekvn:20191223211848p:plain

参考サイト

https://riosu.hateblo.jp/entry/2013/09/06/001208

https://www.microsoftpressstore.com/articles/article.aspx?p=2233328

http://inaz2.hatenablog.com/entry/2015/07/26/175115