インクルードファイル。基本は自分で定義しているファイル。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;
}