Tutorial Hook Detection - Return Address Verification (RAV)

Hexui Undetected CSGO Cheats Sinkicheat PUBG Cheat

timb3r

Semi-Retired
Dank Tier VIP
Jul 15, 2018
767
22,668
47
Introduction:

Usually I tie my articles into a game however I was unable to find something that actually implemented this. It's possible an AC like Battleye or EAC might implement a form of it however reversing that stuff is for another time. So I ended up writing my own code that works in a manner you'd expect it too.

So what is Return Address Verification?

If you haven't read my MASM tutorial I'd give that a quick look because we're going to be talking about the stack, stack frames and stack pointers. Essentially what we want to do is examine the return address of a function and determine where in memory that address is located. The reason you'd want to do this is to detect unknown code execution for example how many times have you seen something like this in a hack DLL?:


Read my damn tutorials.

C++:
while(!GetAsyncKeyState(VK_DELETE))
    Sleep(80);
FreeLibraryAndExitThread();
Well knowing that there's generally a small pool of API that are commonly called due to *cough* pasting *cough* we can apply hooks on all those functions and redirect them to our return address verifier. In this example we're going to focus on GetAsyncKeyState but realistically it can be applied to anything.

Setup:

First we need to get our hook installed into GetAsyncKeyState since I'm lazy I just overwrote some of the function:

before-hook.PNG


And this is what it looks like after:

2.PNG


So since we've trashed the function now we need to add the function preamble (the bytes we overwrote) to our trampoline. But before we do that we need to implement our RAV code:

3.PNG


So this code pops the return address off the stack (remember when you CALL, you automatically PUSH the return address onto the stack). Then it runs two checks against the value of RAX. If it's less than the minimum it jumps to fail, if its greater than the maximum it jumps to fail.

Then minimum and maximum addresses are determined by this function:

C++:
     MODULEINFO mi = { 0 };
    GetModuleInformation(GetCurrentProcess(), GetModuleHandle(NULL), &mi, sizeof(MODULEINFO));

    minSafeAddress = (uintptr_t)mi.lpBaseOfDll;
    maxSafeAddress = (uintptr_t)mi.lpBaseOfDll + mi.SizeOfImage;
So essentially we're saying if the return address is outside our module then crash the program. As this will mean that another piece of code is calling it from a DLL or allocated page and we know our code doesn't do that.

Finally here's the trampoline back to the original function:

4.PNG


It's pretty hastily coded but you can dump it into a project and try it out. Try injecting a DLL and calling GetAsyncKeyState it should crash the program.

RAV:
#include <stdio.h>
#include <Windows.h>
#include <inttypes.h>
#include <intrin.h>
#include <Psapi.h>

typedef SHORT(WINAPI* GetAsyncKeyState_t)(int);
GetAsyncKeyState_t fnGetAsyncKeyState = NULL, fnNtGetAsyncKeyState = NULL;

extern "C" void RetAddrCheck();

extern "C"
{
    uintptr_t jmpBackAddress = 0;
    uintptr_t minSafeAddress = 0;
    uintptr_t maxSafeAddress = 0;
}

int main(void)
{
    fnGetAsyncKeyState = (GetAsyncKeyState_t)GetProcAddress(LoadLibrary(L"user32.dll"), "GetAsyncKeyState");

    MODULEINFO mi = { 0 };
    GetModuleInformation(GetCurrentProcess(), GetModuleHandle(NULL), &mi, sizeof(MODULEINFO));

    minSafeAddress = (uintptr_t)mi.lpBaseOfDll;
    maxSafeAddress = (uintptr_t)mi.lpBaseOfDll + mi.SizeOfImage;
    printf("Accepted memory range: [ %Ix -> %Ix]\n", minSafeAddress, maxSafeAddress);

    LPVOID pNewFunc = VirtualAlloc(NULL, 1024, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    if (!pNewFunc)
        return -2;

    // Copy the first 19 bytes of the function to a buffer
    const int bytesSize = 19;
    uint8_t savedBytes[bytesSize] = { 0 };
    memcpy(savedBytes, fnGetAsyncKeyState, bytesSize);

    // This is our function postamble to jump back to the original function
    // MOV RAX, FFFFFFFFFFFFFFFFFFh
    // PUSH RAX
    // RET
    uint8_t retInstruction[] = { 0x48, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x50, 0xC3 };
    uintptr_t* retAddr = (uintptr_t*)(retInstruction + 2);
    *retAddr = ((uintptr_t)fnGetAsyncKeyState + 12);

    memcpy(pNewFunc, savedBytes, bytesSize);
    uintptr_t newOffset = (uintptr_t)pNewFunc + bytesSize;
    memcpy((void*)newOffset, retInstruction, 12);

    // Install hook
    // MOV RAX, FFFFFFFFFFFFFFFFFFh
    // JMP RAX
    uint8_t patch[] = { 0x48, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 };
    retAddr = (uintptr_t*)(patch + 2);
    *retAddr = (uintptr_t)RetAddrCheck;

    DWORD oldProt = 0;
    if (!VirtualProtect(fnGetAsyncKeyState, bytesSize, PAGE_EXECUTE_READWRITE, &oldProt)) {
        printf("[-] VP Failed: %d\n", GetLastError());
        return -1;
    }

    memcpy(fnGetAsyncKeyState, patch, bytesSize);

    if (!VirtualProtect(fnGetAsyncKeyState, bytesSize, oldProt, &oldProt)) {
        printf("[-] VP Failed: %d\n", GetLastError());
        return -1;
    }

    // Address to jump back to
    jmpBackAddress = (uintptr_t)pNewFunc;

    // Load our "hack" to simulate injection
    uintptr_t hookBase = (uintptr_t)LoadLibrary(L"GAKS_Hook.dll");

    // Call the protected function
    while (!GetAsyncKeyState(VK_ESCAPE))
        Sleep(80);

    // Cleanup & exit
    VirtualFree(pNewFunc, 0, MEM_RELEASE);
    printf("Done.\n");
    return 0;
}
Code:
PUBLIC RetAddrCheck

EXTERN jmpBackAddress:QWORD
EXTERN minSafeAddress:QWORD
EXTERN maxSafeAddress:QWORD

.code

RetAddrCheck PROC
    pop rax
    cmp rax, minSafeAddress
    jl fail
    cmp rax, maxSafeAddress
    ja fail
    push rax
fail: ; Failing to push rax will corrupt the stack and cause the program to crash randomly
    jmp jmpBackAddress

RetAddrCheck ENDP

END
Well that's all the boring defense out of the way let's talk about attacking this code.

 
Last edited:

timb3r

Semi-Retired
Dank Tier VIP
Jul 15, 2018
767
22,668
47
Things to consider:

Now chances are if someone went to the effort of hooking, trampolining and verifying they're going to notice if you touch their hook code. So simply overwriting it or changing it in someway is not the solution here. In-fact that is very rarely the solution anywhere especially if you're dealing with anti cheats. So what we need to do is borrow a technique from our sneaky infosec friends and abuse something called ROP chains.



What's a ROP?

ROP stands for Return Oriented Programming. You can get a pretty good base understanding of it here. But a simpler explanation may help you to understand it:

We can't write to the program's memory, we want to call GetAsyncKeyState but if we call it directly from our memory address we will be detected. We need to somehow call that function from inside the address space of the main module. So what we can do instead is utilise the program's own code to perform our nefarious deeds.


Pictured: Nefarious deeds and anti-cheat.

Wtf how?

First we need to break down our code to determine what gadgets we will require. So what are we doing? Calling a simple WINAPI that takes one parameter? So under x64 we utilise RCX for the first parameter and RAX will be our return value. RAX is typically volatile under x64 as it's used as the return code in most functions. So the disassembly of our code will look something like this:

Code:
loop:
    mov rcx, VK_DELETE
    call __imp_GetAsyncKeyState
    cmp rax, 1
    jne loop
Obviously in its current state we cannot utilize it however we can search for Gadgets in the target program and see what we have to work with.



What's a gadget?

A 'gadget' is just an assembly instruction followed by a RET instruction. There's many ways to search for gadgets but a really simple way is a basic pattern scan in x64dbg for "?? C3" and just take a poke around.

Here's an example:

push-rax.PNG

We can use this gadget to control the value of RAX and push it into the stack.

Then we can use this gadget to modify the RBP (the bottom of the stack) to the address we just pushed into the stack:
pop-rbp.PNG


Well that's not helpful Timb3r the RSP is completely fucked now!

add-rsp.PNG

Or is it? Bonus points to anyone who can figure out how to use these first three gadgets to correctly set up the stack. You can control the value of RBP but you can only add 28 to RSP. Hmmm what a conundrum ;).



Finally this gadget lets us control the value of multiple registers:
multi-set.PNG


You get the idea since our program is small and lacking in complexity it will be more difficult to find useful gadgets. However there's nothing stopping us from setting up the registers exactly how we want them to be and then calling our desired gadget.

I created a fake gadget for this tutorial to demonstrate how you can abuse something similar in another program.

This is our disassembly:

C++:
00007FF6AA161130  mov         qword ptr [rsp+8],rcx
00007FF6AA161135  sub         rsp,28h
00007FF6AA161139  mov         rcx,qword ptr [rsp+30]
00007FF6AA16113E  call        rax
00007FF6AA161143  add         rsp,28h
00007FF6AA161147  ret
So we can see this is a class type of function because it's moving the value of RCX immediately into the the stack then pulling it back out again prior to calling the value stored in RAX. From inside our DLL we can directly set the value of RAX to whatever we want then (GetAsyncKeyState) then set RCX to be our desired key.

Now what happens if we call this code from our DLL?

Like magic:

Here's our allocated code this could be a new page or a code cave doesn't matter:

allocated-memory.PNG


We setup the registers and then call our gadget.

gadget.PNG


Now did we make it through the check?

If we examine the stack during the check we can see that:

stack.PNG

Uh, just ignore that class name.

The return address points to valid memory address inside the main module!


Elephant smooth.
 
Last edited:

timb3r

Semi-Retired
Dank Tier VIP
Jul 15, 2018
767
22,668
47
And if we continue debugging we will see our code return to our gadget:

after-call.PNG


And then back to our own code:

done.PNG


Conclusion:

Learning how to tie together ROP chains is a very useful skill and something you definitely want to add to your technique database. Although this was a pretty straight forward example if a game is complex enough there's dozens of available gadgets letting you do whatever you want to do, you're only limited by your own creativity.


Don't look at me like that, you knew this GIF was coming.
 
Last edited:

Raylands

Punching Bag
Meme Tier VIP
Trump Tier Donator
Feb 17, 2016
227
2,338
11
  • Like
Reactions: Kleon742

Rake

Cesspool Admin
Administrator
Jan 21, 2014
11,573
78,998
2,316
This excellent tutorial has been released from the premium section, to the masses. p.s. Jim Carrey is the man
 
  • Like
Reactions: Petko123

Hazey

Newbie
Full Member
Oct 8, 2017
6
1,908
0
Great post. I think Roblox does this for a lot of there lua functions
 
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