Skip to content

Help! I need to write code in C! – Part 3: PEB and best practices

Preamble

Welcome to part 3 of this writing stuff in C blogpost series.
If you have made it this far and are still reading my blogposts, I must commend you on your perseverance. At this point I’m just assuming you are here for my sick memes, not for the content :P.

Can Imposter Syndrome Be Good?
Relevant Meme


If you haven’t read part 1 or part 2 , I would strongly advise you to start there πŸ™‚ . In part 3 of this series we are going to take a look at using our previously discovered “PebBaseAddress” and how we can leverage this address to eventually end up in the “Nt Headers” section of the remote process, most notably the machine field. In this (probably?) final blogpost of this series, we are also going to take a look at proper memory management as, for now, we kinda have just been opening handles and doing all kinds of shenanigans without really cleaning up after ourselves.

Process Environment Block

Ah, the Process Environment Block what a magnificent piece of art.
In order to prove how deep we are in the rabbit hole, I will show you a screenshot of the first result you get when you google “process environment block”

Needless to say, the PEB has been interesting research matter for years now and have led to cool manipulations such as process argument spoofing, which made it into frameworks such as Cobalt Strike (argue command) and have led to interesting blogposts such as this one by Adam Chester https://blog.xpnsec.com/how-to-argue-like-cobalt-strike/

If you wanna dive deeper into the PEB, Geoff Chappell has a very nice article about the PEB on his website: https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/pebteb/peb/index.htm

What we need to know about the PEB for this blog however is that, once again, Microsoft doesn’t really like you fiddling around with that structure too much, so they do not bother to document it properly. Never fear though, the vergilius project is here: https://www.vergiliusproject.com/kernels/x64/Windows%2011/21H2%20(RTM)/_PEB
The first thing that we should notice is how huge this structure is! The total size is 2000 bytes!

Image result for owen wilson wow meme | Wow meme, Memes, Owen wilson

Traversing the PEB

Alright, you might ask yourself the question, We got the pointer to the PEB, what do we do now? Well we are interested in finding the DOS_HEADER (if you remember from part 2 of this series), and then using the DOS_HEADER’s e_lfanew field to find the correct offset to the start of the “Nt Headers” and then use the correct offset to end up in the File Header, and find the machine field in there.

So the DOS_HEADER starts where the program itself resides in memory. This is conveniently referenced in the PEB’s ImageBaseAddress field on offset 0x10 as seen in the structure definition from the vergilius project (note for readability the entire PEB structure is not shown, only the part that interests us):

We can now do two things, either we copy paste the structure definition of the PEB into our C code (following best practices, this would go into the header file), or work with offsets fro, the PEBPointer that we already have. Neither is better than the other, but for code readability and maintainability I would suggest going for the header approach.

I will copy the already defined RVA2VA function, the PROCESS_BASIC_INFORMATION strucutre and the NtQueryInformation function definition into a header file which I called blogpost.h and include the structure definition of PEB and UNICODE_STRING from the Vergilius project.

We then (obviously) need to #include “blogpost.h” in our blogpost.c file

In our previous blogpost, we already found the “PebBaseAddress” by filling up our BASIC_INFORMATION structure using the NtQueryInformationProcess API call.


Since we now have defined the PEB structure in our header file thanks to Vergilius, we can instruct our C program to take the pointer we found and use it as a pointer to the PEB structure as follows:

pPEB pebPtr = pbi.PebBaseAddress;

What we are now going to do is read the remote processes virtual memory starting at the ImageBaseAddress pointer in the PEB, as this will give us a pointer back to the DOS_Header.

	success = ReadProcessMemory(hProcess, &pebPtr->ImageBaseAddress, &pExeBaseAddr, sizeof(PVOID), NULL);
	if (!success)
	{
		printf("ReadProcessMemory failed\n");
		return -1;
	}
	printf("pExeBaseAddr: 0x%p\n", pExeBaseAddr);

So what we are doing here is we are using the handle to remote process and we are reading the ImageBaseAddress value and are storing it in a variable pExeBaseAddr which we will then be able to subsequently use to get the DOS_HEADER.


Let’s see this in action, what we expect is that we use the PEBs ImageBaseAddress value to start parsing the remote process and find the DOS_HEADER. This DOS_HEADER should start with MZ as it is the beginning of the actual PE file in memory:


We see that we have an ExeBaseAddr of 0x140000000. Let’s open that up in Process Hacker and see if this address indeed points to our PE (starting with MZ) …

Our theory is correct! We are indeed able to get to the DOS_Header

Who says corporate meme sucks - 9GAG

From DOS to Machine

So now that we have a pointer to the DOS header, we can read the DOS header and use the e_lfanew field to get to the NT Header.

success = ReadProcessMemory(hProcess, pExeBaseAddr, &ImageDosHeader, sizeof(ImageDosHeader), NULL);
	if (!success)
	{
		printf("ReadProcessMemory failed\n");
		return -1;
	}
	pNtHeaderAddr = RVA2VA(PVOID, pExeBaseAddr, ImageDosHeader.e_lfanew);

Once again we are using the ReadProcessMemory API call with the handle to the open remote process to read a specific section of memory.
This time we want to get the DOS_Header out of the remote process so:
1. We specify the start of the DOS_Header (pExeBaseAddress).
2. Where we want to store the information (the address we reserved for the ImageDosHeader structure).
3. The amount of memory we want to read (the size of the structure)

Once we have the information, we want to move our memory reading forward to start parsing the NtHeader. Keep in mind that we are reading from a remote process so all the information we receive back is relative to the image base address. Luckily for us we have the RVA2VA method we can use to translate everything back to a memory pointer in our current process that we can then use in a subsequent ReadProcessMemory call.

success = ReadProcessMemory(hProcess, pNtHeaderAddr, &ImageNtHeader, sizeof(ImageNtHeader), NULL);
	if (!success)
	{
		printf("ReadProcessMemory failed\n");
		return -1;
	}

	printf("Process Machine Type: 0x%x\n", ImageNtHeader.FileHeader.Machine);

That’s it πŸ™‚ We made a program that is capeable of reading the machine field of a remote process’s NT Header.

Note: This program will work when compiled for x64 – it will not work when compiled to x86, as a good exercise, can you modify the program to work for x86 as well?
Hint: Compare the PEB structure on the Vergilius project, can you spot a difference?

Unlike other people I don't want to end up in the middle of invalid memory  - Maurice Moss and Milk | Meme Generator

I C you want to follow best practices

In part 2 of the blogpost series I showed you some boilerplate code to get to the PEB base address. Thanks to an extremely generous reader @sm0ke1337 I was made aware that the code, although working, is not really following best practices.

Here are some tips and tricks to keep in mind when writing C:

  • If you open handles, don’t forget to close them again πŸ™‚
  • Since ntdll is already loaded in the process memory, it’s better to call GetModuleHandle instead of LoadLibrary, both will work but the reference counter of the module will not go up with GetModuleHandle, it will go up with LoadLibrary however.
  • If structures have a fixed size, its easier to allocate them directly to the stack instead of allocating it on the heap. It saves you the memalloc call as well.
  • NT functions have a NTAPI* in their function definition, not a WINAPI*, although code still works, it’s always better to use the correct macros.

The new c code can be found here, and the code plus header file can be found on: https://github.com/jfmaes/blogposts-talks-and-tidbits/tree/main/C-GetRemoteProcArch

#include <stdio.h>
#include <Windows.h>
#include "blogpost.h"

BOOL  isX86(int pid)
{
	NTSTATUS status; 
	HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
	PROCESS_BASIC_INFORMATION pbi;
	ZeroMemory(&pbi, sizeof(pbi));
	PEB peb;
	ZeroMemory(&peb, sizeof(peb));
	BOOL success;
	PVOID pExeBaseAddr;
	IMAGE_DOS_HEADER ImageDosHeader;
	ZeroMemory(&ImageDosHeader, sizeof(ImageDosHeader));
	IMAGE_NT_HEADERS ImageNtHeader;
	ZeroMemory(&ImageNtHeader, sizeof(ImageNtHeader));
	PVOID pNtHeaderAddr;

	if (!hProcess)
	{
		printf("OpenProcess failed");
		return -1;
	}
	
	DWORD dwSize = sizeof(PROCESS_BASIC_INFORMATION);
	HMODULE hNtdDll = GetModuleHandle(L"ntdll.dll");
	myNtQueryInformationProcess NtQueryInformationProcess = (myNtQueryInformationProcess)GetProcAddress(hNtdDll, "NtQueryInformationProcess");
	status = NtQueryInformationProcess(hProcess, 0, &pbi, dwSize, NULL);
	//https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/596a1078-e883-4972-9bbc-49e60bebca55
	if (!status == 0x00000000)
	{
		printf("NtQueryInformationProcess failed\n");
		return -1;
	}
	printf("PEB pointer is 0x%p\n",&pbi.PebBaseAddress);
	pPEB pebPtr = pbi.PebBaseAddress;
	success = ReadProcessMemory(hProcess, &pebPtr->ImageBaseAddress, &pExeBaseAddr, sizeof(PVOID), NULL);
	if (!success)
	{
		printf("ReadProcessMemory failed\n");
		return -1;
	}
	printf("pExeBaseAddr: 0x%p\n", pExeBaseAddr);


	success = ReadProcessMemory(hProcess, pExeBaseAddr, &ImageDosHeader, sizeof(ImageDosHeader), NULL);
	if (!success)
	{
		printf("ReadProcessMemory failed\n");
		CloseHandle(hProcess);
		return -1;
	}
	pNtHeaderAddr = RVA2VA(PVOID, pExeBaseAddr, ImageDosHeader.e_lfanew);
	success = ReadProcessMemory(hProcess, pNtHeaderAddr, &ImageNtHeader, sizeof(ImageNtHeader), NULL);
	if (!success)
	{
		printf("ReadProcessMemory failed\n");
		CloseHandle(hProcess);
		return -1;
	}

	printf("Process Machine Type: 0x%x\n", ImageNtHeader.FileHeader.Machine);
	CloseHandle(hProcess);
	if (ImageNtHeader.FileHeader.Machine == IMAGE_FILE_MACHINE_I386)
	{
		return 1;
	}
	else
	{
		return 0;
	}
	
}

int main()
{
	int pid;
	printf("Enter a pid: \n");
	scanf_s("%i", &pid);
	if (isX86(pid))
	{
		printf("process with PID: %i is x86",pid);
	}
	else
	{
		printf("process with PID: %i is x64", pid);
	}
	return 0;
}

Conclusion

Actually used this meme at the end of a presentation in class today - 9GAG

Well, this blogpost series has come to an end! Perhaps down the line I will go into more in depth C topics. If you liked this series feel free to let me know. The whole reason I am creating this blogs is to have them be read, and hopefully be found useful for people around the world.
It’s sometimes awkward for a blogger to post stuff and then have little to no reaction on it πŸ˜›

Published inTips & Tutorials

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *