I’m looking into the GDI object abuse techniques for kernel pool exploitation, and found that there is no documentation about how large memory is allocated to the Bitmap object in the kernel paged pool. I read though many exploit codes, articles, but it seemed that everyone is doing this by trial and error, so I decided to take a look, and try to find logic in the allocation.
The function to create a bitmap is:
HBITMAP CreateBitmap(
_In_ int nWidth,_In_ int nHeight,_In_ UINT cPlanes,_In_ UINT cBitsPerPel,_In_ const VOID *lpvBits);
Every code I saw sets the cPlanes to “1” and the *lpvBits to NULL, so let’s ignore them, and use that setting. The rest of the variables are related to the bitmap’s actual size, and it makes perfect sense for those to affect the object size allocated.
We will also need to make difference between two cases:
- When the bitmap < 0x1000
- When the bitmap >= 0x1000, in this case the allocation goes to the large paged pool allocation table
Let’s start with the first one, and try a few cases on Windows 10x64 v1511. My first try is:
create_bitmap(820, 2, 8)
where:
nWidth = 820
nHeight = 2
cBitsPerCel = 8
I use some functions to leak the address to the kernel so I can easily find it. Unfortunately WinDBG is not really helpful with the !pool, !poolfind, etc… commands (they don’t work) and I’m not sure why. Also the
dt nt!_POOL_HEADER
returns:Symbol nt!_POOL_HEADER not found.
So I need to do this the hard way. This is the dump of the bitmap with the POOL_HEADER., which is the first 0x10 bytes. It is followed by the Bitmap object. The pvscan0 value, which is the most interesting to everyone usually, points to 0x258 offset from the beginning of the OBJECT (SURFACE64 in this case - obviously this symbol is also not found by WinDBG, why would it ease things…).
kd> dc 0xfffff90144314010-10
fffff901`44314000 238d0000 35306847 00000000 00000000 ...#Gh05........
fffff901`44314010 060509ac 00000000 00000000 00000000 ................
fffff901`44314020 00000000 00000000 00000000 00000000 ................
fffff901`44314030 060509ac 00000000 00000000 00000000 ................
fffff901`44314040 00000000 00000000 00000334 00000002 ........4.......
fffff901`44314050 00000668 00000000 44314268 fffff901 h.......hB1D....
fffff901`44314060 44314268 fffff901 00000334 00002402 hB1D....4....$..
fffff901`44314070 00000003 00010000 00000000 00000000 ................
If I take the size of the bitmap: (820 x 2 x 8) / 8 bits = 0x668
POOL_HEADER = 0x10
SURFACE64 + STUFF = 0x258
This sums up to 0x8d0, and if we check:
kd> dc 0xfffff90144314010-10+8d0 L4
fffff901`443148d0 0073008d 00000000 00000000 00000000 ..s.............
The next POOL_HEADER indeed reports that the previous pool size is 0x8d (x 0x10) = 0x8d0, so looks like the above calculation is about right, but we will need to refine it.
Next:
create_bitmap(1000, 2, 8)
Here we need to refine it a bit:
BITMAP: (1000 x 2 x 8) / 8 bits = 0x7d0
POOL_HEADER = 0x10
SURFACE64 + STUFF = 0x258
If we sum it, it add to 0xa38, however we need to pad it so:
SIZE mod 0x10 = 0
Which gives us 0xa40
and indeed we can see this:
kd> dc 0xfffff90140764010-0x10
fffff901`40764000 23a40000 35306847 9bbcb758 8476aa01 ...#Gh05X.....v.
fffff901`40764010 da0530cd ffffffff 00000000 00000000 .0..............
fffff901`40764020 00000000 00000000 00000000 00000000 ................
fffff901`40764030 da0530cd ffffffff 00000000 00000000 .0..............
fffff901`40764040 00000000 00000000 000003e8 00000002 ................
fffff901`40764050 000007d0 00000000 40764268 fffff901 ........hBv@....
fffff901`40764060 40764268 fffff901 000003e8 000090b3 hBv@............
fffff901`40764070 00000003 00010000 00000000 00000000 ................
kd> dc 0xfffff90140764010-0x10+a40
fffff901`40764a40 001600a4 65657246 742b06f2 fffff802 ....Free..+t....
fffff901`40764a50 422a8470 fffff901 40788470 fffff901 p.*B....p.x@....
fffff901`40764a60 00000000 00000000 00000000 00000000 ................
fffff901`40764a70 00000000 00000000 00000000 00000000 ................
fffff901`40764a80 00000000 00000000 00000000 00000000 ................
fffff901`40764a90 00000000 00000000 00000000 00000000 ................
fffff901`40764aa0 423d2018 fffff901 000000c2 000001fe . =B............
fffff901`40764ab0 00000087 00000000 423d2138 fffff901 ........8!=B....
Let’s see if it work reverse:
I want an allocation of size: 0xe70, the following should do it:
create_bitmap(0xc08, 1, 8)
and it works:
lkd> dc 0xFFFFF901407D1010-10
fffff901`407d1000 23e70000 35306847 00000000 00000000 ...#Gh05........
fffff901`407d1010 08050a4b 00000000 00000000 00000000 K...............
fffff901`407d1020 00000000 00000000 00000000 00000000 ................
fffff901`407d1030 08050a4b 00000000 00000000 00000000 K...............
fffff901`407d1040 00000000 00000000 00000c08 00000001 ................
fffff901`407d1050 00000c08 00000000 407d1268 fffff901 ........h.}@....
fffff901`407d1060 407d1268 fffff901 00000c08 00002ea2 h.}@............
fffff901`407d1070 00000003 00010000 00000000 00000000 ................
lkd> dc 0xFFFFF901407D1010-10+e70
fffff901`407d1e70 001900e7 00000000 00000000 00000000 ................
fffff901`407d1e80 407d3e80 fffff901 407cfe80 fffff901 .>}@......|@....
fffff901`407d1e90 00000000 00000000 00000000 00000000 ................
fffff901`407d1ea0 00000000 00000000 00000000 00000000 ................
fffff901`407d1eb0 00000000 00000000 00000000 00000000 ................
fffff901`407d1ec0 00000000 00000000 00000000 00000000 ................
fffff901`407d1ed0 00000000 00000000 00000000 00000000 ................
fffff901`407d1ee0 00000000 00000000 00000000 00000000 ................
So the function would be:
def allocate_bitmap_with_given_size(s):
width = s - 0x258 - 0x10
create_bitmap (width, 1, 8)
There is one more thing: it seems that if the bitmap is small, it will be at least 0x370 in size:
create_bitmap(100, 1, 8)
lkd> dc 0xFFFFF9014269B320-10
fffff901`4269b310 23370009 35616c47 65b7acb6 cf1485cc ..7#Gla5...e....
fffff901`4269b320 06050ad3 00000000 00000000 80000000 ................
fffff901`4269b330 00000000 00000000 00000000 00000000 ................
fffff901`4269b340 06050ad3 00000000 00000000 00000000 ................
fffff901`4269b350 00000000 00000000 00000064 00000001 ........d.......
fffff901`4269b360 00000064 00000000 4269b578 fffff901 d.......x.iB....
fffff901`4269b370 4269b578 fffff901 00000064 00003c23 x.iB....d...#<..
fffff901`4269b380 00000003 00010000 00000000 00000000 ................
lkd> dc 0xFFFFF9014269B320-10+370
fffff901`4269b680 00030037 65657246 65b7a926 cf1485cc 7...Free&..e....
fffff901`4269b690 46b60e20 ffffd000 4251c970 fffff901 ..F....p.QB....
fffff901`4269b6a0 4269b6b0 fffff901 13995a90 fffff960 ..iB.....Z..`...
fffff901`4269b6b0 230f0003 34616c47 423b7108 fffff901 ...#Gla4.q;B....
fffff901`4269b6c0 00000000 00000000 00000000 80000000 ................
fffff901`4269b6d0 00000000 00000000 000000d8 00000000 ................
fffff901`4269b6e0 00000000 72724401 4269b738 fffff901 .....Drr8.iB....
fffff901`4269b6f0 4269b6f0 fffff901 4269b6f0 fffff901 ..iB......iB....
create_bitmap(1, 1, 8)
lkd> dc 0xFFFFF90142615370-10
fffff901`42615360 2337000e 35616c47 42615360 fffff901 ..7#Gla5`SaB....
fffff901`42615370 13050ad9 00000000 00000000 80000000 ................
fffff901`42615380 00000000 00000000 00000000 00000000 ................
fffff901`42615390 13050ad9 00000000 00000000 00000000 ................
fffff901`426153a0 00000000 00000000 00000001 00000001 ................
fffff901`426153b0 00000004 00000000 426155c8 fffff901 .........UaB....
fffff901`426153c0 426155c8 fffff901 00000004 0000407f .UaB.........@..
fffff901`426153d0 00000003 00010000 00000000 00000000 ................
lkd> dc 0xFFFFF90142615370-10+370
fffff901`426156d0 00250037 65657246 65bf4976 cf1485cc 7.%.FreevI.e....
fffff901`426156e0 42417570 fffff901 46b61040 ffffd000 puAB....@..F....
fffff901`426156f0 40768dd0 fffff901 488e1a50 000001f8 ..v@....P..H....
fffff901`42615700 00000000 00000000 00000000 00000000 ................
fffff901`42615710 00000000 00000000 00000000 00000000 ................
fffff901`42615720 00000000 00000000 00000000 00000000 ................
fffff901`42615730 00000000 00000000 00000000 00000000 ................
fffff901`42615740 00000000 00000000 00000000 00000000 ................
So this is for the smaller allocations. For allocations at least 0x1000, we don’t have POOL_HEADER, as they go to the large pool, so if I do:
create_bitmap(0xda8, 1, 8)
(0xda8 + 0x258 = 0x1000)
They are allocated right after each other:
[+] Bitmap handle: 0x370508e8L
[*] Bitmap's kernel address: 0xFFFFF901426E0000
[+] Bitmap handle: 0xffffffffa80507d0L
[*] Bitmap's kernel address: 0xFFFFF901426E1000
[+] Bitmap handle: 0x38050680L
[*] Bitmap's kernel address: 0xFFFFF901426E2000
[+] Bitmap handle: 0x2405067fL
[*] Bitmap's kernel address: 0xFFFFF901426E3000
If I do:
create_bitmap(0x12a8, 1, 8)
That will create an 0x1500 byte allocation, and finally the !pool command started to produce output:
lkd> !pool 0xFFFFF90142768000
Pool page fffff90142768000 region is Paged session pool
fffff90142768000 is not a valid large pool allocation, checking large session pool...
*fffff90142768000 : large page allocation, tag is Gh05, size is 0x1500 bytes
Pooltag Gh05 : GDITAG_HMGR_SURF_TYPE, Binary : win32k.sys
We can also see the “Frag” and “Free” tags at the end, marking the end of the allocation:
lkd> dc 0xFFFFF90142768000 + 1500
fffff901`42769500 23020000 67617246 00000000 00000000 ...#Frag........
fffff901`42769510 00001500 00000000 00000000 00000000 ................
fffff901`42769520 00ae0002 65657246 00000000 00000000 ....Free........
fffff901`42769530 4276b530 fffff901 42767530 fffff901 0.vB....0uvB....
fffff901`42769540 00000000 00000000 00000000 00000000 ................
fffff901`42769550 00000000 00000000 00000000 00000000 ................
fffff901`42769560 00000000 00000000 00000000 00000000 ................
fffff901`42769570 00000000 00000000 00000000 00000000 ................
So our final function would be:
def allocate_bitmap_with_given_size(s):
if s < 0x370:
print "[-] Too small size, such Bitmap can’t be allocated…"
sys.exit(-1)
elif s < 0x1000:
print "[+] Allocating Bitmap in the Paged paged pool"
width = s - 0x258 - 0x10
create_bitmap (width, 1, 8)
else:
print "[+] Allocating Bitmap in the Paged session pool / large pool"
width = s - 0x258
create_bitmap (width, 1, 8)
If I made any mistakes, let me know. This was tested on Win10 x64 v1511 only. The structures are different on x86 so that will be different for sure.
Here is the Python code I used for testing:
from ctypes import *
from ctypes.wintypes import *
ULONG_PTR = PVOID = LPVOID = PVOID64 = c_void_p
PROCESSINFOCLASS = DWORD
ULONG = c_uint32
PULONG = POINTER(ULONG)
NTSTATUS = DWORD
class PEB(Structure):
_fields_ = [
("Stuff", c_byte * 0xF8),
("GdiSharedHandleTable", PVOID)
]
class PROCESS_BASIC_INFORMATION(Structure):
_fields_ = [
("Reserved1", PVOID),
("PebBaseAddress", POINTER(PEB)),
("Reserved2", PVOID * 2),
("UniqueProcessId", ULONG_PTR),
("Reserved3", PVOID)
]
class GDICELL64(Structure):
_fields_ = [
("pKernelAddress", PVOID64),
("wProcessId", USHORT),
("wCount", USHORT),
("wUpper", USHORT),
("wType", USHORT),
("pUserAddress", PVOID64)
]
ntdll = windll.ntdll
gdi32 = windll.gdi32
kernel32 = windll.kernel32
ntdll.NtQueryInformationProcess.argtypes = [HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG]
ntdll.NtQueryInformationProcess.restype = NTSTATUS
gdi32.CreateBitmap.argtypes = [c_int, c_int, UINT, UINT, c_void_p]
gdi32.CreateBitmap.restype = HBITMAP
ProcessBasicInformation = 0 #Retrieves a pointer to a PEB structure that can be used to determine whether the specified process is being debugged, and a unique value used by the system to identify the specified process. It is best to use the CheckRemoteDebuggerPresent and GetProcessId functions to obtain this information.
def create_bitmap(width, height, cBitsPerPel):
bitmap_handle = HBITMAP()
bitmap_handle = gdi32.CreateBitmap(width, height, 1, cBitsPerPel, None)
if bitmap_handle == None:
print "[-] Error creating manager bitmap, exiting...."
print "[+] Bitmap handle: %s" % hex(bitmap_handle)
return bitmap_handle
def get_gdisharedhandletable():
"""
This function will return the GdiSharedHandleTable address of the current process
"""
process_basic_information = PROCESS_BASIC_INFORMATION()
ntdll.NtQueryInformationProcess(kernel32.GetCurrentProcess(), ProcessBasicInformation, byref(process_basic_information), sizeof(process_basic_information), None)
peb = process_basic_information.PebBaseAddress.contents
return peb.GdiSharedHandleTable
def get_bitmap_kernel_address(bitmap_handle):
"""
Get the kernel address of the bitmap, works up to Windows 10 v1511
"""
gdicell64_address = get_gdisharedhandletable() + (bitmap_handle & 0xFFFF) * sizeof(GDICELL64()) #the address is in user space
gdicell64 = cast(gdicell64_address,POINTER(GDICELL64))
print "[*] Bitmap's kernel address: 0x%X" % gdicell64.contents.pKernelAddress
return gdicell64.contents.pKernelAddress
for i in range(100):
bitmap_handle = create_bitmap(0x12a8, 1, 8)
bitmap_kernel_address = get_bitmap_kernel_address(bitmap_handle)
raw_input()
Windows 10x64 v1607
Looks like the !poolfind and !pool commands are not broken when debugging this version, so that makes things easier, on the other hand I can’t leak the address of the bitmap with the previous technique. There is an universal method but for that I need to know the size of the bitmap that will be allocated, and also calculate the size of the other object which helps leaking the bitmap address, so it’s a chicken and egg problem. Anyhow, luckily I can use the commands.
create_bitmap(1640, 1, 8)
kd> dc fffff027023f3730-10
fffff027`023f3720 238e004a 35306847 00000000 00000000 J..#Gh05........
fffff027`023f3730 1d050b24 00000000 00000000 00000000 $...............
fffff027`023f3740 00000000 00000000 00000000 00000000 ................
fffff027`023f3750 1d050b24 00000000 00000000 00000000 $...............
fffff027`023f3760 00000000 00000000 00000668 00000001 ........h.......
fffff027`023f3770 00000668 00000000 023f3990 fffff027 h........9?.'...
fffff027`023f3780 023f3990 fffff027 00000668 00005d70 .9?.'...h...p]..
fffff027`023f3790 00000003 00010000 00000000 00000000 ................
kd> !pool fffff027023f3730
Pool page fffff027023f3730 region is Paged session pool
fffff027023f3000 is not a valid large pool allocation, checking large session pool...
fffff027023f3260 size: 20 previous size: 0 (Allocated) Frag
fffff027023f3280 size: 4a0 previous size: 20 (Free) Free
*fffff027023f3720 size: 8e0 previous size: 4a0 (Allocated) *Gh05
Pooltag Gh05 : GDITAG_HMGR_SURF_TYPE, Binary : win32k.sys
This became larger (on Win10x64 v1511 this should have been 0x8d0), and the reason for this is that the BITMAP_DATA offset changed from 0x258 to 0x260 (pvscan0 points to here from the beginning on the object).
Let’s take a look on small bitmaps:
create_bitmap(1, 1, 8)
kd> !poolfind Gla5 -session
Scanning large pool allocation table for tag 0x35616c47 (Gla5) (ffffbc07f20c0000 : ffffbc07f20c6000)
fffff027046815c0 : tag Gla5, size 0x360, Paged session pool
fffff02704681930 : tag Gla5, size 0x360, Paged session pool
fffff02704681ca0 : tag Gla5, size 0x360, Paged session pool
fffff02701b792b0 : tag Gla5, size 0x360, Paged session pool
fffff027023af930 : tag Gla5, size 0x360, Paged session pool
fffff027023afca0 : tag Gla5, size 0x360, Paged session pool
kd> dc fffff02702305ca0-10
fffff027`02305c90 23370037 35616c47 00000000 00000000 7.7#Gla5........
fffff027`02305ca0 02050886 00000000 00000000 80000000 ................
fffff027`02305cb0 00000000 00000000 00000000 00000000 ................
fffff027`02305cc0 02050886 00000000 00000000 00000000 ................
fffff027`02305cd0 00045010 fffff027 00000020 00000040 .P..'... ...@...
fffff027`02305ce0 00000100 00000000 02305f00 fffff027 ........._0.'...
fffff027`02305cf0 02305f00 fffff027 00000004 000010c3 ._0.'...........
fffff027`02305d00 00000001 00010000 00000000 00000000 ................
kd> !pool fffff02702305ca0
Pool page fffff02702305ca0 region is Paged session pool
fffff02702305000 is not a valid large pool allocation, checking large session pool...
fffff02702305260 size: 20 previous size: 0 (Allocated) Frag
fffff02702305280 size: 10 previous size: 20 (Free) Free
fffff02702305290 size: b0 previous size: 10 (Allocated) Uscu Process: ffffbc07f150c800
fffff02702305340 size: e0 previous size: b0 (Allocated) Gla8
fffff02702305420 size: 370 previous size: e0 (Allocated) Gla5
fffff02702305790 size: e0 previous size: 370 (Allocated) Gla8
fffff02702305870 size: b0 previous size: e0 (Allocated) Uscu Process: ffffbc07f150c800
fffff02702305920 size: 370 previous size: b0 (Allocated) Gla5
*fffff02702305c90 size: 370 previous size: 370 (Allocated) *Gla5
Pooltag Gla5 : GDITAG_HMGR_LOOKASIDE_SURF_TYPE, Binary : win32k.sys
So that remained 0x370.
What about large pools?
create_bitmap(0xda0, 1, 8)
kd> !pool fffff02704f5a000
Pool page fffff02704f5a000 region is Paged session pool
fffff02704f5a000 is not a valid large pool allocation, checking large session pool...
*fffff02704f5a000 : large page allocation, tag is Gh05, size is 0x1000 bytes
Pooltag Gh05 : GDITAG_HMGR_SURF_TYPE, Binary : win32k.sys
kd> dc fffff02704f5a000
fffff027`04f5a000 17050c9c 00000000 00000000 00000000 ................
fffff027`04f5a010 00000000 00000000 00000000 00000000 ................
fffff027`04f5a020 17050c9c 00000000 00000000 00000000 ................
fffff027`04f5a030 00000000 00000000 00000da0 00000001 ................
fffff027`04f5a040 00000da0 00000000 04f5a260 fffff027 ........`...'...
fffff027`04f5a050 04f5a260 fffff027 00000da0 0000b2b7 `...'...........
fffff027`04f5a060 00000003 00010000 00000000 00000000 ................
fffff027`04f5a070 04800200 00000000 00000000 00000000 ................
Looks to follow the same pattern, again, the only change is the BITMAP_DATA offset. So the logic for Win10x64 v1607:
def allocate_bitmap_with_given_size(s):
if s < 0x370:
print "[-] Too small size, such Bitmap can’t be allocated…"
sys.exit(-1)
elif s < 0x1000:
print "[+] Allocating Bitmap in the Paged paged pool"
width = s - 0x260 - 0x10
create_bitmap (width, 1, 8)
else:
print "[+] Allocating Bitmap in the Paged session pool / large pool"
width = s - 0x260
create_bitmap (width, 1, 8)
Update:
Win7x64 to Win10v1607:
https://github.com/theevilbit/kex/blob/master/kex.py
See the details in:
calculate_bitmap_size_parameters
No comments:
Post a Comment