Solved Run-Time Check Failure on __fastcall trampoline hook

Hexui Undetected CSGO Cheats Sinkicheat PUBG Cheat

Braindrool

Dank Tier Donator
Jul 28, 2020
7
224
0
Game Name
Star Wars Battle Front II (Classic)
Anticheat
None
Tutorial Link
https://guidedhacking.com/threads/simple-x86-c-trampoline-hook.14188/
How long you been coding/hacking?
~12 years coding, but new to hacking
Coding Language
C++
Run-Time Check Failure #0 - The value of ESP was not properly saved across a function call

I played around with this error for quite a while and assumed it was something in my code. Eventually I resorted to the GH copy pasta Trampoline Hook code assuming it would work, but no luck. Somehow I'm managing to break what seems to be very simple code. Can someone please assist me or point me in the right direction?


Maybe there's an obvious solution that the error message gives away, but I haven't found it yet.
2_Error.PNG


I took out everything except what's needed and continue to receive the error, so I'm pretty confident the error is right about the calling convention


C++:
#include "stdafx.h"
#include "BF2.h"

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 void(_fastcall* tTicket)(int p1, int p2, int p3);
tTicket oTicket = nullptr;

void __fastcall hTicket(int p1, int p2, int p3) {
    std::cout << "Hooked" << std::endl;
    oTicket(p1, p2, p3);
}

BF2::BF2()
{
    moduleBase = (uintptr_t)GetModuleHandleA(NULL);
    uintptr_t ticketFuncStart = moduleBase + 0x163410;

    oTicket = (tTicket)TrampHook32((char*)ticketFuncStart, (char*)hTicket, 9);
}
The calling convention according to decompiler
5_decomp.png


And the start of the function, if it matters
1_Original.PNG


From what I've found online it's probably related to a corrupted stack, but with the minimal code I'm not really seeing where that could be. I found this question asked once on this forum before, but that particular thread was of limited help


Apologies if this is an obvious question, I'm still going through the basics and haven't encountered this before :)
 

Rake

Cesspool Admin
Administrator
Jan 21, 2014
12,349
78,998
2,412
IDA decompiles it as:

C++:
DWORD __usercall sub_563410@<eax>(int a1@<edx>, int a2@<ecx>, int a3
IDA identifies it as a non-standard function (that's what usercall means), so something weird generated this non standard function. (alternatively IDA is just stupid, but in this case it's probably not because GHIDRA was stupid too)

First arg in EDX, second in ECX and the third pushed on the stack. Return value in EAX. Again no guarantee IDA is correct

You will probably need to hook it as _declspec(naked) function which will be tricky if you don't have experience doing that
 
  • Like
Reactions: Petko123

mambda

headass
Escobar Tier VIP
Trump Tier Donator
Jun 25, 2014
2,304
37,938
270
fastcall with 2 parameters cause i dont see the argument pushed in EDI being used in the first bit, and the stack was fixed by 4 bytes after the call which would normally happen in the fast/stdcall, therefore the push edi is something separate to the function call (or the stack was adjusted because of something even further up)

remove p3

future advice: Breakpoint function before your hook -> note ESP value, breakpoint after hook -> note ESP value

subtract the 2 values to see how far your stack is off by
 
  • Like
Reactions: obdr and Kekz

Rake

Cesspool Admin
Administrator
Jan 21, 2014
12,349
78,998
2,412
show us the 10 instructions above and below the call to this function, and show us the last 10 instruction of this function itself, so we can define the calling convention
 
Last edited:
  • Like
Reactions: Nutrition

Braindrool

Dank Tier Donator
Jul 28, 2020
7
224
0
show us the 10 instructions above and below the call to this function, and show us the last 10 instruction of this function itself, so we can define the calling convention
Above the call (00563cf2)
6_Before.PNG


After the call
8_after.PNG


The last instructions of the function
9_last.PNG
 

Braindrool

Dank Tier Donator
Jul 28, 2020
7
224
0
fastcall with 2 parameters cause i dont see the argument pushed in EDI being used in the first bit, and the stack was fixed by 4 bytes after the call which would normally happen in the fast/stdcall, therefore the push edi is something separate to the function call (or the stack was adjusted because of something even further up)

remove p3

future advice: Breakpoint function before your hook -> note ESP value, breakpoint after hook -> note ESP value

subtract the 2 values to see how far your stack is off by
Removing the 3rd parameter gave a bit of progress, now the std::cout is working. However, it crashes shortly after without any message. It looks like it's caused by something in the hook setting EDI to 0 and later it gets accessed. It's something for me to look into though

Just to make sure I understand your post: the 'push edi' just before the call is normally a parameter with fastcall but the function doesn't pop it off at the end, so that's why it corrects the stack after with 'ADD esp, 0x4' and you know it's actually separate / 2 params? If so, any idea why the compiler would have pushed it in the first place? Doesn't seem like it'd be necessary since it does a push/pop edi inside the function, right?
 

Rake

Cesspool Admin
Administrator
Jan 21, 2014
12,349
78,998
2,412
future advice: Breakpoint function before your hook -> note ESP value, breakpoint after hook -> note ESP value

subtract the 2 values to see how far your stack is off by
#protip - just did this yesterday and instantly solved my problem

fast call:
left to right, first 2 params in EDX and ECX, all others pushed on stack

You can see the first 2 vars being placed in EDX and ECX, so that push edi, could possibly be the third argument (but maybe not)

__fastcall
 

Rake

Cesspool Admin
Administrator
Jan 21, 2014
12,349
78,998
2,412
Your binary doesn't match mine, I'm using the Origin version of this game, which version are you using? Post the exe
 

mambda

headass
Escobar Tier VIP
Trump Tier Donator
Jun 25, 2014
2,304
37,938
270
alternatively this is a __thiscall and EDX isnt actually being used, as in your function its immediately overwritten and only stored in EDI

then it could be a __thiscall with 1 parameter (push edi)
 
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