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