Forum > Coding >

[RELUPD] Caller Mutation Prototype Update

Posts: 224

Threads: 43

Joined: May, 2022

Reputation: -5

Posted

On my last post about this, I released this same caller mutation prototype but missing one important feature:

The ability to call callee by yourself and only yourself.

 

 

How this works:

 

This works by hooking a function to execute our instructions which will mutate that function's caller's instruction which calls our function to 0x90 ( nop / no operation ) which will prevent any calls to the callee, it will then create a copy of the callee with fixed relative calls and returns the address of that copy, allowing you to call the function with the same behavior as the callee.

 

#include <iostream>
#include <Windows.h>
#include <thread>
#include <span>
#include <array>
#include <unordered_map>
#define _DEBUG

std::vector< std::uint8_t > ret_function_bytes( void* address )
{
	auto byte = static_cast< std::uint8_t* >( address );
	std::vector< std::uint8_t > function_bytes{ };

	static const std::unordered_map< std::uint8_t, std::uint8_t > ret_bytes_map
	{
		{
			0xC2,
			0x03
		},
		{
			0xC3,
			0x01
		}
	};

	static const auto alignment_bytes = std::to_array< std::uint8_t >
	( 
		{ 0xCC, 0x90 } 
	);

	while( true )
	{
		function_bytes.push_back( *byte );

		for( const auto& ret_byte : ret_bytes_map )
		{
			const auto& [ opcode, opcode_sz ] = ret_byte;
			if( *byte == opcode )
			{
				for( const auto& alignment_byte : alignment_bytes )
				{
					if( *( byte + opcode_sz ) == alignment_byte )
						return function_bytes;
				}
			}
			
		}
		++byte;
	}
	
}


void* map_mutation_function( void* callee_function_address, std::span< std::uint8_t > overwritten_function_bytes )
{
	const auto callee_function_instrs = ret_function_bytes( callee_function_address );

	const auto mutation_func = VirtualAlloc( nullptr, overwritten_function_bytes.size( ), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE );
	const auto new_callee = VirtualAlloc( nullptr, callee_function_instrs.size( ), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE );
	
	if( !mutation_func )
		throw std::runtime_error( "failed to allocate mutation function" );
	if( !new_callee )
		throw std::runtime_error( "failed to allocate new callee" );

#ifdef _DEBUG
	std::printf( "allocated mutation function:  %p\n", mutation_func );
	std::printf( "allocated new callee: %p\n", new_callee );
#endif
	
	for( auto it = callee_function_instrs.begin( ); it < callee_function_instrs.end( ); ++it )
	{
		if( *it == 0xE9 || *it == 0xE8 )
		{
			// getting the offset of bytes from the beginning of the function 
			// so we can add it later to get the next instruction address
			// of both the original function and our new function.

			const auto dist = std::distance( callee_function_instrs.begin( ), it );
			// old offset used by the old function to make a relative CALL or JMP  
			const auto o_rel_offset = *reinterpret_cast< std::uint32_t* >( it._Ptr + 1 );
			// the address of the instruction after the relative CALL instruction 
			const auto o_next_instr = reinterpret_cast< std::uint32_t >( callee_function_address ) + dist + 5;
			// absolute address of the callee
			const auto callee_abs_address = o_next_instr + o_rel_offset;
			// same as before except it's the next instruction of the new function
			const auto next_instr = reinterpret_cast< std::uint32_t >( new_callee ) + dist + 5;
			// relative address calculation
			const auto rel_addr = callee_abs_address - next_instr;
			// setting the new relative address operand value
			*reinterpret_cast< std::uint32_t* >( it._Ptr + 1 ) = rel_addr;
		}
	}

	std::memcpy( mutation_func, overwritten_function_bytes.data( ), overwritten_function_bytes.size( ) );
	std::memcpy( new_callee, callee_function_instrs.data( ), callee_function_instrs.size( ) );
		
	DWORD vp_old_protection{ 0u };

	VirtualProtect( callee_function_address, 5,  PAGE_EXECUTE_READWRITE, &vp_old_protection );

	const auto relative_address = reinterpret_cast< std::uint32_t >( mutation_func ) - reinterpret_cast< std::uint32_t >( callee_function_address ) - 5;

	*static_cast< std::uint8_t* >( callee_function_address ) = 0xE9;
	*reinterpret_cast< std::uint32_t* >( reinterpret_cast< std::uint32_t >( callee_function_address ) + 1 ) = relative_address;

	VirtualProtect( callee_function_address, 5,  vp_old_protection, &vp_old_protection );

	return new_callee;
}



DWORD vp_old_protection{ 0u };
void* caller_next_instr{ nullptr };

_declspec( naked ) void mutate( )
{
	__asm
	{
		mov eax, [ esp ]
		mov caller_next_instr, eax
		xor eax, eax
	}
	caller_next_instr = reinterpret_cast< void* >( reinterpret_cast< std::uint32_t >( caller_next_instr ) - 5 );

	VirtualProtect( caller_next_instr , 5, PAGE_EXECUTE_READWRITE, &vp_old_protection );
	std::memset( caller_next_instr,  0x90, 5 );
	VirtualProtect( caller_next_instr, 5, vp_old_protection, &vp_old_protection );

	__asm
	{
		pop ebp
		jmp caller_next_instr 
	}
}

void main_thread( HMODULE dll_module )
{
	const auto base = reinterpret_cast< std::uint32_t >( GetModuleHandleA( nullptr ) );
	constexpr auto rel_addr_of_callee = 0x1000;

	const auto callee_address = base + rel_addr_of_callee;

	auto mutate_function_bytes = ret_function_bytes( &mutate );
	const auto new_callee = reinterpret_cast< void( __cdecl * )( const char* ) >( map_mutation_function( reinterpret_cast< void* >( callee_address ), mutate_function_bytes ) );

	new_callee( "exprssn 2 good" );
}



bool __stdcall DllMain( HMODULE dll_module, const std::uint32_t reason_for_call, void* )
{
	if( reason_for_call == DLL_PROCESS_ATTACH )
	{
		std::thread{ main_thread, dll_module }.detach( );
	}
	return true;
}

 

https://github.com/expressiongz/caller-mutation-prototype

  • 0

https://media.discordapp.net/attachments/1044764388546068510/1051935933836050482/Signature_4.png

Users viewing this thread:

( Members: 0, Guests: 1, Total: 1 )