BlackBone
Windows memory hacking library
 All Classes Functions
RemoteFunction.hpp
1 #pragma once
2 
3 #include "../../Asm/AsmHelperBase.h"
4 #include "../Process.h"
5 
6 // TODO: Find more elegant way to deduce calling convention
7 // than defining each one manually
8 
9 namespace blackbone
10 {
11 
15 template< typename Fn >
17 {
18 protected:
19  typedef Fn type; // Function pointer type
20 
21 protected:
22 
23  RemoteFuncBase( Process& proc, eCalligConvention conv )
24  : _callConv( conv )
25  , _process( proc )
26  , _pfn( nullptr ) { }
27 
28  RemoteFuncBase( Process& proc, type ptr, eCalligConvention conv )
29  : _callConv( conv )
30  , _process( proc )
31  , _pfn( ptr ) { }
32 
33 // conditional expression is constant
34 #pragma warning(disable : 4127)
35 
43  template<typename T>
44  NTSTATUS Call( T& result, std::vector<AsmVariant>& args, Thread* contextThread = nullptr )
45  {
46  uint64_t result2 = 0;
47  AsmJitHelper a;
48 
49  // Ensure RPC environment exists
50  if (!NT_SUCCESS( _process.remote().CreateRPCEnvironment( contextThread == _process.remote().getWorker(), contextThread != nullptr ) ))
51  return LastNtStatus();
52 
53  // FPU check
54  bool isFloat = std::is_same<T, float>::value;
55  bool isDouble = std::is_same<T, double>::value || std::is_same<T, long double>::value;
56 
57  // Deduce return type
58  eReturnType retType = rt_int32;
59 
60  if (isFloat)
61  retType = rt_float;
62  else if (isDouble)
63  retType = rt_double;
64  else if (sizeof(T) == sizeof(uint64_t))
65  retType = rt_int64;
66  else if (!std::is_reference<T>::value && sizeof(T) > sizeof(uint64_t))
67  retType = rt_struct;
68 
69  auto pfnNew = brutal_cast<const void*>(_pfn);
70 
71  _process.remote().PrepareCallAssembly( a, pfnNew, args, _callConv, retType );
72 
73  // Choose execution thread
74  if (contextThread == nullptr)
75  _process.remote().ExecInNewThread( a->make(), a->getCodeSize(), result2 );
76  else if (*contextThread == _process.remote()._hWorkThd)
77  _process.remote().ExecInWorkerThread( a->make(), a->getCodeSize(), result2 );
78  else
79  _process.remote().ExecInAnyThread( a->make(), a->getCodeSize(), result2, *contextThread );
80 
81  // Get function return value
82  _process.remote().GetCallResult<T>( result );
83 
84  return STATUS_SUCCESS;
85  }
86 
87 #pragma warning(default : 4127)
88 
89  inline type ptr() const { return _pfn; }
90 
91 private:
92  RemoteFuncBase( const RemoteFuncBase& ) = delete;
93  RemoteFuncBase& operator =(const RemoteFuncBase&) = delete;
94 
95 private:
96  eCalligConvention _callConv; // Calling convention
97  type _pfn; // Function pointer
98  Process& _process; // Underlying process
99 };
100 
101 // Function arguments
102 template<typename... Args>
104 {
105  static const size_t arg_count = sizeof...(Args);
106 
107 public:
108 
109  // Get arguments. Either in variant form, or as declared type
110  inline std::vector<AsmVariant>& getArgsRaw() const { return _args; }
111  inline std::tuple<Args...>& getArgs() const { return _targs; }
112 
113  // Get argument by index
114  // Index is zero-based
115  template<int pos>
116  auto getArg() -> decltype(std::get<pos>( _targs ))
117  {
118  return std::get<pos>( _targs );
119  }
120 
121  // Update any changes to arguments passed by reference or pointer
122  void updateArgs() const
123  {
124  for (auto& arg : _args)
125  if (arg.type == AsmVariant::dataPtr)
126  _process.memory().Read( arg.new_imm_val, arg.size, (void*)arg.imm_val );
127  }
128 
129  // Manually set argument to custom value
130  void setArg( int pos, const AsmVariant& newVal )
131  {
132  if (_args.size() > (size_t)pos)
133  _args[pos] = newVal;
134  }
135 
136 protected:
137  template<typename... TArgs>
138  FuncArguments( Process& proc, TArgs&&... args )
139  : _process( proc )
140  , _targs( static_cast<Args&&>(args)... )
141  , _args( std::vector<AsmVariant>{ static_cast<Args&&>(args)... } ) { }
142 
143 private:
144  FuncArguments( const FuncArguments& ) = delete;
145  FuncArguments& operator =(const FuncArguments&) = delete;
146 
147 private:
148  mutable std::vector<AsmVariant> _args; // Generic arguments
149  mutable std::tuple<Args...> _targs; // Real arguments
150  Process& _process; // Process routines
151 };
152 
153 // Remote function pointer
154 template< typename Fn >
156 
157 #define DECLPFN(CALL_OPT, CALL_DEF, ...) \
158 template<__VA_ARGS__ typename R, typename... Args > \
159 class RemoteFunction< R( CALL_OPT* )(Args...) > : public RemoteFuncBase<R( CALL_OPT* )(Args...)>, public FuncArguments<Args...> \
160 { \
161 public: \
162  typedef typename std::conditional<std::is_same<R, void>::value, int, R>::type ReturnType; \
163  \
164 public: \
165  template<typename... TArgs> \
166  RemoteFunction( Process& proc, typename RemoteFuncBase::type ptr, TArgs&&... args ) \
167  : RemoteFuncBase( proc, ptr, CALL_DEF ) \
168  , FuncArguments( proc, static_cast<Args&&>(args)... ) { } \
169  \
170  inline DWORD Call( ReturnType& result, Thread* contextThread = nullptr ) \
171  { \
172  NTSTATUS status = RemoteFuncBase::Call( result, getArgsRaw(), contextThread ); \
173  FuncArguments::updateArgs( ); \
174  return status; \
175  } \
176 }
177 
178 //
179 // Calling convention specialization
180 //
181 DECLPFN( __cdecl, cc_cdecl );
182 
183 // Under AMD64 these will be same declarations as __cdecl, so compilation will fail.
184 #ifdef _M_IX86
185 DECLPFN( __stdcall, cc_stdcall );
186 DECLPFN( __thiscall, cc_thiscall );
187 DECLPFN( __fastcall, cc_fastcall );
188 #endif
189 
190 // Class member function
191 template< class C, typename R, typename... Args >
192 class RemoteFunction< R( C::* )(Args...) > : public RemoteFuncBase<R( C::* )(const C*, Args...)>, public FuncArguments<const C*, Args...>
193 {
194 public:
195  typedef typename std::conditional<std::is_same<R, void>::value, int, R>::type ReturnType;
196  typedef typename ReturnType( C::*type )(Args...);
197 
198 public:
199  RemoteFunction( Process& proc, type ptr )
200  : RemoteFuncBase( proc, ptr, cc_thiscall )
201  , FuncArguments( proc ) { }
202 
203  RemoteFunction( Process& proc, type ptr, const C* pClass, const Args&... args )
204  : RemoteFuncBase( proc, (typename RemoteFuncBase::type)ptr, cc_thiscall )
205  , FuncArguments( proc, pClass, args... ) { }
206 
207  inline DWORD Call( R& result, Thread* contextThread = nullptr )
208  {
209  NTSTATUS status = RemoteFuncBase::Call( result, getArgsRaw(), contextThread );
210  FuncArguments::updateArgs();
211  return status;
212  }
213 };
214 
215 }
BLACKBONE_API Thread * getWorker()
Get worker thread
Definition: RemoteExec.h:114
BLACKBONE_API NTSTATUS ExecInAnyThread(PVOID pCode, size_t size, uint64_t &callResult, Thread &thread)
Execute code in context of any existing thread
Definition: RemoteExec.cpp:154
BLACKBONE_API NTSTATUS Read(ptr_t dwAddress, size_t dwSize, PVOID pResult, bool handleHoles=false)
Read data
Definition: ProcessMemory.cpp:81
BLACKBONE_API NTSTATUS ExecInWorkerThread(PVOID pCode, size_t size, uint64_t &callResult)
Execute code in context of our worker thread
Definition: RemoteExec.cpp:92
General purpose assembly variable
Definition: AsmVariant.hpp:16
Base class for remote function pointer
Definition: RemoteFunction.hpp:16
BLACKBONE_API NTSTATUS CreateRPCEnvironment(bool bThread=true, bool bEvent=true)
Create environment for future remote procedure calls
Definition: RemoteExec.cpp:270
Definition: Process.h:43
Definition: RemoteFunction.hpp:103
NTSTATUS Call(T &result, std::vector< AsmVariant > &args, Thread *contextThread=nullptr)
Perform remote function call
Definition: RemoteFunction.hpp:44
Definition: RemoteFunction.hpp:155
Thread management
Definition: Thread.h:44
BLACKBONE_API NTSTATUS ExecInNewThread(PVOID pCode, size_t size, uint64_t &callResult)
Create new thread and execute code in it. Wait until execution ends
Definition: RemoteExec.cpp:34
Definition: AsmHelper32.cpp:6