突破C++类的访问控制机制

It is well known that class members in C++ can have three access specifiers: public, protected, and private:
[ISO/IEC 14882:2014] A member of a class can be

  • private: that is, its name can be used only by members and friends of the class in which it is declared.
  • protected: that is, its name can be used only by members and friends of the class in which it is declared, by classes derived from that class, and by their friends (see 11.4).
  • public: that is, its name can be used anywhere without access restriction.

From the standard’s intention, it aims to hide implementation details and underlying data of a class, which is encapsulation. However, we can also bypass access restrictions through some special means.

First, let’s correct a misconception: in C++, class access control restricts the access permissions of members, not their visibility.

[ISO/IEC 14882:2014] It should be noted that it is access to members and base classes that is controlled, not their visibility.

Any member of a class is visible to any code that can see its class implementation; the issue is whether it can be accessed. When you directly access a member’s name (identifier) inside a class, it will check whether you have permission to access that member. If you mark it as private and access it from outside the class definition or a friend, it will prompt a compilation error. For a specific description of visibility and accessibility in access control, please refer to my other article: Visibility and Accessibility in Access Control Mechanisms.
Thus, we can access class members without triggering the access control mechanism by not using member names. One way to access class members without using class member names is through pointers to class members. For detailed content on pointers to class members in C++, please check my previous article: Pointers to Class Members Are Not Pointers.

From an invasive implementation perspective, we can actively provide methods to access class private members through class member functions.
A common way is to return a reference to the private data member or return a pointer to class members. I prefer returning a pointer to members since if a reference is returned, only a specific object’s members can be accessed later, whereas a function pointer can access members of any object of that class.

1
2
3
4
5
6
7
8
9
10
class A{
public:
A(int x=0):private_(x){}
void print(){ std::cout<<private_<<std::endl; }
auto retPrivateVal(){ return &A::private_; }
auto retPrivateFunc(){ return &A::privateFunc; }
private:
void privateFunc(){ std::cout<<"A::privateFunc()"<<std::endl; }
int private_;
};

Since what we get here is the address of the object (data member/function object), it can be accessed externally through an object of the class or a pointer to the class object, as accessing the pointer to class members is actually calculating the offset of that member through this, so it won’t trigger any name lookup or accessibility checks:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
A example(456);
example.print();

auto memPtr=example.retPrivateVal();
example.*memPtr=123;
example.print();

auto memFuncPtr=example.retPrivateFunc();
(example.*memFuncPtr)();
/*
output:
456
123
A::privateFunc()
*/

As we can see, the private data member of class A was modified externally, and its private member function was also callable externally. This approach can be combined into STL’s operator library to perform certain operations on class objects in bulk.
However, this is based on the prerequisite that the class implementer actively provides access permissions.

Sometimes, for various reasons, we do not wish to manually modify class code (adding or removing class interfaces), but still have a need to access private members. In this case, we can use the following two methods.
Assuming we have the following class:

1
2
3
4
5
6
7
8
9
class A{
public:
A(int x=0):private_(x){}
template<typename T>
void func(const T& x){/* something...*/}
void print(){cout<<private_<<endl;}
private:
int private_;
};

Because it has a template member function, we can break through the class access permissions through this template member function.
The method is to add a specialized version of this member function externally, because it is a specialization of the class member, and it also has permission to access all members inside the class:

1
2
3
4
5
6
7
8
9
10
namespace {
struct ZZZ{
ZZZ(int i):ival(i){}
int ival;
};
}
template<>
void A::func(const ZZZ& z){
private_=z.ival;
}

Here, when we use ZZZ as a parameter to call A::func member function, we can access the private member, thereby breaking the access restrictions of the C++ class. Whether directly manipulating private inside or passing out member pointers (to incoming objects), the private members of class A can be accessed externally.

1
2
3
4
5
6
7
8
9
10
11
A example(456);
example.print();

example.func(ZZZ(123));
example.print();

/*
output:
456
123
*/

If a class has a friend template, the same behavior can also be achieved, and this method is fully compliant with the C++ standard.

Next, I’ll discuss a method to bypass access permissions dependent on compiler implementation:
Similarly using class A as the target for breaking through.
We can mimic a class with the same layout as the target, but change the access specifier of the corresponding private member to public or protected, or other more relaxed access permissions (this actually depends on the implementation).

1
2
3
4
5
6
7
8
9
// The only difference is that private_ is public in class B
class B{
public:
B(int x=0):private_(x){}
template<typename T>
void func(const T& x){/* something...*/}
void print(){cout<<private_<<endl;}
int private_;
};

Then it can be used to manipulate:

1
2
3
4
5
6
7
8
9
10
11
12
A example(456);
example.print();

(reinterpret_cast<B&>(example)).private_=123;

example.print();

/*
output:
456
123
*/

The core idea here is reinterpret_cast<B&>(example), which forces the compiler to interpret an object of class A as an object of class B, then access the public member private_ in class B, but actually modifies class A’s private member private_. However, this also relies on the compiler’s implementation since we assume that the modified class B and class A have identical layouts, which is not guaranteed (this works in G++).

[TC++PL4th] A compiler may reorder sections of a class with separate access specifiers.

We can also place objects of class A and class B in a union, storing in A’s way and reading in B’s way:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
union U{
A aobj;
B bobj;
};
int main()
{
U uobj{456};
uobj.aobj.print();
uobj.bobj.private_=123;
uobj.aobj.print();
}
/*
output:
456
123
*/

Another trick to access a class’s private members is shown in the code below (the latest code will be placed on gist: hack-private-data-member.cpp):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#include <iostream>
#include <cstdio>
using namespace std;
namespace Hacker
{
template<typename Tag, typename Tag::MemType M>
struct PrivateMemberStealer
{
// define friend function GetPrivate, return class member pointer
friend typename Tag::MemType GetPrivate(Tag) { return M; }
};
}

#define DECL_HACK_PRIVATE_DATA(ClassName,DataType,DataName) namespace Hacker{\
struct ClassName##_##DataName\
{\
typedef DataType ClassName::*MemType;\
friend MemType GetPrivate(ClassName##_##DataName);\
};\
template struct PrivateMemberStealer<ClassName##_##DataName, &ClassName::DataName>;\
}

#define DECL_HACK_PRIVATE_STATIC_DATA(ClassName,DataType,DataName) namespace Hacker{\
struct ClassName##_##DataName\
{\
typedef DataType* MemType;\
friend MemType GetPrivate(ClassName##_##DataName);\
};\
template struct PrivateMemberStealer<ClassName##_##DataName, &ClassName::DataName>;\
}

#define DECL_HACK_PRIVATE_STATIC_FUNCTION(ClassName,MemberName,ReturnType,...) \
namespace Hacker{\
struct ClassName##_##MemberName \
{\
typedef ReturnType (*MemType)(__VA_ARGS__);\
friend MemType GetPrivate(ClassName##_##MemberName);\
};\
template struct PrivateMemberStealer<ClassName##_##MemberName, &ClassName::MemberName>;\
}

#define DECL_HACK_PRIVATE_NOCONST_FUNCTION(ClassName,MemberName,ReturnType,...) \
namespace Hacker{\
struct ClassName##_##MemberName \
{\
typedef ReturnType (ClassName::*MemType)(__VA_ARGS__);\
friend MemType GetPrivate(ClassName##_##MemberName);\
};\
template struct PrivateMemberStealer<ClassName##_##MemberName, &ClassName::MemberName>;\
}
#define DECL_HACK_PRIVATE_CONST_FUNCTION(ClassName,MemberName,ReturnType,...) \
namespace Hacker{\
struct ClassName##_##MemberName \
{\
typedef ReturnType (ClassName::*MemType)(__VA_ARGS__)const;\
friend MemType GetPrivate(ClassName##_##MemberName);\
};\
template struct PrivateMemberStealer<ClassName##_##MemberName, &ClassName::MemberName>;\
}

#define GET_VAR_PRIVATE_DATA_MEMBER(ClassInstancePointer,ClassName,DataName) ClassInstancePointer->*GetPrivate(::Hacker::ClassName##_##DataName())
#define GET_REF_PRIVATE_DATA_MEMBER(RefName,ClassInstancePointer,ClassName,DataName) auto& RefName = ClassInstancePointer->*GetPrivate(::Hacker::ClassName##_##DataName())
#define GET_PRIVATE_STATIC_DATA_MEMBER_PTR(PtrName,ClassName,DataName) auto PtrName = GetPrivate(::Hacker::ClassName##_##DataName())
// using ADL found to ::Hacker::Getprivate
#define GET_PRIVATE_MEMBER_FUNCTION(ClassName,MemberName) GetPrivate(::Hacker::ClassName##_##MemberName())
#define CALL_MEMBER_FUNCTION(ClassPointer,MemberFuncPointer,...) (ClassPointer->*MemberFuncPointer)(__VA_ARGS__)

class A{
public:
A(int ivalp=0):ival{ivalp}{}

static void printStaticIval2()
{
printf("%d\n",A::static_ival2);
}
private:
int func(int ival)const
{
printf("A::func(int)\t%d\n",ival);
printf("ival is %d\n",ival);
return 0;
}
int ival;

static int static_ival2;

};

// private member data
DECL_HACK_PRIVATE_DATA(A,int,ival)
// private member function
DECL_HACK_PRIVATE_CONST_FUNCTION(A, func, int, int)
// private static member
DECL_HACK_PRIVATE_STATIC_DATA(A,int,static_ival2)
// private static member function
DECL_HACK_PRIVATE_STATIC_FUNCTION(A,printStaticIval2,void);

int A::static_ival2 = 666;

int main()
{
A aobj(789);
// get private non-static data member
GET_REF_PRIVATE_DATA_MEMBER(ref_ival, &aobj, A, ival);
ref_ival=456;
std::cout<<GET_VAR_PRIVATE_DATA_MEMBER(&aobj, A, ival)<<endl;

// call private non-static member function
auto A_func=GET_PRIVATE_MEMBER_FUNCTION(A, func);
std::cout<<CALL_MEMBER_FUNCTION(&aobj, A_func, 123)<<std::endl;

// get private static data member
GET_PRIVATE_STATIC_DATA_MEMBER_PTR(A_static_ival2, A, static_ival2);
std::cout<<*A_static_ival2<<std::endl;
*A_static_ival2 = 123456;

// call private static function member
auto A_printStaticIval2_func_ptr = GET_PRIVATE_MEMBER_FUNCTION(A,printStaticIval2);
A_printStaticIval2_func_ptr();
}

The implementation of the above code utilizes templates to acquire pointers to member functions, resulting in the following expansion:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class A{
void func(){
printf("A::func\n");
}
};

template<typename T,typename T::MemType M>
struct PrivateMemberHacker{
friend typename T::MemType GetPrivate(T){return M;};
};

struct A_func
{
typedef void (A::*MemType)(void);
friend MemType GetPrivate(A_func);
};
template struct PrivateMemberHacker<A_func,&A::func>;


int main()
{
void (A::*MemType)(void)=GetPrivate(A_func());
A aobj;
(&aobj->*MemType)();
return 0;
}

The reason for using templates is to bypass access permission checks at compile time.
Since we need to acquire the address of A::func, there would be access permission checks at runtime when calling &A::func, which will not compile, thus necessitating the use of a template for early execution.
Hence, we define a new class where the type of the member we want to acquire is represented as a typedef (MemType). This transforms the inaccessible member type A::func into accessible member type A_func::MemType, and importantly, it passes the template parameter (&A::func) to it (T::MemType M) to break through access control of the class.

Moreover, polymorphism is also supported:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class A{
public:
A()=default;

protected:
virtual void virtualfunc()
{
printf("A::virtualfunc\n");
printf("%d %d\n",ival,ival2);
}

int ival = 123;
int ival2 = 456;
};

class B:public A
{
protected:
virtual void virtualfunc()override
{
printf("B::virtualfunc\n");
printf("%d\n",ival3);
}
int ival3 = 789;
};

// private member function
DECL_HACK_PRIVATE_NOCONST_FUNCTION(A, virtualfunc,void)

int main()
{
B bobj;

auto A_virtualfunc=GET_PRIVATE_MEMBER_FUNCTION(A, virtualfunc);
CALL_MEMBER_FUNCTION(&bobj, A_virtualfunc);
}

Conclusion: From the first method of bypassing class access control, one can see the influence of member function templates on class access control. It can be done by using member templates to bypass access control mechanisms. Importantly, this approach is portable and allows for code upgrades (as long as member names do not change). However, any attempts to circumvent member access restrictions are not recommended.
It is well known that in C++, class members can have three types of access control: public / protected / private:
[ISO/IEC 14882:2014] A member of a class can be

  • private: that is, its name can be used only by members and friends of the class in which it is declared.
  • protected: that is, its name can be used only by members and friends of the class in which it is declared, by classes derived from that class, and by their friends (see 11.4).
  • public: that is, its name can be used anywhere without access restriction.

From the perspective of the standard’s intent, it aims to hide the implementation details and underlying data of the class, which is encapsulation. However, we can also break through the access control restrictions through some special means.

First, let’s correct a misconception: the access control of class in C++ restricts the access permission of members, not their visibility.

[ISO/IEC 14882:2014] It should be noted that it is access to members and base classes that is controlled, not their visibility.

Any member in a class is visible to any code that can see the implementation of that class; the issue is whether it can be accessed. That is, when you directly access the name (identifier) of a member in a class, it will check whether you have the right to access that member. If you mark it as private and attempt to access it from outside the class definition or outside a friend, a compilation error will be raised. For a detailed description of the visibility and accessibility of access control, please refer to my other article: Visibility and Accessibility of Access Control Mechanisms.
So we can bypass the access control mechanism by not using the member’s name. A way to access class members without using the class member names is—pointers to the class members. For specific content on pointers to class members in C++, you can refer to my previous article: Pointers to Class Members in C++ are Not Pointers.

From the perspective of intrusive implementation, we can actively provide access to class private members through class member functions.
A common method is to return a reference to the private data member or return pointers to class members. I prefer returning a pointer to the member, because if you return a reference, the accessible member later can only be that of a specific object, while a function pointer can access any member of that class object.

1
2
3
4
5
6
7
8
9
10
class A{
public:
A(int x=0):private_(x){}
void print(){ std::cout<<private_<<std::endl; }
auto retPrivateVal(){ return &A::private_; }
auto retPrivateFunc(){ return &A::privateFunc; }
private:
void privateFunc(){ std::cout<<"A::privateFunc()"<<std::endl; }
int private_;
};

Since what we obtain here is the object address (data member/function object), it can be accessed externally through a class object or a pointer to a class object. This is because the access to class member pointers is essentially calculated through this to determine the offset of that member, thus not triggering any name lookup or accessibility checks:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
A example(456);
example.print();

auto memPtr=example.retPrivateVal();
example.*memPtr=123;
example.print();

auto memFuncPtr=example.retPrivateFunc();
(example.*memFuncPtr)();
/*
output:
456
123
A::privateFunc()
*/

As can be seen, the private data member of class A has been modified externally, and we can also call its private member function from outside. They can also be combined with the STL operator library to perform certain operations on a batch of class objects.
However, this is based on the premise of the class implementer proactively providing access rights.

There are also cases where, for some reasons, we may not want to manually modify the class code (add or delete class interfaces), yet still require access to private members. In such cases, there are two approaches we can use.
Assuming we have the following class:

1
2
3
4
5
6
7
8
9
class A{
public:
A(int x=0):private_(x){}
template<typename T>
void func(const T& x){/* something...*/}
void print(){cout<<private_<<endl;}
private:
int private_;
};

Since it has a template member function, we can use this template member function to break through the class’s access permissions. The method is to add a specialization of this member function from outside because it is a specialization of the class member and it has access to all members of the class:

1
2
3
4
5
6
7
8
9
10
namespace {
struct ZZZ{
ZZZ(int i):ival(i){}
int ival;
};
}
template<>
void A::func(const ZZZ& z){
private_=z.ival;
}

Here, when we use ZZZ as a parameter to call the A::func member function, we can access the private member, thus breaking the access limitations of the C++ class. We can either directly manipulate the private member or pass out the member pointer (to the incoming object), and we can access class A’s private member externally.

1
2
3
4
5
6
7
8
9
10
11
A example(456);
example.print();

example.func(ZZZ(123));
example.print();

/*
output:
456
123
*/

If a class has friend templates, the same behavior can also be achieved, and this method is fully compliant with the C++ standard.

Next, let’s discuss a method for breaking access permissions that depends on the compiler implementation:
Using the above class A as the target for the breakthrough.
We can create a class that mimics the same layout as the target class but changes the access specifiers for corresponding private members to public or protected and other more lenient access privileges (this is actually implementation-dependent).

1
2
3
4
5
6
7
8
9
// The only difference is that private_ is public in class B
class B{
public:
B(int x=0):private_(x){}
template<typename T>
void func(const T& x){/* something...*/}
void print(){cout<<private_<<endl;}
int private_;
};

Then it can be used to manipulate:

1
2
3
4
5
6
7
8
9
10
11
12
A example(456);
example.print();

(reinterpret_cast<B&>(example)).private_=123;

example.print();

/*
output:
456
123
*/

The core of this is reinterpret_cast<B&>(example), which forces the compiler to interpret an object of class A as an object of class B, allowing access to the public member private_ in class B, but actually modifying the private member private_ of class A.
However, this also relies on the compiler’s implementation because we assume that the layout of the modified class B and class A is exactly the same, which is not guaranteed (works in G++).

[TC++PL4th] A compiler may reorder sections of a class with separate access specifiers.

We can also place the objects of class A and class B in a union, storing them as A’s type and reading them as B’s type:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
union U{
A aobj;
B bobj;
};
int main()
{
U uobj{456};
uobj.aobj.print();
uobj.bobj.private_=123;
uobj.aobj.print();
}
/*
output:
456
123
*/

Another clever trick to access class private members is shown directly in the code below (the latest code will be placed on gist: hack-private-data-member.cpp):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#include <iostream>
#include <cstdio>
using namespace std;
namespace Hacker
{
template<typename Tag, typename Tag::MemType M>
struct PrivateMemberStealer
{
// define friend function GetPrivate, return class member pointer
friend typename Tag::MemType GetPrivate(Tag) { return M; }
};
}

#define DECL_HACK_PRIVATE_DATA(ClassName,DataType,DataName) namespace Hacker{\
struct ClassName##_##DataName\
{\
typedef DataType ClassName::*MemType;\
friend MemType GetPrivate(ClassName##_##DataName);\
};\
template struct PrivateMemberStealer<ClassName##_##DataName, &ClassName::DataName>;\
}

#define DECL_HACK_PRIVATE_STATIC_DATA(ClassName,DataType,DataName) namespace Hacker{\
struct ClassName##_##DataName\
{\
typedef DataType* MemType;\
friend MemType GetPrivate(ClassName##_##DataName);\
};\
template struct PrivateMemberStealer<ClassName##_##DataName, &ClassName::DataName>;\
}

#define DECL_HACK_PRIVATE_STATIC_FUNCTION(ClassName,MemberName,ReturnType,...) \
namespace Hacker{\
struct ClassName##_##MemberName \
{\
typedef ReturnType (*MemType)(__VA_ARGS__);\
friend MemType GetPrivate(ClassName##_##MemberName);\
};\
template struct PrivateMemberStealer<ClassName##_##MemberName, &ClassName::MemberName>;\
}

#define DECL_HACK_PRIVATE_NOCONST_FUNCTION(ClassName,MemberName,ReturnType,...) \
namespace Hacker{\
struct ClassName##_##MemberName \
{\
typedef ReturnType (ClassName::*MemType)(__VA_ARGS__);\
friend MemType GetPrivate(ClassName##_##MemberName);\
};\
template struct PrivateMemberStealer<ClassName##_##MemberName, &ClassName::MemberName>;\
}
#define DECL_HACK_PRIVATE_CONST_FUNCTION(ClassName,MemberName,ReturnType,...) \
namespace Hacker{\
struct ClassName##_##MemberName \
{\
typedef ReturnType (ClassName::*MemType)(__VA_ARGS__)const;\
friend MemType GetPrivate(ClassName##_##MemberName);\
};\
template struct PrivateMemberStealer<ClassName##_##MemberName, &ClassName::MemberName>;\
}

#define GET_VAR_PRIVATE_DATA_MEMBER(ClassInstancePointer,ClassName,DataName) ClassInstancePointer->*GetPrivate(::Hacker::ClassName##_##DataName())
#define GET_REF_PRIVATE_DATA_MEMBER(RefName,ClassInstancePointer,ClassName,DataName) auto& RefName = ClassInstancePointer->*GetPrivate(::Hacker::ClassName##_##DataName())
#define GET_PRIVATE_STATIC_DATA_MEMBER_PTR(PtrName,ClassName,DataName) auto PtrName = GetPrivate(::Hacker::ClassName##_##DataName())
// using ADL found to ::Hacker::Getprivate
#define GET_PRIVATE_MEMBER_FUNCTION(ClassName,MemberName) GetPrivate(::Hacker::ClassName##_##MemberName())
#define CALL_MEMBER_FUNCTION(ClassPointer,MemberFuncPointer,...) (ClassPointer->*MemberFuncPointer)(__VA_ARGS__)

class A{
public:
A(int ivalp=0):ival{ivalp}{}

static void printStaticIval2()
{
printf("%d\n",A::static_ival2);
}
private:
int func(int ival)const
{
printf("A::func(int)\t%d\n",ival);
printf("ival is %d\n",ival);
return 0;
}
int ival;

static int static_ival2;

};

// private member data
DECL_HACK_PRIVATE_DATA(A,int,ival)
// private member function
DECL_HACK_PRIVATE_CONST_FUNCTION(A, func, int, int)
// private static member
DECL_HACK_PRIVATE_STATIC_DATA(A,int,static_ival2)
// private static member function
DECL_HACK_PRIVATE_STATIC_FUNCTION(A,printStaticIval2,void);

int A::static_ival2 = 666;

int main()
{
A aobj(789);
// get private non-static data member
GET_REF_PRIVATE_DATA_MEMBER(ref_ival, &aobj, A, ival);
ref_ival=456;
std::cout<<GET_VAR_PRIVATE_DATA_MEMBER(&aobj, A, ival)<<endl;

// call private non-static member function
auto A_func=GET_PRIVATE_MEMBER_FUNCTION(A, func);
std::cout<<CALL_MEMBER_FUNCTION(&aobj, A_func, 123)<<std::endl;

// get private static data member
GET_PRIVATE_STATIC_DATA_MEMBER_PTR(A_static_ival2, A, static_ival2);
std::cout<<*A_static_ival2<<std::endl;
*A_static_ival2 = 123456;

// call private static function member
auto A_printStaticIval2_func_ptr = GET_PRIVATE_MEMBER_FUNCTION(A,printStaticIval2);
A_printStaticIval2_func_ptr();
}

The implementation above utilizes templates to obtain pointers to member functions, and it unfolds as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class A{
void func(){
printf("A::func\n");
}
};

template<typename T,typename T::MemType M>
struct PrivateMemberHacker{
friend typename T::MemType GetPrivate(T){return M;};
};

struct A_func
{
typedef void (A::*MemType)(void);
friend MemType GetPrivate(A_func);
};
template struct PrivateMemberHacker<A_func,&A::func>;


int main()
{
void (A::*MemType)(void)=GetPrivate(A_func());
A aobj;
(&aobj->*MemType)();
return 0;
}

The reason for using templates is to bypass access permission checks at compile time. Since what we want to obtain is the address of A::func, the access permission check for &A::func occurs at runtime and cannot be compiled. Therefore, we need templates to perform early execution. Thus, we create a class definition, using a typedef (MemType) for the member type we wish to acquire and turning the non-accessible member type A::func into the accessible member type A_func::MemType. The key is passing the template’s actual parameter (&A::func) to it (T::MemType M) to break through the access control of the class.

Moreover, polymorphism is also supported:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class A{
public:
A()=default;

protected:
virtual void virtualfunc()
{
printf("A::virtualfunc\n");
printf("%d %d\n",ival,ival2);
}

int ival = 123;
int ival2 = 456;
};

class B:public A
{
protected:
virtual void virtualfunc()override
{
printf("B::virtualfunc\n");
printf("%d\n",ival3);
}
int ival3 = 789;
};

// private member function
DECL_HACK_PRIVATE_NOCONST_FUNCTION(A, virtualfunc,void)

int main()
{
B bobj;

auto A_virtualfunc=GET_PRIVATE_MEMBER_FUNCTION(A, virtualfunc);
CALL_MEMBER_FUNCTION(&bobj, A_virtualfunc);
}

Conclusion: We can see from the first method of breaking class access control that member function templates influence access control mechanisms, allowing access control to be bypassed through member templates. Most importantly, this approach is portable and can be code-upgradeable (as long as the member names do not change). However, any behavior that breaks member access restrictions is not recommended.

The article is finished. If you have any questions, please comment and communicate.

Scan the QR code on WeChat and follow me.

Title:突破C++类的访问控制机制
Author:LIPENGZHA
Publish Date:2017/05/12 11:58
Update Date:2021/05/24 17:28
World Count:11k Words
Link:https://en.imzlp.com/posts/12080/
License: CC BY-NC-SA 4.0
Reprinting of the full article is prohibited.
Your donation will encourage me to keep creating!