친구를 사용하지 않고 클래스 외부에서 비공개 멤버에 액세스할 수 있나요?
-
05-07-2019 - |
문제
부인 성명
예, 저는 제가 묻는 것이 완전히 어리석은 일이며 프로덕션 코드에서 그런 일을 시도하려는 사람은 누구나 해고되거나 총살되어야 한다는 것을 충분히 알고 있습니다.주로 알아보고 있어요 ~할 수 있다 완료됩니다.
이제 문제가 해결되었으니 클래스 외부에서 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에는 없습니다.
결론은 다음과 같습니다. 집주인은 모든 백도어를 빌드합니다. 도둑은 항상 무자비하고 나쁜 방법이 있습니다. 실수로 해커는 우아하고 자동화 된 방법을 가지고 있습니다.