I know it's a long post but it's mostly code and pictures, it's a quick read! First of all, here is what I'm trying to do:
I'm trying to execute a BYTE array in a detoured function in order to go back to the original code as if I didn't detour anyhting Here is my code:
DllMain (DetourAddress is all that matter):
BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved )
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
AllocConsole();
freopen("CONOUT$", "w", stdout);
DetourAddress((void*)HookAddress, (void*)&DetourFunc);
case DLL_PROCESS_DETACH:
FreeConsole();
break;
}
return TRUE;
}
DetourAddress (code is self-explanatory, I think):
void DetourAddress(void* funcPtr, void* hook)
{
// write jmp
BYTE cmd[5] =
{
0xE9, //jmp
0x00, 0x00, 0x00, 0x00 //address
};
// make memory readable/writable
DWORD dwProtect;
VirtualProtect(funcPtr, 5, PAGE_EXECUTE_READWRITE, &dwProtect);
// read bytes about to be replaced
ReadProcessMemory(GetCurrentProcess(), (LPVOID)funcPtr, mem, 5, NULL);
// write jmp in cmd
DWORD offset = ((DWORD)hook - (DWORD)funcPtr - 5); // (dest address) - (source address) - (jmp size)
memcpy(&cmd[1], &offset, 4); // write address into jmp
WriteProcessMemory(GetCurrentProcess(), (LPVOID)funcPtr, cmd, 5, 0); // write jmp
// reprotect
VirtualProtect(funcPtr, 5, dwProtect, NULL);
}
DetourFunc:
_declspec(naked) void DetourFunc()
{
__asm
{
PUSHFD
PUSHAD
}
printf("function detoured\n");
__asm
{
POPAD
POPFD
}
// make memory readable/writable
DWORD dwProtect;
VirtualProtect(mem, 6, PAGE_EXECUTE_READWRITE, &dwProtect);
pByteExe();
// reprotect
VirtualProtect(mem, 6, dwProtect, NULL);
__asm
{
jmp HookReturnAddress
}
}
And finaly the global variables, typedef for pByteExe() and includes:
#include <Windows.h>
#include <cstdio>
DWORD HookAddress = 0x08B1418,
HookReturnAddress = HookAddress+5;
typedef void ( * pFunc)();
BYTE mem[6] = { 0x0, 0x0, 0x0, 0x0, 0x0, 0xC3 };
pFunc pByteExe = (pFunc) &mem
As you can see in DetourFunc, I'm trying to execute my byte array (mem) directly. Using OllyDbg, this gets me there:
Which is exactly the bytes I'm trying to execute. Only problem is that it gives me an Access violation error when executing... Any idea why? I would have thought "VirtualProtect(mem, 5, PAGE_EXECUTE_READWRITE, &dwProtect);" would have made it safe to access... Thanks for your help!
EDIT: I just realized something wierd was happening... when I "Step into" with ollydbg, the mem instructions are correct, but as soon as I scroll a little, they change back to this:
Any idea why?
You've forgot the module offset...
DWORD module = (DWORD)GetModuleHandle(NULL);
DWORD real_address = module + (DWORD)ADDRESS;
ADDRESS have to of course relative to your module. (The module offset isn't allways the same)
And btw. why you take WriteProcessMemory, when you inject your DLL? A simple memcpy is enought...
Related
I am trying to perform a system call on 32-bit, but there is an issue.
I originally had a naked function as my stub and used inline assembly, but when I tried to turn it into shellcode, despite it being a 1-to-1 copy of the naked function (When looking at it in Visual Studio's disassembly), it does not function (Access Violation Executing NULL).
It worked perfectly with the naked function, by the way.
Here is the shellcode I wrote:
0: b8 26 00 00 00 mov eax,0x26
5: 64 ff 15 c0 00 00 00 call DWORD PTR fs:0xc0
c: c3 ret
And here is the code: Everything works fine. Memory gets allocated successfully, the problem is whenever I attempt to call NtOpenProcess: it attempts to execute a null pointer, resulting in an access execution violation.
typedef NTSTATUS(NTAPI * f_NtOpenProcess)(PHANDLE, ACCESS_MASK, OBJECT_ATTRIBUTES *, CLIENT_ID *);
INT
main(
VOID
)
{
HANDLE hProcess = NULL;
OBJECT_ATTRIBUTES oaAttributes;
memset(&oaAttributes,
NULL,
sizeof(oaAttributes));
oaAttributes.Length = sizeof(oaAttributes);
CLIENT_ID ciClient;
ciClient.UniqueProcess = GetCurrentProcessId();
ciClient.UniqueThread = NULL;
BYTE Stub[] = { 0xB8, 0x00, 0x00, 0x00, 0x00, 0x64, 0xFF, 0x15, 0x0C, 0x00, 0x00, 0x00, 0xC3 };
*(DWORD*)(Stub + 1) = 0x26;
PVOID Mem = VirtualAlloc(NULL, sizeof(Stub), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
memcpy(Mem, &Stub, sizeof(Stub));
DWORD Old = NULL;
VirtualProtect(Mem, sizeof(Stub), PAGE_EXECUTE, &Old);
f_NtOpenProcess NtOpenProcess = (f_NtOpenProcess)Mem;
DWORD Status = NtOpenProcess(&hProcess,
PROCESS_ALL_ACCESS,
&oaAttributes,
&ciClient);
printf("Status: 0x%08X\nHandle: 0x%08X\n",
Status,
hProcess);
getchar();
return NULL;
}
If anyone is wondering why am I doing this, I am really bored and I like to mess around with code when I am :)
As noted by Micheal Petch, the shellcode was wrong.
I only missed one byte (0x0C) that should be 0xC0.
If anyone will ever attempt something so stupid and useless like I did, double-check your shellcode first!
I made a WOW64 syscall hook for the `NtCreateSection` function using the following code:
#include "Funcs.h"
#include <cstdio>
#include <Windows.h>
const int PAGE_SIZE = 0x1000;
const int SYSCALL_INTERCEPT = 0x4A;
const int NUM_WOW64_BYTES = 0x9;
using pNtCreateSection =
NTSTATUS (NTAPI*)(PHANDLE SectionHandle, ULONG DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes,
PLARGE_INTEGER MaximumSize, ULONG PageAttributess, ULONG SectionAttributes, HANDLE FileHandle);
pNtCreateSection NtCreateSection = nullptr;
DWORD_PTR dwWow64Address = 0;
LPVOID lpJmpRealloc = nullptr;
ULONG SectionAttributes;
void __declspec(naked) NtCreateSectionHook()
{
__asm
{
pushad
}
fprintf(stderr, "NtCreateSectionHook called !\n");
__asm
{
popad
jmp lpJmpRealloc
}
}
DWORD_PTR __declspec(naked) GetWow64Address()
{
__asm
{
mov eax, dword ptr fs:[0xC0]
ret
}
}
void __declspec(naked) Wow64Trampoline()
{
__asm
{
cmp eax, SYSCALL_INTERCEPT
jz NtCreateSectionHook
jmp lpJmpRealloc
}
}
LPVOID CreateNewJump(const DWORD_PTR dwWow64Address)
{
lpJmpRealloc = VirtualAlloc(nullptr, PAGE_SIZE, MEM_RESERVE | MEM_COMMIT,
PAGE_EXECUTE_READWRITE);
(void)memcpy(lpJmpRealloc, (const void *)dwWow64Address, NUM_WOW64_BYTES);
return lpJmpRealloc;
}
void EnableWow64Redirect(const DWORD_PTR dwWow64Address, const LPVOID lpNewJumpLocation)
{
unsigned char trampolineBytes[] =
{
0x68, 0xDD, 0xCC, 0xBB, 0xAA, /*push 0xAABBCCDD*/
0xC3, /*ret*/
0xCC, 0xCC, 0xCC /*padding*/
};
memcpy(&trampolineBytes[1], &lpNewJumpLocation, sizeof(DWORD_PTR));
WriteJump(dwWow64Address, trampolineBytes, sizeof trampolineBytes);
}
void WriteJump(const DWORD_PTR dwWow64Address, const void *pBuffer, size_t ulSize)
{
DWORD dwOldProtect = 0;
(void)VirtualProtect(reinterpret_cast<LPVOID>(dwWow64Address), PAGE_SIZE, PAGE_EXECUTE_READWRITE, &dwOldProtect);
(void)memcpy(reinterpret_cast<void *>(dwWow64Address), pBuffer, ulSize);
(void)VirtualProtect(reinterpret_cast<LPVOID>(dwWow64Address), PAGE_SIZE, dwOldProtect, &dwOldProtect);
}
int main(int argc, char *argv[])
{
const auto hModule = GetModuleHandle(L"ntdll.dll");
NtCreateSection = reinterpret_cast<pNtCreateSection>(GetProcAddress(hModule, "NtCreateSection"));
dwWow64Address = GetWow64Address();
const auto lpNewJumpLocation = CreateNewJump(dwWow64Address);
EnableWow64Redirect(dwWow64Address, static_cast<LPVOID>(Wow64Trampoline));
//Test syscall
HANDLE hSection;
NtCreateSection(&hSection, SECTION_ALL_ACCESS, nullptr, nullptr, PAGE_EXECUTE_READWRITE, SEC_COMMIT | SEC_NOCHANGE, nullptr);
getchar();
return 0;
}
The code works fine until I change the hooked function to this
void __declspec(naked) NtCreateSectionHook()
{
__asm
{
pushad
mov eax, [esp + 28]
mov SectionAttributes, eax
}
fprintf(stderr, "NtCreateSectionHook called !\n");
if ((SectionAttributes & SEC_NOCHANGE) != 0)
{
fprintf(stderr, "SEC_NOCHANGE found !\n");
}
__asm
{
popad
jmp lpJmpRealloc
}
}
The problem in my code is that the pushad instruction messes with the esp therefore I can't access the stack anymore and if I don't use pushad/popap the app crashes since I'm messing up with the stack then jumping to the real function address.
The argument I wanna access and change is the 6th argument of NtCreateSection
function.
pushad does not prevent you from accessing the stack. pushad pushes 32 bytes (8 registers, 4 bytes each) into the stack, hence, any offset after pushad should be corrected by adding 32.
I'm trying to hook a function on x64 application. Here's my code:
int __stdcall nRecv(SOCKET s, char* buf, int len, int flags)
{
Log("Receving!");
return 0;
}
BOOL HookFunction(LPCWSTR moduleName, LPCSTR funcName, LPVOID pDestination)
{
BYTE stub[6] = { 0xe9, 0x00, 0x00, 0x00, 0x00, 0xc3 };
DWORD pProtection;
DWORD pSource = (DWORD)GetProcAddress(GetModuleHandle(moduleName), funcName);
LPVOID pTrampoline = VirtualAlloc(NULL, 6 + sizeof(stub), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
VirtualProtect((LPVOID)pSource, 6, PAGE_EXECUTE_READWRITE, &pProtection);
CopyMemory(stub + 1, &pDestination, 4);
CopyMemory((LPVOID)((DWORD_PTR)pTrampoline), &pSource, 6);
CopyMemory((LPVOID)((DWORD_PTR)pTrampoline + 6), stub, sizeof(stub));
CopyMemory(stub + 1, &pTrampoline, 4);
CopyMemory(&pSource, &stub, sizeof(stub));
VirtualProtect((LPVOID)pSource, 6, pProtection, NULL);
return TRUE;
}
BOOL recvHook = HookFunction(L"ws2_32.dll", "recv", &nRecv);
I've attached a debugger and spot an error:
Stack around the variable pSource was corrupted.
I couldn't really find a definitive reason for this happening, am I doing something wrong? Thanks!
This line here is copying 6 bytes of memory into a 4 byte variable
CopyMemory(&pSource, &stub, sizeof(stub));
const char* GetName()
{
std::string s = *(std::string*)(this + 0x28);
return s.c_str();
}
Update:
struct Entity
{
static Entity* GetCurrentPlayer()
{
return (Entity*)(*(DWORD*)((DWORD)GetModuleHandleA("League of Legends.exe") + (DWORD)ADR_LocalPlayer));
}
}
Update2:
Actually, all i need to do is the same like Cheat Engine does: Read the string located at some pointer (this) + Offset 0x28 returned as const char* as i need this datatype right after.
I also tried directly accessing the address with const char*, this gave me cryptic symbols. Example at the top is crashing the game.
I guess i either need another datatype or another way to access the data?!
Note: This is an injected DLL and i'm accessing the data via an address
I think this is what you need, set the target address to Read, Write and Execute (if you even want to change code).
VirtualProtect((LPVOID)targetaddress,5,PAGE_EXECUTE_READWRITE,&oldp);
This is a full scenario:
Write a DLL with a shared memory section. The DLL should be linked to your main application to enable receiving notifications or data (the string), and to enable sending commands from your main app if you need to control the remote process.
Your main application should create the remote thread, the code in that thread would be to Load the DLL into the remote process, and then the DLL should take over from there.
Using the inter-process communication mechanism of your choice, start sending/receiving data between the remote process and your main application.
Here's a DLL injection function (in your main app) to inject your DLL into the remote process:
int Inject(char *fname,char *dllname,int NewProcess,DWORD PID) // 1=new process, 2= PID, 3=current process
{
STARTUPINFOA si;
PROCESS_INFORMATION pi;
BOOL rv;
void *pr;
HANDLE hh;
SIZE_T bwrit;
DWORD par,tid;HANDLE th;
FARPROC LoadLibProc = GetProcAddress(GetModuleHandleA("KERNEL32.dll"), "LoadLibraryA");
FARPROC ExitThreadProc = GetProcAddress(GetModuleHandleA("KERNEL32.dll"), "ExitThread");
char InjectedCode[500] =
{// 0xcc,
0x60, // pushad
0xB8, 00, 00, 00, 00, // mov EAX, 0h | Pointer to LoadLibraryA() (DWORD)
0xBB, 00, 00, 00, 00, // mov EBX, 0h | DLLName to inject (DWORD)
0x53, // push EBX
0xFF, 0xD0, // call EAX
0x5b, // pop EBX
0xB8, 00, 00, 00, 00, // EAX 2 mov EAX, 0h | Pointer to ExitThreadProc() (DWORD)
0x6a,00, // Push 00
0xFF, 0xD0, // call EAX
//0xcc // INT 3h
0x61, // popad
//0xcc
// 0xc3 // ret
};
int nob=30;
char *DLLName;
DWORD *EAX, *EBX, *EAX2;
DLLName = (char*)((DWORD)InjectedCode + nob);
strcpy( DLLName, dllname );
EAX = (DWORD*)( InjectedCode + 2);
EBX = (DWORD*) ( InjectedCode + 7);
EAX2 = (DWORD*)( InjectedCode + 16);
*EAX=(DWORD)LoadLibProc;
*EAX2=(DWORD)ExitThreadProc;
*EBX=nob;
ZeroMemory((VOID*)&si, sizeof(si));
si.dwFlags=STARTF_USESHOWWINDOW;//1
si.wShowWindow=SW_HIDE;//0
if (NewProcess==1) {
rv=CreateProcessA(fname,0,0,0,FALSE,CREATE_SUSPENDED,0,0,&si,&pi);
if (rv==FALSE) {return -1;}
hh=pi.hProcess;
}
TCHAR bb[200];
if (NewProcess==2) {////PROCESS_ALL_ACCESS
hh=OpenProcess(PROCESS_CREATE_THREAD|PROCESS_VM_WRITE|PROCESS_VM_OPERATION ,0,PID);
if (hh==NULL) return -6;
}
if (NewProcess==3) {
hh=GetCurrentProcess();
}
//if (hh==INVALID_HANDLE_VALUE) {printf("\nError Opening Process...");return;}
pr=VirtualAllocEx(hh,0,500,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
if (pr==NULL) return -7;
Sleep(100);
//printf("\nAddress Allocated=%x",pr);
*EBX+=(DWORD)pr;
rv=WriteProcessMemory(hh,pr,InjectedCode,500,&bwrit);
if (rv==0) return -8;
th=CreateRemoteThread(hh,NULL,0,(LPTHREAD_START_ROUTINE)pr,&par,0,&tid);
if (th==NULL) return -9;
return 0;
}
Now for the DLL, in your DLL CPP, add the shared memory section to hold data for inter-process communication:
#pragma data_seg("shared")
char whateverdatatoshare[16384]={0};
// you can also define events or variables to use to signal the remote process or the main app of any incoming data
#pragma data_seg()
#pragma comment(linker, "/section:shared,rws") // This instructs the linker to make this section readable,writable and shared
In your APIENTRY dllmain function, create a thread to do whatever you want, like reading your string or data every second to send it back to the main app.
BOOL APIENTRY DllMain1( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
DWORD par,tid;
HANDLE thandle;
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
thandle= CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)&MsgThread,&par,0,&tid);
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
The your thread code should do the actual work of sniffing the string you want.
Very Important: To make sure you can read/write in memory, use this:
First set the page to PAGE_EXECUTE_READWRITE
**VirtualProtect((LPVOID)targetaddress,5,PAGE_EXECUTE_READWRITE,&oldp);**
Second: IsBadWritePtr or IsBadReadPtr, to make sure you can write or read.
As Steve already mentioned you won't be able to access an other application's memory since it is protected by the OS. You can check that by running two little programs, the first creates a variable, stores its pointer in a file and keeps running after that (by waiting for user input or whatever). The second program reads the pointer from the file and then tries to use its content. Won't work.
Furthermore check out the documentation for GetModuleHandleA: http://msdn.microsoft.com/en-us/library/windows/desktop/ms683199%28v=vs.85%29.aspx
It says:
"Retrieves a module handle for the specified module. The module must have been loaded by the calling process."
And:
"The name is compared (case independently) to the names of modules currently mapped into the address space of the calling process."
As you want to get a module which was loaded by another process this won't work either.
I'm interested in hooking and I decided to see if I could hook some functions. I wasn't interested in using a library like detours because I want to have the experience of doing it on my own. With some sources I found on the internet, I was able to create the code below. It's basic, but it works alright. However when hooking functions that are called by multiple threads it proves to be extremely unstable. If two calls are made at nearly the same time, it'll crash. After some research I think I need to create a trampoline function. After looking for hours all I was not able to find anything other that a general description on what a trampoline was. I could not find anything specifically about writing a trampoline function, or how they really worked. If any one could help me write one, post some sources, or at least point me in the right direction by recommending some articles, sites, books, etc. I would greatly appreciate it.
Below is the code I've written. It's really basic but I hope others might learn from it.
test.cpp
#include "stdafx.h"
Hook hook;
typedef int (WINAPI *tMessageBox)(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType);
DWORD hMessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType)
{
hook.removeHook();
tMessageBox oMessageBox = (tMessageBox)hook.funcPtr;
int ret =oMessageBox(hWnd, lpText, "Hooked!", uType);
hook.applyHook(&hMessageBox);
return ret;
}
void hookMessageBox()
{
printf("Hooking MessageBox...\n");
if(hook.findFunc("User32.dll", "MessageBoxA"))
{
if(hook.applyHook(&hMessageBox))
{
printf("hook applied! \n\n");
} else printf("hook could not be applied\n");
}
}
hook.cpp
#include "stdafx.h"
bool Hook::findFunc(char* libName, char* funcName)
{
Hook::funcPtr = (void*)GetProcAddress(GetModuleHandleA(libName), funcName);
return (Hook::funcPtr != NULL);
}
bool Hook::removeHook()
{
DWORD dwProtect;
if(VirtualProtect(Hook::funcPtr, 6, PAGE_EXECUTE_READWRITE, &dwProtect))
{
WriteProcessMemory(GetCurrentProcess(), (LPVOID)Hook::funcPtr, Hook::origData, 6, 0);
VirtualProtect(Hook::funcPtr, 6, dwProtect, NULL);
return true;
} else return false;
}
bool Hook::reapplyHook()
{
DWORD dwProtect;
if(VirtualProtect(funcPtr, 6, PAGE_EXECUTE_READWRITE, &dwProtect))
{
WriteProcessMemory(GetCurrentProcess(), (LPVOID)funcPtr, Hook::hookData, 6, 0);
VirtualProtect(funcPtr, 6, dwProtect, NULL);
return true;
} else return false;
}
bool Hook::applyHook(void* hook)
{
return setHookAtAddress(Hook::funcPtr, hook);
}
bool Hook::setHookAtAddress(void* funcPtr, void* hook)
{
Hook::funcPtr = funcPtr;
BYTE jmp[6] = { 0xE9, //jmp
0x00, 0x00, 0x00, 0x00, //address
0xC3 //retn
};
DWORD dwProtect;
if(VirtualProtect(funcPtr, 6, PAGE_EXECUTE_READWRITE, &dwProtect)) // make memory writable
{
ReadProcessMemory(GetCurrentProcess(), (LPVOID)funcPtr, Hook::origData, 6, 0); // save old data
DWORD offset = ((DWORD)hook - (DWORD)funcPtr - 5); //((to)-(from)-5)
memcpy(&jmp[1], &offset, 4); // write address into jmp
memcpy(Hook::hookData, jmp, 6); // save hook data
WriteProcessMemory(GetCurrentProcess(), (LPVOID)funcPtr, jmp, 6, 0); // write jmp
VirtualProtect(funcPtr, 6, dwProtect, NULL); // reprotect
return true;
} else return false;
}
If you want your hook to be safe when called by multiple threads, you don't want to be constantly unhooking and rehooking the original API.
A trampoline is simply a bit of code you generate that replicates the functionality of the first few bytes of the original API (which you overwrote with your jump), then jumps into the API after the bytes you overwrote.
Rather than unhooking the API, calling it and rehooking it you simply call the trampoline.
This is moderately complicated to do on x86 because you need (a fairly minimal) disassembler to find the instruction boundaries. You also need to check that the code you copy into your trampoline doesn't do anything relative to the instruction pointer (like a jmp, branch or call).
This is sufficient to make calls to the hook thread-safe, but you can't create the hook if multiple threads are using the API. For this, you need to hook the function with a two-byte near jump (which can be written atomically). Windows APIs are frequently preceded by a few NOPs (which can be overwritten with a far jump) to provide a target for this near jump.
Doing this on x64 is much more complicated. You can't simply patch the function with a 64-bit far jump (because there isn't one, and instructions to simulate it are often too long). And, depending on what your trampoline does, you may need to add it to the OS's stack unwind information.
I hope this isn't too general.
The defacto standard hooking tutorial is from jbremer and available here
Here is a simple x86 detour and trampoline hook based on this tutorial using Direct3D's EndScene() function as a example:
bool Detour32(char* src, char* dst, const intptr_t len)
{
if (len < 5) return false;
DWORD curProtection;
VirtualProtect(src, len, PAGE_EXECUTE_READWRITE, &curProtection);
intptr_t relativeAddress = (intptr_t)(dst - (intptr_t)src) - 5;
*src = (char)'\xE9';
*(intptr_t*)((intptr_t)src + 1) = relativeAddress;
VirtualProtect(src, len, curProtection, &curProtection);
return true;
}
char* TrampHook32(char* src, char* dst, const intptr_t len)
{
// Make sure the length is greater than 5
if (len < 5) return 0;
// Create the gateway (len + 5 for the overwritten bytes + the jmp)
void* gateway = VirtualAlloc(0, len + 5, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
//Write the stolen bytes into the gateway
memcpy(gateway, src, len);
// Get the gateway to destination addy
intptr_t gatewayRelativeAddr = ((intptr_t)src - (intptr_t)gateway) - 5;
// Add the jmp opcode to the end of the gateway
*(char*)((intptr_t)gateway + len) = 0xE9;
// Add the address to the jmp
*(intptr_t*)((intptr_t)gateway + len + 1) = gatewayRelativeAddr;
// Perform the detour
Detour32(src, dst, len);
return (char*)gateway;
}
typedef HRESULT(APIENTRY* tEndScene)(LPDIRECT3DDEVICE9 pDevice);
tEndScene oEndScene = nullptr;
HRESULT APIENTRY hkEndScene(LPDIRECT3DDEVICE9 pDevice)
{
//do stuff in here
return oEndScene(pDevice);
}
//just an example
int main()
{
oEndScene = (tEndScene)TrampHook32((char*)d3d9Device[42], (char*)hkEndScene, 7);
}