如何不修改C++类的代码,访问C++类中private成员?
C++ 的模板显式实例化有一个合法的“漏洞”:在显式实例化时,编译器不进行访问权限检查。你可以利用这个特性,在不触碰开源库任何一行代码的前提下,合法地窃取私有变量的指针。
核心原理代码示例:
访问私有成员变量
#include <iostream>
class OpenSourceClass {
private:
int hidden_data = 42;
};
// 1. 全局特化标签(此处 typename 必须保留)
template<typename Tag, typename Tag::type M>
struct Rob {
friend typename Tag::type get(Tag) { return M; }
};
// 2. 使用 modern C++ 的 using 替代 typedef
struct Class_hidden_data {
using type = int OpenSourceClass::*; // 换成了 using,可读性更好
friend type get(Class_hidden_data);
};
// 3. 显式实例化
template struct Rob<Class_hidden_data, &OpenSourceClass::hidden_data>;
int main() {
OpenSourceClass obj;
// 4. 调用
auto mp = get(Class_hidden_data());
obj.*mp = 999;
std::cout << obj.*mp << std::endl; // 输出 999
}
访问私有成员函数
#include <iostream>
// 假设这是开源库的类
class OpenSourceClass {
private:
int add(int a, int b) {
return a + b;
}
};
// 1. 全局特化标签(保持不变)
template<typename Tag, typename Tag::type M>
struct Rob {
friend typename Tag::type get(Tag) { return M; }
};
// 2. 标签结构体:注意 type 变成了“函数指针”类型
struct Class_private_func {
// 语法解析:定义一个指向 OpenSourceClass 中,接收两个 int 并返回 int 的成员函数的指针
using type = int (OpenSourceClass::*)(int, int);
friend type get(Class_private_func);
};
// 3. 显式实例化:传入私有函数的地址 &OpenSourceClass::add
template struct Rob<Class_private_func, &OpenSourceClass::add>;
int main() {
OpenSourceClass obj;
// 4. 获取私有成员函数的指针
auto func_ptr = get(Class_private_func());
// 5. 语法:通过 (obj.*func_ptr)(参数) 来调用该私有函数
int result = (obj.*func_ptr)(10, 20);
std::cout << "Result: " << result << std::endl; // 输出 30
}
auto mp = get(Class_hidden_data()); 这行代码,为什么get可以直接调用
这行代码之所以能直接调用 get,是因为触发了 C++ 的 ADL(Argument-Dependent Lookup,参数依赖查找) 机制,俗称魔术查找。
以下是导致它可以直接调用的底层逻辑:
1. 什么是 ADL(参数依赖查找)?
在 C++ 中,当你调用一个函数且没有指定命名空间(比如没有写 std:: 或 Namespace::)时,编译器不仅会在当前作用域寻找该函数,还会自动进入该函数参数的类型所属的命名空间或类内部去寻找。
在代码中:
get(Class_hidden_data())的参数是Class_hidden_data()。- 这个参数的类型是结构体
Class_hidden_data。 - 因此,编译器会自动去
Class_hidden_data的内部查找名为get的函数。
2. 为什么在类内部用 friend 声明的函数能被找到?
我们在 Class_hidden_data 内部写了这行代码:
friend type get(Class_hidden_data);
在 C++ 标准中,在类内部直接定义的 friend 函数,其本质是一个非成员函数(全局函数),但它的作用域被隐藏了。普通的全局调用找不到它,它唯一的曝光方式就是通过 ADL。
当编译器因为 ADL 机制进入 Class_hidden_data 内部寻找 get 时,刚好匹配到了这个友元函数。
3. 最关键的隐蔽点:这个 get 函数是在哪里被真正“实现”的?
你可能会奇怪,Class_hidden_data 内部只是声明了 friend,并没有写函数体,那它的实体是谁实现的?
答案在模板显式实例化的那一行:
template struct Rob<Class_hidden_data, &OpenSourceClass::hidden_data>;
当这行代码执行时,编译器实例化了 Rob 结构体。而 Rob 内部刚好也定义了一个同名的友元函数:
template<typename Tag, typename Tag::type M>
struct Rob {
// 实例化时,这一行在全局动态生成了 get 函数的实体!
friend typename Tag::type get(Tag) { return M; }
};
因为 Tag 被替换成了 Class_hidden_data,所以这里生成的函数签名,完美对应了 Class_hidden_data 内部声明那个 get。
get是Rob中的成员函数,为什么不需要一个Rob的对象就能调用
这是一个非常经典的误解:在 C++ 中,写在类(struct/class)内部的 friend 函数,并不是这个类的成员函数!
它本质上是一个全局函数(非成员函数),只是恰好被“寄养”在类的内部定义而已。
以下是导致它不需要对象就能调用的底层原因:
1. 友元函数不是成员函数
在 C++ 标准中,friend 关键字修饰的函数具有以下特性:
- 它没有隐式的
this指针。 - 它不属于该类的名字空间作用域。
- 你不能通过
object.get()或Rob::get()来调用它。
它唯一的身份是一个全局函数。既然是全局函数,自然不需要实例化 Rob 对象就能调用。
2. 为什么要把全局函数写在 Rob 内部?
这种在类内部直接定义全局友元函数的写法,在 C++ 中被称为 “In-class friend definition”(类内友元定义)。
它的核心目的是为了偷渡。
- 如果你直接在全局定义一个普通的模板函数,编译器在编译到那一行时,会立刻进行严格的
private访问权限检查,代码会直接报错报错。 - 但是,如果你把函数写成
Rob的friend,并放在Rob内部,这个函数只有在Rob被显式实例化时才会被编译器生成。而正如前面所说,C++ 标准规定:显式实例化时不做权限检查。
3. 编译器视角的真相
当你写下显式实例化这行代码时:
template struct Rob<Class_hidden_data, &OpenSourceClass::hidden_data>;
编译器在后台其实为你默默生成了一个普通的全局函数,看起来就像这样:
// 编译器在全局作用域生成的代码(伪代码)
int OpenSourceClass::* get(Class_hidden_data) {
return &OpenSourceClass::hidden_data; // 此时已经绕过了权限检查
}
看,生成的 get 函数前面没有任何 Rob:: 前缀,它就是一个彻头彻尾的全局函数。所以你只需要直接输入 get(...) 就能调用它。
🛠️ 通用黑客宏定义
你只需要把这一段代码放入一个公共头文件(如 HackPrivate.hpp)中:
#pragma once
// 1. 核心模板(保持通用)
template<typename Tag, typename Tag::type M>
struct Rob {
friend typename Tag::type get(Tag) { return M; }
};
// 2. 访问【私有变量】的通用宏
#define DEFINE_ACCESS_VARIABLE(ClassName, VarType, VarName) \
struct Tag_##ClassName##_##VarName { \
using type = VarType ClassName::*; \
friend type get(Tag_##ClassName##_##VarName); \
}; \
template struct Rob<Tag_##ClassName##_##VarName, &ClassName::VarName>;
// 3. 访问【私有函数】的通用宏(支持任意返回值和参数)
#define DEFINE_ACCESS_FUNCTION(ClassName, ReturnType, FuncName, ...) \
struct Tag_##ClassName##_##FuncName { \
using type = ReturnType (ClassName::*)(__VA_ARGS__); \
friend type get(Tag_##ClassName##_##FuncName); \
}; \
template struct Rob<Tag_##ClassName##_##FuncName, &ClassName::FuncName>;
🚀 如何使用它?(极简示例)
#include <iostream>
// 假设这是开源库的类,你无法修改
class OpenSourceClass {
private:
int hidden_data = 100;
void print_msg(int a, double b) {
std::cout << "Private Func Called: " << a << ", " << b << std::endl;
}
};
// ==================== 自动化注入 ====================
// 一行搞定变量注册:类名, 变量类型, 变量名
DEFINE_ACCESS_VARIABLE(OpenSourceClass, int, hidden_data)
// 一行搞定函数注册:类名, 返回值类型, 函数名, 参数列表...
DEFINE_ACCESS_FUNCTION(OpenSourceClass, void, print_msg, int, double)
// ====================================================
int main() {
OpenSourceClass obj;
// 1. 极简访问私有变量
auto var_ptr = get(Tag_OpenSourceClass_hidden_data());
obj.*var_ptr = 555; // 直接修改
std::cout << "Modified Var: " << obj.*var_ptr << std::endl;
// 2. 极简调用私有函数
auto func_ptr = get(Tag_OpenSourceClass_print_msg());
(obj.*func_ptr)(42, 3.14); // 直接调用
}