+- +-

+-User

Welcome, Guest.
Please login or register.
 
 
 
Forgot your password?

+-Stats

Members
Total Members: 129
Latest: dilpreetkaur
New This Month: 1
New This Week: 1
New Today: 0
Stats
Total Posts: 319
Total Topics: 160
Most Online Today: 2
Most Online Ever: 68
(October 18, 2019, 12:38:07 am)
Users Online
Members: 0
Guests: 1
Total: 1

Author Topic: Linux functions detouring.  (Read 477 times)

XutaxKamay

  • Newbie
  • *
  • Posts: 12
    • View Profile
Linux functions detouring.
« on: May 24, 2018, 03:01:59 am »
Here a simple linux detouring class. It should work for x64/x86 architecture.

Basically, what it's doing is just replacing the function/address you want to detour with a jmp instruction. If you call the original function, it will reset the original bytes and restore the detour.

Most of detours I saw are using 0xE9 for a jmp (I don't even know why), but it can't use a relative address based on 64 bits, because there is a limitation of 2GB (32bits) of address space with this jmp.

You need to set the value of RAX/EAX of your new function that will be detoured, and then write a jmp with EAX/RAX depending on 32/64 bits accordignly.

For converting into windows, it should be pretty easy too aswell.

This might be a bad example, as some functions on a debug build could make like 5 bytes wich is actually not enough for some detours because the program could call an instruction in another thread overwritten by the jmp routine and result in a crash.

Code: [Select]
// 64 bits
/*
0:  48 b8 45 55 46 84 45    movabs rax,0x454584465545
7:  45 00 00
a:  ff e0                   jmp    rax
*/

// 32 bits
/*
0:  b8 01 00 00 00          mov    eax,0x1
5:  ff e0                   jmp    eax
*/

class CDetour
{
public:

    CDetour( Pointer pFunction , Pointer pNewFunction )
        : m_pFunction( nullptr ) , m_pNewFunction( nullptr ) , m_SavedBytes{ 0 } , m_PageSize( 0 ) , m_PageStart( 0 )
    #ifdef MX64
        , m_LongJmp{ 0x48 , 0xB8 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0xFF , 0xE0 }
    #else
        , m_LongJmp{ 0xB8 , 0x00 , 0x00 , 0x00 , 0x00 , 0xFF , 0xE0 }
    #endif
    {
        m_pFunction = pFunction;
        m_pNewFunction = pNewFunction;
        m_SavedBytes.resize( m_LongJmp.size() );

        m_PageSize = ( unsigned long ) sysconf( _SC_PAGE_SIZE );

        m_PageStart = reinterpret_cast< uintptr_t >( m_pFunction ) & -m_PageSize;

        memcpy( m_SavedBytes.data() , m_pFunction , m_SavedBytes.size() );
    #ifdef MX64
        memcpy( reinterpret_cast< Pointer >( reinterpret_cast< uintptr_t >( m_LongJmp.data() ) + 2 ) , &m_pNewFunction , sizeof( Pointer ) );
    #else
        memcpy( reinterpret_cast< Pointer >( reinterpret_cast< uintptr_t >( m_LongJmp.data() ) + 1 ) , &m_pNewFunction , sizeof( Pointer ) );
    #endif
    }

    FORCEINLINE auto IsValid() -> bool
    {
        return IsPointer( m_pFunction ) && IsPointer( m_pNewFunction );
    }

    auto Detour() -> bool
    {
        if ( !IsValid() )
            return false;

        if ( mprotect( reinterpret_cast< Pointer >( m_PageStart ) , m_PageSize , PROT_READ | PROT_WRITE | PROT_EXEC ) != -1 )
        {
            memcpy( m_pFunction , m_LongJmp.data() , m_LongJmp.size() );

            // We assume that it was read + exec.
            return mprotect( reinterpret_cast< Pointer >( m_PageStart ) , m_PageSize , PROT_READ | PROT_EXEC ) != -1;
        }

        return false;
    }

    auto Reset() -> bool
    {
        if ( !IsValid() )
            return false;

        if ( mprotect( reinterpret_cast< Pointer >( m_PageStart ) , m_PageSize , PROT_READ | PROT_WRITE | PROT_EXEC ) != -1 )
        {
            memcpy( m_pFunction , m_SavedBytes.data() , m_SavedBytes.size() );

            // We assume that it was read + exec.
            return mprotect( reinterpret_cast< Pointer >( m_PageStart ) , m_PageSize , PROT_READ | PROT_EXEC ) != -1;
        }

        return false;
    }

    FORCEINLINE auto GetFunction() -> Pointer
    {
        return m_pFunction;
    }

    FORCEINLINE auto GetNewFunction() -> Pointer
    {
        return m_pNewFunction;
    }

    template< typename RetType = void , typename ... vArgs >
    constexpr FORCEINLINE auto _CallFunction( vArgs ... pArgs ) -> RetType
    {
        return reinterpret_cast< RetType(*)( vArgs ... ) >( m_pFunction )( pArgs ... );
    }

    template< typename RetType = void , typename ... vArgs >
    FORCEINLINE auto CallFunction( vArgs ... pArgs ) -> RetType
    {
        if ( Reset() )
        {
            _CallFunction<RetType , vArgs...>( pArgs... );
            Detour();
        }
    }

    ~CDetour()
    {
        Reset();
        m_LongJmp.clear();
        m_SavedBytes.clear();
        m_pFunction = nullptr;
        m_pNewFunction = nullptr;
    }

private:
    Pointer m_pFunction , m_pNewFunction;
    std::vector<BYTE> m_SavedBytes;
    unsigned long m_PageSize;
    uintptr_t m_PageStart;
    std::vector<BYTE> m_LongJmp;

};

typedef CDetour* pCDetour;


Simple test under linux:

Code: [Select]

pCDetour DetourTest = nullptr;

void Test()
{
    printf( "Hello!\n" );
}

void new_Test()
{
    printf( "OoooOps!\n" );
    DetourTest->CallFunction();
}

int main()
{
    Test();

    DetourTest = new CDetour( ( Pointer ) Test , ( Pointer ) new_Test );

    DetourTest->Detour();

    Test();

    return 0;
}



Code: [Select]

Hello!
OoooOps!
Hello!

« Last Edit: May 25, 2018, 05:04:14 am by XutaxKamay »

Share on Facebook Share on Twitter


xchg

  • Administrator
  • Newbie
  • *****
  • Posts: 35
    • View Profile
Re: Linux functions detouring.
« Reply #1 on: May 29, 2018, 10:49:02 am »
cpp :)

Excellent work my man.

XutaxKamay

  • Newbie
  • *
  • Posts: 12
    • View Profile
Re: Linux functions detouring.
« Reply #2 on: July 03, 2018, 01:36:08 am »
Thank you!
Code: [Select]
class CHookCall
{
public:

    CHookCall( Pointer pFunction , Pointer pNewFunction )
        : m_FoundFunctionsRVACall( 0 ) , m_pFunction( nullptr ) , m_pNewFunction( nullptr ) , m_pOldRelativeRVA( nullptr )
    {
        m_pFunction = pFunction;
        m_pNewFunction = pNewFunction;
    }

    ~CHookCall()
    {
        UnHook();
        m_FoundFunctionsRVACall.clear();
        m_pFunction = nullptr;
        m_pNewFunction = nullptr;
        m_pOldRelativeRVA = nullptr;
    }

    auto Hook() -> bool
    {
        auto pDosHeader = ( PIMAGE_DOS_HEADER ) FindModule();

        if ( !IsPointer( pDosHeader ) )
            return false;

        auto pNtHeaders = reinterpret_cast< pNTHeaders >( ( reinterpret_cast< uintptr_t >( pDosHeader ) + pDosHeader->e_lfanew ) );

        auto FunctionRVA = reinterpret_cast< uintptr_t >( m_pFunction ) - reinterpret_cast< uintptr_t >( pDosHeader );

        auto ImageSize = pNtHeaders->OptionalHeader.SizeOfImage;

        auto TempModule = Alloc<uintptr_t>( ImageSize );

        // We copy the module so we don't need to change all the protections on it.

        memcpy( ( Pointer ) TempModule , pDosHeader , ImageSize );

        for ( uintptr_t Slot = 0; Slot != ImageSize; Slot++ )
        {
            auto Address = TempModule + Slot;

            BYTE ByteRead = Read<BYTE>( Address );

            if ( ByteRead == 0xE8
                 || ByteRead == 0xE9 )
            {
                // Let's check if it's what we wanted.

                // call 0x...
                // call RelativeAddress ( FunctionToCall - ( FunctionFromBeingCalled + sizeof( Pointer ) ) )
                // or
                // jmp 0x...
                // jmp RelativeAddress ( FunctionToCall - ( FunctionFromBeingCalled + sizeof( Pointer ) ) )

                auto RelativeAddress = Read<uintptr_t>( Address + 1 );

                // + 1 byte for call opcode, and add a size of a pointer
                // because the CPU call our function once it has finished to read the address.

                auto CalledFrom = Address + 1 + sizeof( Pointer );

                auto CalledFromRVA = CalledFrom - TempModule;

                auto CalledFunctionRVA = RelativeAddress + CalledFromRVA;

                // Is That our function ?

                if ( CalledFunctionRVA == FunctionRVA )
                {
                    // Yup.
                    m_FoundFunctionsRVACall.push_back( CalledFromRVA - sizeof( Pointer ) );
                }
            }
        }

        for ( auto && FunctionRVACall : m_FoundFunctionsRVACall )
        {
            auto CallerAddress = reinterpret_cast< uintptr_t >( pDosHeader ) + FunctionRVACall;

            // We want to access it.

            DWORD dwOldFlag;
            if ( VirtualProtect( reinterpret_cast< Pointer >( CallerAddress ) , sizeof( CallerAddress ) , PAGE_EXECUTE_READWRITE , &dwOldFlag ) )
            {
                uintptr_t *pWriteAddress = Write<uintptr_t>( CallerAddress );

                m_pOldRelativeRVA = reinterpret_cast< Pointer >( *pWriteAddress );

                // Add a size of a pointer because the CPU call our function once it has finished to read the address.
                *pWriteAddress = reinterpret_cast< uintptr_t >( m_pNewFunction ) - ( CallerAddress + sizeof( Pointer ) );

                if ( !VirtualProtect( reinterpret_cast< Pointer >( CallerAddress ) , sizeof( CallerAddress ) , dwOldFlag , &dwOldFlag ) )
                {
                    return false;
                }
            }
        }

        FreeS( TempModule );

        PrintConsole( FOREGROUND_GREEN , TEXT( "Found %i references for 0x%p\n" ) , m_FoundFunctionsRVACall.size() , m_pFunction );

        return m_FoundFunctionsRVACall.size() != 0;
    }

    auto FindModule() -> Pointer
    {
        Pointer pProcess = GetCurrentProcess();

        auto SnapShot = CreateToolhelp32Snapshot( TH32CS_SNAPMODULE , GetProcessId( pProcess ) );

        MODULEENTRY32 ModuleEntry;
        auto FirstModule = Module32First( SnapShot , &ModuleEntry );

        if ( IsPointer( FirstModule ) )
        {
            do
            {
                auto ModuleBase = reinterpret_cast< uintptr_t >( ModuleEntry.hModule );
                auto Function = reinterpret_cast< uintptr_t >( m_pFunction );

                if ( ( Function >= ModuleBase )
                     && ( Function < ( ModuleBase + ModuleEntry.modBaseSize ) ) )
                {
                    CloseHandle( SnapShot );

                    return ModuleEntry.hModule;
                }
            } while ( Module32Next( SnapShot , &ModuleEntry ) );
        }

        CloseHandle( SnapShot );

        return nullptr;
    }

    auto UnHook() -> bool
    {
        auto pDosHeader = ( PIMAGE_DOS_HEADER ) FindModule();

        if ( !IsPointer( pDosHeader ) )
            return false;

        for ( auto && FunctionRVACall : m_FoundFunctionsRVACall )
        {
            auto CallerAddress = reinterpret_cast< uintptr_t >( pDosHeader ) + FunctionRVACall;

            DWORD dwOldFlag;
            if ( VirtualProtect( reinterpret_cast< Pointer >( CallerAddress ) , sizeof( CallerAddress ) , PAGE_EXECUTE_READWRITE , &dwOldFlag ) )
            {
                uintptr_t *pWriteAddress = Write<uintptr_t>( CallerAddress );

                // Push back the old function.
                *pWriteAddress = reinterpret_cast< uintptr_t >( m_pOldRelativeRVA );

                if ( !VirtualProtect( reinterpret_cast< Pointer >( CallerAddress ) , sizeof( CallerAddress ) , dwOldFlag , &dwOldFlag ) )
                {
                    return false;
                }
            }
        }

        m_FoundFunctionsRVACall.clear();

        return true;
    }

    FORCEINLINE auto GetFunction() -> Pointer
    {
        return m_pFunction;
    }

    FORCEINLINE auto GetNewFunction() -> Pointer
    {
        return m_pNewFunction;
    }

    template< typename RetType = void , typename ... vArgs >
    constexpr FORCEINLINE auto CallFunction( vArgs ... pArgs )->RetType
    {
        return ( ( RetType( __cdecl* )( vArgs ... ) ) m_pFunction )( pArgs ... );
    }

private:
    std::vector<uintptr_t> m_FoundFunctionsRVACall;
    Pointer m_pOldRelativeRVA , m_pNewFunction , m_pFunction;
};
Here another approach for detouring functions. Instead of directly push a jmp into the function, I search for all callers that contains the address of a wanted function in the sections. This way is ghetto and is for windows but it works. In a certain amount, for example if your function is a (pure) virtual function it won't be able detour/hook the function. Mapped modules aswell could call the original function and this won't be able to catch it, but it's interesting if you're searching to do it in more in a static way I guess. I could add more like, we could scan all the process with all mapped virtual memory and see wich one contains our caller.
« Last Edit: July 03, 2018, 01:42:46 am by XutaxKamay »

 

+-Recent Topics

Independent Call Girls in Chandigarh by dilpreetkaur
June 21, 2021, 01:02:52 pm

Hi zwclose7. How to create process by using NT apis? by zwclose7
June 01, 2021, 03:09:52 pm

Poison of the Day by zwclose7
March 16, 2020, 06:45:08 pm

IRC by AzeS
February 17, 2020, 08:18:01 am

Native API tutorial by hMihaiDavid
January 08, 2019, 02:11:02 am

The properties of GP nerve agent by xchg
October 19, 2018, 07:40:57 pm

A new route of synthesis for G-series agents by Basquyatti
October 15, 2018, 06:12:57 am

Synthesis of Methylisobutylcarbinylsarin (GH) by APC process by Basquyatti
October 14, 2018, 07:55:33 am

Synthesis conventional of Sarin by Basquyatti
October 02, 2018, 07:57:32 am

Reaction CX-7 (Experimental) by zwclose7
October 02, 2018, 12:46:47 am