Tutorial DLL Hijacking & Vulkan Hook Tutorial - Quake 2 Hack

Hexui Undetected CSGO Cheats Sinkicheat PUBG Cheat

timb3r

Semi-Retired
Dank Tier VIP
Jul 15, 2018
768
22,668
47
This is a 2 part tutorial, the second part follows this post.

spock.jpg

"Peace among worlds"

Introduction

There's generally three agreed upon "standard" methods for code injection:
  1. Static Modification of a PE file: Such as editing it's Import Address Table (IAT) or adding some shell code to it's startup routine.
  2. Injecting code into a live process through the use of WPM/RPM and CreateRemoteThread.
  3. Exploiting Window's DLL search order path to load a module (Also known as ProxyDLLs).
We're going to focus on the third option since it's not often discussed or even that well understood.

So what IS a Proxy DLL?

A Proxy DLL is named as such because it acts as a "proxy" between a process and the actual library it wants to access. Much like how at school or work when you browse the internet the proxy server fetches web pages for you. This allows the system administrators to filter the internet and improve response times by caching similar requests that are requested frequently (www.google.com for example).

A Proxy DLL functions much in the same way. It works as an interface between a process and the real DLL allowing us to apply hooks, modify return data and even be automagically injected into the target process by simply placing our DLL in the game's directory (amazing right?).

To understand exactly how this works you need to know a little bit about how PE files are structured and how Windows behaves under certain conditions. Previously I mentioned the Import Address Table (IAT) which is a data structure common to all PE files on Windows. This data structure holds the names of all the external libraries and functions that an executable (or DLL) needs to run successfully.

Let's examine the function D3D11CreateDevice which is the startup function for accessing the DirectX11 Library. MSDN tells us that in order to use this function we need to link our executable against D3D11.lib. When we link against a library (using Run-time Linking, static is different) we're telling the compiler that we want to use a function that is located outside of our executable failing to do this will result in the standard "unresolved external" error you may have seen. This is because the compiler doesn't know what to point the code to because we didn't let it know where the function resided.

When the compiler links against D3D11.lib it updates our executable's IAT with the name of the library (d3d11.dll) and the offset of where that function is located. This way when we launch our executable the windows loader sees this information and goes "This process requires DX11: better make sure that is mapped into memory before calling main".

However this is where things get interesting. Remember I said you'd need to know about Windows behavior in certain situations? This is one of them. There are two types of paths in Windows:
  1. Relative (Ex: "kernel32.dll")
  2. Absolute (Ex: "C:\Windows\System32\kernel32.dll")
A relative path is resolved according to the directory where the process is running from (and some environment variables). If you specify a relative path when calling LoadLibrary Windows applies the following search logic:
  1. If the DLL is on the list of known DLLs for the version of Windows on which the application is running, the system uses its copy of the known DLL (and the known DLL's dependent DLLs, if any). The system does not search for the DLL. For a list of known DLLs on the current system, see the following registry key: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs.
  2. The directory from which the application loaded.
  3. The system directory. Use the GetSystemDirectory function to get the path of this directory.
  4. The 16-bit system directory. There is no function that obtains the path of this directory, but it is searched.
  5. The Windows directory. Use the GetWindowsDirectory function to get the path of this directory.
  6. The current directory.
  7. The directories that are listed in the PATH environment variable. Note that this does not include the per-application path specified by the App Paths registry key. The App Paths key is not used when computing the DLL search path.
If you go to that registry key you'll find a list of the "protected" dynamic link libraries. These libraries cannot be proxied by default however, you'll notice in that list a lot of very common DLLs are missing (d3d9, d3d11, dinput, etc). Anything not in this list can be proxied and therefore hijacked for our purposes. If our chosen DLL is not in the KnownDLLs registry key it can be proxied because the next place Windows looks for it is our application directory.

If you want to test this with a game create a DLL, put a MessageBox in DLL_PROCESS_ATTACH rename it to a DLL that game uses (like d3d9.dll) and place it in the directory. Running the game will result in your message showing: then the game will crash.



Avoiding the crash

The reason the game will immediately crash is because your DLL does not export the symbols that the game is expecting. In order to successfully proxy it we need mimic what the game is expecting: but how can we find out exactly what the game needs to work?

On Windows we can use DUMPBIN to pull information from PE files. Open the VS Tools command prompt and navigate to your game's folder containing the executable.

DUMPBIN /IMPORTS ref_vk.dll > dump.txt
This is the DLL containing all the Vulkan logic for vkQuake2.

This will create a new text document called dump containing the output of the command we just ran.

Code:
Microsoft (R) COFF/PE Dumper Version 14.22.27905.0
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file ref_vk.dll

File Type: DLL

  Section contains the following imports:

    vulkan-1.dll
             18002B310 Import Address Table
             18003A1D8 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference

                          10 vkCmdBlitImage
                          47 vkCreateImageView
                          15 vkCmdCopyBufferToImage
                          17 vkCmdCopyImageToBuffer
                          6E vkEnumerateDeviceExtensionProperties
                          74 vkEnumeratePhysicalDevices
                          A8 vkGetPhysicalDeviceSurfacePresentModesKHR
                          A0 vkGetPhysicalDeviceProperties
                          81 vkGetDeviceQueue
                          A2 vkGetPhysicalDeviceQueueFamilyProperties
                          97 vkGetPhysicalDeviceFeatures
                          A9 vkGetPhysicalDeviceSurfaceSupportKHR
                          3F vkCreateDevice
                          A7 vkGetPhysicalDeviceSurfaceFormatsKHR
                          BA vkResetFences
                          4F vkCreateSemaphore
                          4D vkCreateSampler
                           3 vkAllocateDescriptorSets
                          44 vkCreateFramebuffer
                          6A vkDestroySurfaceKHR
                           B vkCmdBeginRenderPass
                          66 vkDestroySampler
                          3C vkCreateDescriptorPool
                          60 vkDestroyInstance
                          38 vkCreateBuffer
                          75 vkFlushMappedMemoryRanges
                          8A vkGetImageMemoryRequirements
                          25 vkCmdPipelineBarrier
                           1 vkAcquireNextImageKHR
                          21 vkCmdEndRenderPass
                          6C vkDeviceWaitIdle
                          54 vkDestroyBuffer
                          1D vkCmdDrawIndexed
                          3D vkCreateDescriptorSetLayout
                           8 vkBindImageMemory
                           4 vkAllocateMemory
                           E vkCmdBindPipeline
                           6 vkBindBufferMemory
                          78 vkFreeMemory
                          9D vkGetPhysicalDeviceMemoryProperties
                          30 vkCmdSetScissor
                          5F vkDestroyImageView
                          65 vkDestroyRenderPass
                          AF vkInvalidateMappedMemoryRanges
                          5E vkDestroyImage
                          46 vkCreateImage
                          B0 vkMapMemory
                          79 vkGetBufferMemoryRequirements
                          26 vkCmdPushConstants
                          1C vkCmdDraw
                          76 vkFreeCommandBuffers
                          52 vkCreateSwapchainKHR
                          AE vkGetSwapchainImagesKHR
                          A6 vkGetPhysicalDeviceSurfaceCapabilitiesKHR
                          61 vkDestroyPipeline
                          45 vkCreateGraphicsPipelines
                          80 vkGetDeviceProcAddr
                          4A vkCreatePipelineLayout
                          8F vkGetInstanceProcAddr
                          B3 vkQueuePresentKHR
                          BD vkUnmapMemory
                           F vkCmdBindVertexBuffers
                          63 vkDestroyPipelineLayout
                          58 vkDestroyDescriptorSetLayout
                          4C vkCreateRenderPass
                          BF vkUpdateDescriptorSets
                          6B vkDestroySwapchainKHR
                          57 vkDestroyDescriptorPool
                          99 vkGetPhysicalDeviceFormatProperties
                          5A vkDestroyDevice
                          77 vkFreeDescriptorSets
                          69 vkDestroyShaderModule
                           C vkCmdBindDescriptorSets
                          34 vkCmdSetViewport
                          68 vkDestroySemaphore
                           D vkCmdBindIndexBuffer
                          56 vkDestroyCommandPool
                          48 vkCreateInstance
                          5D vkDestroyFramebuffer
                          43 vkCreateFence
                          C0 vkWaitForFences
                          B4 vkQueueSubmit
                           5 vkBeginCommandBuffer
                           2 vkAllocateCommandBuffers
                          5C vkDestroyFence
                          3A vkCreateCommandPool
                          6D vkEndCommandBuffer
                          14 vkCmdCopyBuffer
                          50 vkCreateShaderModule
                          53 vkCreateWin32SurfaceKHR
As you can see vkQuake2 requires ref_vk.dll which in turn requires vulkan-1.dll and 90 imported functions to correctly launch. It's also important to note that some executables and DLLs may have a dependency chain that needs to be resolved prior to launch.

Here's a dump from Doom 2016 which is what we're going to focus on:

Code:
DLL Name: vulkan-1.dll
    vma:  Hint/Ord Member-Name Bound-To
    2c37fb4       57  vkCreateDevice
    2c37fc6       79  vkDestroyDevice
    2c37fd8       98  vkEnumerateDeviceExtensionProperties
    2c38000      110  vkGetDeviceQueue
    2c38014      139  vkQueueWaitIdle
    2c38026       81  vkDestroyFence
    2c38038       92  vkDestroySemaphore
    2c3804e       89  vkDestroyQueryPool
    2c38064       55  vkCreateDescriptorPool
    2c3807e       77  vkDestroyDescriptorPool
    2c379ce      148  vkWaitForFences
    2c380b0      104  vkFreeCommandBuffers
    2c380c8      127  vkGetPhysicalDeviceSurfaceSupportKHR
    2c37968      138  vkQueueSubmit
    2c37978        5  vkBindBufferMemory
    2c3798e      107  vkGetBufferMemoryRequirements
    2c379ae       59  vkCreateFence
    2c379be      144  vkResetFences
    2c37f9e      109  vkGetDeviceProcAddr
    2c37f78      120  vkGetPhysicalDeviceMemoryProperties
    2c37f4c      122  vkGetPhysicalDeviceQueueFamilyProperties
    2c37f2c      121  vkGetPhysicalDeviceProperties
    2c37f06      118  vkGetPhysicalDeviceFormatProperties
    2c37ee8      117  vkGetPhysicalDeviceFeatures
    2c37eca      102  vkEnumeratePhysicalDevices
    2c37eb6       85  vkDestroyInstance
    2c37ea2       64  vkCreateInstance
    2c37e8e       96  vkDeviceWaitIdle
    2c37e74       18  vkCmdCopyBufferToImage
    2c37e5e       84  vkDestroyImageView
    2c37e4a       63  vkCreateImageView
    2c37e38       83  vkDestroyImage
    2c37e28       62  vkCreateImage
    2c37e08      113  vkGetImageMemoryRequirements
    2c37df4        6  vkBindImageMemory
    2c37dde       29  vkCmdEndRenderPass
    2c37dc6        8  vkCmdBeginRenderPass
    2c37db0       50  vkCmdWriteTimestamp
    2c37d9a       36  vkCmdResetQueryPool
    2c37d8a       28  vkCmdEndQuery
    2c37d78        7  vkCmdBeginQuery
    2c37d60       33  vkCmdPipelineBarrier
    2c37d4e       17  vkCmdCopyBuffer
    2c37d3e       22  vkCmdDispatch
    2c37d22       26  vkCmdDrawIndexedIndirect
    2c37d0e       25  vkCmdDrawIndexed
    2c37cf4       12  vkCmdBindVertexBuffers
    2c37cdc       10  vkCmdBindIndexBuffer
    2c37cc2        9  vkCmdBindDescriptorSets
    2c37ca6       45  vkCmdSetStencilReference
    2c37c88       44  vkCmdSetStencilCompareMask
    2c37c74       39  vkCmdSetDepthBias
    2c37c62       43  vkCmdSetScissor
    2c37c4e       47  vkCmdSetViewport
    2c37c3a       11  vkCmdBindPipeline
    2c37c24       97  vkEndCommandBuffer
    2c37c0c        4  vkBeginCommandBuffer
    2c37bf0        1  vkAllocateCommandBuffers
    2c37bda       53  vkCreateCommandPool
    2c37bc4       90  vkDestroyRenderPass
    2c37bae       68  vkCreateRenderPass
    2c37b96       82  vkDestroyFramebuffer
    2c37b80       60  vkCreateFramebuffer
    2c37b66      147  vkUpdateDescriptorSets
    2c37b4a        2  vkAllocateDescriptorSets
    2c37b32      142  vkResetDescriptorPool
    2c37b12       78  vkDestroyDescriptorSetLayout
    2c37af4       56  vkCreateDescriptorSetLayout
    2c37ada       88  vkDestroyPipelineLayout
    2c37ac0       66  vkCreatePipelineLayout
    2c37aac       86  vkDestroyPipeline
    2c37a90       54  vkCreateComputePipelines
    2c37a74       61  vkCreateGraphicsPipelines
    2c37a5c       93  vkDestroyShaderModule
    2c37a44       71  vkCreateShaderModule
    2c37a32       74  vkDestroyBuffer
    2c37a20       51  vkCreateBuffer
    2c37a08      130  vkGetQueryPoolResults
    2c379f4       67  vkCreateQueryPool
    2c379e0       70  vkCreateSemaphore
    2c38098       76  vkDestroyCommandPool
    2c381fa       20  vkCmdCopyImageToBuffer
    2c381e8       69  vkCreateSampler
    2c381cc      103  vkFlushMappedMemoryRanges
    2c381bc      146  vkUnmapMemory
    2c381ae      134  vkMapMemory
    2c3819e      106  vkFreeMemory
    2c3818a        3  vkAllocateMemory
    2c38170       73  vkCreateWin32SurfaceKHR
    2c38144      126  vkGetPhysicalDeviceSurfacePresentModesKHR
    2c3811c      125  vkGetPhysicalDeviceSurfaceFormatsKHR
    2c380f0      124  vkGetPhysicalDeviceSurfaceCapabilitiesKHR
As you can see there's similarities between the two games. Now you can make a choice either to just support the necessary functions for a single game or completely support the entirety of the Vulkan API meaning the ProxyDLL will work with ANY Vulkan powered game.

Getting to Work:

I think everyone reading this has compiled a DLL before so I wont waste words going over how to do that. However we do need to go over the design of the ProxyDLL because that is quite important. You can't just export a bunch of symbols and hope for the best, you need to understand how the library that you're hooking works. For example with DirectX 9 we know that applications must call Direct3DCreate9 first. However in DirectX11 applications can either call D3D11CreateDevice or D3D11CreateDeviceAndSwapChain first. This is important because in certain situations your DllMain method may be called after one of the APIs are called. If your trampolines aren't initialized when that happens you'll crash the game.

So how you design your ProxyDLL will matter. I've written a number of ProxyDLLs over the years and each one required it's own unique approach. You'll have to debug, tweak and adjust until you get everything perfect and yes this will take a fair amount of time.

You'll also need to consider how much of the target API you want to support. You don't need to support 100% of the API just those required by the game. If you want universal support you'll need to implement everything in it's entirety. Or perhaps you could do it the Timb3r way: support enough of the API to make it work in one game, try loading it in another game make it crash, then update the supported API and finally get completely sidetracked writing green font to the game's console.

Screenshot from 2019-09-17 12-59-36.png

"It was totally worth all the time I wasted in x64dbg"

Coding up a storm:

I'm not going to put a massive amount of code in this article because I'll be uploading the source code. However I might go over why I implemented things the way I did.

To start with my project does not require any external dependencies like the Vulkan SDK. This was the first choice I made in regards to the project: I wanted it to be really easy to compile, really simply laid out and easy to mess with. The down side of course was I had to manually define all of Vulkan's various types and structs so I could easily interact with them inside the hooks:

C++:
#define VKAPI_CALL __stdcall
#define VKAPI_PTR  VKAPI_CALL
#define VK_NULL_HANDLE 0

typedef enum VkResult {
    VK_SUCCESS = 0,
    VK_RESULT_MAX_ENUM = 0x7FFFFFFF
} VkResult;

#define VK_DEFINE_HANDLE(object) typedef struct object##_T* object;
#define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef struct object##_T *object;

typedef uint32_t VkFlags;
typedef uint32_t VkBool32;
typedef uint64_t VkDeviceSize;
typedef uint32_t VkSampleMask;
VK_DEFINE_HANDLE(VkInstance)
VK_DEFINE_HANDLE(VkPhysicalDevice)
VK_DEFINE_HANDLE(VkDevice)
VK_DEFINE_HANDLE(VkQueue)
VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkSemaphore)
VK_DEFINE_HANDLE(VkCommandBuffer)
VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkFence)
VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkDeviceMemory)
VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkBuffer)
VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkImage)
VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkEvent)
VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkQueryPool)
VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkBufferView)
VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkImageView)
VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkShaderModule)
VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkPipelineCache)
VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkPipelineLayout)
VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkRenderPass)
VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkPipeline)
VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkDescriptorSetLayout)
VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkSampler)
VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkDescriptorPool)
VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkDescriptorSet)
VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkFramebuffer)
VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkCommandPool)
VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkSwapchainKHR)
Before you ask I copied all this information from the Vulkan SDK. The header inside the project is much much much larger. But like I said earlier that was a design choice I didn't want any external dependencies. I just wanted a small subset of the Vulkan definitions so I could examine structures in memory easily. If you don't actually care about modifying or changing parameters of calls you could easily just make everything generic pointers or a base type instead.

The OTHER reason I did it this way as I was able to simply call my functions exactly the same as the real vulkan-1.dll. If I'd linked to the SDK I wouldn't be able to call my function VkCreateInstance as it's already defined, I'd have to call it something like VkCreateInstance_Hook. One second if you're calling it VkCreateInstance_Hook and exporting it wont the exported function also be called that? How will the proxying work?

The magic of DEF files:

A DEF file also known as a Module Definition File is a special file that tells the build chain how you want to export symbols. It's basic structure looks like this:

Exports.def:
EXPORTS

vkAcquireNextImageKHR
vkAllocateCommandBuffers 
vkAllocateDescriptorSets 
vkAllocateMemory 
vkBeginCommandBuffer 
vkBindBufferMemory 
vkBindImageMemory
These match up with the definitions in the code:

VulkanHooks.h:
#define DLLEXPORT __declspec(dllexport)

extern "C"
{
// Function prototypes
VkResult DLLEXPORT vkAcquireNextImageKHR(VkDevice device, VkSwapchainKHR swapchain, uint64_t timeout, VkSemaphore semaphore, VkFence fence, uint32_t* pImageIndex);
VkResult DLLEXPORT vkAllocateCommandBuffers(VkDevice device, const VkCommandBufferAllocateInfo* pAllocateInfo, VkCommandBuffer* pCommandBuffers);
VkResult DLLEXPORT vkAllocateDescriptorSets(VkDevice device, const VkDescriptorSetAllocateInfo* pAllocateInfo, VkDescriptorSet* pDescriptorSets);
VkResult DLLEXPORT vkAllocateMemory(VkDevice device, const VkMemoryAllocateInfo* pAllocateInfo, const VkAllocationCallbacks* pAllocator, VkDeviceMemory* pMemory);
VkResult DLLEXPORT vkBeginCommandBuffer(VkCommandBuffer commandBuffer, const VkCommandBufferBeginInfo* pBeginInfo);
VkResult DLLEXPORT vkBindBufferMemory(VkDevice device, VkBuffer buffer, VkDeviceMemory memory, VkDeviceSize memoryOffset);
VkResult DLLEXPORT vkBindImageMemory(VkDevice device, VkImage image, VkDeviceMemory memory, VkDeviceSize memoryOffset);
void DLLEXPORT vkCmdBeginQuery(VkCommandBuffer commandBuffer, VkQueryPool queryPool, uint32_t query, VkQueryControlFlags flags);
// ...
}
If you want to use different names you can specify aliases in the DEF file:

Code:
EXPORTS

vkAcquireNextImageKHR = vkAcquireNextImageHKR_Hook
Then define your hook in code like this:

C++:
extern "C"
{
// Function prototypes
VkResult DLLEXPORT vkAcquireNextImageKHR_Hook(VkDevice device, VkSwapchainKHR swapchain, uint64_t timeout, VkSemaphore semaphore, VkFence fence, uint32_t* pImageIndex);
}
This way if you want to link against the SDK you can without getting any conflicts.

Continued in Part 2.
 
Last edited by a moderator:

timb3r

Semi-Retired
Dank Tier VIP
Jul 15, 2018
768
22,668
47
Missed Part 1? Click here.

The Trampolines:

We're half way there! Now we just need our hook to call the original function passing the arguments it receives from the game. In the first iteration of my Vulkan DLL I had an array of pointers to the various functions which was resolved on DllMain, however there's two functions in Vulkan that causes us some issues with this approach:

vkGetInstanceProcAddr
vkGetDeviceProcAddr
One game in particular used these two functions to resolve all the API it needed. Because this function was getting called prior to my DllMain method the game was crashing due to a nullptr exception. The way I fixed this was pretty simple but time consuming: I made each function resolve itself the first time it was called and cache the pointer.

Here's what the code looks like:

C++:
FARPROC LookupAndCacheAPI(int id)
{
    if(hVulkanMod == NULL)
    {
        char dllPath[MAX_PATH] = { 0 };
        GetSystemDirectory(dllPath, MAX_PATH);
        strcat(dllPath, "\\vulkan-1.dll");
        hVulkanMod = LoadLibrary(dllPath);
    }
    if(VulkanOriginalAPI[id] == NULL && hVulkanMod != NULL)
    {
        char szBuf[255] = { 0 };
        VulkanOriginalAPI[id] = GetProcAddress(hVulkanMod, VulkanExportedAPINames[id]);
        return VulkanOriginalAPI[id];
    }
    // There should probably be an additional checks here but I had no issues
    // As the DLL will crash if the API is not defined.
    return VulkanOriginalAPI[id];
}

VkResult vkAcquireNextImageKHR(VkDevice device, VkSwapchainKHR swapchain, uint64_t timeout, VkSemaphore semaphore, VkFence fence, uint32_t* pImageIndex)
{
    const int id = 0;
    typedef VkResult (VKAPI_PTR *VulkanPrototype_t)(VkDevice, VkSwapchainKHR, uint64_t, VkSemaphore, VkFence, uint32_t* );
    VulkanPrototype_t fn = (VulkanPrototype_t)LookupAndCacheAPI(id); PrintDebuggingInfo(id);
    return fn(device, swapchain, timeout, semaphore, fence, pImageIndex);
}
The "cache" is just an array of FARPROC pointers.

Also the reason I wrote the hook code in the way that did was because I needed to easily copy and paste the function prototype and update it easily. If you check the source you'll just see the id and the typedef change within each function.

Debugging:

One the biggest challenges to overcome during development of something like this is the debugging. You're going to have to get really comfortable with a debugger like x64dbg to track down the various issues you may run into. Also another thing to note is if you're logging huge amounts of data by say writing to a file or the console you're going to really slow the game down. Vulkan can be a bit temperamental and if the rendering gets out sync due to logging, breakpoints or whatever sometimes the process will crash or exit.

However since we're hooking Doom 2016 we have a prebuilt solution in calling the game's code to write directly to the game's console. Since the way idtech games work this is usually a memory buffer which is super fast to log to. Doom itself will log a metric crap ton of data to the console normally, so it doesn't mind if we do the same.

Take a look:
l33t-hax.png

"Nice, very nice"

freopen.jpeg


If you want to do this yourself you can use the following code:

Writing to the Doom Console:
typedef void (*Doom4ConsoleMsg_t)(const char *, const char *);
Doom4ConsoleMsg_t fnD4Msg = NULL;
uintptr_t baseAddress = 0;

baseAddress = (uintptr_t)GetModuleHandle(NULL);
fnD4Msg = (Doom4ConsoleMsg_t)(baseAddress + 0x161BDC0);

char dllPath[MAX_PATH] = { 0 };
GetSystemDirectory(dllPath, MAX_PATH);
strcat(dllPath, "\\vulkan-1.dll");

hVulkanMod = LoadLibrary(dllPath);
char szBuf[255] = { 0 };
fnD4Msg("%s\n", "------- timb3r's Vulkan Proxy v0.1 -------");
fnD4Msg("[+] Loading original dll at: \"%s\"\n", dllPath);
If you want to know exactly how I found this code and interfaced with it (well maybe that's a future tutorial ;)).

There you have it! Pretty simple and straight forward to do. I had to split the content up because I maxed out the limit for an individual thread (hopefully it's not that annoying).

I'm sure some people will have some questions just ask in the thread and I'll reply as quickly as I can.
 

Attachments

Last edited:

LLC

Full Member
Jan 17, 2019
10
168
0
This is great. Your advice in the chat last week on proxy dlls was very helpful. One thing I just ran in to was what this means when I have a function in ida that's just extern __imp_ functionname:qword ... I've got no idea what it means, or what I should even Google...
 

timb3r

Semi-Retired
Dank Tier VIP
Jul 15, 2018
768
22,668
47
This is great. Your advice in the chat last week on proxy dlls was very helpful. One thing I just ran in to was what this means when I have a function in ida that's just extern __imp_ functionname:qword ... I've got no idea what it means, or what I should even Google...
It's probably an mangled exported function if you post a screenshot or a bit more information I can take a look.
 

Kauv

Just lookin for a lil tenderoni
Fleep Tier Donator
Sep 1, 2019
31
2,062
0
Great read!

So I was reading a bit about static analysis right before this and these two sources seem to be relevant in helping to understand portable executable (PE) structure.
People may already know this stuff, but if not, these sources may help aid in understanding.
If external links are frowned upon, I can remove them - I sometimes feel strange that my contribution is pointing to other people's work. For now, it is what it is.


Basic Static Analysis (Part 1)
Ring3 / Ring0 Rootkit Hook Detection 1/2 - MalwareTech

Thanks again for this post, really great work!
 

LLC

Full Member
Jan 17, 2019
10
168
0
I'm guess it's just asking for a pointer and qword is the return? bcrypt above is very clear but I'm not sure how to intercept this one.
 

Attachments

timb3r

Semi-Retired
Dank Tier VIP
Jul 15, 2018
768
22,668
47
Bink.dll? Yeah so that function is extern because it points at Bink.dll (extern meaning the actual function resides outside the program). That particular function BinkClose takes a pointer and returns no value.

EDIT - Here's some info I found which might be out of date.

BinkClose:
typedef struct BINK {
  uint32_t Width;         // Width (1 based, 640 for example)
  uint32_t Height;        // Height (1 based, 480 for example)
  uint32_t Frames;        // Number of frames (1 based, 100 = 100 frames)
  uint32_t FrameNum;      // Frame to *be* displayed (1 based)
  uint32_t LastFrameNum;  // Last frame decompressed or skipped (1 based)

  uint32_t FrameRate;     // Frame Rate Numerator
  uint32_t FrameRateDiv;  // Frame Rate Divisor (frame rate=numerator/divisor)
} BINK;

typedef void*(__stdcall* BINKOPEN)(const char* name, uint32_t flags);
typedef void(__stdcall* BINKCLOSE)(BINK* bnk);
BinkClose takes a 64bit pointer to a Bink Struct object.

You could figure out more about it by attaching a break point and see if params are passed or if the function returns a value in RAX. I usually do live analysis of things I find it much easier than static analysis.
 
Last edited:
  • Like
Reactions: Kauv

LLC

Full Member
Jan 17, 2019
10
168
0
cool thanks, that struct actually makes some sense.

This has been a work in progress recently... trying to create a framework that reduces the number of offsets I have to update and a tied together with a threaded event logger ... Xis-Ytneves/EXproxification ... the original idea being that it'd be helpful to use keystrokes as markers to identify network packets; but then I figured why not just do them all =P

For anyone interested this will produce a compilable, runnable, VS project so you can quickly start coding instead of copy and pasting hundreds of variables:
Xis-Ytneves/DLL_Wrapper-1 ... my only change from the original 2 years ago was fixing a small bug that was breaking the output.

Regarding IDA - there are a few tools to make some static analysis more helpful in your live analysis. I updated SigMaker for IDA with a batch mode so I could paste in a couple hundred offsets and do something else when there's a version update: Xis-Ytneves/SigMaker-x64.

Also funcap PDevil/funcap allows you to set a bunch of breakpoints, run the game as usual, and it will log all the registers right by the function in IDA. Helps make quick work of figuring out what's what so you can look at something more closely resembling code.
 
  • Like
Reactions: timb3r

Hype

Meme Tier VIP
Mar 16, 2019
412
2,758
9
So this is how all those "modloaders" to games work. Thank you for interesting thread :)
 

LLC

Full Member
Jan 17, 2019
10
168
0
Athought i just had - Is it possible to include an additional export as part of the proxy that another app can call?

Also @timb3r I noticed you're using vscode ... a tutorial/tips on best practices using it would be awesome. It seems like it could be good for rapid prototyping, but I don't like having to do a ton of work to get to doing the thing I want to do. Visual Studio knows where my libraries are, how I want to compile the thing, all without typing out a json.
 

timb3r

Semi-Retired
Dank Tier VIP
Jul 15, 2018
768
22,668
47
VSCode will take care of most crap for you (through plugins). It's a bit of pain to configure it exactly how you want the first time.

Here's my JSON config you can tweak however you'd like:
c_cpp_properties.json:
{
    "configurations": [
        {
            "name": "Win32",
            "includePath": [
                "${env:MINGW64}/include/*",
                "${workspaceFolder}/AoE_IMGUI/*",
                "${workspaceFolder}/ImGui/*"
            ],
            "intelliSenseMode": "${default}",
            "cStandard": "c11",
            "cppStandard": "c++17",
            "compilerPath": "${env:MINGW64}/bin/g++"
        },
        {
            "name": "Linux",
            "intelliSenseMode": "${default}",
            "cStandard": "c11",
            "cppStandard": "c++17",
            "compilerPath": "/bin/i686-w64-mingw32-g++",
            "includePath": [
                "/usr/i686-w64-mingw32/include/",
                "${workspaceFolder}/AoE_IMGUI/",
                "${workspaceFolder}/ImGui/"
            ],
            "browse": {
                "path": [
                    "/usr/i686-w64-mingw32/include",
                    "${workspaceRoot}"
                ],
                "limitSymbolsToIncludedHeaders": true,
                "databaseFilename": ""
            }
        }
    ],
    "version": 4
}
 

Kage

Dank Tier Donator
Nobleman
Jan 26, 2019
53
2,373
1
Awesome work :)
Would it not be easier to tell the Linker too forward all the Function?
For example like this:
#pragma comment(linker, "/export:VulkanFunction=Vulkandll.VulkanFunction");

Then you only need to intercept the function we are need.
 

ahiddenmessi2

Semi-newbie
Dank Tier Donator
Nobleman
Jul 24, 2018
63
353
1
Thanks for the article. However I have still so many lack of understanding in PE format and library linking stuff. Could u suggest some books that I could read to learn those?
 

timb3r

Semi-Retired
Dank Tier VIP
Jul 15, 2018
768
22,668
47
There's no one book that will fix anything just try to read everything. Wininternals etc, but I learned mostly by reversing my own code and figuring out what it does.
 

timb3r

Semi-Retired
Dank Tier VIP
Jul 15, 2018
768
22,668
47
Awesome work :)
Would it not be easier to tell the Linker too forward all the Function?
For example like this:
#pragma comment(linker, "/export:VulkanFunction=Vulkandll.VulkanFunction");

Then you only need to intercept the function we are need.
Yes it would be but I wanted to create a "library" so you could easily apply what hooks you needed. I wanted total control over everything. If you only need a few api than sure. It was more a learning experience since not many people talk about vulkan.
 
Attention! Before you post:

Read the How to Ask Questions Guide
99% of questions are answered in the Beginner's Guide, do it before asking a question.

No Hack Requests. Post in the correct section.  Search the forum first. Read the rules.

How to make a good post:

  • Fill out the form correctly
  • Tell us the game name & coding language
  • Post everything we need to know to help you
  • Ask specific questions, be descriptive
  • Post errors, line numbers & screenshots
  • Post code snippets using code tags
  • If it's a large project, zip it up and attach it

If you do not comply, your post may be deleted.  We want to help, please make a good post and we will do our best to help you.

Community Mods League of Legends Accounts