친구를 사용하지 않고 클래스 외부에서 비공개 멤버에 액세스할 수 있나요?

StackOverflow https://stackoverflow.com/questions/424104

문제

부인 성명

예, 저는 제가 묻는 것이 완전히 어리석은 일이며 프로덕션 코드에서 그런 일을 시도하려는 사람은 누구나 해고되거나 총살되어야 한다는 것을 충분히 알고 있습니다.주로 알아보고 있어요 ~할 수 있다 완료됩니다.

이제 문제가 해결되었으니 클래스 외부에서 C++의 전용 클래스 멤버에 액세스할 수 있는 방법이 있습니까?예를 들어, 포인터 오프셋을 사용하여 이를 수행할 수 있는 방법이 있습니까?

(순진하고 생산 준비가 되지 않은 기술을 환영합니다)

업데이트

댓글에서 언급했듯이, 나는 과잉 캡슐화(및 이것이 TDD에 미치는 영향)에 대한 블로그 게시물을 작성하고 싶었기 때문에 이 질문을 했습니다."개인 변수를 사용하는 것은 C ++에서도 캡슐화를 시행하는 100% 신뢰할 수있는 방법이 아닙니다"라고 말하는 방법이 있는지 알고 싶었습니다. 결국, 나는 문제가되는 이유보다 문제를 해결하는 방법에 더 집중하기로 결정했기 때문에 계획대로 여기에 제기 된 것들 중 일부를 두드러지지 않았지만 여전히 링크를 남겼습니다.

어쨌든, 그것이 어떻게 나왔는지에 관심이 있는 사람이 있다면, 여기 있습니다: 테스트 주도 개발의 적 1부:캡슐화 (내가 미쳤다고 결정하기 전에 읽어 보는 것이 좋습니다.)

도움이 되었습니까?

해결책

클래스에 템플릿 멤버 기능이 포함 된 경우 해당 멤버 기능을 귀하의 요구에 맞게 전문화 할 수 있습니다. 원래 개발자가 생각하지 않더라도.

안전 .h

class safe
{
    int money;

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

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

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

다른 팁

나는 추가했다 내 블로그 입력 (아래 참조)는 어떻게 할 수 있는지 보여줍니다. 다음은 다음 수업에 사용하는 방법에 대한 예입니다.

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 ++의 액세스 규칙이 신뢰할 수 없음을 나타내지 않습니다. 언어 규칙은 우발적 인 실수로부터 보호하도록 설계되었습니다. 디자인에 의해 당신을 막기 위해 오랜 방법이 필요하지 않습니다.

다음은 교활하고 불법적이며 컴파일러에 따라 다르며 다양한 구현 세부 사항에 따라 작동하지 않을 수 있습니다.

#define private public
#define class struct

그러나 그것은 당신이 당신의 OP에 대한 답으로, 당신은 "완전히 바보 같은 기술을 명시 적으로 초대하고"생산 코드에서 그러한 것을 시도하려는 사람은 누구나 발사 및/또는 총을 쏘아야한다 "고 명시 적으로 초대합니다.


또 다른 기술은 객체의 시작 부분에서 하드 코딩/핸드 코딩 오프셋을 사용하여 포인터에 위배되어 개인 멤버 데이터에 액세스하는 것입니다.

흠, 이것이 효과가 있는지 모르겠지만 시도해 볼 가치가 있습니다. 개인 구성원이 있지만 개인이 공개적으로 변경된 객체와 동일한 레이아웃을 가진 다른 클래스를 만듭니다. 이 클래스에 대한 포인터 변수를 만듭니다. 간단한 캐스트를 사용하여 개인 구성원과 함께 객체를 가리키고 개인 기능을 호출하십시오.

불꽃과 충돌을 기대합니다;)

class A 
{ 
   int a; 
}
class B
{
   public: 
   int b;
}

union 
{ 
    A a; 
    B b; 
};

그렇게해야합니다.

ETA : 이런 종류의 사소한 계급에서 효과가 있지만 일반적으로 그렇지 않습니다.

TC ++ PL 섹션 C.8.3 : "생성자, 파괴자 또는 복사 작업이있는 클래스는 컴파일러가 어떤 멤버를 파괴 할 것인지 알지 못하기 때문에 노조 구성원의 유형이 될 수 없습니다."

그래서 우리는 선언하는 최선의 방법이 남았습니다. class B 일치합니다 A클래스의 개인을보기 위해 레이아웃과 해킹.

클래스 멤버에 대한 포인터를 얻을 수있는 경우 액세스 지정자가 무엇인지 (심지어 메소드) 상관없이 포인터를 사용할 수 있습니다.

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

위의 제작자가 그의 정상적인 용도로 백도어를 정의했다고 가정합니다. 그러나 객체에 액세스하고 개인 회원 변수를보고 싶습니다. 위의 클래스가 정적 라이브러리로 컴파일 된 경우에도 백도어를위한 템플릿 전문화를 추가하여 멤버에 액세스 할 수 있습니다.

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

C ++에서 포인터 오프셋이있는 개인 멤버에 액세스 할 수 있습니다. 액세스하려는 다음 유형의 정의가 있다고 가정하자.

class Bar {
  SomeOtherType _m1;
  int _m2;
};

막대에 가상 메소드가 없다고 가정하면 쉬운 경우는 _m1입니다. C ++의 멤버는 객체의 메모리 위치의 오프셋으로 저장됩니다. 첫 번째 객체는 Offset 0, Sizeof (첫 번째 멤버) 등의 오프셋에서 두 번째 객체입니다.

_m1에 액세스하는 방법이 있습니다.

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

이제 _m2는 조금 더 어렵습니다. 원래 포인터 크기 (일부 론적 유형) 바이트를 원본에서 움직여야합니다. Cast to Char는 내가 바이트 오프셋에서 증가하는지 확인하는 것입니다.

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

멋진 질문 btw ... 여기 내 작품이 있습니다.

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

도움이 되었기를 바랍니다.

이 답변은 @Johannes의 답변/블로그, 그것이 유일한 "합법적 인"방법 인 것처럼 보입니다. 이 예제 코드를 편리한 유틸리티로 변환했습니다. C ++ 03과 쉽게 호환됩니다 (구현에 의해 std::remove_reference & 교체 nullptr).

도서관

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

#define ALLOW_ACCESS(CLASS, TYPE, MEMBER) \
  template<typename Only, TYPE CLASS::*Member> \
  struct CONCATE(MEMBER, __LINE__) { friend TYPE (CLASS::*Access(Only*)) { return Member; } }; \
  template<typename> struct Only_##MEMBER; \
  template<> struct Only_##MEMBER<CLASS> { friend TYPE (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>, <type>, <member>);

용법

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, int, member);

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

C++ 컴파일러가 이름을 어떻게 변경하는지 알고 있다면 그렇습니다.

제 생각엔 가상 기능이 아닌 이상 말이죠.하지만 C++ 컴파일러가 VTABLE을 빌드하는 방법을 알고 있다면 ...

편집하다:다른 답변을 보니 질문을 잘못 읽었고 멤버 데이터가 아니라 멤버 기능에 관한 것이라고 생각했다는 것을 깨달았습니다.그러나 요점은 여전히 ​​유효합니다.컴파일러가 데이터를 배치하는 방법을 알고 있다면 해당 데이터에 액세스할 수 있습니다.

실제로는 매우 쉽습니다.

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

템플릿 백도어 메소드의 대안으로 템플릿 백도어 클래스를 사용할 수 있습니다. 차이점은이 백도어 클래스를 테스트하려는 클래스의 공공 영역에 넣을 필요가 없다는 것입니다. 나는 많은 컴파일러가 중첩 클래스가 클래스를 둘러싸는 개인 영역에 액세스 할 수 있도록 허용한다는 사실을 사용합니다 (정확히 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();
}

옆에 #개인 대중을 정의하십시오 당신은 또한 수 #개인 보호를 정의하십시오 그런 다음 FOO 클래스를 유형 캐스팅을 통해 (현재 보호 된) 방법에 액세스 할 수 있도록 원하는 클래스의 후손으로 일부 Foo 클래스를 정의하십시오.

클래스를 확장하려면 자신만의 액세스 멤버 기능을 작성하십시오.

제안하는 모든 사람들에게 "#개인 대중을 정의하십시오":

이런 종류의 것입니다 불법적인. 표준은 예약 된 언어 키워드와 어휘에 동등한 정의/undef-ing 매크로를 금지합니다. 컴파일러는 아마도 불평하지 않을 것입니다 (아직 컴파일러를 보지 못했습니다). "좋은 일"이 아닙니다.

"개인 변수를 사용하는 것은 C ++에서도 캡슐화를 시행하는 100% 안정적인 방법이 아닙니다."진짜? 필요한 라이브러리를 분해하고 필요한 모든 오프셋을 찾아 사용 할 수 있습니다. 그것은 당신이 좋아하는 개인 구성원을 변경할 수있는 능력을 줄 것입니다 ... 그러나! 더러운 해킹 없이는 개인 회원에게 액세스 할 수 없습니다. 글쓰기를 말해 봅시다 Const 당신의 상수를 실제로 일정하게 만들지 않을 것입니다. 왜냐하면 당신은 캐스트 할 수 있기 때문입니다. Const 멀리 떨어져 있거나 주소를 사용하여 무효화하십시오. MSVC ++를 사용하고 링커에 "-merge : .rdata = .data"를 지정하면 메모리 액세스 결함없이 트릭이 작동합니다. C ++로 앱을 쓰는 것이 프로그램을 작성하는 신뢰할 수있는 방법이 아니라고 말할 수도 있습니다. 그렇다면 캡슐화를 시행하는 신뢰할 수있는 문서화 방법은 무엇입니까? RAM 어딘가에 데이터를 숨기고 코드를 제외하고 데이터에 액세스하는 것을 방지 할 수 있습니까? 내가 가진 유일한 아이디어는 개인 회원을 암호화하고 백업하는 것입니다. 내 대답이 너무 무례하다면 죄송합니다. 다른 사람을 화나게 할 의도는 없었지만 그 진술이 현명하다고 생각하지 않습니다.

필요한 수업의 대상이 있으므로 수업 선언이 있다고 생각합니다. 이제 당신이 할 수있는 일은 동일한 회원으로 다른 클래스를 선언하지만 모든 액세스 지정자를 공개적으로 유지하는 것입니다.

예를 들어 이전 클래스는 다음과 같습니다.

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

public:
    somefunctions
}

수업을 다음과 같이 선언 할 수 있습니다

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

    somefunctions
};

이제 당신이해야 할 일은 클래스의 포인터입니다. Iamcompprivate 수업의 포인터로 NowIampublic 그리고 원하는대로 사용하십시오.

예시:

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

*를 참조하여이것 객체 내의 모든 개인 데이터에 백도어를 활성화합니다.

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

종종 클래스는 개인 데이터 (getters and setters)에 뮤지티 방법을 제공합니다.

클래스가 const 참조를 반환하는 getter를 제공하는 경우 (그러나 세터는 없음), Getter의 반환 값을 const_cast하고 그것을 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 Member에 액세스하기 위해 또 다른 유용한 접근 방식 (및 솔루션)을 사용했습니다.
유일한 조건은 액세스하려는 클래스에서 상속받을 수 있다는 것입니다.
그런 다음 모든 신용이 이동합니다 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, dumpobjectinfo() 그리고 dumpobjecttree(), QT Libs가 디버그 모드에서 컴파일 된 경우에만 작동하며 물론 D_PTR Proteted 멤버 (다른 내부 구조 중)에 액세스해야합니다.
내가 한 일은 제안 된 솔루션을 사용하여 해당 메소드를 다시 구현 (복사 및 붙여 넣기) dumpobjectinfo2() 그리고 dumpObjectTree2() 내 자신의 수업에서 (qobjectWrapper) 해당 디버그 전 프로세서 가드 제거.

다음 코드는 해당 클래스에 대한 포인터를 사용하여 클래스의 개인 구성원에 액세스하고 수정합니다.

#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).*/

연구 목적 만 .... 이것을 시도해보십시오 .... 도움이 될 수 있습니다 .....이 프로그램은 가치를 아는 것만으로도 개인 데이터에 액세스 할 수 있습니다 ...

//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 Schaub에서 영감을 얻은 다음 코드는 소화하기가 조금 더 쉬울 수 있습니다.

    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 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)를 사용하며 항상 옳은 것은 아닙니다. 클래스 테스트 레이아웃이 변경되면 마법 번호는 큰 버그 소스입니다.

방법 C : 슈퍼 해커 분위기. 비 침입 및 비 가슴의 분위기가 있습니까? 클래스 테스트의 레이아웃 정보는 Complier에 의해 숨겨져 있기 때문에 Compie의 입에서 오프셋 정보를 얻을 수 없습니다. 전.

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

또한 수업 테스트에서 회원 포인터를 얻을 수 없습니다. 전.

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

@Johannes Schaub -Litb에는 블로그가 있으며 개인 회원 포인터를 강탈하는 방법을 찾았습니다. 그러나 나는 이것이 Complier의 버그 또는 언어 함정이어야한다고 생각했습니다. GCC4.8에서는 불만을 제기 할 수 있지만 VC8 Complier에는 없습니다.

결론은 다음과 같습니다. 집주인은 모든 백도어를 빌드합니다. 도둑은 항상 무자비하고 나쁜 방법이 있습니다. 실수로 해커는 우아하고 자동화 된 방법을 가지고 있습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top