Bootstrapping

Before, we used GetModuleHandle and GetProcAddress to find the addresses of functions and call them. It is possible to not call any of these and get the address of the functions like how bootstrappers in shellcode work.

This is possible due to the fact that:

  1. The PEB address is located at theGS register: GS:[0x60]

  2. _PEB_LDR_DATA structure which contains information about the loade modules is located at $PEB:[0x18]

  3. The loader structure contains a pointer to InMemoryOrderModuleList at offset 0x20

  4. InMemoryOrderModuleList is a doubly linked list of LDR_DATA_TABLE_ENTRY structures, each contains BaseDllName and DllBase of a single module

  5. We can then browser all loaded modules and find kernel32.dll and it's base address

  6. with the knowledge of its location in memory, we can find its export directory and browser for the GetProcAddress function

  7. We can then use GetProcAddress to find the addresses of other functions

Let's open notepad in windbg and see how this works.

Let's first get the address of the PEB with:

And then overlay the PEB structure to the memory pointed to be peb to see the what the structures members are holding/pointing to:

The field we are interested in is the Ldr field:

Lets click in the highlighted Ldr, which should take you to a structure called PEB_LDR_DATA:

We then have 3 lists listed in the structure, we are interested in the second called, "InMemoryOrderModuleList". Let's click the highlighted name:

And we see that its a double linked list, this is a chain of pointers which points backwards and forwards to structures. This will loop, meaning if we parse the pointers going from structure to structure, we will end up going back to where we started.

To parse this, we can use the LDR DATA TABLE ENTRY structure to do so:

And now lets issue the following command in windbg

The most important thing about this structure is the DllBase field in which we will see later on.

Lets now take the Flink address of the LIST ENTRY structure and parse that in the LDR DATA TABLE ENTRY structure

Now, lets click the hext highlighted flink address, copy that address and parse it like how we did in the above:

As we can see, the FullDllName field holds the string, "ntdll.dll"

...

If we keep on getting the flink addreses, and parsing it through this structure, we will eventually get back to the first structure which in our case had the FullDllName to be "notepad.exe".

The above showed us how to parse through the PEB and get the DllBase addresses of the modules.

We can also see a pointer to the PEB in the TEB structure like so(which is located at offset 0x060):

Note that if is 32 bit, it would be at offset 30, but since we are using 64 bit, the offset is 60.

Lets make a code implementation of the above.

typedef HMODULE(WINAPI *PGetModuleHandleA)(PCSTR);
typedef FARPROC(WINAPI *PGetProcAddress)(HMODULE, PCSTR);

(in main)
#ifdef _M_IX86 
	PEB * ProcEnvBlk = (PEB *) __readfsdword(0x30);
#else
	PEB * ProcEnvBlk = (PEB *)__readgsqword(0x60);
#endif

The above code retrieves a pointer to the PEB by retrieving what's at offset 0x60 from the gs register. Note that it is offset 0x30 for 32 bit and we read from the fs register instead of the gs register. We store the address of PEB at ProcEnvBlk.

We can then start parsing the structures to search for memory addresses and such.

PPEB_LDR_DATA pLoaderData = pPEB->Ldr;
PLIST_ENTRY listHead = &pLoaderData->InMemoryOrderModuleList;
PLIST_ENTRY listCurrent = listHead->Flink;

PLIST_ENTRY * listOfModules = NULL;

The above stores pointer to the necessary data structures we are going to use to resolve the address of the functions.

for (LIST_ENTRY * pListEntry = listCurrent; pListEntry != listOfModules; pListEntry = pListEntry->Flink)	{
		LDR_DATA_TABLE_ENTRY * pEntry = (LDR_DATA_TABLE_ENTRY *) ((BYTE *) pListEntry - sizeof(PLIST_ENTRY));
		LPCSTR dllName = (LPCSTR)pEntry->BaseDllName.Buffer;
		CharUpperA(dllName);
		if (lstrcmpiW(dllName, "KERNEL32.DLL") {
				kernel32Address = (HMODULE) pEntry->DllBase;
				// if making function, return this, but if not, break here
		}
}

The above iterates though all entries of the double linked list and compares the BaseDLLName to kernel32.dll, if it matches, it will assign kernel32Address to the DLLBase address of the dll.

After we get our dll base address, we will have to find its export directory, we will use some pointer arithmetic to get pointers to certain data structures.

  1. We find ImageDosHeader from the base address

  2. With a pointer to the DosHeader, we can then find the e_lfanew field which stores the RVA address of NtHeader, we then add that to the Base address to find its actual location in memory

  3. With a pointer to NtHeader, we can find OptionalHeader

  4. With the OptionalHeader, we can find the DataDirectory which holds a pointer to the export directory's virtual address, we then add that to the base address to find its actual location in memory

  5. We then get the pointer to the EAT's

    1. AddressOfFunctions

    2. AddressOfNames

    3. AddressOfNameOrdinals

PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)kernel32Address;
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((PBYTE)kernel32Address + pDosHeader->e_lfanew);
PIMAGE_OPTIONAL_HEADER pOptionalHeader = (PIMAGE_OPTIONAL_HEADER)&(pNtHeader->OptionalHeader);
PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((PBYTE)kernel32Address + pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
	
PULONG pAddressOfFunctions = (PULONG)((PBYTE)kernel32Address + pExportDirectory->AddressOfFunctions);
PULONG pAddressOfNames = (PULONG)((PBYTE)kernel32Address + pExportDirectory->AddressOfNames);
PUSHORT pAddressOfNameOrdinals = (PUSHORT)((PBYTE)kernel32Address + pExportDirectory->AddressOfNameOrdinals);

We will then go through the Export directory using a for loop, and compare their function names extracted from the table with GetProcAddress and GetModulehandle.

	for (DWORD i = 0; i < pExportDirectory->NumberOfNames; ++i)
	{
		PCSTR pFunctionName = (PSTR)((PBYTE)kernel32Address + pAddressOfNames[i]);
		
		if (!strcmp(pFunctionName, "GetModuleHandleA"))
		{
			pGetModuleHandleA = (PGetModuleHandleA)((PBYTE)kernel32Address + pAddressOfFunctions[pAddressOfNameOrdinals[i]]);
		}
		if (!strcmp(pFunctionName, "GetProcAddress"))
		{
			pGetProcAddress = (PGetProcAddress)((PBYTE)kernel32Address + pAddressOfFunctions[pAddressOfNameOrdinals[i]]);
		}
	}

Here, we get the function's virtual address by adding the RVA and the base address of the function and store it in a pointer if the comparison succeeds.

Now that we have the addresses of the GetModuleHandle and GetProcAddress functions, we can use them to call any function we want. Lets do this by calling VirtualAlloc:

typedef PVOID(WINAPI *PVirtualAlloc)(PVOID, SIZE_T, DWORD, DWORD);

HMODULE hKernel32 = pGetModuleHandleA("kernel32.dll");
PVirtualAlloc funcVirtualAlloc = (PVirtualAlloc)pGetProcAddress(hKernel32, "VirtualAlloc");
PVOID *exec = funcVirtualAlloc(0, sizeof shellcode, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

Conclusion

This was a simple lab which showed you how to call functions without GetProcAddress, GetModuleHandle or the actual function showing up in the import table by using the PEB structure.

Resources

Last updated