Thursday, January 23, 2020

Reverse Engineering the Steam API


Introduction

In this post, I will explain how I realized that writing 480 in steam_appid.txt would enable us to bypass Family Sharing restrictions.

My original goal when reversing Halo was to find a way to play the game with my siblings without buying the game. My first approach was to find a way to start the game successfully without Steam.

Steamworks SDK

One approach to do that would be to look for the first library call that MCC-Win64-Shipping.exe makes in the Steam API module. A way to do that might be to go to Symbols tab in x64dbg, select steam_api64.dll, and break on all the exported functions. I might have done this.

The easier way to do this is to download the Steamworks SDK and read the Steamworks SDK documentation. https://partner.steamgames.com/doc/sdk/api/example

After downloading the SDK, go to Main.cpp. There is a function called RealMain with a comment above it that says "Real main entry point for the program".


SteamAPI_RestartAppIfNecessary


The first function that is called is SteamAPI_RestartAppIfNecessary. To find this in x64dbg, go to Symbols tab, select steam_api64.dll, search for the function. We want to break on this function because we want to find the function in MCC-Win64-Shipping.exe that calls this SteamAPI_RestartAppIfNecessary.

Restart the program and continue until we get to the function.

Then look at the callstack to figure out which function in MCC called this library function. It will be the first mcc-win64-shipping function underneath the steam_api64.dll function.
Double-click on the function to follow it in the CPU window. It should look like this picture below.

The callstack tells us where execution will return after executing the function, so that means the instruction above the highlighted line is the instruction that actually called the function.

The instruction test al, al sets the Zero flag if the value in AL is zero. (See here for the reason why: https://en.wikipedia.org/wiki/TEST_(x86_instruction))

Recall that __fastcall convention places the return value in the RAX register. AL is the lower 16-bits of that register. That means the program is testing whether SteamAPI_RestartAppIfNecessary returned 0.

On the line after the test, (where I commented "jmp if returned false"), the je instruction  means "jump if equal", and it will jump if the Zero flag is set. (In some disassemblers, the instruction will be disassembled into jz for jump zero instead of je.) This means that if SteamAPI_RestartAppIfNecessary  returned 0, then we will take the jump.

Now the question is, do we want to take the jump?

Take the jump? 


The instruction directly after the je is executed if we don't take the jump. xor al, al will clear the bits in AL. There is an unconditional jump after that instruction, which takes us to the line right after the line that has the comment "return 1 (success)". These instructions just clean up the stack, so that means if we don't take the jump, then we immediately return 0.

Technically, there is another function that runs just before we return. See this function in IDA by subtracting the address of this function from the base address of MCC-Win64-Shipping.exe. (Get the base address in Symbols tab. It is the entry in the column labeled "Base", which is left of the mcc-win64-shipping.exe entry.)


Copy the offset, which should be 0x234b2b0. In IDA, press g, paste, and click enter. You should get the same function that has a __security_check_cookie label. This is here because Visual C++ compiler will insert checks for buffer overruns if you compile with /GS. This function will not change the result of AL, so we do not need to worry about it.

Back to figuring out whether we want to take the jump.
Since not taking the jump causes us to return 0, the question now is, do we want to return 0.

Return 0?


To find out, go to the call stack to figure out who calls the function that calls SteamAPI_RestartAppIfNecessary and double click to follow. You should get something like the picture below.


We return to a test al, al again, but this time there is a jne instruction after the test. This instruction is "jump if not equal", which is the opposite of je from before. That means if AL is 1 then we will jump because test al, al will not set the Zero flag.

If we do not jump then we will xor ecx, ecx and call the function at 0x7FF7F413D1EC. Click the function and press enter to follow.


This function terminates the process. We don't want to terminate the process. That means we want to take the jump. In order to take the jump, we need to return 1 in the function on the line labeled "the function that calls RealMain".

How to return 1?


Now in order to return 1, we need to execute the line that says mov al, 1 in the function that calls SteamAPI_RestartAppIfNecessary. This is the line highlighted in the picture below.
The red and blue arrows on the left indicate that there are three ways to get here. Set a breakpoint on all three ways. You can try all three ways, but I will tell you that without changing anything, the only jump that does not cause a Fatal Error is the last one.

Jumping from the other two will cause us to crash here with an Exception Access Violation.


We can go on a short tangent and try to figure out why this fatal errors and whether we can recover from it.

SteamInternal_ContextInit Tangent

Notice that before the fatal error, there was a call to SteamInternal_ContextInit. Double click on it and set a breakpoint. Restart the game and follow the first jump that goes to the mov al, 1 instruction. (In x64dbg, on the right side pane with the registers, under RFLAGS, there is a ZF. Double click the 0 next to the ZF to set the Zero flag.) Press continue. The game should start loading and then break at SteamInternal_ContextInit.

Stepping over the instructions, we can see that we eventually reach a je instruction that jumps to the exit. From reversing other video games, I would say that we usually want to enter the Critical Sections, so let's not take this jump.
To confirm that we want to enter the Critical Section, if you keep stepping, you will see that we copy rcx + 0x10 into rax. The value at this address is 0, so when we dereference the value at rax, we get an exception because it is dereferencing 0, which is not a valid address.

To save time, I will tell  you that if we force execution to not take the jumps, the program will then call SteamInternal_FindOrCreateUserInterface.

Stepping through that, it will take the second je and fail with an error that complains that SteamAPI_Init has not yet succeeded. Notice the test rcx, rcx above that, and the mov rcx, [0x00007FFFC7A73298]. Since we took the jump, that means that the value at 0x00007FFFC7A73298 was 0. The error messages indicate that 0x00007FFFC7A73298 is likely the hardcoded pointer to the Steam interface.

This could be useful in the future. Maybe we want to call Steam interface functions. To figure out the offset, subtract this address from the Base address of steam_api64.dll. You should get 0x43298.

This is probably a dead-end because we cannot easily fake the Steam Interface. But let's not leave any stones unturned.

Fake the SteamAPI?

Run the program again while logged into Steam this time. Continue until we get to SteamInternal_ContextInit.

Go to SteamInternal_FindOrCreateUserInterface to get the address of the Steam API. Open up ReclassEx64.exe and paste the address there.
Opening the first pointer in ReClass, we get an array of addresses. On the plus side, these are not runtime addresses. Perhaps we can just give the program the values at 0x00007FFFC7A73298?
I tried this, but I still got the Fatal Error (at a different place) because there was nothing at 7FFFA41AAA08.

Back to MCC

Now if we get to the last je and decide to not take it, you will get a Sign In prompt. If you click No, everything still seems to work as expected. This suggests that the call above the je checks for an active steam user. This also suggests that once you make it to the game lobby, MCC does not seem to really care whether you are logged in to Steam or not.


SteamAPI_RestartAppIfNecessary must return 0

The entire point of the tangent was to rule out taking the first two jumps to the mov al, 1 instruction. By now, we understand that we need SteamAPI_RestartAppIfNecessary to return 0. I'll show the section of code again with better comments. 

Let's take a look at SteamAPI_RestartAppIfNecessary. Notice the early return at the beginning of the function. There are three arrows going to it.


Pan right to see what happens if we don't early return. It seems that execution looks for the installation path of steam.exe in the registry. If you look farther down, you will see the ShellExecute calls. There are no returns once we take this path, and there is no path to the last return that does not call ShellExecute. 


For a better view of the function, you can open steam_api64.dll in IDA, go to Export, and find the function. 


At this point, I had included the steam_appid.txt file and wrote the app id for Path of Exile. When I tried executing the binary, it took the early return, but the app exited immediately.

This was when I started reading the Unreal Engine 4 documentation for the Steam Online Subsystem.



Very interesting. I tried 480, and the game launched :)

Only 480?

I wondered if only 480 works. It seemed to me that any app id for a game that I own should also work. Then I remembered that I had uninstalled Path of Exile, so I tried the app id for CS:GO, a game that is currently installed on my computer, and as predicted, Steam reports that I am playing CS:GO.




Thursday, January 16, 2020

Reversing Halo and Unreal Engine 4

Since my last post, I have been reversing Halo: The Master Chief Collection game in order to find the functions and offsets needed to make an aimbot. In the past, I only looked at Source Engine games, so it was very exciting and fun to work with Unreal Engine 4.

This post will be about tools and techniques for reversing the game. In the next posts, I will explain how I found each of the offsets that I'll be using to make the aimbot.

Aimbot Specifications

An aimbot is a cheat, and the game developers are within their rights to ban us if they detect that we are cheating. In the previous post, however, we already figured out how to run the game without Anti-Cheat.

Side Note about Anti-Cheat

Recall that we need to load the Easy Anti-Cheat (EAC) DLL regardless of whether we want to play in matchmaking because the EAC module calls CreateGameClient and makes the GameClientInterface.

I placed a breakpoint at the EntryPoint of the EAC library, and at first, I thought that EAC had hooked the CreateThread function because the debugger kept hitting that breakpoint, but actually, in the MSDN documentation for DllMain, it says that when the current process creates a new thread, the system calls the entry-point function of all DLLs currently attached to the process. This means that we want to load our game hack DLL as early as possible since all the DLLs loaded before us will have the opportunity to run whatever code they have in their DllMain and will be in a better position to mess with us.

Desirable Features

  • Only aim at enemy players that are visible to us (don't want to aim through walls or at allies)
  • Change aim gradually in order to prevent snapping to targets
  • Have a way to prioritize targets in order to choose among multiple possible target

Information Needed

At minimum, we need the following information to make an aimbot.
  • A way to set our aim
  • The list of (enemy) players currently visible to us
There are also sub-items we need. For example, in order to get the list of enemy players, we might need a way to distinguish between enemies and allies. We might also need a way to distinguish whether we are in-game or not.

Setup

These are the tools I used. I recommend Visual Studio Code for viewing the Unreal Engine 4 source code. The Peek Definition and Find References features are very helpful. Visual Studio 2019 has the same features and seems less buggy, but my computer will lag if I have that and the game running.

Tools

I find that sometimes the VS Code search tool fails to find keywords that are definitely in the files, so I use PowerShell's Select-String to recursively search the code. 

Get-ChildItem * -Recurse | Select-String -Pattern " GObjects"

Notes

When reversing, it helps to take notes and keep those notes organized. I find that I sometimes get really into the process of reversing and forget or get lazy about taking notes. Therefore, we should setup our document to make taking notes as convenient as possible.

Resources

There is a great tool for generating SDKs for Unreal Engine games, but since this is my first time working with Unreal Engine, I wanted to take the chance to find things myself and learn about the engine.

Dumping the Process

When looking at executables that employ anti-reversing techniques, we are usually better off dumping the process. For this, I used Scylla. Select the MCC-Win64-Shipping.exe process, click IAT Autosearch, and then click Dump to save the dump.


I would suggest saving the dump in the same folder as the original MCC-Win64-Shipping.exe file because the program references DLL dependencies relative to its current directory. If you decide to not do this, IDA will ask you to locate the DLL dependencies.

Rebase at Address 0

When you open the file in IDA, opt to manually load the binary, and then when IDA asks you to specify the base address of the image, enter 0. By loading the image at address 0, all addresses will be offsets, which is nice since the base address of the binary will change pretty much every time you restart your computer but offsets do not change.

If you forget to do this, you can rebase the image by selecting Edit->Segments->Rebase. When rebasing, make sure the Names window is not open. For some reason, this makes the rebase very very slow.

Unicode

Microsoft's Visual C++ compiler uses Unicode. IDA does not process Unicode strings by default, so select right click anywhere in the Strings window, select Setup, and check the Unicode box.




Thing to Remember

  • Make sure to have the correct version of the source code
  • Enable Unicode in IDA
  • Try dumping the game process when in game AND when in lobby

Saturday, January 4, 2020

Bypassing the Easy Anti-Cheat Launcher in Halo

In the last post, I talked about using the app ID 480 trick to bypass the Steam Family sharing restriction. Even though I used the app ID 480 trick, I still had to start the game from the Easy Anti-Cheat launcher (mcclauncher.exe) in order to play Multiplayer. If I did not start the game with the launcher, I'll get an Anti-Cheat Incident pop up when I try to enter Multiplayer games.
My goal now is to find a way to enable multiplayer without using the Easy Anti-Cheat launcher. In this post, I will explain how I did this.

Finding the Easy Anti-Cheat module

The first thing we want to do is find where the game (MCC-Win64-Shipping.exe) first calls functions in the Easy Anti-Cheat API.

Open the binary in x64dbg and break at the EntryPoint. Notice that there does not appear to be any Easy Anti-Cheat module listed in the Symbols table. If we let the rest of the game run until we see the Halo window and pause the debugger, we will then see that easyanticheat_x64.dll is in the Symbols table.

To find out when the game loads this library, set a breakpoint on Export 0 of the easyanticheat_x64.dll. This is the EntryPoint of the DLL and the loader will execute from this address upon loading the library.





Restart the program and press continue until we break on the EntryPoint of easyanticheat_x64.dll. Since we want to know which function in the game binary loaded this library, look at the call stack at this breakpoint. Recall that the call stack lists return addresses.
Look at the first mcc-win64-shipping function (below the kernelbase function). This is where the game calls LoadLibraryA.
The highlighted line is where the program will start executing after it returns from LoadLibraryA. I've added comments to the right of the code. Recall that Microsoft Visual C++ compiler uses the fastcall calling convention when compiling for x64. That means the first four arguments to a function go into registers RCX, RDX, R8, R9 respectively and that the function returns the return value (if any) in RAX.

We have something like this:

eacModule := LoadLibraryA("EasyAntiCheat/easyanticheat_x64.dll")
fnCreateGameClient := GetProcAddress(eacModule, "CreateGameClient")
gameClientInterface := (*fnCreateGameClient) ("GameClientInterfaceV012")

This block of codes retrieves a Game Client interface. This means that we probably cannot avoid loading the library.

Analyzing the CreateGameClient function

To find out what CreateGameClient does, go to the Symbols table again, and set a breakpoint on the CreateGameClient function in the easyanticheat_x64.dll module. Restart the program and press continue (F9) until we get to this function. Press g to switch to graph view. Click drag to look around.
The function is kind of complicated, but if we look at the bottom left of the graph (third box from the bottom in the picture), there is a call to GetCommandLineW. Set a breakpoint there. Ignore the comments "rcx: GameClientInterfaceV012". That is saying what the current value of rcx is.

GetCommandLineW returns a pointer to the command line arguments in RAX. The program then passes this pointer into the first argument of another function. We can reasonably guess that this other function processes the command line arguments.

Click that function and press Enter to follow it.
This is interesting. Notice that the function is checking for "-eac-nop-loaded" in the command line arguments.

Continue to step until we return from this function. The red arrow indicates that we will take the jump.
We could analyze the function that would have been called had we not take the jump, but let's first try to run the program with -eac-nop-loaded as the commandline. To do this in x64dbg, go to File - Change Command Line, and add -eac-nop-loaded to the end.

Disable the breakpoints and restart the program to see what happens. If we click Multiplayer then Social games, we now see the matchmaking screen instead of the Anti-Cheat Incident pop-up.

A More Permanent Solution

Since this seems like a very simple trick, I figured that other people must have already done something like this for other games. It turns out that people have used this trick for another game and the developers for that game quickly patched it.

The easiest way to not rely on this trick is to patch easyanticheat_x64.dll to always execute the instructions after the command that checks whether we have -eac-nop-loaded in our command line.

Do this by NOPing out the je instruction after the function that is called after GetCommandLine.


If you run the program again, and look in the same graph, it should now look like this.



Wednesday, January 1, 2020

Bypassing Steam Family Sharing Limitations


My brother bought Halo: The Master Chief Collection on Steam. He shared it with me and my sister via Steam Family Library Sharing, but it turns out that only one person can play at a time. I wanted to see if I could find a way around this restriction. Ideally, I'd like to play the game with my siblings.

Trying out the Game

The first thing to notice is that the developers likely intended for players to launch the game from Steam. Attempting to launch the binary (MCC-Win64-Shipping.exe) directly causes Steam.exe to execute and prompt the user to sign in.

If my siblings are not currently playing the game, then we will see a Play button in the Steam client.
Once the game is running, the Play button in the Steam client changes into a Stop button, which indicates that Steam knows that the game is running.

Anti-Cheat Enabled/Disabled Launch

Steam provides two launch options: regular and Anti-Cheat Disabled.
If we choose the first option, then Steam launches the game from the Easy Anti-Cheat (EAC) launcher (mcclauncher.exe). There will be an Easy Anti-Cheat logo when the game first starts.
Upon successfully launching the game, we will reach the main menu screen.
If we launch the game with Anti-Cheat disabled, then we will see this message when we click Multiplayer button in the menu.
Although the game has both a campaign and a multiplayer part, both parts require that the user be signed into Steam.

Progress

I spent a week reversing the game and learning about how the Unreal Engine uses the steamworks API. My goal here was to find a way to launch the game without Steam.

Usually, I will get a Fatal Error in the loading screen.
Or, if I successfully got through the loading screen, I would get the Purchase screen.

Solution: The 480 Trick

I ended up finding the idea for the solution in the steamworks documentation. It turns out that I do not need to find a way to launch the game without Steam in order to play the game with  my siblings. Instead, I launch the game with Steam but tell Steam that I am playing a different game.

Create this file: C:\Program Files (x86)\Steam\steamapps\common\Halo The Master Chief Collection\steam_appid.txt and write 480 in it.

To play with Anti-Cheat disable, launch the game from C:\Program Files (x86)\Steam\steamapps\common\Halo The Master Chief Collection\MCC\Binaries\Win64\MCC-Win64-Shipping.exe.

To play with Multiplayer enabled, launch the game from C:\Program Files (x86)\Steam\steamapps\common\Halo The Master Chief Collection\mcclauncher.exe

Why it Works

This works because 480 is the Steam app ID for Spacewar. Every user automatically has Spacewar. The Steamworks documentation suggest developers use 480 as a temporary app ID when working with the Steam API during the development and testing phase of creating the game.

When we launch the game with 480 as our app ID, the Steam client will report that we are playing Spacewar.

Since the lobby is provided by Steam, in order to join the same lobby, my siblings would also have to be detected as playing Spacewar. Therefore, they would also need to be using the 480 trick. Interestingly, when my brother did this trick, Steam reported that he was playing Spacewar but still reported that Halo was not currently available for family sharing. That means that while he is playing, Steam can still tell that he is using the license for Halo.

Future

Although I figured out how to bypass the Family Sharing restriction, I would still like to find a way to launch the game without Steam. I am also interested in figuring out how to launch the game with Multiplayer enabled but without having to use the Easy Anti-Cheat launcher.

The solution might seem kind of random. In a future post, I will explain the steps I took in reversing the game, which led me to realize that the solution had something to do with the steam_appid.txt.


Wednesday, October 16, 2019

DLL Entry-Point Function

DLL Entry-Point Function

BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD fdwReason, LPVOID fImpLoad)

It is not required to implement DllMain. If DllMain is not present, the DLL will still link but no entry function will be called. 

fImpLoad (aka lvpReserved) is nonzero if DLL implicitly loaded (static load) and 0 when explicitly loaded (dynamic load). 
hInstDll identifies the virtual memory address where DLL's file image was mapped into the process space
fdwReason indicates why system is calling the function 

Remember that DLLs use DllMain to initialize themselves. Avoid calling other functions imported from other DLLs inside DllMain since they might not have been initialized yet. 

Also avoid calling LoadLibrary(Ex) and FreeLibrary from DllMain because dependency loops. 
Also avoid calls to User, Shell, ODBC, COM, RPC, socket functions. 

Constructor/Destructor for global/static C++ objects is called at the same time as DllMain function. 

DLL_PROCESS_ATTACH

System passes this as fdwReason only when Dll is first mapped. A thread that calls LoadLibrary(Ex) on this same Dll that has already been mapped to proc address space will not cause DllMain to be called again with DLL_PROCESS_ATTACH.

System only cares about the return value of DllMain when passed DLL_PROCESS_ATTACH. Return true if initialization is successful. 

The process primary thread calls each of the DLL's DllMain functions that the process needs. If any return FALSE, then proc is terminated. 

When DLL is load explicitly, the system will not terminate proc if LoadLibrary(Ex) fails. It will just unmap DLL file and return NULL. 

DLL_PROCESS_DETACH

When DLL is unmapped from process space, system calls DllMain with DLL_PROCESS_DETACH (assuming DLL did not return FALSE when initially called with DLL_PROCESS_ATTACH). 

Primary thread usually makes this call, but if thread called FreeLibrary or FreeLibraryAndExitThread then that thread executes this call. 

OS kills proc only after DLL completes DLL_PROCESS_DETACH notification. 

If a thread calls TerminateProcess, DLL does not get a chance to DLL_PROCESS_DETACH :( 


DLL_THREAD_ATTACH

When process creates a thread, system examines all DLL files currently mapped into proc address space, and calls each DllMain with DLL_THREAD_ATTACH. The new thread is responsible for executing these calls. 

System only makes this call for new threads, not for existing threads. 

DLL_THREAD_DETACH


System does not immediately kill a thread that calls ExitThread. System calls all the mapped DLL's DllMain's functions with DLL_THREAD_DETACH on the thread that wants to die. 

System does not get to make this call when TerminateThread :(

System does not call DLL_THREAD_DETACH for threads that are currently running when DLL is detached. 

Serialized Calls to DllMain

Calls to DllMain are serialized. That means if we call CreateThread in the DLL_PROCESS_ATTACH of one thread, the system suspends the new thread until the current DLL returns from DllMain. Do not try to wait for thread while in PROCESS_ATTACH code because the system will not resume that thread until DllMain is finished. 

DisableThreadLibraryCalls will not solve this problem. Do not call WaitForSingleObject inside and DllMain function. 

(DisableThreadLibraryCalls tells the system that we do not want DLL_THREAD_ATTACH and DLL_THREAD_DETACH notifications sent to the specified DLL's  DllMain)


C/C++ Run-Time Library

Linker embeds address of DLL's entry-point function in resulting DLL file image. 
Specify address of function with linker's /ENTRY switch. 

MSFT linker assumes _DllMainCRTStartup is the entry function (a statically linked function from C/C++ run time library)

_DllMainCRTStartup initializes C/C++ run time library and then calls your DllMain. 

If the linker cannot find a DllMain function in DLL .obj files, it will link the DllMain from C/C++ run-time library. 

Sources

Windows via C/C++ - J. Richter 2008 Chapter 20

Dynamic Linked Libraries Explicit Linking

Explicit DLL Module Loading and Symbol Linking

Implicit load/linking: when loader implicitly loads/links the DLL when application is invoked. This is when application code references symbols contained in DLL

Explicit load/linking: A thread explicitly loads DLL into its calling process' address space, retrieves virtual memory address of a function contained within the DLL, and calls the function using the memory address

LoadLibraryEx to load the DLL into process' address space
GetProcAddress to indirectly reference the DLL's exported symbols

Explicitly Loading the DLL Module

Can use either LoadLibrary or LoadLibraryEx to map the DLL. They will return the virtual memory address where the file image is mapped. (HMODULE == HINSTANCE).
Mapping addresses returned by LoadLibrary and LoadLibraryEx should not be used interchangeably. 

They both increase the library's per-process usage count. 

DllMain entry point returns the virtual memory address where the file is mapped. 

LoadLibraryEx allows you to pass handle to a file and flags. 

DONT_RESOLVE_DLL_REFERENCES

Tell system to map DLL into calling process' address space without calling DllMain nor loading any additional DLLs that the DLL may require. 

LOAD_LIBRARY_AS_DATAFILE

Tells system to map DLL into proc address space as if it were a data file. System does not setup the page file permissions for a DLL module. 
Reasons to use this flag:
- DLL contains resources only and no functions
- This flag can be used to load resources from a .exe without starting a new process

LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE

Similar to LOAD_LIBRARY_AS_DATAFILE but does not let other applications modify the file's contents. 

LOAD_WITH_ALTERED_SEARCH_PATH

Changes the DLL search algorithm depending on what is passed to pszDLLPathName parameter

Can call SetDllDirectory with the library folder as a parameter as well, and the folder set will be searched after the folder containing the application. Retrieve this parameter with GetDllDirectory

LOAD_IGNORE_CODE_AUTHZ_LEVEL

Turns off validation provided by WinSafer (Software Restriction Policies)

Explicitly Unloading the DLL Module

FreeLibrary to explicitly unload DLL from process' address space
FreeLibraryAndExitThread also works

They both decrement the library's per-process usage count. The library is only unmapped from address space when usage count is 0. 

GetModuleHandle to determine if DLL is already mapped into process' address space. Returns NULL if not mapped. 

GetModuleFileName - gets the full pathname of DLL/.exe 

Explicitly Linking to an Exported Symbol

Pass the handle returned from LoadLibrary(Ex) to GetProcAddress in order to get the address where the DLL is loaded. 
pszSymbolName only accepts ANSI strings. Can either pass it the name of the symbol as an ANSI string, or pass it the ordinal number of the symbol. 

GetProcAddress(hInstDll, MAKEINTRESOURCE(2)); 

Warning: Passing an ordinal number that has not been assigned to any of the exported functions might return a non-NULL value. 

Finally, cast the function pointer into something that matches its signature. 

PEN_DUMPMODULE pFnDumpModule = (PFN_DUMPMODULE)GetProcAddress(hDll, "DumpModule"); 
if (pFnDumpModule != NULL) pFnDumpModule(hDll);

Sources

Windows via C/C++ - J. Richter 2008 - Chapter 20

Dynamic-Link Libraries Implicit Linking

Implicit Linking Overview

A DLL module must first be built before an executable module that imports from that DLL can be built. 

  1. Header file of DLL contains function prototypes, structures, symbols that the DLL wants to export
  2. Source code of DLL not required to build an executable module that imports from DLL
  3. Compiler processes each source code module and produces one .obj module per code module
  4. Linker combines .obj modules and produces single DLL image file
  5. If DLL exports at least one function or variable, linker produces a .lib file, which contains a list of exported functions and variable symbol names
  6. Linker combines .obj modules into single executable image file. 
  7. Executable image file contains an import section, which lists all needed DLL module along with their functions/symbols/variables
  8. Loader creates VA space for new process, maps executable module into address space, recursively parses executable module's import section (maps each required DLL into the address space)

Building DLL Module

C++ classes can be exported only if modules importing the C++ class are compiled using a compiler from the same vendor (so do not export C++ classes unless you know executable module uses the same developer tools). This is because of name mangling. 

Header file of DLL contains all the variables and functions DLL wants to export along with any symbols/data structures used with the exported functions/variables. 

Use single header file to include in both executable and DLL source code. Avoid exporting variables because this breaks the abstraction barrier. 

In Mylib.h: 
#ifdef MYLIBAPI
// Functions/variable definitions being exported. 
#else
// Tells the compiler that we import variable/functions from some DLL module
#define MYLIBAPI extern "C" __declspec(dllimport)
#endif


In MyLib.cpp
// Tells the compiler  that the variable, function, C++ class will be exported from the resulting DLL module
#define MYLIBAPI extern "C" __declspec(dllexport)
// Technically the define is not necessary here because compiler remembers the symbols to export when it parses the header file
#include "Mylib.h"


Use extern "C" modifier only when writing C++ code because C++ compilers mangle function and variable names, which causes linker problems. 

extern "C" tells compiler not to mangle the variable or function names, which makes the variable/functions accessible to modules written in C, C++, or other programming languages. 

Don't define MYLIBAPI before the header file in executable since compiler will be confused. 

The __declspec(dllexport) causes compiler to embed additional info in .obj file. When DLL is linked, linker detects information about exported symbol and automatically makes a .lib file. 

The .lib file is required to link any executable module that references the DLL's exported symbols. 
Linker also embed a table of exported symbols and their relative virtual addresses (RVA) in DLL file 

RVA identifies offsets in the DLL file image to where the exported symbol can be found. 

MSFT wants you to link using symbol's name even though you can technically link by ordinal. MSFT guarantees that linking to DLLs by ordinal will work. 

DLLs for Use with Non-Visual C++

MSFT C compiler mangles C functions when your function uses __stdcall (WINAPI) calling convention. 

When you export an __stdcall function, MSFT compiler mangles function names by adding a _ and a @ followed by number of bytes that are passed to the function as a parameter. 

Example:

__declspec(dllexport) LONG __stdcall MyFunc(int a, int b); 

This function will be exported as _MyFunc@8

Building an executable and attempting to link MyFunc will fail if you do not use MSFT compiler. 

To tell MSFT compiler to not export mangled names:

Create a .def file with an EXPORTS section, which will tell linker to export using the .def file name

Or, in the DLL source code modules, add a linker directive to tell linker to export a MyFunc with the same entry point as _MyFunc@8. 
#pragma comment(linker, "/export:MyFunc=_MyFunc@8")

Building Executable Module

When including the DLL header, the __declspec(dllimport) tells compiler that some symbols are imported from some DLL module. 

Linker combines .obj modules and determines which DLL contains imported symbols. User must pass DLL's .lib file (the list of exports) to the linker, so that the linker can figure out where the referenced symbol is and which DLL module contains the symbol. 

When linker resolves import symbols, it embeds import section in the executable module's image, which lists the DLLs required and the symbols referenced from each DLL. 

A memory address next to an import appears in the executable module if the symbol is bound. 

Running Executable Module

Import section does not contain the pathname of the DLL, so loader searches for the DLL. 

Search Order:
  1. Directory of the executable image file
  2. Windows system directory (returned by GetWindowsDirectory)
  3. 16-bit system directory
  4. Process' current directory
  5. Directories listed in PATH env vars
The search order can be changed by setting HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager


Loader only maps a module once regardless of how many other modules need that module. 

When loader finishes mapping DLL modules into the proc address space, it fixes up references to import symbols:
For each symbol listed, loader examines DLL's export section to make sure symbol exists.
Loader then retrieves RVA of symbol and adds it to the base address at which the DLL module is loaded (in the process) and saves the virtual address in the executable's import section.
When code references imported symbol, it will look in the calling module's import section, and retrieve the address of the imported symbol. 

To improve application load time, rebase and bind executable and DLL modules. 

Sources

Windows via C/C++ - J.Richter 2008

Memory-Mapped Files

Physical storage from reserving regions in a memory-mapped file comes from the file itself rather than the system's paging file. A file that has been mapped can be accessed as if the file were loaded in memory.

Uses of memory-mapped files:
  1. Load and execute PE files
  2. Access data file on disk without having to perform file I/O or buffering file contents
  3. Interprocess communication between processes on the same machine (other IPC implemented using memory-mapped files anyway)

Memory-Mapped Executables and DLLs

System reserves a region of address space. That location is specified in the linker's /BASE option (defaults to 0x00400000 for 32-bit applications)

Physical storage backing reserved region is the .exe file on disk (as opposed to the system's paging file)

System recursively calls LoadLibrary on each of the DLLs that the process says it needs (as well as each DLL's dependencies). 

The DLL specifies its preferred image base (usually 0x10000000 or 0x00400000). Override this option using linker's /BASE option. Standard system DLLs specify different base addresses to avoid conflicting when loaded into a single address space. 

If system cannot load DLL at preferred base, then it uses the relocation information to load it somewhere else. If relocation information was removed (using the linker's /FIXED switch), then system will not be able to load the DLL. 

If system needs to relocate DLL, then system notes that some of the physical storage for DLL is mapped to the paging file.

Static Data is Not Shared by Multiple Instances of an Executable or DLL

Memory-mapped files allows multiple running instances of the same application to share the same code and data in RAM. When a second instance of an app is ran, system maps the pages of VA containing the file's code and data into the second app's address space. 

GetSystemInfo to determine the size of the page 

Copy-on-write allows an instance to modify global/static data without changing the global data for all other instances. 

System commits storage in the paging file for all pages that are protected with copy-on-write when the process is first loaded. 

Sharing Data Across Multiple Instances

Compiler places all code in .text, uninitialized data in .bss, and initialized data in .data. Each section marked with some combination of READ, WRITE, EXECUTE, SHARED. Sections marked SHARED are shared across multiple instances (no copy-on-write). 

Use #pragma data_seg("mysection") to create user-defined section.

Initialized data can be placed in the Shared section. If data not initialize, compiler will not place it in the Shared section unless declared with allocate declaration specifier. 

#pragma data_seg("Shared")
LONG g_lInstanceCount = 0;
#pragma data_seg()

To place uninitialized in Shared section:
__declspec(allocate("Shared")) int mydata; 

Make sure the section is first created before __declspec-ing it. 

In the /SECTION switch of the linker's command line, set /SECTION:Shared, RWS which names a section called "Shared" with Read Write Shared attributes. 

Can embed the linker switch in the source:
#pragma comment(linker, "/SECTION:Shared, RWS")

This embeds the string in the .drectve section of the generated .obj file, which makes it seem as if the string were passed as a command line argument. 

MSFT recommends not using Shared sections since anyone can load any DLL and have access to the shared memory. 

Maybe use this method instead of using a MUTEX if okay with dropping files to disk.

Memory-Mapped Data Files

To deal with large streams of data, can memory map data files to process' address space.

Open the file and tell system to reserve a region of VA space. Map the first byte of the file to the first byte of reserved region. Then access the region of virtual memory as if it contained the file. If file ends with single 0 byte, then can treat the data like it were an in-memory text string. (Interruptions during operations can cause problems and corrupt data.)

Using Memory-Mapped Files

A memory mapping is also known as a "section". Use ProcessExplorer to see sections. 

Three steps:
1. Create/open file kernel object identifying the file on disk to be used as a memory-mapped file
2. Create file-mapping kernel object 
3. Tell system to map all or part of the file-mapping into process' address space

Clean up:
1. Unmap the file-mapping kernel object from process' address space
2. Close file-mapping kernel object
3. Close file kernel object

CreateFile tells the OS the location of the file mapping's physical storage. The pathname indicates the exact location of physical storage. 

File-Sharing Modes

Flags that specify shared access to the file's data.
0 to not allow other attempts to open the file
FILE_SHARE_READ - do not allow attempts to open using GENERIC_WRITE
FILE_SHARE_WRITE - do not allow attempts to open using GENERIC_READ

CreateFileMapping

Similar to reserving address space and then committing physical storage to the region. Only difference is that physical storage from the file itself rather than the paging file.  
User must request permissions to the file-mapping object. 
For example, specifying PAGE_READONLY means that at least GENERIC_READ must have been passed to CreateFile. 

Section Attributes

SEC_NOCACHE - update the file's data on disk as you write data to the file
SEC_IMAGE - tells system to map the file with the page protections of a PE file (so PAGE_EXECUTE_READ for .text and PAGE_READWRITE for .data)
CreateFileMapping ignores SEC_RESERVE and SEC_COMMIT. 

File Mapping Size

Specify the maximum size of the file mapping: 

dwMaximumSizeHigh - 32-bit value that expresses the high 32 bits of the size (should always be 0 if the file is less than 4GB)
dwMaximumSizeLow - 32 bit value that expresses the low 32 bits of the size

Passing 0 to both params will create a file-mapping object that reflects current size of the file. This is okay if you only going to read from the file. Do not do this if the current file on disk is 0 bytes. 

pszName

Zero-terminated string assigned to the file-mapping object. The string allows sharing of the object with other processes. 

MapViewOfFile

Tells the system to reserve the region of address space for the file's data and commit the file's data as physical storage mapped to the region. 

Don't have to map the entire file into the process' address space at once. 
Map a small portion of the file - a view. 

Specify which byte in the data file should be mapped as the first byte in the view: 
dwFileOffsetHigh - high 32 bits
dwFileOffsetLow -  low 32 bits
Offset must be a multiple of system's allocation granularity. 

Specify how much of the file to map (same as specifying how large a region to reserve)
dwNumberOfBytesToMap (0 will map a view starting at specified offset to the end of the file) 

Specifying FILE_MAP_COPY will commit physical storage from the system's paging file, 

When system makes copy of original page, the copy has PAGE_READWRITE (as oppose to PAGE_WRITECOPY)

UnmapViewOfFile

Unmap the file's data, pass the base address of the returned region that was returned from MapViewOfFile.

To ensure updates written to disk, call FlushViewOfFile to force system to write a portion or all modified data to the disk image. This is useful if the memory-mapped file is over a network. Use FILE_FLAG_WRITE_THROUGH in CreateFile to ensure that server writes the file's data (and doesn't just cache it) when calling FlushViewOfFile. 

MoveMemory - to copy the page of data from the first view to a second view mapped with PAGE_READWRITE if one wants to save changed page of data. 

Make sure to close all open handles. 

Coherence

As long as we are mapping the same file-mapping object, the view data is guaranteed to be coherent meaning that if one application alters the contents of the file in one view, all other views are updated. 

Windows does not guarantee that view of different file-mapping objects backed by a single data file are coherent - only that multiple views of a single file-mapping object are coherent. 

When calling CreateFile for files that will be memory mapped, specify 0 in dwSharedMode to prevent other processes from opening it. 

Do not use memory-mapped files to share writable files over a network because system can't guarantee coherent view of data. 

Specifying the Base Address of Memory-Mapped File

MapViewOfFileEx to suggest that file be mapped into a particular address in the process' VA space. 
Specify the address in pvBaseAddress parameter. Useful for using memory-mapped files to share data with other processes. 

Using Memory-Mapped Files to Share Data Among Processes

IPC mechanisms such as RPC, COM, OLE, DDE, window messages (WM_COPYDATA), Clipboard, mailslots, pipes, sockets, etc are all based on memory-mapped files :) 

Have the two processes that want to share data map views of the same file-mapping object. This makes them share the same pages of physical storage. (Remember to use the exact same name for the file-mapping object). 

When system starts an application:
1. Calls CreateFile to open the .exe file on disk
2. CreateFileMapping 
3. MapViewOfFileEx with SEC_IMAGE flag and specified to map at the preferred base address
4. Create primary thread and point it to first byte of executable code of the mapped view

A second instance of the same application doesn't create a new file object or file-mapping object. The system simply maps a view of the file a second time in the context of the new process' address space. 

Memory-Mapped Files Backed by Paging File

For creating memory-mapped files backed by system's paging file rather than hard disk file. 

Call CreateFileMapping and pass INVALID_HANDLE_VALUE into hFile. System will create file-mapping object and commit physical storage from paging file. 

To share with other process, specify name in pszName. Other process can use OpenFileMapping to access the storage. 

Call CloseHandle when done. 

When file-mapping object is destroyed, data written to the file-mapping storage is destroyed by the system. 

SEC_RESERVE and SEC_COMMIT

These flags only applicable to file-mapping objects backed by paging file. They allow the creation of a file-mapping object without having to commit all the physical storage upfront. 

SEC_COMMIT commits storage from system paging file. 

SEC_RESERVE does not commit physical storage from the paging file. Calling MapViewOfFile(Ex) then reserves a region but does not commit physical storage back to the region. Attempts to access memory address in reserved region raises an access violation. 

Other threads can use the same file-mapping object to map a view of the same region. 

Use VirtualAlloc to commit physical storage to the region. All other processes that have mapped a view of the same file-mapping object can now successfully access the committed pages. 

Sources:

Jeffrey Richter, Christophe Nasarre - Windows via C/C++ 2008 - Chapter 17

Tuesday, October 15, 2019

Windows Memory Architecture


Virtual Address Space of a Process

Each process has its own address space. 
32-bit processes have 4GB
64-bit processes have 16 EB

Virtual address space is not physical storage. It is just a range of addresses. One does not simply access data without mapping physical storage to portions of the address space. 

Virtual Address Space Partitions

Partitions based on the implementation of the OS.

Null-Pointer Assignment Partition (0x00000000-0x0000FFFF)

This partition is purposely set aside to catch NULL-pointer assignments. Virtual memory in this range may not be reserved with Win32 API functions.

User-Mode Partition

This is the usable address range. The size depends on the CPU architecture. 
x86 0x00010000-0x7FFEFFFF ( ~ 2GB)
x64 0x00000000'00010000 - 0x000007FF'FFFEFFFF (~8192 GB)

Kernel-Mode Partition 

x86 0x80000000-0xFFFFFFFF
x64 0x00000800'00000000 - 0xFFFFFFFF'FFFFFFFF

Contains the OS code (thread scheduling, memory management, file systems support, networking support, device drivers) 

Everything in this partition is shared among all processes.

Regions in Address Space

Most of the address space is free when process is created. 
Use VirtualAlloc to allocate (reserve) regions within the address space. 

Allocation requests rounded to a 64-KB boundary. 
When reserving a region, the size of the region needs to be a multiple of the system's page size (usually 4-KB but IA-64 uses 8-KB). System will round up the request. 

System allocates PEB on behalf of the process.
System also allocates TEB upon thread creation and destruction. 
System can allocate wherever it wants, so PEB and TEB will not likely start on 64-KB boundary (but the size of the regions will still be a multiple of CPU page size) 

Call VirtualFree when the region is no longer needed. 

Committing Physical Storage Within a Region

Physical storage must be committed to a reserved region in order to actually use the region. To commit physical storage, call VirtualAlloc. It is not required to commit physical storage to the entire region. 

Decommit physical storage with VirtualFree.

Physical Storage and the Paging File

RAM is expensive, so OS can make disk space look like RAM. 

A paging file contains virtual memory available to all processes and is located on disk. 

CPU needs to know if memory that a thread is requesting to access is in RAM or on disk. 

OS and CPU save portions of RAM to paging file and loads portions of paging file back into RAM when requested.  

When an application commits memory with VirtualAlloc, that physical space is allocated from a file on the hard disk. 


When thread tries to access data in process' address space:

- if data is in RAM
   translate virtual memory to physical address in memory
- if data not in RAM but in paging file
   page fault

When there is a page fault, OS locates free page of memory in RAM (or frees one if there are none), loads the data from the paging file to the free page in RAM. The OS then updates the translation table to indicate the physical location of the newly loaded data. Try the instruction that caused the page fault again. 

Physical Storage Not Maintained in the Paging File

When invoking an application, system reserves a region of address space and uses the contents (image) of the .exe file as the program's reserved region of address space. 

A memory-mapped file refers to a program's file on the hard disk that the system is using as the physical storage for a region of address space. The system maps the file's image to the reserved region of address space when the PE file is loaded. 

Using multiple page files on different physical hard drives is actually nice because OS can write to multiple drives simultaneously. Add/Remove paging files in Virtual Memory settings. 

System only copies images from removable media to RAM if the image is linked using /SWAPRUN:CD or /SWAPRUN:NET. 

Protection Attributes

Only PAGE_EXECUTE_* memory can execute without throwing access violation (when DEP enabled). 

Copy-on-Write Access

PAGE_WRITECOPY and PAGE_EXECUTE_WRITECOPY are used by OS when mapping PE files to images. Do not pass these to VirtualAlloc. 

When module is first mapped to the process' address space, the system allocates storage for all potential writable pages. Paging file storage not used unless module's writable pages are actually written to. 

When attempting to write to shared block, system finds one of the pages that it allocated when the module was loaded for writing. It then copies contents of the page to the free page so that original page does not have to change. 


Special Access Protection Attribute Flags

PAGE_NOCACHE - disables caching of committed pages (usually for hardware device-drivers)

PAGE_WRITECOMBINE - allows multiple writes to single device to be combined 

PAGE_GUARD - allows an app to be notified when a page has been written to

These attributes are never associated with a region, but can be associated with a block.

A block is a set of contiguous pages that have the same protection attributes and are backed by the same type of physical storage.

GetMappedFileName to get regions backed by data files
Use ToolHelp functions to get regions backed by executable files

Memory Region Types

Free - region not backed by any storage and not reserved
Private - region backed by system's paging file
Image - region backed by memory-mapped image file (.exe or DLL). Not necessarily backed by image file since copy-on-write mechanism will change the region to be backed by a page from the paging file. 

Sources:

Windows via C/C++ - J.Richter, 2008. Chapter 13

Leetcode: Meeting Rooms 2

Given an array of meeting time intervals consisting of start and end times [[s1,e1],[s2,e2],...] find the minimum number of conference rooms required.

Example


Notice how we need 4 meeting rooms because a maximum number of 4 intervals overlap even though there are more than a total of four overlapping intervals.

Approach


The idea is to count the maximum number of overlapping intervals. First, sort the intervals in order of finishing time. O(n lg n) Second, maintain a min heap of intervals sorted by finishing time (earliest finishing time is at the top). This min heap represents the meeting rooms. Process each interval. At each interval, if the current interval starts after the last interval scheduled finishes, then we can replace that interval with the current interval. Otherwise, we must allocate another room.

Process Doppelganging Notes

This post consists of notes on the process doppelganging technique.

One of the limitations of the Process Hollowing technique is its use of API calls: NtUnmapViewOfSection, VirtualProtectEx, and SetThreadContext. Process doppelganging eliminates the need to make these API calls nor unmap the main module of a process.

The process doppelganging technique was first presented at blackhat Europe 2017 by researches from enSilo. It is a method for PE injection. It leverages Windows Transactional NTFS (TxF), which was introduced as a way to perform safe file operations.

The main reasons to use process doppelganging is that it bypasses whitelisting policies, does not unmap code,  does not need manual mapping, can be used to load DLLs. For these reasons, process doppelganging appears highly promising as a first stage loader.

Transactional NTFS (TxF) Concepts

TxF is essentially an implementation that solves the readers-writer problem as applied to file systems. The possible parties involve are the transacted writer, non-transacted writers, transacted readers and non-transacted readers.

TxF binds a file handle to a transaction. TxF allows only one transacted writer at a time. All other attempts to open the file are blocked. Non-transacted writers are always blocked.

Non-transacted readers will not see changes until the transacted writer completes the transaction. They see the changes since the last transaction on the file. When the transacted writer commits the transaction, non-transacted readers will receive the updated file. Non-transacted readers are the AV processes.

In contrast, transacted readers see the last commit to the file even if the transacted writer has not completed the transaction. In contrast to the non-transacted readers, transacted readers see a consistent view of the file. They do not receive updates when the transacted-writer completes the transaction unless they close and reopen the handle.

Process doppelganging leverages the fact that nontransacted readers can only see the file since the last transaction. By rolling back changes, our changes to the transacted file will not be saved to disk.

Also CreateProccess creates sections with PAGE_EXECUTE. AV scans for sections with PAGE_EXECUTE. We create with PAGE_READ.

Process Doppelganging as presented at blackhat

High Level

  1. Open the legit executable and overwrite it with ours
  2. Load malicious executable by creating a section in the transacted file
  3. Rollback to get original executable
  4. Resume Thread

Details

  1. Use CreateTransaction to create a transaction
  2. Get the transacted file handle with CreateFileTransacted 
  3. Write our executable into the transacted file
  4. Use NtCreateSection to create section from the transacted file to point to our executable
  5. RollbackTransaction to remove our changes
  6. NtCreateProcessEx, NtCreateThreadEx (using our entry point - this is Windows XP CreateProcess)
  7. RtlCreateProcessParametersEx 
  8. VirtualAllocEx with PAGE_READWRITE
  9. Write parameters to remote process
  10. NtResumeThread
Windows XP uses NtCreateProcessEx to create a process. This function takes a handle to a section (as oppose to a handle to the file path of the executable). Also, the user is responsible for setting up process parameters. The Windows 10 loader uses NtCreateUserProcess, which takes a file path. However, NtCreateProcessEx is still available because Windows creates minimal processes with it.

Avoid AV Hooks

NTDLL is the usermode wrapper for syscalls. AV will often monitor functions exported by NTDLL. One solution is to load our own copy of NTDLL. The authors of the Osiris Banking Trojan loaded the ntdll.dll file from disk as a section. This maps the DLL as an image, which is good because manually loading DLLs makes them stand out from normally-loaded DLLs.
  1. NtCreateFile to open ntdll.dll
  2. NtCreateSection
  3. ZwMapViewOfSection to map the section to the our address space

Process Doppelganging and Process Hollowing loader

The Osiris Banking Trojan uses a combination of process doppelganging and process hollowing.

  1. Create the suspended process (use CreateProcessInternal from Kernel32)
  2. Map the image of the suspended process to our address space (using same ntdll technique)
  3. Create the transaction with ZwCreateTransaction from NTDLL
  4. Open the transacted file by calling RtlSetCurrentTransaction and ZwCreateFile
  5. Write our executable to the file with RtlSetCurrentTransaction and ZwWriteFile
  6. Create a section from the transacted file (ZwCreateSection)
  7. Rollback the file with ZwRollbackTransaction 
  8. ZwQuerySection
  9. NtClose, NtClose handles
  10. ZwMapViewOfSection to map the section from the transacted file to the suspended process
  11. ZwProtectVirtualMemory, ZwWriteVirtualMemory to patch PEB.ImageBase
  12. ZwProtectVirtualMemory, ZwWriteVirtualMemory to patch Entry Point
  13. If Entry Point patch does not work, then do ZwGetThreadContext, ZwSetThreadContext
  14. ZwResumeThread

Limitations

TxF is technically deprecated although it is still enabled as of Windows 10.

The CreateTransaction, CreateFileTransacted, RollbackTransaction calls are not commonly used. AV might detect this technique by monitoring these functions.

Original method requires the Windows XP NtCreateProcessEx and NtCreateThreadEx functions.

Other Notes

Sandboxing may be detected if the executable is not launched from its original directory.

Clear event logs with EvtOpenChannelEnum, EvtNextChannelPath, EvtClearLog from Wevtapi.dll.


Sources

https://www.blackhat.com/docs/eu-17/materials/eu-17-Liberman-Lost-In-Transaction-Process-Doppelganging.pdf

https://blog.malwarebytes.com/threat-analysis/2018/08/process-doppelganging-meets-process-hollowing_osiris/

https://attack.mitre.org/techniques/T1186/

https://securelist.com/synack-targeted-ransomware-uses-the-doppelganging-technique/85431/