Dream Of Every Reverserのソースコードリード

インクルードファイル。基本は自分で定義しているファイル。ntimage.hはDOS_IMAGE_HEADERなどが定義されている。

---tracer.c

#include "ntddk.h"
#include "ntimage.h"
#include "my_ring0.h"
#include "tracer.h"
#include "pe.h"
利用するカーネル関数たち。
---tracer.c

__declspec(dllimport) NTSTATUS PsLookupProcessByProcessId(IN DWORD, OUT PEPROCESS);
__declspec(dllimport) NTSTATUS PsSetCreateProcessNotifyRotuine(IN PCREATE_PROCESS_NOTIFY_ROUTINE, IN BOOLEAN);
__declspec(dllimport) void KeAttachProcess(IN PEPROCESS);//指定プロセスのアドレススペースにアタッチできる
__declspec(dllimport) void KeDetachProcess();

プロセスが生成、消滅するたびに呼び出される関数。Bpxはブレークポイントの情報を保持しているデータ構造体。この関数では主にプロセスが消滅するときにBpxデータ構造体の値をクリアすることが目的。引数のCreateで生成時の呼び出しか、消滅時の呼び出しかを見分けている。

---tracer.c

void __stdcall NotifyRoutine(IN HANDLE ParentId, IN HANDLE ProcessId, IN BOOLEAN Create){
        DWORD c_Cr3;
        
        __asm   mov     eax, cr3
        __asm   mov     c_Cr3, eax
        
        if (Bpx.Cr3 != c_Cr3)
                return;
        if (!Create){
                __asm mov  eax, false
                __asm lock xchg Bpx.Active, al          
                        
                Bpx.Cr3 = 0;                            //kill breakpoint :)
                Bpx.StartRange = 0;
                Bpx.Size = 0;
                GlobalEprocess = 0;                
        }
        return;
} 
ServiceHandleの前半。IoControlで呼び出される。
---tracer.c
NTSTATUS ServiceHandle(IN PDEVICE_OBJECT pDevice, IN PIRP pIrp){
        PIO_STACK_LOCATION StackLocation;
        DWORD status = 0;
        DWORD information = 0;
        ProcessInfo *pinfo;
        NTSTATUS retStatus;
        PEPROCESS eprocess;
        DWORD current;
        
        StackLocation = IoGetCurrentIrpStackLocation (pIrp);
重要なのはここからswitch文の後。SET_RANGEコマンドの場合。最初のほうは、バッファサイズのチェックなど。
        switch(StackLocation->Parameters.DeviceIoControl.IoControlCode){
                case SET_RANGE:
                        if (StackLocation->Parameters.DeviceIoControl.InputBufferLength < sizeof (ProcessInfo)){
                                status = STATUS_INVALID_PARAMETER;
                                break;
                        }
                        
                        pinfo = (ProcessInfo *)pIrp->AssociatedIrp.SystemBuffer;
                        
                        Bpx.StartRange = pinfo->StartRange & 0xFFFFF000;                 //round to 4kb
                        
                        //ring0 pages not supported!!!
                        if (Bpx.StartRange > 0x80000000){
                                DbgPrint("No r0 memory tracing is supported atm\n");
                                status = STATUS_INVALID_PARAMETER;
                                break;
                        }
                        //round size to 1PAGE (on ia32 it is alwas 4kb for r3)
                        if ((pinfo->Size % 0x1000))
                                Bpx.Size = pinfo->Size + 0x1000 - (pinfo->Size % 0x1000);
                        else
                                Bpx.Size = pinfo->Size;
PsLookupProcessByProcessId関数を呼び出し、特定のプロセスのEPROCESS構造体を取得している。これは、後でKeAttachProcess()するためだと思う。 KeAttachProcessを呼び出すことで、対象のプロセスのアドレス空間にアクセスできるようになる。
                        retStatus = PsLookupProcessByProcessId(pinfo->Pid, (PEPROCESS)&eprocess);                    
                        
                        if (retStatus != 0){
                                DbgPrint("Failed to obtain EPROCESS of process : 0x%.08X\n", pinfo->Pid);
                                status = STATUS_INVALID_PARAMETER;
                                break;
                        }
                        
                        KeAttachProcess(eprocess);
コメント文にもあるが、対象とするアドレスレンジがPageOutしていたときの場合に備えて、単純にアクセスすることでPageInさせる。 ProbeForRead「The ProbeForRead routine checks that a user-mode buffer actually resides in the user portion of the address space, and is correctly aligned.」
                        __try{
                                //simply load pages by accessing them
                                //this will page in pages which are paged out
                                //not needed, but I'm checking addresses range!!!!
                                ProbeForRead((void *)Bpx.StartRange, Bpx.Size,  1);
                                current = Bpx.StartRange;
                                while (current < Bpx.StartRange + Bpx.Size){
                                        __asm mov eax, current
                                        __asm mov eax, [eax]
                                        current+=0x1000;
                                }
                                
                        }__except(EXCEPTION_EXECUTE_HANDLER){
                                DbgPrint("faild to access pages...\n");
                                KeDetachProcess();
                                ObDereferenceObject(eprocess);
                                status = STATUS_INVALID_PARAMETER;
                                break;
                        }
                        NumberOfBreaks = pinfo->NumberOfBreaks;
                        BreakCount = 0;
Bpx構造体にブレークポイントのアドレスを格納し、終了処理を行う。
                        __asm mov eax, cr3
                        __asm mov Bpx.Cr3, eax
                        Bpx.Pid = pinfo->Pid;                        
                        //MemoryBreak points are set by SwapContext hook         
                        KeDetachProcess();
                        ObDereferenceObject(eprocess);
                        GlobalEprocess = eprocess;

                        __asm mov eax, true
                        __asm lock xchg Bpx.Active, al          ;tell int 0e hook that it should trace now!!!!
                                                                
                        status = STATUS_SUCCESS;
                        break;

新しく置き換えるPageFaultHandler。ここのソースコードは重要。
__declspec(naked) void New_Int0E_PAE(){
        __asm{
                pushad                                  ;r0 prolog
                push    fs
                push    ds
				push    es
KPCR(Kernel Processor Control Region)って何じゃ?何かの構造体みたい。 参考(http://www.msuiche.net/papers/Windows_Vista_32bits_and_unexported_kernel_symbols.pdf)
typedef struct _KPCR {
	KPCR_TIB Tib; // +0x000
	PKPCR SelfPcr; // +0x01C
	PKPRCB Prcb; // +0x020
	KIRQL Irql; // +0x024
	ULONG IRR; // +0x028
	ULONG IrrActive; // +0x02C
	ULONG IDR; // +0x030
	PVOID KdVersionBlock; // +0x034
	PKIDTENTRY IDT; // +0x038
	PKGDTENTRY GDT; // +0x03C
	PKTSSENTRY TSS; // +0x040
	USHORT MajorVersion; // +0x044
	USHORT MinorVersion; // +0x046
	KAFFINITY SetMember; // +0x048
	ULONG StallScaleFactor; // +0x04C
	UCHAR SpareUnused; // +0x050
	UCHAR Number; // +0x051
	UCHAR Spare0; // +0x052
	UCHAR SecondLevelCacheAssociativity; // +0x053
	UINT VdmAlert; // +0x054
	UINT KernelReserved[14]; // +0x058
	UINT SecondLevelCacheSize; // +0x090
	UINT HalReserved[16]; // +0x094
	UINT InterruptMode; // +0x0d4
	UCHAR Spare1; // +0x0d8
	UINT KernelReserved2[17]; // +0x0dc
	KPRCB PrcbData; // +0x120
} KPCR, *PKPCR;
よく分からんが、fsをこの構造体にセットした様子。dsやesの23hってのはなんなんだろう?とりあえず何かのデータを指すんだろう。きっと。
                mov     eax, 30h                        ;KPCR
                mov     fs, ax
                mov     eax, 23h
                mov     ds, ax
                mov     es, ax

Bpx.ActiveがTrueかを確認。PafeFaultが発生したアドレス(esp.regEip)が8000000h以下、つまりユーザランドで発生したPageFaultかを確認している。異なる場合は、__old_ini0eにジャンプして通常のPFHに処理を行わせる。
                cmp     Bpx.Active, true
                jne     __old_int0e
                cmp     [esp.regEip], 80000000h
                ja      __old_int0e                
プロセスIDを取得して、ブレークポイントがセットされていたアドレスかを確認する。
                call    PsGetCurrentProcessId
                cmp     Bpx.Pid, eax
                jne     __old_int0e
cr2レジスタには、PageFaultの原因となったアドレス(アクセスしようとしたアドレス)が格納される。こいつが、8000000以上かをチェック→ユーザランドか?(CR2 contians the address that the program tried to access)


                mov     eax, cr2                        ;determine if it is traced range...
                cmp     eax, 80000000h                  ;dont handle kernel memory
				ja      __old_int0e
ブレークの張ってるあるアドレスレンジかをチェックする。異なる場合は通常のPFHへ。
                mov     ecx, Bpx.StartRange
                cmp     eax, ecx
                jb      __old_int0e
                add     ecx, Bpx.Size
                cmp     eax, ecx
                jae     __old_int0e
コメントにあるとおり。
                test    [esp.regErrorCode], 1           ;pf occured because user tried to access supervisor page :)
                jnz     __fault_supervisor        
														;otherwise it is fault because this page is paged out...

以下、このFaultがページがアウトされていたため発生したと断定できる。 cr2に格納されているFaultの原因となったアドレスが保存されているEIPと一致しなかった場合、このフォールトはReadWriteアクセスのため発生したFaultになる。
                mov     eax, cr2
                cmp     [esp.regEip], eax              
                jne     __readwritePbit
                
                call    NumBreaksReached
                test    eax, eax
                jz      __readwritePbit
DbgPrintでこのフォールトのアドレス(実行アクセス)を表示する。 EipInRangeは char EipInRange[] = "eip in range at 0x%.08X\n"; となっている。
                mov     eax, cr2
                push    eax
                push    offset EipInRange
                call    DbgPrint
				add     esp, 8
                call    DeactivateAll
                mov     al, false
                lock    xchg    Bpx.Active, al
                jmp     __old_int0e                     ;handle P bit by default handler...
Bpx.FlagsにSINGLE_STEP_MODEフラグをセットする。 [esp.regEflags]の100hをセットする。0x100=000100000000(9bit目=IFフラグ)
__readwritePbit:
                call    DeactivateAll
                or      Bpx.Flags, SINGLE_STEP_MODE
                mov     eax, [esp.regEip]
                mov     Bpx.ReadWriteEip, eax
                or      [esp.regEflags], 100h
                jmp     __old_int0e

__fault_supervisor:
                mov     eax, cr2
                cmp     [esp.regEip], eax
                jne     __readwrite
                
                call    NumBreaksReached
                test    eax, eax
                jz      __readwrite                
                
                mov     eax, cr2
                push    eax
                push    offset EipInRange
                call    DbgPrint
                add     esp, 8
                
                call    DeactivateAll
                mov     al, false
                lock    xchg    Bpx.Active, al
                jmp     __invlpg

__readwrite:    

                call    DeactivateAll                   ;remove all breaks while copying is in progress!!!
                or      Bpx.Flags, SINGLE_STEP_MODE     ;
                mov     eax, [esp.regEip]
                mov     Bpx.ReadWriteEip, eax
                or      [esp.regEflags], 100h           ;set T flag and execute code!!!!

TLBをフラッシュする。invlpg命令。引数のアドレスを含むページをTLBからフラッシュする。
__invlpg:
                mov     eax, cr2
                invlpg  [eax]                           ;flush tlb and use new set of permissions for this page...
                push    eax
                call    PaeSpecialCase                  ;handle special case when PAE is enabled
                test    eax, eax
                jnz     __old_int0e
                test    [esp.regErrorCode], 2           ;if exception occured due to write call old int handler!!!
                jnz     __old_int0e                     ;since on PAE r/w bit is not set but is managed by windows
                pop     es                              ;internaly so still old handler should be called
                pop     ds
                pop     fs
                popad
                add     esp, 4
                iretd
        
        
        
__old_int0e:    pop     es
                pop     ds
                pop     fs
                popad
                jmp     [Old_Int0E]
        }
}
デバッガ割り込み用ハンドラ ブレークポイントのチェックとプロセスIDのチェック。 ※esp.regErrorCodeの部分にeipが格納されている。
__declspec(naked) void New_Int01(){
        __asm{
                pushad
                push    fs
                push    ds
                push    es
                mov     eax, 30h
                mov     fs, ax
                mov     eax, 23h
                mov     ds, ax
                mov     es, ax
                
                cmp     Bpx.Active, 0
                je      __old_int01     
                ;use same struct as above but keep in mind that here is not present ErrorCode, so ErrorCode is eip now :)
                call    PsGetCurrentProcessId
                cmp     eax, Bpx.Pid
                jne     __old_int01
ブレークがカーネルレイヤで発生したいた場合は、オリジナルのint01ハンドラへジャンプ。 SINGLE_STEP_MODEのフラグたっていなっかったら、ジャンプ。 これは、ここで通常のハンドラの処理をさせると上え待ち構えているデバッガにはBreakPointとしてキャッチされるのかな?
                ;allow softice debugging of hooks, dont handle TF in kernel memory
                cmp     [esp.regErrorCode], 80000000h
                ja      __old_int01              ;call sice... 
                test    Bpx.Flags, SINGLE_STEP_MODE
                jz      __old_int01               
                mov     eax, [esp.regErrorCode]  ;instruction is completly executed
                cmp     eax, Bpx.ReadWriteEip
                jne     __stoptf
                or      [esp.regCs], 100h
                pop     es
                pop     ds
                pop     fs
                popad
                iretd

__stoptf:       mov     Bpx.Flags, 0
                mov     Bpx.ReadWriteEip, 0
                call    ActivateAll             ;activate all breakpoints on memory
                and     [esp.regCs], 0FFFFFEFFh
                pop     es
                pop     ds
                pop     fs
                popad
                iretd                

__old_int01:    pop     es
                pop     ds
                pop     fs
                popad
                jmp     [Old_Int01]
        }
}
WP=1の時は、スーパバイザ・レベルのプロシージャがユーザ・レベル読み取り専用ページに書き込むことを禁止する。 WP=0の時は、スーパバイザ・レベルのプロシージャがユーザ・レベル読み取り専用ページに書き込むことを許可する。 参考 http://caspar.hazymoon.jp/OpenBSD/annex/intel_arc.html
__declspec(naked) void SetWP(){
        __asm{
                push    eax
                mov     eax, cr0
                or      eax, 10000h
                mov     cr0, eax
                pop     eax
                ret
        }
}

__declspec(naked) void ClearWP(){
        __asm{
                push    eax
                mov     eax, cr0
                and     eax, 0FFFEFFFFh
                mov     cr0, eax
                pop     eax
                ret
    
		}
}

DriverEntory。PAEの有無のチェック。
NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegPath){ 
        UNICODE_STRING device, symlink;
        PDEVICE_OBJECT pDeviceObject;
        DWORD ntosbase, current, c_Cr4;
        IMAGE_DOS_HEADER *mz;
        peheader_struct *pe;
        
        DbgPrint("Dream Of Every Reverser tracing engine\n");
        DbgPrint("             (c) 2007 deroko of ARTeam\n");    
        /* 
                mov eax, cr4
                need to detect if PAE is enabled or not
        */
        __asm  _emit  0x0F           
        __asm  _emit  0x20
        __asm  _emit  0xE0
        __asm   mov c_Cr4, eax
        
        if (c_Cr4 & 0x20){
                DbgPrint("PAE Enabled\n");
                PaeEnabled = true;
        }else{
                DbgPrint("PAE Disabled\n");
                PaeEnabled = false;
        }
                     
ntoskrnlのPEヘッダを取得。 ※この手法はみたことがない。覚えておこう。 HookSwapContextあたりがまだちゃんと理解できていないなぁ。
		//__try/__except protects us from memory searchg page faults
        //if those go fine, then there is nothing that can stop us
        //form hooking code :)
        __try{        
                ntosbase = (DWORD)&NtCreateFile;
                ntosbase &= 0xFFFFF000;
        
                while (*(WORD *)ntosbase != 'ZM')
                        ntosbase-=0x1000; 
      
                mz = (IMAGE_DOS_HEADER *)ntosbase;
                pe = (peheader_struct *)(ntosbase + mz->e_lfanew);  
        
                //find SwapContext
                current = ntosbase;
                while (!(current > ntosbase + pe->pe_sizeofimage - sizeof(SwapContextPatern))){
                        if (!(memcmp((void *)current, (void *)SwapContextPatern, sizeof(SwapContextPatern)))){
                                SwapContext = current;
                                DbgPrint("ntoskrnl base located at 0x%.08X\n", ntosbase);
                                DbgPrint("SwapContext located at 0x%.08X\n", SwapContext);
                                HookSwapContext();
                                DbgPrint("Hooking SwapContext\n");
                                //MP Support
                                //PKTHREAD cthread;
                                //UCHAR i;
                                //for (i = 0; i < KeNumberProcessors; i++){
                                //      KeSetAffinityThread(cthread, 1 << i);
                                HookInterrupt(0x0E, (DWORD)New_Int0E_PAE, &Old_Int0E );
                                DbgPrint("Hooking Int 0Eh\n");
                                HookInterrupt(0x01, (DWORD)New_Int01, &Old_Int01);
                                DbgPrint("Hooking Int 01h\n");
                                //}
                                PsSetCreateProcessNotifyRoutine((PCREATE_PROCESS_NOTIFY_ROUTINE)NotifyRoutine, FALSE);
                                DbgPrint("Installing onexit notify routine...\n");
                                break;
                        }
                        current++;
                }
        }__except(EXCEPTION_EXECUTE_HANDLER){
                DbgPrint("Failed to retrive SwapContext and ntosbase\n");
                return STATUS_DEVICE_CONFIGURATION_ERROR;
        }
             
        RtlInitUnicodeString(&device, szDevice);
        IoCreateDevice(pDriverObject, 0, &device, FILE_DEVICE_UNKNOWN, 0, 0, &pDeviceObject); 
        RtlInitUnicodeString(&symlink, szSymlink);
        IoCreateSymbolicLink(&symlink,&device);
        
        pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = ServiceHandle;
        pDriverObject->MajorFunction[IRP_MJ_CREATE] = \
        pDriverObject->MajorFunction[IRP_MJ_CLOSE] = CreateCloseHandler;
        /* 
         Due to hook in SwapContext this driver must not be unloaded
        */
        //pDriverObject->DriverUnload = UnloadMe;
        DbgPrint("Tracing engine loaded...\n");
        return STATUS_SUCCESS;
}