如何不修改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 访问权限检查,代码会直接报错报错。
  • 但是,如果你把函数写成 Robfriend,并放在 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); // 直接调用
}

发表回复