💾 Archived View for aphrack.org › issues › phrack59 › 16.gmi captured on 2021-12-03 at 14:04:38. Gemini links have been rewritten to link to archived content
-=-=-=-=-=-=-
==Phrack Inc.== Volume 0x0b, Issue 0x3b, Phile #0x10 of 0x12 |=----------------=[ Playing with Windows /dev/(k)mem ]=-----------------=| |=-----------------------------------------------------------------------=| |=---------------=[ crazylord <crazylord@minithins.net> ]=---------------=| 1 - Introduction 2 - Introduction to Windows Objects 2.1 What are they ? 2.2 Their structure 2.3 Objects manipulation 3 - Introduction to \Device\PhysicalMemory 3.1 The object 3.2 Need writing access ? 4 - Having fun with \Device\PhysicalMemory 4.1 Reading/Writing to memory 4.3 What's a Callgate ? 4.4 Running ring0 code without the use of Driver 4.2 Deeper into Process listing 4.5 Bonus Track 5 - Sample code 5.1 kmem.h 5.2 chmod_mem.c 5.3 winkdump.c 5.2 winkps.c 5.4 fun_with_ipd.c 6 - Conclusion 7 - References --[ 1 - Introduction This papers covers an approch to Windows /dev/kmem linux like object. My research has been done on a Windows 2000 professional version that means that most of the code supplied with the article should work with all Windows 2000 version and is supposed to work with Windows XP with little code modification. Windows 9x/Me are clearly not supported as they are not based on the same kernel architecture. --[ 2 - Introduction to Windows Objects Windows 2000 implements an object models to provide a way of easy manipulating the most basic elements of the kernel. We will briefly see in this chapter what are these objects and how we can manipulate them. ----[ 2.1 What are they ? According to Microsoft, the object manager was designed to meet these goals * use named object for easy recognition * support POSIX subsystem * provide a easy way for manipulating system resources * provide a charge mechanism to limit resource used by a process * be C2 security compliant :) (C2: Controlled Access Protection) There are 27 differents objects types: * Adapter * File * Semaphore * Callback * IoCompletion * SymbolicLink * Controler * Job * Thread * Desktop * Key * Timer * Device * Mutant * Token * Directory * Port * Type * Driver * Process * WaitablePort * Event * Profile * WindowStation * EventPair * Section * WmiGuid Most of these names are explicit enough to understand what's they are about. I will just explain some obscure names: * an EventPair is just a couple of 2 Event objects. * a Mutant also called Mutex is a synchronization mechanism for resource access. * a Port is used by the LPC (Local Procedure Call) for Inter-Processus Communication. * a Section (file mapping) is a region of shared memory. * a Semaphore is a counter that limit access to a resource. * a Token (Access Token) is the security profile of an object. * a WindowStation is a container object for desktop objects. Objects are organised into a directory structure which looks like this: - \ - ArcName (symbolic links to harddisk partitions) - NLS (sections ...) - Driver (installed drivers) - WmiGuid - Device (/dev linux like) - DmControl - RawDmVolumes - HarddiskDmVolumes - PhysicalDmVolumes - Windows - WindowStations - RPC Control - BaseNamedObjects - Restricted - ?? (current user directory) - FileSystem (information about installable files system) - ObjectTypes (contains all avaible object types) - Security - Callback - KnownDlls (Contains sections of most used DLL) The "??" directory is the directory for the current user and "Device" could be assimiled as the "/dev" directory on Linux. You can explore these structures using WinObj downloadable on Sysinternals web sites (see [1]). ----[ 2.2 Their structure Each object is composed of 2 parts: the object header and the object body. Sven B. Schreiber defined most of the non-documented header related structures in his book "Windows 2000 Undocumented Secrets". Let's see the header structure. --- from w2k_def.h: typedef struct _OBJECT_HEADER { /*000*/ DWORD PointerCount; // number of references /*004*/ DWORD HandleCount; // number of open handles /*008*/ POBJECT_TYPE ObjectType; // pointer to object type struct /*00C*/ BYTE NameOffset; // OBJECT_NAME offset /*00D*/ BYTE HandleDBOffset; // OBJECT_HANDLE_DB offset /*00E*/ BYTE QuotaChargesOffset; // OBJECT_QUOTA_CHARGES offset /*00F*/ BYTE ObjectFlags; // OB_FLAG_* /*010*/ union { // OB_FLAG_CREATE_INFO ? ObjectCreateInfo : QuotaBlock /*010*/ PQUOTA_BLOCK QuotaBlock; /*010*/ POBJECT_CREATE_INFO ObjectCreateInfo; }; /*014*/ PSECURITY_DESCRIPTOR SecurityDescriptor; /*018*/ } OBJECT_HEADER, *POBJECT_HEADER; --- Each offset in the header are negative offset so if you want to find the OBJECT_NAME structure from the header structure, you calculate it by doing: address = object_header_address - name_offset OBJECT_NAME structure allows the creator to make the object visible to other processes by giving it a name. OBJECT_HANDLE_DB structure allows the kernel to track who is currently using this object. OBJECT_QUOTA_CHARGES structure defines the resource charges levied against a process when accessing this object. The OBJECT_TYPE structure stocks global informations about the object type like default security access, size of the object, default charge levied to process using an object of this type, ... A security descriptor is bound to the object so the kernel can restrict access to the object. Each object type have internal routines quite similar to C++ object constructors and destructors: * dump method - maybe for debugging purpose (always NULL) * open method - called when an object handle is opened * close method - called when an object handle is closed * delete method - called when an object is deleted * parse method - called when searching an object in a list of object * security method - called when reading/writing a protection for the current object * query name method - called when a thread request the name of the object * "ok to close" - called when a thread is closing a handle The object body structure totally depends on the object type. A very few object body structure are documented in the DDK. If you are interested in these structures you may google :) or take a look at chapeaux-noirs home page in the kernel_reversing section (see [4]). ---- [ 2.3 Object manipulation On the user-mode point of view, objects manipulation is done through the standart Windows API. For example, in order to access a file object you can use fopen()/open() which will call CreateFile(). At this point, we switch to kernel-mode (NtCreateFile()) which call IoCreateFile() in ntoskrnl.exe. As you can see, we still don't know we are manipulating an "object". By disassembling IoCreateFile(), you will see some function like ObOpenObjectByName, ObfDereferenceObject, ... (By the way you will only see such functions if you have win2k symbols downloadable on Microsoft DDK web site (see [2]) and disassemblingbwith a disassembler supporting Windows Symbols files like IDA/kd/Softicevbecause these functions are not exported.) Each function's name begining with "Ob" is related to the Object Manager. So basically, a standart developper don't have to deal with object but we want to. All the object manager related function for user-mode are exported by ntdll.dll. Here are some examples: NtCreateDirectoryObject, NtCreateSymbolicLinkObject, NtDuplicateObject, NtMakeTemporaryObject, NtOpenDirectoryObject, ... Some of these functions are documented in the MSDN some (most ?) are not. If you really want to understand the way object works you should better take a look at the exported function of ntoskrnl.exe beginning with "Ob". 21 functions exported and 6 documented =] If you want the prototypes of the 15 others, go on the ntifs.h home page (see [3]) or to chapeaux-noirs web site (see [4]). --[ 3 - Introduction to \Device\PhysicalMemory As far as i know, \Device\PhysicalMemory object was discovered by Mark Russinovich from Sysinternals (see [1]). He coded the first code using it : Physmem avaible on his site. Enough greeting :), now we will try to understand what is this object used for and what we can do with it. ----[ 3.1 - the object In order to look at the object information, we are going to need a tool like the Microsoft Kernel Debugger avaible in the Microsoft DDK (see [2]). Ok let's start working ... Microsoft(R) Windows 2000 Kernel Debugger Version 5.00.2184.1 Copyright (C) Microsoft Corp. 1981-1999 Symbol search path is: c:\winnt\symbols Loading Dump File [livekd.dmp] Full Kernel Dump File Kernel Version 2195 UP Free Kernel base = 0x80400000 PsLoadedModuleList = 0x8046a4c0 Loaded kdextx86 extension DLL Loaded userkdx extension DLL Loaded dbghelp extension DLL f1919231 eb30 jmp f1919263 kd> !object \Device\PhysicalMemory !object \Device\PhysicalMemory Object: e1001240 Type: (fd038880) Section ObjectHeader: e1001228 HandleCount: 0 PointerCount: 3 Directory Object: fd038970 Name: PhysicalMemory The basic object parser from kd (kernel debugger) tells us some information about it. No need to explain all of these field means, most of them are explicit enough if you have readen the article from the beginning if not "jmp dword Introduction_to_Windows_Objects". Ok the interesting thing is that it's a Section type object so that clearly mean that we are going to deal with some memory related toy. Now let's dump the object's header structure. kd> dd e1001228 L 6 dd e1001228 L 6 e1001228 00000003 00000000 fd038880 12200010 e1001238 00000001 e1008bf8 details: --> 00000003 : PointerCount = 3 --> 00000000 : HandleCount = 0 --> fd038880 : pointer to object type = 0xfd038880 --> 12200010 --> 10 : NameOffset --> 00 : HandleDBOffset --> 20 : QuotaChargeOffset --> 12 : ObjectFlags = OB_FLAG_PERMANENT & OB_FLAG_KERNEL_MODE --> 00000001 : QuotaBlock --> e1008bf8 : SecurityDescriptor Ok the NameOffset exists, well no surprise, this object has a name .. but the HandleDBOffset don't. That means that the object doesnt track handle assigned to it. The QuotaChargeOffset isn't really interesting and the ObjectFlags tell us that this object is permanent and has been created by the kernel. For now nothing very interesting ... We dump the object's name structure just to be sure we are not going the wrong way :). (Remember that offset are negative). kd> dd e1001228-10 L3 dd e1001228-10 L3 e1001218 fd038970 001c001c e1008ae8 --> fd038970 : pointer to object Directory --> 001c001c --> 001c : UNICODE_STRING.Length --> 001c : UNICODE_STRING.MaximumLength --> e1008ae8 : UNICODE_STRING.Buffer (pointer to wide char string) kd> du e1008ae8 du e1008ae8 e1008ae8 "PhysicalMemory" Ok now, let's look at the interesting part, the security descriptor: kd> !sd e1008bf8 !sd e1008bf8 ->Revision: 0x1 ->Sbz1 : 0x0 ->Control : 0x8004 SE_DACL_PRESENT SE_SELF_RELATIVE ->Owner : S-1-5-32-544 ->Group : S-1-5-18 ->Dacl : ->Dacl : ->AclRevision: 0x2 ->Dacl : ->Sbz1 : 0x0 ->Dacl : ->AclSize : 0x44 ->Dacl : ->AceCount : 0x2 ->Dacl : ->Sbz2 : 0x0 ->Dacl : ->Ace[0]: ->AceType: ACCESS_ALLOWED_ACE_TYPE ->Dacl : ->Ace[0]: ->AceFlags: 0x0 ->Dacl : ->Ace[0]: ->AceSize: 0x14 ->Dacl : ->Ace[0]: ->Mask : 0x000f001f ->Dacl : ->Ace[0]: ->SID: S-1-5-18 ->Dacl : ->Ace[1]: ->AceType: ACCESS_ALLOWED_ACE_TYPE ->Dacl : ->Ace[1]: ->AceFlags: 0x0 ->Dacl : ->Ace[1]: ->AceSize: 0x18 ->Dacl : ->Ace[1]: ->Mask : 0x0002000d ->Dacl : ->Ace[1]: ->SID: S-1-5-32-544 ->Sacl : is NULL In other words that means that the \Device\PhysicalMemory object has this following rights: user SYSTEM: Delete, Change Permissions, Change Owner, Query Data, Query State, Modify State user Administrator: Query Data, Query State So basically, user Administrator as no right to Write here but user SYSTEM do, so that mean that Administrator does too. You have to notice that in fact THIS IS NOT LIKE /dev/kmem !! /dev/kmem maps virtual memory on Linux, \Device\PhysicalMemory maps physical memory, the right title for this article should be "Playing with Windows /dev/mem" as /dev/mem maps physical memory but /dev/kmem sounds better and much more wellknown :). As far as i know the Section object body structure hasn't been yet reversed as i'm writing the article so we can't analyze it's body. ----[ 3.2 need writing access ? Ok .. we are user administrator and we want to play with our favourite Object, what can we do ? As most Windows administrators should know it is possible to run any process as user SYSTEM using the schedule service. If you want to be sure that you can, just start the schedule with "net start schedule" and then try add a task that launch regedit.exe c:\>at <when> /interactive regedit.exe After that try to look at the SAM registry key, if you can, you are user SYSTEM otherwise you are still administrator since only user SYSTEM has reading rights. Ok that's fine if we are user Administrator but what's up if we want to allow somebody/everyone to write to \Device\PhysicalMemory (for learning purpose off course). We just have to add another ACL (access-control list) to this object. To do this you have to follow these steps: 1) Open a handle to \Device\PhysicalMemory (NtOpenSection) 2) Retrieve the security descriptor of it (GetSecurityInfo) 3) Add Read/Write authorization to the current ACL (SetEntriesInAcl) 4) Update the security descriptor (SetSecurityInfo) 5) Close the handle previously opened see chmod_mem.c sample code. After having run chmod_mem.exe we dump another time the security descriptor of \Device\PhysicalMemory. kd> !object \Device\PhysicalMemory !object \Device\PhysicalMemory Object: e1001240 Type: (fd038880) Section ObjectHeader: e1001228 HandleCount: 0 PointerCount: 3 Directory Object: fd038970 Name: PhysicalMemory kd> dd e1001228+0x14 L1 dd e1001228+0x14 L1 e100123c e226e018 kd> !sd e226e018 !sd e226e018 ->Revision: 0x1 ->Sbz1 : 0x0 ->Control : 0x8004 SE_DACL_PRESENT SE_SELF_RELATIVE ->Owner : S-1-5-32-544 ->Group : S-1-5-18 ->Dacl : ->Dacl : ->AclRevision: 0x2 ->Dacl : ->Sbz1 : 0x0 ->Dacl : ->AclSize : 0x68 ->Dacl : ->AceCount : 0x3 ->Dacl : ->Sbz2 : 0x0 ->Dacl : ->Ace[0]: ->AceType: ACCESS_ALLOWED_ACE_TYPE ->Dacl : ->Ace[0]: ->AceFlags: 0x0 ->Dacl : ->Ace[0]: ->AceSize: 0x24 ->Dacl : ->Ace[0]: ->Mask : 0x00000002 ->Dacl : ->Ace[0]: ->SID: S-1-5-21-1935655697-436374069-1060284298-500 ->Dacl : ->Ace[1]: ->AceType: ACCESS_ALLOWED_ACE_TYPE ->Dacl : ->Ace[1]: ->AceFlags: 0x0 ->Dacl : ->Ace[1]: ->AceSize: 0x14 ->Dacl : ->Ace[1]: ->Mask : 0x000f001f ->Dacl : ->Ace[1]: ->SID: S-1-5-18 ->Dacl : ->Ace[2]: ->AceType: ACCESS_ALLOWED_ACE_TYPE ->Dacl : ->Ace[2]: ->AceFlags: 0x0 ->Dacl : ->Ace[2]: ->AceSize: 0x18 ->Dacl : ->Ace[2]: ->Mask : 0x0002000d ->Dacl : ->Ace[2]: ->SID: S-1-5-32-544 ->Sacl : is NULL Our new Ace (access-control entry) is Ace[0] with a 0x00000002 (SECTION_MAP_WRITE) right. For more information about Security win32 API see MSDN ([9]). --[ 4 - Having fun with \Device\PhysicalMemory Why playing with \Device\PhysicalMemory ? reading, writing, patching memory i would say. That should be enough :) ----[ 4.1 Reading/Writing to memory Ok let's start playing... In order to read/write to \Device\PhysicalMemory, you have do this way: 1) Open a Handle to the object (NtOpenSection) 2) Translate the virtual address into a physical address 3) Map the section to a memory space (NtMapViewOfSection) 4) Read/Write data where the memory has been mapped 5) Unmap the section (NtUnmapViewOfSection) 6) Close the object's Handle (NtClose) Our main problem for now is how to translate the virtual address to a physical address. We know that in kernel-mode (ring0), there is a function called MmGetPhysicalAddress exported by ntoskrnl.exe which do that. But we are in ring3 so we have to "emulate" such function. --- from ntddk.h PHYSICAL_ADDRESS MmGetPhysicalAddress(void *BaseAddress); --- PHYSICAL_ADDRESS is a quad-word (64 bits). At the beginning i wanted to join with the article the analysis of the assembly code but it's too long. And as address translation is sort of generic (cpu relative) i only go fast on this subject. The low part of the quad-word is passed in eax and the high part in edx. For virtual to physical address translation we have 2 cases: * case 0x80000000 <= BaseAddress < 0xA0000000: the only thing we need to do is to apply a 0x1FFFF000 mask to the virtual address. * case BaseAddress < 0x80000000 && BaseAddress >= 0xA0000000 This case is a problem for us as we have no way to translate addresses in this range because we need to read cr3 register or to run non ring3 callable assembly instruction. For more information about Paging on Intel arch take a look at Intel Software Developer's Manual Volume 3 (see [5]). EliCZ told me that by his experience we can guess a physical address for this range by masking the byte offset and keeping a part of the page directory index. mask: 0xFFFF000. We can know produce a light version of MmGetPhysicalAddress() PHYSICAL_MEMORY MyGetPhysicalAddress(void *BaseAddress) { if (BaseAddress < 0x80000000 || BaseAddress >= 0xA0000000) { return(BaseAddress & 0xFFFF000); } return(BaseAddress & 0x1FFFF000); } The problem with the addresses outside the [0x80000000, 0xA0000000] is that they can't be guessed with a very good sucess rate. That's why if you want good results you would rather call the real MmGetPhysicalAddress(). We will see how to do that in few chapter. See winkdump.c for sample memory dumper. After some tests using winkdump i realised that in fact there is another problem in our *good* range :>. When translating virtual address above 0x877ef000 the physical address is getting above 0x00000000077e0000. And on my system this is not *possible*: kd> dd MmHighestPhysicalPage l1 dd MmHighestPhysicalPage l1 8046a04c 000077ef We can see that the last physical page is locate at 0x0000000077ef0000. So in fact that means that we can only dump a small section of the memory. But anyway the goal of this chapter is much more an explaination about how to start using \Device\PhysicalMemory than to create a *good* memory dumper. As the dumpable range is where ntoskrnl.exe and HAL.dll (Hardware Abstraction Layer) are mapped you can still do some stuff like dumping the syscall table: kd> ? KeServiceDescriptorTable ? KeServiceDescriptorTable Evaluate expression: -2142852224 = 8046ab80 0x8046ab80 is the address of the System Service Table structure which looks like: typedef struct _SST { PDWORD ServiceTable; // array of entry points PDWORD CounterTable; // array of usage counters DWORD ServiceLimit; // number of table entries PBYTE ArgumentTable; // array of byte counts } SST, *PSST; C:\coding\phrack\winkdump\Release>winkdump.exe 0x8046ab80 16 *** win2k memory dumper using \Device\PhysicalMemory *** Virtual Address : 0x8046ab80 Allocation granularity: 65536 bytes Offset : 0xab80 Physical Address : 0x0000000000460000 Mapped size : 45056 bytes View size : 16 bytes d8 04 47 80 00 00 00 00 f8 00 00 00 bc 08 47 80 | ..G...........G. Array of pointers to syscalls: 0x804704d8 (symbol KiServiceTable) Counter table : NULL ServiceLimit : 248 (0xf8) syscalls Argument table : 0x804708bc (symbol KiArgumentTable) We are not going to dump the 248 syscalls addresses but just take a look at some: C:\coding\phrack\winkdump\Release>winkdump.exe 0x804704d8 12 *** win2k memory dumper using \Device\PhysicalMemory *** Virtual Address : 0x804704d8 Allocation granularity: 65536 bytes Offset : 0x4d8 Physical Address : 0x0000000000470000 Mapped size : 4096 bytes View size : 12 bytes bf b3 4a 80 6b e8 4a 80 f3 de 4b 80 | ..J.k.J...K. * 0x804ab3bf (NtAcceptConnectPort) * 0x804ae86b (NtAccessCheck) * 0x804bdef3 (NtAccessCheckAndAuditAlarm) In the next section we will see what are callgates and how we can use them with \Device\PhysicalMemory to fix problems like our address translation thing. ----[ 4.2 What's a Callgate Callgate are mechanisms that enable a program to execute functions in higher privilege level than it is. Like a ring3 program could execute ring0 code. In order to create a Callgate yo must specify: 1) which ring level you want the code to be executed 2) the address of the function that will be executed when jumping to ring0 3) the number of arguments passed to the function When the callgate is accessed, the processor first performs a privilege check, saves the current SS, ESP, CS and EIP registers, then it loads the segment selector and stack pointer for the new stack (ring0 stack) from the TSS into the SS and ESP registers. At this point it can switch to the new ring0 stack. SS and ESP registers are pushed onto the stack, the arguments are copied. CS and EIP (saved) registers are now pushed onto the stack for the calling procedure to the new stack. The new segment selector is loaded for the new code segment and instruction pointer from the callgate is loaded into CS and EIP registers. Finnaly :) it jumps to the function's address specified when creating the callgate. The function executed in ring0 MUST clean its stack once it has finished executing, that's why we are going to use __declspec(naked) (MS VC++ 6) when defining the function in our code (similar to __attribute__(stdcall) for GCC). --- from MSDN: __declspec( naked ) declarator For functions declared with the naked attribute, the compiler generates code without prolog and epilog code. You can use this feature to write your own prolog/epilog code using inline assembler code. --- For more information about callgates look at Intel Software Developer's Manual Volume 1 (see [5]). In order to install a Callgate we have 2 choices: or we manually seek a free entry in the GDT where we can place our Callgate or we use some undocumented functions of ntoskrnl.exe. But these functions are only accessible from ring0. It's useless in our case since we are not in ring0 but anyway i will very briefly show you them: NTSTATUS KeI386AllocateGdtSelectors(USHORT *SelectorArray, USHORT nSelectors); NTSTATUS KeI386ReleaseGdtSelectors(USHORT *SelectorArray, USHORT nSelectors); NTSTATUS KeI386SetGdtSelector(USHORT Selector, PVOID Descriptor); Their names are explicits enough i think :). So if you want to install a callgate, first allocate a GDT selector with KeI386AllocateGdtSelectors(), then set it with KeI386SetGdtSelector. When you are done just release it with KeI386ReleaseGdtSelectors. That's interesting but it doesn't fit our need. So we need to set a GDT selector while executing code in ring3. Here comes \Device\PhysicalMemory. In the next section i will explain how to use \Device\PhysicalMemory to install a callgate. ----[ 4.3 Running ring0 code without the use of Driver First question, "why running ring0 code without the use of Device Driver ?" Advantages: * no need to register a service to the SCM (Service Control Manager). * stealth code ;) Inconvenients: * code would never be as stable as if running from a (well coded) device driver. * we need to add write access to \Device\PhysicalMemory So just keep in mind that you are dealing with hell while running ring0 code through \Device\PhysicalMemory =] Ok now we can write the memory and we know that we can use callgate to run ring0 so what are you waiting ? First we need to know what part of the section to map to read the GDT table. This is not a problem since we can access the global descriptor table register using "sgdt" assembler instruction. typedef struct _KGDTENTRY { WORD LimitLow; // size in bytes of the GDT WORD BaseLow; // address of GDT (low part) WORD BaseHigh; // address of GDT (high part) } KGDTENTRY, *PKGDTENTRY; KGDT_ENTRY gGdt; _asm sgdt gGdt; // load Global Descriptor Table register into gGdt We translate the Virtual address from BaseLow/BaseHigh to a physical address and then we map the base address of the GDT table. We are lucky because even if the GDT table adddress is not in our *wanted* range, it will be right translated (in 99% cases). PhysicalAddress = GetPhysicalAddress(gGdt.BaseHigh << 16 | gGdt.BaseLow); NtMapViewOfSection(SectionHandle, ProcessHandle, BaseAddress, // pointer to mapped memory 0L, gGdt.LimitLow, // size to map &PhysicalAddress, &ViewSize, // pointer to mapped size ViewShare, 0, // allocation type PAGE_READWRITE); // protection Finally we loop in the mapped memory to find a free selector by looking at the "Present" flag of the Callgate descriptor structure. typedef struct _CALLGATE_DESCRIPTOR { USHORT offset_0_15; // low part of the function address USHORT selector; UCHAR param_count :4; UCHAR some_bits :4; UCHAR type :4; // segment or gate type UCHAR app_system :1; // segment descriptor (0) or system segment (1) UCHAR dpl :2; // specify which privilege level can call it UCHAR present :1; USHORT offset_16_31; // high part of the function address } CALLGATE_DESCRIPTOR, *PCALLGATE_DESCRIPTOR; offset_0_15 and offset_16_31 are just the low/high word of the function address. The selector can be one of this list: --- from ntddk.h #define KGDT_NULL 0 #define KGDT_R0_CODE 8 // <-- what we need (ring0 code) #define KGDT_R0_DATA 16 #define KGDT_R3_CODE 24 #define KGDT_R3_DATA 32 #define KGDT_TSS 40 #define KGDT_R0_PCR 48 #define KGDT_R3_TEB 56 #define KGDT_VDM_TILE 64 #define KGDT_LDT 72 #define KGDT_DF_TSS 80 #define KGDT_NMI_TSS 88 --- Once the callgate is installed there are 2 steps left to supreme ring0 power: coding our function called with the callgate and call the callgate. As said in section 4.2, we need to code a function with a ring0 prolog / epilog and we need to clean our stack. Let's take a look at this sample function: void __declspec(naked) Ring0Func() { // our nude function :] // ring0 prolog _asm { pushad // push eax,ecx,edx,ebx,ebp,esp,esi,edi onto the stack pushfd // decrement stack pointer by 4 and push EFLAGS onto the stack cli // disable interrupt } // execute your ring0 code here ... // ring0 epilog _asm { popfd // restore registers pushed by pushfd popad // restore registers pushed by pushad retf // you may retf <sizeof arguments> if you pass arguments } } Pushing all registers onto the stack is the way we use to save all registers while the ring0 code execution. 1 step left, calling the callgate... A standart call won't fit as the callgate procedure is located in a different privilege level (ring0) than the current code privilege level (ring3). We are doing to do a "far call" (inter-privilege level call). So in order to call the callgate you must do like this: short farcall[3]; farcall[0 --> 1] = offset from the target operand. This is ignored when a callgate is used according to "IA-32 Intel Architecture Software Developer's Manual (Volume 2)" (see [5]). farcall[2] = callgate selector At this time we can call our callgate using inline assembly. _asm { push arg1 ... push argN call fword ptr [farcall] } I forgot to mention that as it's a farcall first argument is located at [ebp+0Ch] in the callgate function. ----[ 4.4 Deeper into Process listing Now we will see how to list process in the kernel the lowest level we can do :). The design goal of creating a Kernel process lister at the lowest level could be to see process hidden by a rootkit (taskmgr.exe patched, Syscall hooked, ...). You remember that Jamirocai song: "Going deeper underground". We will do the same. Let's see which way we can use to list process. - Process32First/Process32Next, the easy documented way (ground level) - NtQuerySystemInformation using Class 5, Native API way. Basicly not documented but there are many sample on internet (level -1) - ExpGetProcessInformation, called internally by NtQuerySystemInformation (level -2) - Reading the double chained list PsActiveProcessHead (level -3) :p Ok now we are deep enough. The double chained list scheme looks like: APL (f): ActiveProcessLinks.FLink APL (b): ActiveProcessLinks.BLink process1 process2 process3 processN 0x000 |----------| |----------| |----------| | EPROCESS | | EPROCESS | | EPROCESS | | ... | | ... | | ... | 0x0A0 | APL (f) |----->| APL (f) |----->| APL (f) |-----> ... 0x0A4 | APL (b) | \-<--| APL (b) | \-<--| APL (b) | \-<-- ... | ... | | ... | | ... | |----------| |----------| |----------| As you can see (well ... my scheme is not that good :/) the next/prev pointers of the ActiveProcessLinks struct are not _EPROCESS structure pointers. They are pointing to the next LIST_ENTRY struct. That means that if we want to retrieve the _EPROCESS structure address, we have to adjust the pointer. (look at _EPROCESS struct definition in kmem.h in sample code section) LIST_ENTRY ActiveProcessLinks is at offset 0x0A0 in _EPROCESS struct: --> Flink = 0x0A0 --> Blink = 0x0A4 So we can quickly create some macros for later use: #define TO_EPROCESS(_a) ((char *) _a - 0xA0) // Flink to _EPROCESS #define TO_PID(_a) ((char *) _a - 0x4) // Flink to UniqueProcessId #define TO_PNAME(_a) ((char *) _a + 0x15C) // Flink to ImageFileName The head of the LIST_ENTRY list is PsActiveProcessHead. You can get its address with kd for example: kd> ? PsActiveProcessHead ? PsActiveProcessHead Evaluate expression: -2142854784 = 8046a180 Just one thing to know. As this List can change very quickly, you may want to lock it before reading it. Reading ExpGetProcessInformation assembly, we can see: mov ecx, offset _PspActiveProcessMutex call ds:__imp_@ExAcquireFastMutex@4 [...] mov ecx, offset _PspActiveProcessMutex call ds:__imp_@ExReleaseFastMutex@4 ExAcquireFastMutex and ExReleaseFastMutex are __fastcall defined so the arguments are pushed in reverse order (ecx, edx,...). They are exported by HAL.dll. By the way i don't lock it in winkps.c :) Ok, first we install a callgate to be able to execute the ring0 function (MmGetPhysicalAddress and ExAcquireFastMutex/ExReleaseFastMutex if you want), then we list the process and finally we remove the callgate. See winkps.c in sample code section. Installing the callgate is an easy step as you can see in the sample code. The hard part is reading the LIST_ENTRY struct. It's kinda strange because reading a chained list is not supposed to be hard but we are dealing with physical memory. First in order to avoid too much use of our callgate we try to use it as less as we can. Remember, running ring0 code in ring3 is not