#pragma once #include "../../Include/CallResult.h" #include "../../Asm/IAsmHelper.h" #include "../Process.h" #include // TODO: Find more elegant way to deduce calling convention // than defining each one manually namespace blackbone { template class RemoteFunctionBase { public: using ReturnType = std::conditional_t, int, R>; struct CallArguments { CallArguments( const Args&... args ) : arguments{ AsmVariant( args )... } { } template CallArguments( const std::tuple& args, std::index_sequence ) : arguments{ std::get( args )... } { } CallArguments( const std::initializer_list& args ) : arguments{ args } { // Since initializer_list can't be moved from, dataStruct types must be fixed for (auto& arg : arguments) { if (!arg.buf.empty()) arg.imm_val = reinterpret_cast(arg.buf.data()); } } // Manually set argument to custom value void set( int pos, const AsmVariant& newVal ) { if (arguments.size() > static_cast(pos)) arguments[pos] = newVal; } std::vector arguments; }; public: RemoteFunctionBase( Process& proc, ptr_t ptr ) : _process( proc ) , _ptr( ptr ) { static_assert( (... && !std::is_reference_v), "Please replace reference type with pointer type in function type specification" ); } call_result_t Call( CallArguments& args, ThreadPtr contextThread = nullptr ) { ReturnType result = {}; uint64_t tmpResult = 0; NTSTATUS status = STATUS_SUCCESS; auto a = AsmFactory::GetAssembler( _process.core().isWow64() ); // Ensure RPC environment exists status = _process.remote().CreateRPCEnvironment( Worker_None, contextThread != nullptr ); if (!NT_SUCCESS( status )) return call_result_t( result, status ); // FPU check constexpr bool isFloat = std::is_same_v; constexpr bool isDouble = std::is_same_v || std::is_same_v; // Deduce return type eReturnType retType = rt_int32; if constexpr (isFloat) retType = rt_float; else if constexpr (isDouble) retType = rt_double; else if constexpr (sizeof( ReturnType ) == sizeof( uint64_t )) retType = rt_int64; else if constexpr (!std::is_reference_v && sizeof( ReturnType ) > sizeof( uint64_t )) retType = rt_struct; _process.remote().PrepareCallAssembly( *a, _ptr, args.arguments, Conv, retType ); // Choose execution thread if (!contextThread) { status = _process.remote().ExecInNewThread( (*a)->make(), (*a)->getCodeSize(), tmpResult ); } else if (contextThread == _process.remote().getWorker()) { status = _process.remote().ExecInWorkerThread( (*a)->make(), (*a)->getCodeSize(), tmpResult ); } else { status = _process.remote().ExecInAnyThread( (*a)->make(), (*a)->getCodeSize(), tmpResult, contextThread ); } // Get function return value if (!NT_SUCCESS( status ) || !NT_SUCCESS( status = _process.remote().GetCallResult( result ) )) return call_result_t( result, status ); // Update arguments for (auto& arg : args.arguments) if (arg.type == AsmVariant::dataPtr) _process.memory().Read( arg.new_imm_val, arg.size, reinterpret_cast(arg.imm_val) ); return call_result_t( result, STATUS_SUCCESS ); } call_result_t Call( const Args&... args ) { CallArguments a( args... ); return Call( a, nullptr ); } call_result_t Call( const std::tuple& args, ThreadPtr contextThread = nullptr ) { CallArguments a( args, std::index_sequence_for() ); return Call( a, contextThread ); } call_result_t Call( const std::initializer_list& args, ThreadPtr contextThread = nullptr ) { CallArguments a( args ); return Call( a, contextThread ); } call_result_t operator()( const Args&... args ) { CallArguments a( args... ); return Call( a ); } auto MakeArguments( const Args&... args ) { return CallArguments( args... ); } auto MakeArguments( const std::initializer_list& args ) { return CallArguments( args ); } auto MakeArguments( const std::tuple& args ) { return CallArguments( args, std::index_sequence_for() ); } bool valid() const { return _ptr != 0; } explicit operator bool() const { return valid(); } private: Process& _process; ptr_t _ptr = 0; }; // Remote function pointer template class RemoteFunction; // // Calling convention specialization // template \ class RemoteFunction : public RemoteFunctionBase { public: using RemoteFunctionBase::RemoteFunctionBase; RemoteFunction( Process& proc, R( __cdecl* ptr )(Args...) ) : RemoteFunctionBase( proc, reinterpret_cast(ptr) ) { } }; // Under AMD64 these will be same declarations as __cdecl, so compilation will fail. #ifdef USE32 template class RemoteFunction : public RemoteFunctionBase { public: using RemoteFunctionBase::RemoteFunctionBase; RemoteFunction( Process& proc, R( __stdcall* ptr )(Args...) ) : RemoteFunctionBase( proc, reinterpret_cast(ptr) ) { } }; template class RemoteFunction : public RemoteFunctionBase { public: using RemoteFunctionBase::RemoteFunctionBase; RemoteFunction( Process& proc, R( __thiscall* ptr )(Args...) ) : RemoteFunctionBase( proc, reinterpret_cast(ptr) ) { } }; template class RemoteFunction : public RemoteFunctionBase { public: using RemoteFunctionBase::RemoteFunctionBase; RemoteFunction( Process& proc, R( __fastcall* ptr )(Args...) ) : RemoteFunctionBase( proc, reinterpret_cast(ptr) ) { } }; #endif /// /// Get remote function object /// /// Function address in the remote process /// Function object template RemoteFunction MakeRemoteFunction( Process& process, ptr_t ptr ) { return RemoteFunction( process, ptr ); } /// /// Get remote function object /// /// Function address in the remote process /// Function object template RemoteFunction MakeRemoteFunction( Process& process, T ptr ) { return RemoteFunction( process, ptr ); } /// /// Get remote function object /// /// Remote module name /// Function name or ordinal /// Function object template RemoteFunction MakeRemoteFunction( Process& process, const std::wstring& modName, const char* name_ord ) { auto ptr = process.modules().GetExport( modName, name_ord ); return RemoteFunction( process, ptr ? ptr->procAddress : 0 ); } }