76

免责声明

是的,我完全清楚我所问的完全是愚蠢的,任何想在生产代码中尝试这种事情的人都应该被解雇和/或枪杀。我主要看不能做。

既然已经不碍事了,有没有办法从类外部访问 C++ 中的私有类成员?例如,有没有办法用指针偏移量来做到这一点?

(欢迎幼稚和其他非生产就绪技术)

更新

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

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

4

27 回答 27

80

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

安全.h

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
于 2009-01-08T16:43:42.970 回答
65

我在我的博客中添加了一个条目(见下文),展示了它是如何完成的。这是有关如何将其用于以下课程的示例

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++ 的访问规则不可靠。语言规则旨在防止意外错误 - 如果您试图窃取对象的数据,语言设计不会花很长时间来阻止您。

于 2010-07-03T22:38:43.413 回答
30

以下是偷偷摸摸的、非法的、依赖于编译器的,并且可能无法工作,具体取决于各种实现细节。

#define private public
#define class struct

但这是对您的 OP 的回答,您在其中明确邀请了一种技术,我引用该技术是“完全愚蠢的,任何希望在生产代码中尝试这种事情的人都应该被解雇和/或枪杀”。


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

于 2009-01-08T12:53:43.700 回答
24

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

期待火花,也许会崩溃;)

于 2009-01-08T13:04:52.930 回答
12
class A 
{ 
   int a; 
}
class B
{
   public: 
   int b;
}

union 
{ 
    A a; 
    B b; 
};

那应该这样做。

ETA:它适用于这种琐碎的课程,但一般来说它不会。

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

所以我们最好的选择是声明class B匹配A的布局并破解查看一个类的私人信息。

于 2009-01-09T03:41:22.097 回答
10

如果您可以获得指向类成员的指针,则无论访问说明符是什么(甚至是方法),您都可以使用该指针。

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。但是您想访问该对象并查看私有成员变量。即使上面的类已经被编译成一个静态库,你也可以为 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());
}
于 2009-01-08T19:13:56.570 回答
9

在 C++ 中使用指针偏移量访问私有成员绝对是可能的。假设我有以下想要访问的类型定义。

class Bar {
  SomeOtherType _m1;
  int _m2;
};

假设 Bar 中没有虚方法,最简单的情况是 _m1。C++ 中的成员存储为对象内存位置的偏移量。第一个对象在偏移量 0 处,第二个对象在 sizeof(first member) 的偏移量处,等等...

所以这里有一种访问_m1的方法。

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

现在 _m2 有点困难。我们需要从原始指针 sizeof(SomeOtherType) 字节中移动原始指针。转换为 char 是为了确保我在字节偏移量中递增

int& GetM2(Bar* pBar) {
  char* p = reinterpret_cast<char*>(pBar);
  p += sizeof(SomeOtherType);
  return *(reinterpret_cast<int*>(p));
}
于 2009-01-08T15:34:39.383 回答
5

该答案基于@Johannes 的 answer/blog所展示的确切概念,因为这似乎是唯一“合法”的方式。我已经将该示例代码转换为一个方便的实用程序。它很容易与 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)

API

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;
}
于 2017-01-01T15:25:43.290 回答
3

如果您知道您的 C++ 编译器如何破坏名称,是的。

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

编辑:查看其他回复,我意识到我误读了这个问题,并认为它与成员函数有关,而不是成员数据。但是,重点仍然存在:如果您知道编译器如何布置数据,那么您就可以访问该数据。

于 2009-01-08T12:50:19.213 回答
3

很酷的问题顺便说一句......这是我的作品:

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;
}

希望这可以帮助。

于 2011-03-29T09:03:22.457 回答
1

其实很简单:

class jail {
    int inmate;
public:
    int& escape() { return inmate; }
};
于 2009-01-08T16:22:48.847 回答
1

作为模板后门方法的替代方法,您可以使用模板后门类。不同的是你不需要把这个后门类放到你要测试的类的公共区域。我使用的事实是,许多编译器允许嵌套类访问封闭类的私有区域(这不完全是 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();
}
于 2013-07-08T11:57:52.070 回答
0

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

于 2009-01-08T13:01:40.533 回答
0

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

于 2009-01-08T13:30:34.827 回答
0

对于所有建议“ #define private public ”的人:

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

于 2009-01-08T16:16:12.917 回答
0

因为你有一个必需类的对象,我猜你有类的声明。现在您可以做的是声明另一个具有相同成员的类,但将所有访问说明符保持为公共。

例如上一课是:

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;
}
于 2010-06-30T09:56:53.113 回答
0

“即使在 C++ 中,使用私有变量也不是 100% 可靠的强制封装方式。” 真的吗?你可以反汇编你需要的库,找到所有需要的偏移量并使用它们。这将使您能够更改您喜欢的任何私人成员......但是!如果没有一些肮脏的黑客攻击,您将无法访问私人成员。让我们说写const不会让你的常量真正保持不变,因为你可以转换const离开或只是使用它的地址使其无效。如果您使用的是 MSVC++ 并且您将“-merge:.rdata=.data”指定给链接器,则该技巧将有效,不会出现任何内存访问错误。我们甚至可以说,用 C++ 编写应用程序并不是编写程序的可靠方式,因为当你的应用程序运行时,可能会从外部的某个地方修补生成的低级代码。那么执行封装的可靠记录方法是什么?我们能否将数据隐藏在 RAM 中的某个位置,并阻止除我们的代码之外的任何东西访问它们?我唯一的想法是加密私人成员并备份它们,因为某些东西可能会破坏这些成员。对不起,如果我的回答太粗鲁,我并没有冒犯任何人的意思,但我真的不认为这种说法是明智的。

于 2010-12-22T21:55:26.667 回答
0

通过引用 * this,您可以为对象内的所有私有数据启用后门。

class DumbClass
{
private:
    int my_private_int;
public:
    DumbClass& backdoor()
    {
        return *this;
    }
}
于 2013-07-03T19:32:51.533 回答
0

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

如果一个类确实提供了一个返回 const 引用(但没有 setter)的 getter,那么您可以只 const_cast getter 的返回值,并将其用作左值:

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;
于 2013-07-16T10:14:59.493 回答
0

我使用了另一种有用的方法(和解决方案)来访问 c++ 私有/受保护成员。
唯一的条件是您能够从要访问的类继承。
然后所有功劳归于reinterpret_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 库的解决方案。QObject
中有 2 个方法,dumpObjectInfo () 和dumpObjectTree (),如果 QT 库在调试模式下编译,它们就可以工作,它们当然需要访问 d_ptr 受保护的成员(以及其他内部结构)。 我所做的是使用建议的解决方案在我自己的类(QObjectWrapper )中重新实现(复制和粘贴) dumpObjectInfo2 () 和dumpObjectTree2 () 中的那些方法,删除那些调试预处理器保护。

于 2014-04-08T15:31:07.930 回答
0

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

#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).*/
于 2014-08-10T12:58:58.537 回答
0

仅用于学习目的....试试这个....可能会有所帮助我猜.....这个程序可以通过知道值来访问私人数据......

//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.....";
}
于 2016-09-18T18:11:30.710 回答
0

受@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;

  }
于 2018-07-22T21:36:46.490 回答
0

好吧,有了指针偏移,这很容易。困难的部分是找到偏移量:

其他.hpp

class Foo
{
  public:
    int pub = 35;

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

主文件

#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.

于 2020-04-20T02:53:02.570 回答
0

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

#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();
}
于 2021-10-05T11:20:54.703 回答
-1
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 编译器上编译。

所以结论可能是: 楼主都建了后门。小偷总是有蛮横的闯入方式。黑客意外地以优雅而自动化的方式闯入。

于 2014-09-11T02:25:31.173 回答
-1

我让约翰内斯的回答更笼统。您可以在此处获取源代码: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"
}

调用私有函数,获取私有成员的类型也可以只使用标签。

于 2021-04-24T11:50:25.533 回答