我可以不通过朋友从课外访问私人成员吗?

免责声明

是的,我完全知道我所问的问题是非常愚蠢的,任何想在生产代码中尝试这样做的人都应该被解雇或者枪毙。我主要是想看看 可以是否完成了。

既然这个问题已经解决了,那么有没有办法从类外部访问 C + + 中的私有类成员呢?例如,有什么方法可以用指针偏移量做到这一点吗?

(欢迎天真和其他非生产准备的技术)

更新

正如在评论中提到的,我问这个问题是因为我想写一篇关于过度封装(以及它如何影响 TDD)的博客文章。我想知道是否有一种方法可以说“即使在 C + + 中,使用私有变量也不是100% 可靠的执行封装的方法。”最后,我决定把更多的精力放在如何解决这个问题上,而不是为什么它是一个问题,所以我没有像我计划的那样突出这里提到的一些东西,但我仍然留下了一个链接。

无论如何,如果有人对它是如何出来的感兴趣,这里是: 测试驱动开发的敌人第一部分: 封装(我建议在你认为我疯了之前先看看它)。

77589 次浏览

如果您知道 C + + 编译器是如何混淆名称的,那么是的。

除非,我想,它是一个虚函数。但是,如果你知道你的 C + + 编译器是如何构建 VTABLE 的..。

编辑: 看看其他的回答,我意识到我误解了这个问题,认为它是关于成员函数的,而不是成员数据。但是,这一点仍然成立: 如果您知道编译器如何布局数据,那么就可以访问该数据。

以下内容是狡猾的、非法的、依赖于编译器的,并且可能根据不同的实现细节而无法工作。

#define private public
#define class struct

但是这是对你的 OP 的一个回答,在 OP 中你明确地邀请了一个技术,我引用一下,这个技术是“完全愚蠢的,任何想要在生产代码中尝试这样一个东西的人都应该被解雇和/或枪毙”。


另一种技术是访问私有成员数据,方法是从对象的开始使用硬编码/手工编码的偏移量构造指针。

除了 定义私人公共之外,您还可以 # Definition private protected,然后定义一些 foo 类作为想要的类的后代,以便通过类型强制转换访问它的(现在受保护的)方法。

嗯,不知道这是否可行,但可能值得一试。创建另一个类,其布局与具有私有成员但将 private 更改为 public 的对象相同。创建指向此类的指针的变量。使用简单的强制转换将其指向具有私有成员的对象,并尝试调用私有函数。

可能会擦出火花,也可能会发生碰撞;)

只需创建您自己的访问成员函数来扩展类。

使用 C + + 中的指针偏移量访问私有成员是完全可能的。让我们假设我有以下类型定义,我想访问。

class Bar {
SomeOtherType _m1;
int _m2;
};

假设 Bar 中没有虚方法,最简单的例子是 _ m1。C + + 中的成员存储为对象的内存位置的偏移量。第一个对象的偏移量为0,第二个对象的偏移量为 sizeof (第一个成员) ,等等。

这里有一种访问 _ m1的方法。

SomeOtherType& GetM1(Bar* pBar) {
return*(reinterpret_cast<SomeOtherType*>(pBar));
}

现在,_ m2有点难度。我们需要从原始指针移动原始指针 sizeof (Some OtherType)字节。转换为 char 是为了确保在字节偏移量中递增

int& GetM2(Bar* pBar) {
char* p = reinterpret_cast<char*>(pBar);
p += sizeof(SomeOtherType);
return *(reinterpret_cast<int*>(p));
}

对所有建议“ 定义私人公共”的人:

这种东西就是 非法的。该标准禁止定义/取消定义在词法上等同于保留语言关键字的宏。虽然你的编译器可能不会抱怨(我还没有看到一个编译器会抱怨) ,但这不是一件“好事”。

其实很简单:

class jail {
int inmate;
public:
int& escape() { return inmate; }
};

如果该类包含任何模板成员函数,则可以专门化该成员函数以满足您的需要。即使最初的开发者没有想到这一点。

安全

class safe
{
int money;


public:
safe()
: money(1000000)
{
}


template <typename T>
void backdoor()
{
// Do some stuff.
}
};

Cpp:

#include <safe.h>
#include <iostream>


class key;


template <>
void safe::backdoor<key>()
{
// My specialization.
money -= 100000;
std::cout << money << "\n";
}


int main()
{
safe s;
s.backdoor<key>();
s.backdoor<key>();
}

产出:

900000
800000

如果您可以得到一个指向类成员的指针,那么您可以使用该指针,而不管访问说明符是什么(甚至是方法)。

class X;
typedef void (X::*METHOD)(int);


class X
{
private:
void test(int) {}
public:
METHOD getMethod() { return &X::test;}
};


int main()
{
X      x;
METHOD m = x.getMethod();


X     y;
(y.*m)(5);
}

当然,我最喜欢的小黑客是朋友模板后门。

class Z
{
public:
template<typename X>
void backDoor(X const& p);
private:
int x;
int y;
};

假设上面的创建者已经为他的正常使用定义了后门。但是您希望访问该对象并查看私有成员变量。即使上面的类已经被编译成一个静态库,您也可以为 backDoor 添加自己的模板专门化,从而访问成员。

namespace
{
// Make this inside an anonymous namespace so
// that it does not clash with any real types.
class Y{};
}
// Now do a template specialization for the method.
template<>
void Z::backDoor<Y>(Y const& p)
{
// I now have access to the private members of Z
}


int main()
{
Z  z;   // Your object Z


// Use the Y object to carry the payload into the method.
z.backDoor(Y());
}
class A
{
int a;
}
class B
{
public:
int b;
}


union
{
A a;
B b;
};

应该可以了。

埃塔: 对于这类琐碎的类,它可以工作,但一般情况下它不会工作。

TC + + PL C.8.3节: “具有构造函数、析构函数或复制操作的类不能是联合成员的类型... ... 因为编译器不知道要销毁哪个成员。”

所以我们最好声明 class B来匹配 A的布局,然后查看类的私有属性。

既然你有一个必需类的对象,我猜你有类的声明。 现在,您可以使用相同的成员声明另一个类,但是将所有访问说明符都保持为 public。

例如上一节课是:

class Iamcompprivate
{
private:
Type1 privateelement1;
Typ2 privateelement2;
...


public:
somefunctions
}

可以将类声明为

class NowIampublic
{
**public:**
Type1 privateelement1;
Type2 privateelement2;
...


somefunctions
};

现在您需要做的就是将类 Iamcompprivate的指针强制转换为类 NowIampublic的指针,并使用它们作为 U 愿望。

例如:

NowIampublic * changetopublic(Iamcompprivate *A)
{
NowIampublic * B = (NowIampublic *)A;
return B;
}

我已经添加了一个 进入我的博客(见下文) ,说明如何可以做到这一点。下面的示例说明如何将其用于以下类

struct A {
private:
int member;
};

只需在描述它的地方为它声明一个结构,并实例化用于抢劫的实现类

// tag used to access A::member
struct A_member {
typedef int A::*type;
friend type get(A_member);
};


template struct Rob<A_member, &A::member>;


int main() {
A a;
a.*get(A_member()) = 42; // write 42 to it
std::cout << "proof: " << a.*get(A_member()) << std::endl;
}

Rob类模板是这样定义的,只需要定义一次,不管您计划访问多少个私有成员

template<typename Tag, typename Tag::type M>
struct Rob {
friend typename Tag::type get(Tag) {
return M;
}
};

但是,这并不表示 c + + 的访问规则不可靠。语言规则是为了防止意外错误而设计的——如果你试图抢劫一个对象的数据,语言 设计不会花很长时间来阻止你。

“即使在 C + + 中,使用私有变量也不是100% 可靠的封装方式。” 真的吗? 您可以反汇编您需要的库,找到所有需要的偏移量并使用它们。 这将给你一个能力,改变任何私人成员你喜欢... 但! 没有黑客技术,你无法访问私人用户。 让我们说写 康斯特不会使你的常数真的是恒定的,因为你可以 抛弃 康斯特或者仅仅使用它的地址来使其无效。如果您正在使用 MSVC + + 并且您指定了“-merge: 。Rdata = .“ data”连接到一个链接器,这个技巧将在没有任何内存访问错误的情况下工作。 我们甚至可以说,用 C + + 编写应用程序并不是一种可靠的编写程序的方式,因为当你的应用程序运行的时候,可能会从外面的某个地方修补低级代码。 那么,什么是实施封装的可靠的文档化方法呢?我们能把数据隐藏在 RAM 的某个地方,除了我们的代码之外,防止任何东西访问它们吗?我唯一的想法就是给私人成员加密并备份,因为有些东西可能会损坏这些成员。 如果我的回答过于粗鲁,我很抱歉,我无意冒犯任何人,但我真的不认为这种说法是明智的。

顺便说一句,这是我的问题:

using namespace std;


class Test
{


private:


int accessInt;
string accessString;


public:


Test(int accessInt,string accessString)
{
Test::accessInt=accessInt;
Test::accessString=accessString;
}
};


int main(int argnum,char **args)
{
int x;
string xyz;
Test obj(1,"Shit... This works!");


x=((int *)(&obj))[0];
xyz=((string *)(&obj))[1];


cout<<x<<endl<<xyz<<endl;
return 0;
}

希望这个能帮上忙。

通过引用 * 这个,可以对对象中的所有私有数据启用后门。

class DumbClass
{
private:
int my_private_int;
public:
DumbClass& backdoor()
{
return *this;
}
}

作为模板后门方法的替代方法,您可以使用模板后门类。不同之处在于,您不需要将这个后门类放到您要测试的类的公共区域中。我使用的事实是,许多编译器允许嵌套类访问封闭类的私有区域(这不完全是1998年的标准,但被认为是“正确的”行为)。当然,在 C + + 11中,这变成了合法行为。

看这个例子:

#include <vector>
#include <cassert>
#include <iostream>
using std::cout;
using std::endl;




///////// SystemUnderTest.hpp
class SystemUnderTest
{
//...put this 'Tested' declaration into private area of a class that you are going to test
template<typename T> class Tested;
public:
SystemUnderTest(int a): a_(a) {}
private:
friend std::ostream& operator<<(std::ostream& os, const SystemUnderTest& sut)
{
return os << sut.a_;
}
int a_;
};


/////////TestFramework.hpp
class BaseTest
{
public:
virtual void run() = 0;
const char* name() const { return name_; }
protected:
BaseTest(const char* name): name_(name) {}
virtual ~BaseTest() {}
private:
BaseTest(const BaseTest&);
BaseTest& operator=(const BaseTest&);
const char* name_;
};


class TestSuite
{
typedef std::vector<BaseTest*> Tests;
typedef Tests::iterator TIter;
public:
static TestSuite& instance()
{
static TestSuite TestSuite;
return TestSuite;
}
void run()
{
for(TIter iter = tests_.begin(); tests_.end() != iter; ++iter)
{
BaseTest* test = *iter;
cout << "Run test: " << test->name() << endl;
test->run();
}
}
void addTest(BaseTest* test)
{
assert(test);
cout << "Add test: " << test->name() << endl;
tests_.push_back(test);
}
private:
std::vector<BaseTest*> tests_;
};


#define TEST_CASE(SYSTEM_UNDER_TEST, TEST_NAME) \
class TEST_NAME {}; \
template<> \
class SYSTEM_UNDER_TEST::Tested<TEST_NAME>: public BaseTest \
{ \
Tested(): BaseTest(#SYSTEM_UNDER_TEST "::" #TEST_NAME) \
{ \
TestSuite::instance().addTest(this); \
} \
void run(); \
static Tested instance_; \
}; \
SYSTEM_UNDER_TEST::Tested<TEST_NAME> SYSTEM_UNDER_TEST::Tested<TEST_NAME>::instance_; \
void SYSTEM_UNDER_TEST::Tested<TEST_NAME>::run()




//...TestSuiteForSystemUnderTest.hpp
TEST_CASE(SystemUnderTest, AccessPrivateValueTest)
{
SystemUnderTest sut(23);
cout << "Changed private data member from " << sut << " to ";
sut.a_ = 12;
cout << sut << endl;
}


//...TestRunner.cpp
int main()
{
TestSuite::instance().run();
}

类经常为私有数据(getter 和 setter)提供 mutator 方法。

如果一个类确实提供了返回一个 const 引用(但是没有 setter)的 getter,那么您可以直接 const _ cast 这个 getter 的返回值,并将其作为一个 l 值使用:

class A {
private:
double _money;
public:
A(money) :
_money(money)
{}


const double &getMoney() const
{
return _money;
}
};


A a(1000.0);
const_cast<double &>(a.getMoney()) = 2000.0;

我使用了另一种有用的方法(和解决方案)来访问 c + + private/protected 成员。
唯一的条件是您能够从要访问的类继承。
然后所有的功劳归于 重新解释 _ cast < > ()

一个可能的问题是,如果插入一个虚函数,它将修改虚表,从而修改对象的大小/对齐方式,那么它将无法工作。

class QObject
{
Q_OBJECT
Q_DECLARE_PRIVATE(QObject)
void dumpObjectInfo();
void dumpObjectTree();
...
protected:
QScopedPointer<QObjectData> d_ptr;
...
}


class QObjectWrapper : public QObject
{
public:
void dumpObjectInfo2();
void dumpObjectTree2();
};

然后你只需要像下面这样使用这个类:

QObject* origin;
QObjectWrapper * testAccesor = reinterpret_cast<QObjectWrapper *>(origin);
testAccesor->dumpObjectInfo2();
testAccesor->dumpObjectTree2();

我最初的问题如下: 我需要一个不需要重新编译 QT 库的解决方案。
QObjectDumpObjectInfo()和 DumpObjectTree()中有两种方法,即 如果 QT 库是在调试模式下编译的,并且它们当然需要访问 d _ ptr 保护的成员(以及其他内部结构) ,那么它们就可以工作了。
我所做的就是使用提议的解决方案在我自己的类(QObjectWrapper)中重新实现(通过复制和粘贴) DumpObjectInfo2()和 DumpObjectTree2()中的那些方法,删除那些调试预处理器保护。

下面的代码使用指向该类的指针访问和修改该类的私有成员。

#include <iostream>
using namespace std;
class A
{
int private_var;
public:
A(){private_var = 0;}//initialized to zero.
void print(){cout<<private_var<<endl;}
};


int main()
{
A ob;
int *ptr = (int*)&ob; // the pointer to the class is typecast to a integer pointer.
(*ptr)++; //private variable now changed to 1.
ob.print();
return 0;
}
/*prints 1. subsequent members can also be accessed by incrementing the pointer (and
type casting if necessary).*/
class Test{
int a;
alignas(16) int b;
int c;
};


Test t;

方法 A: 侵入性情绪。 因为我们可以访问源代码并重新编译它,我们可以使用 很多其他方式像好友类访问私人会员,他们都是合法的后门。

方法 B: 野蛮的心情

int* ptr_of_member_c = reinterpret_cast<int*>(reinterpret_cast<char*>(&t) + 20);

我们使用一个神奇的数字(20) ,它并不总是正确的。当类 Test 的布局发生变化时,这个神奇的数字是一个很大的 bug 源。

方法 C: 超级黑客情绪。 是否有任何不打扰和不粗暴的情绪? 因为类 Test 的布局信息被编译器隐藏, 我们无法从编者口中得到偏移信息。 前男友。

offsetof(Test,c); //complie error. they said can not access private member.

我们也不能从类 Test 中获取成员指针。 前男友。

&Test::c ;  //complie error. they said can not access private member.

@ Johannes Schaub-litb 有个博客,他找到了抢劫私人会员指针的方法。 但我认为这应该是编译器的错误或语言陷阱。 我可以在 gcc4.8上完成,但不能在 vc8编译器上完成。

因此,结论可能是: 房东建了所有的后门。 小偷总是有野蛮和不好的方式进入。 黑客的意外入侵有着优雅而自动化的方式。

只供学习用途。 试试这个... ... 也许会有帮助,我想... ..。 这个程序可以访问私人数据只需要知道..。

//GEEK MODE....;)
#include<iostream.h>
#include<conio.h>


class A
{
private :int iData,x;
public: void get()             //enter the values
{cout<<"Enter iData : ";
cin>>iData;cout<<"Enter x : ";cin>>x;}


void put()                               //displaying values
{cout<<endl<<"sum = "<<iData+x;}
};


void hack();        //hacking function


void main()
{A obj;clrscr();
obj.get();obj.put();hack();obj.put();getch();
}


void hack()         //hack begins
{int hck,*ptr=&hck;
cout<<endl<<"Enter value of private data (iData or x) : ";
cin>>hck;     //enter the value assigned for iData or x
for(int i=0;i<5;i++)
{ptr++;
if(*ptr==hck)
{cout<<"Private data hacked...!!!\nChange the value : ";
cin>>*ptr;cout<<hck<<" Is chaged to : "<<*ptr;
return;}
}cout<<"Sorry value not found.....";
}

这个答案是基于 @ Johannes 的回答/博客演示的确切概念,因为这似乎是唯一的“合法”方式。我已经将这个示例代码转换成了一个方便的实用程序。它很容易与 C + + 03兼容(通过实现 std::remove_reference并替换 nullptr)。

图书馆

#define CONCATE_(X, Y) X##Y
#define CONCATE(X, Y) CONCATE_(X, Y)


#define ALLOW_ACCESS(CLASS, MEMBER, ...) \
template<typename Only, __VA_ARGS__ CLASS::*Member> \
struct CONCATE(MEMBER, __LINE__) { friend __VA_ARGS__ CLASS::*Access(Only*) { return Member; } }; \
template<typename> struct Only_##MEMBER; \
template<> struct Only_##MEMBER<CLASS> { friend __VA_ARGS__ CLASS::*Access(Only_##MEMBER<CLASS>*); }; \
template struct CONCATE(MEMBER, __LINE__)<Only_##MEMBER<CLASS>, &CLASS::MEMBER>


#define ACCESS(OBJECT, MEMBER) \
(OBJECT).*Access((Only_##MEMBER<std::remove_reference<decltype(OBJECT)>::type>*)nullptr)

空气污染指数

ALLOW_ACCESS(<class>, <member>, <type>);

用法

ACCESS(<object>, <member>) = <value>;   // 1
auto& ref = ACCESS(<object>, <member>); // 2

演示

struct X {
int get_member () const { return member; };
private:
int member = 0;
};


ALLOW_ACCESS(X, member, int);


int main() {
X x;
ACCESS(x, member) = 42;
std::cout << "proof: " << x.get_member() << std::endl;
}

受@Johannes Schaub-litb 的启发,下面的代码可能更容易理解。

    struct A {
A(): member(10){}
private:
int get_member() { return member;}
int member;
};


typedef int (A::*A_fm_ptr)();
A_fm_ptr  get_fm();


template<   A_fm_ptr p>
struct Rob{
friend A_fm_ptr  get_fm() {
return p;
}
};


template struct Rob<  &A::get_member>;


int main() {
A a;
A_fm_ptr p = get_fm();


std::cout << (a.*p)() << std::endl;


}

有了指针偏移量,这很简单。难点在于找到偏移量:

其他人

class Foo
{
public:
int pub = 35;


private:
int foo = 5;
const char * secret = "private :)";
};

Main.cpp

#include <iostream>
#include <fstream>
#include <string>
#include <regex>


#include "other.hpp"


unsigned long long getPrivOffset(
const char * klass,
const char * priv,
const char * srcfile
){


std::ifstream read(srcfile);
std::ofstream write("fork.hpp");
std::regex r ("private:");
std::string line;
while(getline(read, line))
// make all of the members public
write << std::regex_replace(line, r, "public:") << '\n';
write.close();
read.close();
// find the offset, using the clone object
std::ofstream phony("phony.cpp");
phony <<
"#include <iostream>\n"
"#include <fstream>\n"
"#include \"fork.hpp\"\n"
"int main() {\n";
phony << klass << " obj;\n";
// subtract to find the offset, the write it to a file
phony <<
"std::ofstream out(\"out.txt\");\n out <<   (((unsigned char *) &(obj."
<< priv << ")) -((unsigned char *)   &obj)) << '\\n';\nout.close();";
phony << "return 0;\n}";
phony.close();
system(
"clang++-7 -o phony phony.cpp\n"
"./phony\n"
"rm phony phony.cpp fork.hpp");
std::ifstream out("out.txt");
// read the file containing the offset
getline(out, line);
out.close();
system("rm out.txt");
unsigned long long offset = strtoull(line.c_str(), NULL, 10);
return offset;
}




template <typename OutputType, typename Object>
OutputType hack(
Object obj,
const char * objectname,
const char * priv_method_name,
const char * srcfile
) {
unsigned long long o = getPrivOffset(
objectname,
priv_method_name,
srcfile
);
return *(OutputType *)(((unsigned char *) (&obj)+o));
}
#define HACK($output, $object, $inst, $priv, $src)\
hack <$output, $object> (\
$inst,\
#$object,\
$priv,\
$src)


int main() {
Foo bar;
std::cout << HACK(
// output type
const char *,
// type of the object to be "hacked"
Foo,
// the object being hacked
bar,
// the desired private member name
"secret",
// the source file of the object's type's definition
"other.hpp"
) << '\n';
return 0;
}
clang++ -o main main.cpp
./main

产出:

private :)

您也可以使用 reinterpret_cast

我把 约翰内斯回答做得更通用。你可以在这里找到来源: https://github.com/lackhole/Lupin
您只需要知道类的名称和成员。

你可以用,

#include <iostream>


#include "access/access.hpp"


struct foo {
private:
std::string name = "hello";
int age = 27;
void print() {}
};


using tag_foo_name = access::Tag<class foo_name>;
template struct access::Accessor<tag_foo_name, foo, decltype(&foo::name), &foo::name>;


int main() {
foo f;
  

// peek hidden data
std::cout << access::get<tag_foo_name>(f) << '\n'; // "hello"
  

// steal hidden data
access::get<tag_foo_name>(f) = "lupin";
std::cout << access::get<tag_foo_name>(f) << '\n'; // "lupin"
}

调用私有函数,获取私有成员的类型也是可能的,只需使用标记即可。

也许一些指针算法可以做到这一点

#pragma pack(1)
class A
{
int x{0};
char c{0};
char s[8]{0};
    

public:
void display()
{
print(x);
print(c);
print(s);
};
};




int main(void)
{
A a;


int *ptr2x = (int *)&a;
*ptr2x = 10;
    

char *ptr2c = (char *)ptr2x+4;
*ptr2c = 'A';
    

char *ptr2s = (char *)ptr2c+1;
strcpy(ptr2s ,"Foo");
    

a.display();
}