Universal Windows Exploit — part 1
Writing a universal exploit for Windows Operating Systems
This will be a multipart post to writing a universal exploit for windows. I divided this to make the learning step easier and more comprehendible.
If you’ve ever written an exploit for windows before, sometimes the exploit is limited to specific windows versions and builds. For example, you may have successfully written an exploit for Windows XP, but it may or may not work on Windows 7. Bummer right? 🤯
Disclaimer: This example only applies for 32 bit versions of Windows. I’ll try to add another blog for 64 bit machines.
The Root Problem
To execute system commands on Windows OS, Windows Native API is introduced. User applications can call these native APIs through the ntdll.dll library. However, this is not reliable to write a cross-platform shellcode as the system call numbers will most definitely change between versions of Windows. We must know the actual address in memory of a Windows API function to execute it. This is where the challenge is, locating the base address on various operating systems versions and service packs.
In general, we want to be able to load any dynamic library (dll) and use the function in that library. This can be achieved using 2 Windows API functions:
- LoadLibraryA() — Loads the specified module into the address space of the calling process. The specified module may cause other modules to be loaded.
- GetProcAddress() — Retrieves the address of an exported function or variable from the specified dynamic-link library (DLL).
Those 2 functions are in the kernel32.dll library. Now we need to figure out the base address of kernel32.dll
Since kernel32.dll is most likely mapped into the process space, we can use the PEB (Process Environment Block) technique to determine its base address.
The PEB Method
The PEB can be accessed from the TEB (Thread Environment Block), which is a data structure of the currently running thread. In 32-bit OS, it can be accessed from the FS register. The pointer to the PEB is at [FS:0x30].
To visualize this, I’ll open 32-bit calc.exe and attach it in WinDbg 32bit. Hopefully that will makes things more clear. First off, load the symbols and reload:
.sympath srv*
.reload
The PEB is in offset 0x30 from TEB
Once inside the PEB, we want to locate the pointer to Ldr where it stores the base address of kernel32.dll
Inside the pointer to _PEB_LDR_DATA is where things get a little tricky. We’ll map it according to the official documentation from Microsoft which says “The head of a doubly-linked list that contains the loaded modules for the process. Each item in the list is a pointer to an LDR_DATA_TABLE_ENTRY structure”
So now it has become more clear, we’re interested in _LDR_DATA_TABLE_ENTRY because it has the DLL base address.
Testing The Theory
Now let’s apply those in the debugger. Before we start, I’ve taken a screenshot of what the base address of kernel32.dll is. This is to check our work in the end.
The address of kernel32.dll is 0x754e0000.
The address of InInitializationOrderModuleList is 0x002a1980
Starting from the FS register which has the information about the TEB,
The above command simply means display type for _peb in the reference of 0x30 offset from teb. We can also see the address of _PEB_LDR_DATA to be 0x76f98880.
The next command is displaying the type for _peb_ldr_data in the reference of 0xc offset from (@$teb+0x30)
This is where we are greeted with the 3 list entries. We’ll be using the InInitializationOrderModuleList.
Continue to access the InInitializationOrderModuleList at offset 0x01c
At this point, its really important to read through this short explanation of a LIST_ENTRY type from Microsoft’s official documentation.
The Flink, which is the next entry in the list, is located at 0x002a1d60 and it’s address range is [0x2a1c78–0x2a1980]
If we try to access the LDR_DATA_TABLE_ENTRY of the next entry 0x2a1c78, we get the base address of kernel32.dll. The below command substracts 10h from the address because the address 0x2a1c78 references the InInitializationOrderLinks which is 0x10 from the start.
The Assembly Code
Follow these instructions to set up an asm project in visual studio 2019.
.386
.model flat, stdcall
assume fs:flat.data
.code
start:
xor eax, eax ; zero out EAX
mov eax, [fs:30h] ; Pointer to PEB
mov eax, [eax + 0ch] ; Pointer to Ldr
mov eax, [eax + 1ch] ; Pointer to InInitializationOrderModuleList
mov eax, [eax] ; Load the first item on the list
mov eax, [eax] ; Load the second item on the lilst
mov eax, [eax + 8h] ; kernel32.dll base address
end start
END
Run that program then you’ll see that EAX will hold the base address of kernel32.dll. The final address of kernel32.dll is [eax + 8h], in case you’re wondering, this is because the DLLBase is at offset 8h from InInitializationOrderLinks.
I guess that is enough for this write up. A lot to digest. In the next series, we’ll talk about retrieving addresses of specific functions inside kernel32.dll
Stay tuned!