deroko
August 13th, 2008, 23:21
Well I was writing one extension for softice, and I faced one serious problem which in turn might not be that big problem if softice authors decided to write softice code properly at some points. SoftICE manual doesn't provide us with concept how to write KDExtensions, but in turn it gives us tools which we might use to convert existing windbg extensions into softice extension. One of rules is that we may not use Exception Handling in KDExtension (taken from SoftICE manual), and silently it refuses usage of many exports from ntoskrnl.exe...
KD2SYS.exe works simply by adding extra code to your dll, and changing it's entrypoint to code which looks like this:
when extension is loaded, it MUST have Debug symbols so softice will know that it should check EntryPoint for mov eax, 0/retn 8 using INT 2D (during driver loading ntoskrnl.exe will call -> DbgLoadImageSymbols which in turns will call int 2D, hooked by SoftICE which will examine entrypoint of driver and substitute mov eax, 0 with jmp __softice_code which will in turn call DllEntryPoint.
Upper code shows part of ntos which checks if Debug directory is used, and after that it will call DbgLoadImageSymbols.
If you take a look at upper Disassm code, you may see that right after retn 08 is stored : 1000h which is RVA of DllEntryPoint... You may examine a little bit hook of int2D and you will see how loading of KD takes place in SoftICE, not a nuclear physics as you may trace Int2D hook in SoftICE without a problem, as it will be running at PASSIVE_LEVEL (level at which drivers are being loaded).
Next step is to create such driver that will have similar if not the same code which will be handld by SoftICE. My walkaround was to define DriverEntry in asm code like this:
Also make sure that TARGETTYPE=MINIPORT to link directly with DriverEntry@8 as your entrypoint, as DRIVER type will link using GsDriverEntry:
Which is not what I want...
Next step is to write convert.c/asm code which will:
1. open your file
2. locate entry point
3. calculate relative offset of DllEntryPoint
4. store it in placess of 0FFFFFFFF
5. update checksum
6. save changes
Now you may have need extension (at least that's how I write them). Kayaker probably has better solution
Now comes funny part which I figured after making dump of whole memory in VMWare, as minidump wasn't enough for me.
I tried to call some procedures which require dropping of IRQL like ExAllocatePool, which will eventually endup in ExAcquireQueuedSpinLock, which will drop IRQL to DISPATCH_LEVEL. I've started receiving numerous BSODs, and I tought that IRQL was an issue... and those BSODs occured only, and only when I was breaking in softice from ring3 applications, so I figured something had to be wrong, but in my wildest dreams I wouldn't suspect that solution was that stupid...
Let's have a look at code responsible for calling KDExtension in softice:
Now comes funny part, really funny part!!!!
This is not kd_extension_fs, this is FS of interupted TASK!!!!!!!!!! So if you are debugging ring3 code, KDExtension will be called with FS = 0x3B which points to TEB instead of KPCR, what most exports from ntoskrnl.exe will expect it to be!!! Of course, this is not the problem when you interupt TASK which is running in ring0, but I want my extension to work the same way no matter if interupted task is in ring0 or ring3.
That's the reason why KeSetEvent, ExAllocatePool, KeInsertQueueDpc and many, many others will fail, as those at some point expect FS to point to KPCR instead of TEB!
My solution was to create 2 functions, and call them, one at the beginning of exported function, and one at the end:
Although those seem like not safe functions, remember that softice uses NMI to suspend all other CPUs while it works, so this code is absolutely safe, as all other CPUs are stoped while SoftICE code is executing (at least it seems so), and current CPU is executing at HIGH_IRQL so no synchronization is required with global varaibla, as softice ensurs that only one thread can touch it
Does anyone remember this exception in SoftICE window when dumping memory from ring3 process using IceExt?
Well here is the answer why it occurs
FS is wrongly set by SoftICE 
KD2SYS.exe works simply by adding extra code to your dll, and changing it's entrypoint to code which looks like this:
Code:
.1000147F: B800000000 mov eax,0
.10001484: C20800 retn 8
.10001487: 0010 add [eax],dl
.10001489: 0000 add [eax],al
when extension is loaded, it MUST have Debug symbols so softice will know that it should check EntryPoint for mov eax, 0/retn 8 using INT 2D (during driver loading ntoskrnl.exe will call -> DbgLoadImageSymbols which in turns will call int 2D, hooked by SoftICE which will examine entrypoint of driver and substitute mov eax, 0 with jmp __softice_code which will in turn call DllEntryPoint.
Code:
PAGE:004D7D27 push dword ptr [edi] ; ImageBase
PAGE:004D7D29 call _CacheImageSymbols@4 ; CacheImageSymbols(x)
PAGE:004D7D2E test eax, eax
PAGE:004D7D30 jz __no_debug_symbols
Upper code shows part of ntos which checks if Debug directory is used, and after that it will call DbgLoadImageSymbols.
If you take a look at upper Disassm code, you may see that right after retn 08 is stored : 1000h which is RVA of DllEntryPoint... You may examine a little bit hook of int2D and you will see how loading of KD takes place in SoftICE, not a nuclear physics as you may trace Int2D hook in SoftICE without a problem, as it will be running at PASSIVE_LEVEL (level at which drivers are being loaded).
Next step is to create such driver that will have similar if not the same code which will be handld by SoftICE. My walkaround was to define DriverEntry in asm code like this:
Code:
extern DllEntryPoint@12:dword
public C DriverEntry@8
DriverEntry@8: mov eax, 0
ret 8
dd 0FFFFFFFFh
dd offset DllEntryPoint@12
Also make sure that TARGETTYPE=MINIPORT to link directly with DriverEntry@8 as your entrypoint, as DRIVER type will link using GsDriverEntry:
Code:
INIT:00011185 public GsDriverEntry
INIT:00011185 GsDriverEntry proc near
INIT:00011185 mov edi, edi
INIT:00011187 push ebp
INIT:00011188 mov ebp, esp
INIT:0001118A mov eax, __security_cookie
INIT:0001118F test eax, eax
...
INIT:000111B8 mov __security_cookie_complement, eax
INIT:000111BD pop ebp
INIT:000111BE jmp DriverEntry
Which is not what I want...
Next step is to write convert.c/asm code which will:
1. open your file
2. locate entry point
3. calculate relative offset of DllEntryPoint
4. store it in placess of 0FFFFFFFF
5. update checksum
6. save changes

Now you may have need extension (at least that's how I write them). Kayaker probably has better solution

Now comes funny part which I figured after making dump of whole memory in VMWare, as minidump wasn't enough for me.
I tried to call some procedures which require dropping of IRQL like ExAllocatePool, which will eventually endup in ExAcquireQueuedSpinLock, which will drop IRQL to DISPATCH_LEVEL. I've started receiving numerous BSODs, and I tought that IRQL was an issue... and those BSODs occured only, and only when I was breaking in softice from ring3 applications, so I figured something had to be wrong, but in my wildest dreams I wouldn't suspect that solution was that stupid...
Let's have a look at code responsible for calling KDExtension in softice:
Code:
.text:A7AB9D3A si_callExtension proc near
.text:A7AB9D3A
.text:A7AB9D3A ExtensionApi = dword ptr 8
.text:A7AB9D3A hCurrentProcess = dword ptr 0Ch
.text:A7AB9D3A hCurrentThread = dword ptr 10h
.text:A7AB9D3A dwCurrentPc = dword ptr 14h
.text:A7AB9D3A dwProcessor = dword ptr 18h
.text:A7AB9D3A args = dword ptr 1Ch
.text:A7AB9D3A
.text:A7AB9D3A push ebp
.text:A7AB9D3B mov ebp, esp
.text:A7AB9D3D push ds
.text:A7AB9D3E push es
.text:A7AB9D3F push fs
.text:A7AB9D41 push gs
.text:A7AB9D43 pusha
.text:A7AB9D44 pushf
.text:A7AB9D45 mov edi, kd_extension_esp_start
.text:A7AB9D4B mov ecx, kd_extension_stack_size
.text:A7AB9D51 shr ecx, 2
.text:A7AB9D54 xor eax, eax
.text:A7AB9D56 cld
.text:A7AB9D57 rep stosd
.text:A7AB9D59 cli
.text:A7AB9D5A mov save_sice_esp, esp
.text:A7AB9D60 mov save_sice_ebp, ebp
.text:A7AB9D66 mov ErrorString_to_display, 0
.text:A7AB9D70 mov si_extension_aborted_pagefault, 0
.text:A7AB9D77 mov b_extension_executing, 1
.text:A7AB9D7E mov dl, 1
.text:A7AB9D80 call Install_Reinsall_DivideOverflowHandler
.text:A7AB9D85 mov esp, kd_extension_esp
.text:A7AB9D8B sti
.text:A7AB9D8C mov fs, word ptr kd_extension_fs
.text:A7AB9D92 call sub_A7AB9C86
.text:A7AB9D97 push [ebp+args]
.text:A7AB9D9A push [ebp+dwProcessor]
.text:A7AB9D9D push [ebp+dwCurrentPc]
.text:A7AB9DA0 push [ebp+hCurrentThread]
.text:A7AB9DA3 push [ebp+hCurrentProcess]
.text:A7AB9DA6 call [ebp+ExtensionApi]
.text:A7AB9DA9 loc_A7AB9DA9:
.text:A7AB9DA9 cli
.text:A7AB9DAA mov esp, save_sice_esp
.text:A7AB9DB0 mov ebp, save_sice_ebp
.text:A7AB9DB6 mov b_extension_executing, 0
.text:A7AB9DBD call restore_SEH
.text:A7AB9DC2 xor dl, dl
.text:A7AB9DC4 call Install_Reinsall_DivideOverflowHandler
.text:A7AB9DC9 sti
.text:A7AB9DCA mov edi, kd_extension_esp_start
.text:A7AB9DD0 mov ecx, kd_extension_stack_size
.text:A7AB9DD6 shr ecx, 2
.text:A7AB9DD9 xor eax, eax
.text:A7AB9DDB cld
.text:A7AB9DDC repe scasd
.text:A7AB9DDE mov eax, ecx
.text:A7AB9DE0 inc eax
.text:A7AB9DE1 shl eax, 2
.text:A7AB9DE4 popf
.text:A7AB9DE5 popa
.text:A7AB9DE6 pop gs
.text:A7AB9DE8 pop fs
.text:A7AB9DEA pop es
.text:A7AB9DEB pop ds
.text:A7AB9DEC pop ebp
.text:A7AB9DED retn 18h
.text:A7AB9DED si_callExtension endp
Now comes funny part, really funny part!!!!
Code:
.text:A7AB9D8C mov fs, word ptr kd_extension_fs
This is not kd_extension_fs, this is FS of interupted TASK!!!!!!!!!! So if you are debugging ring3 code, KDExtension will be called with FS = 0x3B which points to TEB instead of KPCR, what most exports from ntoskrnl.exe will expect it to be!!! Of course, this is not the problem when you interupt TASK which is running in ring0, but I want my extension to work the same way no matter if interupted task is in ring0 or ring3.
That's the reason why KeSetEvent, ExAllocatePool, KeInsertQueueDpc and many, many others will fail, as those at some point expect FS to point to KPCR instead of TEB!
My solution was to create 2 functions, and call them, one at the beginning of exported function, and one at the end:
Code:
ULONG old_fs;
void set_fs()
{
__asm{
xor eax, eax
mov ax, fs
mov old_fs, eax
mov eax, 30h
mov fs, ax
}
}
void restore_fs()
{
__asm{
mov eax, old_fs
mov fs, ax
}
}
Although those seem like not safe functions, remember that softice uses NMI to suspend all other CPUs while it works, so this code is absolutely safe, as all other CPUs are stoped while SoftICE code is executing (at least it seems so), and current CPU is executing at HIGH_IRQL so no synchronization is required with global varaibla, as softice ensurs that only one thread can touch it

Does anyone remember this exception in SoftICE window when dumping memory from ring3 process using IceExt?
Code:
A page fault at CS:EIP 0008:12345678 occurred when address 12345678 was referenced SS:EBP 0010:12345678
Well here is the answer why it occurs

