インクルードファイル。基本は自分で定義しているファイル。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 esKPCR(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, axBpx.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_int0ecr2レジスタには、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 __readwritePbitDbgPrintでこのフォールトのアドレス(実行アクセス)を表示する。 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; }