Linux 在RING3环境下面几乎难以实现一个高效可靠可穿插在任何代码位置的拦截器(C/C++),RING3可用的办法都非常麻烦同时也很消耗性能,例如通过调试器写进程函数机器代码。
拦截器本质原理是通过在函数头插入 JMP 汇编实现的重定向(inline-HOOK),类似像通过C/C++ Linker(连接器)特性覆盖第三方函数的办法其局限性是很大,它不适用于也很难胜任拦截器的设计。
例如 Windows 上面有IAT(导入表)、EAT(导出表)这类型通过PE覆盖的 Hook 的办法,也有与 Linux 上面 GCC 通过 Linker 特性覆盖 dlsym 函数来实现 hook 导出函数的办法;但这些办法对作用拦截器而言都是歪门邪道、都是小道。
Linux RING0 环境下可以编写相应 “软驱动” 配合 RING3 环境下的应用由驱动级对RING3层程序代码内存修改,是可以办到的,但是话说回来;为了这么一个工具而让服务器运行这么一个驱动,本身已经没有什么太大的意义了。
当然这里不得不提到,让我感动到流泪的 dotNet-Core(Linux、MacOS、Windows) ,其 Core-CLR / JIT 动态编译的代码内存都至少具有 R/W/E 权限的(重要是W权限)所以我们可以在 dotNet-Core 程序中对其 “.NET 托管函数” 进行 HOOK 重定向的,而且一次汇编代码可以跨任何一个可被运行的 X86/X64 平台【】,这让实现强大可靠的 “拦截器” 成为可被想象也可以被具象的。
值得一提 “Interceptor” 的实现还是有缺陷与兼容性要求的,即必须设置 “函数调用协议” = “__cdcel” 通常默认情况下 C/C++ 编译的代码都是 “__cdcel” 调用协议的(除非工程内修改或者函数显示指定调用协议,例如:__fastcall、__stdcall)
sample
class Value
{
public:
Value() : n(100) {}
int SetValue(int x)
{
printf("SetValue=%d\n", x);
return 0;
}
private:
int n;
};
int __fastcall ValueSetValue(Value* value, int x)
{
printf("ValueSetValue=%d\n", x);
return 1;
}
int main()
{
Interceptor<int(int)> interceptor(&Value::SetValue, &ValueSetValue);
Value v;
int rc = v.SetValue(1);
printf("%d\n", rc);
interceptor.Invoke(&v, 1234);
return getchar();
}
impl:
#ifdef WIN32
#include <Windows.h>
#endif
template<typename E> class Interceptor;
template<typename R, typename... A>
class Interceptor<R(A...)>
{
friend class Pointer;
public:
typedef R(*Function)(A...);
public:
template<typename T>
inline Interceptor(R(T::*source)(A...), R(__fastcall *destination)(T*, A...))
: _source(NULL)
, _destination(NULL)
, _intercepting(false)
{
union
{
R(T::*x)(A...);
Function y;
} us;
us.x = source;
_source = us.y;
_destination = (Function)destination;
Install(us.y, _destination);
}
inline Interceptor(const Function& source, const Function& destination)
: _source(source)
, _destination(destination)
, _intercepting(false)
{
Install(source, destination);
}
virtual ~Interceptor()
{
Suspend();
}
private:
inline static void* AddReadWriteExecPrivileges(const void* p, unsigned int sz)
{
#ifdef WIN32
DWORD flOldProtect;
VirtualProtect((void*)p, sz, PAGE_EXECUTE_READWRITE, &flOldProtect);
VirtualProtect((void*)p, sz, PAGE_EXECUTE_READWRITE, &flOldProtect);
#endif
return (void*)p;
}
inline void Install(const Function& source, const Function& destination)
{
AddReadWriteExecPrivileges(source, JMP_SIZE);
AddReadWriteExecPrivileges(destination, JMP_SIZE);
memcpy(_rawsl, _source, JMP_SIZE);
_newsl[0] = '\xE9';
int* rva = (int*)&_newsl[1];
*rva = (int)((long long)destination - ((long long)source + JMP_SIZE));
Resume();
}
public:
inline void Suspend()
{
std::unique_lock<std::recursive_mutex> scope(_syncobj);
if (_intercepting) {
_intercepting = false;
memcpy(_source, _rawsl, JMP_SIZE);
}
}
inline void Resume()
{
std::unique_lock<std::recursive_mutex> scope(_syncobj);
if (!_intercepting) {
_intercepting = true;
memcpy(_source, _newsl, JMP_SIZE);
}
}
inline bool IsIntercepting() { return _intercepting; }
template<typename T>
inline R Invoke(T* obj, A&&... s)
{
std::unique_lock<std::recursive_mutex> scope(_syncobj);
Suspend();
R r;
if (obj) {
auto f = (R(__fastcall *)(T*, int, A...))_source;
r = f(obj, 0, s...);
}
else {
r = _source(s...);
}
Resume();
return r;
}
private:
enum
{
JMP_SIZE = 5
};
char _rawsl[JMP_SIZE];
char _newsl[JMP_SIZE];
bool _intercepting;
Function _source;
Function _destination;
std::recursive_mutex _syncobj;
};