# Bypassing app protection using proxy DLLs

I've been modding some games on Steam recently, and some of them make use of the Steamworks product to add an extra layer of security, as well as adding other features such as the overlay and cloud saves. This isn't an article on how Steam DRM and Steamworks works so I'm not going to get into the details, but as part of my work I decided it would be good to build a proxy DLL that I can put in place of the real Steamworks DLL and then have the option to either forward calls, return a cached value, or breakpoint when certain functions were hit. Here's how I did it:

## Creating the DLL skeleton

I've done this for x86, (x64 is an exercise for the reader), as the app I'm modding is an x86 app. Fortunately we're not using any inline asm (this doesn't work in x64 apps) so most of it will translate across the same.

### Start a DLL project

In Visual Studio 2019 we click *Create a new project* and search for a DLL project, a normal one, not the MFC option:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1672737718358/8e57ecbc-0466-4579-b24e-4eef81d9b836.png align="center")

We give it a name and then carry on. The next step is to enable ASM files to be included, which is a short process but poorly documented

### Enable asm building

We'll start off by adding an `.asm` file. Right click on `source files` in the Solution Explorer, and under the `Add` menu click `New Item`

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1672737817304/5a36f371-efe5-4547-90d1-922e316e713c.png align="center")

It should select `C++ file` by default, just ignore this and overwrite the extension in the name field to call it something else, we'll use `detours.asm` for this project:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1672737869622/06d1673b-8928-41e1-833c-b44af4b94468.png align="center")

Asm files are not built by default so we need to enable MASM in this project and then add our `detours.asm` file. Right-click on the name of the project (not the solution), and under `Build Dependencies` click on the `Build Customizations...` option

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1672737973633/c5a302e2-8e40-4506-bad3-0269796ff068.png align="center")

By default `.masm` is unchecked, so check this and click OK

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1672738012074/7d56b099-0304-4693-87f1-4531419c188f.png align="center")

Now we've added MASM to the project we need to make our `.asm` file build, so right click on our `detours.asm` and click `Properties` and in the dropdown by `Item Type` select `Microsoft Macro Assembler`

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1672738084271/0ca9a39e-e938-469d-8a8e-3d79a07ab3cc.png align="center")

Note that we're doing this for all configurations and all platforms, you can easily do separate x86 and x64 files and add them to specific platforms, for example. Now we've done this, we can expect our `.asm` file will build.

### Building from a .def file

Normally you'd use the `__declspec(dllexport)` keyword to annotate functions that you want to export, but that won't let you choose the ordinal. If we want to build a good proxy DLL we need to ensure that the app we're modding can import either by name or by ordinal, otherwise we might run into some unexpected results later. We add our def file (mine is called `exports.def`) in the same way as we added our `.asm` file above, and then we add it to the build in project properties:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1672740458458/737c59ca-0224-46ce-b270-740d8a12bc3c.png align="center")

We're all good to go, let's get some code in place!

## Cloning the exports

[LIEF](https://lief-project.github.io/) is my go-to tool for any time I need to work with a binary. Install it via PIP, import, and then we can load the DLL export table in a couple of lines:

```python
import lief
binary = lief.parse("/mnt/c/Program Files (x86)/Steam/steamapps/common/Reversed Dreamland/RD_Data/Plugins/steam_api.dll")
exports = [(e.name, e.ordinal) for e in binary.get_export().entries]
```

Note the `/mnt/c` because I do most of my coding from WSL but Steam is installed in the base Windows environment.

Once we've loaded the exports, we need to generate two things: export definitions, and stubs that we can populate. We can generate the def file like this:

```python
with output = open("exports.txt", "w"):
    export_entries = ["\t%s @%d" % (x[0], x[1]) for x in exports]
    output.write("\n".join(export_entries))
```

And then if we want to generate some stubs we can do something similar:

```python
with output = open("stubs.txt", "w"):
    funcs = ["%s PROC\n\tpush hOldDll\n\tpush %d\n\tcall [_imp__GetProcAddress@8]\n\tjmp eax\n%s ENDP" % (x[0], x[1], x[0]) for x in exports]
    output.write("\n".join(funcs))
```

We can then copy these into our files. Our `exports.def` file needs to start like this:

```plaintext
LIBRARY csteamworks_proxy
EXPORTS
    Function1 @1
    Function2 @2
    ...
```

We can then paste our defs underneath (the `Function1` stuff is just an example by the way and should be removed). The stubs need a little bit more work, and I've done mine like this:

```plaintext
.386
.model flat, stdcall

.data
hOldDll DWORD 0;
pDllName BYTE "targetdll_old.dll",0
.code

EXTERN _imp__LoadLibraryA@4 : dword
EXTERN _imp__GetProcAddress@8 : dword

OPTION LANGUAGE: syscall
@init@0 PROC
    lea eax, [pDllName]
    ;push eax    
    ;call [_imp__LoadLibraryA@4]
    mov hOldDll, eax
    ret
@init@0 ENDP

Func1 PROC
	push hOldDll
	push 1
	call [_imp__GetProcAddress@8]
	jmp eax
Func1 ENDP

...

END
```

From here we can finally set up the `dllmain.cpp` that drives the whole operation:

```c
// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"

extern "C" void __fastcall init(void);

BOOL APIENTRY DllMain(HMODULE hModule,
    DWORD  ul_reason_for_call,
    LPVOID lpReserved
)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        init();
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}
```

So when we load the DLL, it will call `init()` the first time which will call `LoadLibrary()` to put the DLL in memory, and then whenever we hit a function that we've exported it will jump straight through to the real thing, leaving us with an easy place to put our own breakpoints if we like. There's also nothing to stop you changing these proxy functions to do something else, add some logging, or just plain return a fixed value instead. Just copy this into the directory where the real DLL is, rename it to `targetdll_old.dll` or similar, and then you have your very own customisable proxy.

I hope you find this useful, this is just one more tool that we have at our disposal when it comes to analysing and modding.
