Могу ли я получить доступ к закрытым участникам из-за пределов класса, не используя друзей?

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

Вопрос

Отказ от ответственности

Да, я полностью осознаю, что то, о чем я спрашиваю, абсолютно глупо и что любой, кто хотел бы попробовать такую вещь в производственном коде, должен быть уволен и / или застрелен.В основном я хочу узнать, есть ли может будет сделано.

Теперь, когда с этим покончено, есть ли какой-нибудь способ получить доступ к закрытым членам класса в C ++ извне класса?Например, есть ли какой-нибудь способ сделать это со смещениями указателей?

(Приветствуются наивные и в остальном непроизводственные методы)

Обновить

Как отмечалось в комментариях, я задал этот вопрос, потому что хотел написать сообщение в блоге о чрезмерной инкапсуляции (и о том, как это влияет на TDD).Я хотел посмотреть, есть ли способ сказать: "использование закрытых переменных не является на 100% надежным способом обеспечения инкапсуляции даже в C ++". В конце я решил больше сосредоточиться на том, как решить проблему, а не на том, почему это проблема, поэтому я не стал показывать некоторые материалы, приведенные здесь, так широко, как планировал, но я все равно оставил ссылку.

В любом случае, если кому-то интересно, как это вышло, вот оно: Враги разработки, основанной на тестировании, часть I:инкапсуляция (Я предлагаю прочитать это, прежде чем вы решите, что я сумасшедший).

Это было полезно?

Решение

Если класс содержит какие-либо шаблонные функции-члены, вы можете специализировать эту функцию-член в соответствии с вашими потребностями.Даже если первоначальный разработчик об этом не подумал.

безопасно.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 ++ ненадежны. Языковые правила предназначены для защиты от случайных ошибок - если вы попытаетесь ограбить данные объекта, язык by-design не займет много времени, чтобы предотвратить вас.

Следующее является подлым, недопустимым, зависит от компилятора и может не работать в зависимости от различных деталей реализации.

#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: & Класс с конструктором, деструктором или операцией копирования не может быть типом элемента объединения ..., поскольку компилятор не будет знать, какой элемент уничтожить. Quot &;

Итак, нам лучше всего объявить, что 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;
};

Предполагая, что создатель выше определил 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());
}

Определенно возможно получить доступ к закрытым членам со смещением указателя в C ++. Предположим, у меня было следующее определение типа, к которому я хотел получить доступ.

class Bar {
  SomeOtherType _m1;
  int _m2;
};

Предполагая, что в Bar нет виртуальных методов, простой случай - _m1. Члены в C ++ хранятся как смещения места в памяти объекта. Первый объект со смещением 0, второй объект со смещением sizeof (первый элемент) и т. Д.

Итак, вот способ доступа к _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));
}

крутой вопрос, кстати ... вот мой кусок:

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

Надеюсь, это поможет.

Этот ответ основан на точной концепции, продемонстрированной @ ответом / блогом Йоханнеса , поскольку это, кажется, единственное & Quot; & легитимной Quot; путь. Я преобразовал этот пример кода в удобную утилиту. Он легко совместим с C ++ 03 (благодаря реализации std::remove_reference & Amp; замена 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; }
};

В качестве альтернативы шаблону backdoor-метода вы можете использовать шаблон backdoor class. Разница в том, что вам не нужно помещать этот бэкдор в публичную область класса, который вы собираетесь тестировать. Я использую тот факт, что многие компиляторы разрешают вложенным классам обращаться к закрытой области включающего класса (что не совсем соответствует стандарту 1998 года, но считается & Quot; правильным & Quot; поведением). И, конечно, в 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();
}

Кроме #define private public , вы также можете #define private protected и затем определить некоторый класс foo как потомок класса, который будет иметь доступ к его (теперь защищенным) методам. через тип литья.

просто создайте свою собственную функцию-член доступа для расширения класса.

Всем, кто предлагает " #define private public ":

Такие вещи незаконны . Стандарт запрещает определять / отменять определение макросов, которые лексически эквивалентны зарезервированным ключевым словам языка. Хотя ваш компилятор, вероятно, не будет жаловаться (я еще не видел компилятор, который это делает), это не то, что & Quot; Good Thing & Quot; делать.

&, использование закрытых переменных не является 100% надежным способом принудительной инкапсуляции, даже в C ++. " В самом деле? Вы можете разобрать нужную вам библиотеку, найти все необходимые смещения и использовать их. Это даст вам возможность изменить любого частного участника, которого вы любите ... НО! Вы не можете получить доступ к частным пользователям без грязного взлома. Допустим, что написание const не сделает вашу константу действительно постоянной, потому что вы можете отбросьте const или просто используйте его адрес, чтобы сделать его недействительным. Если вы используете MSVC ++ и указали & Quot; -merge: .rdata = .data & Quot; для компоновщика трюк будет работать без ошибок доступа к памяти. Можно даже сказать, что написание приложений на C ++ не является надежным способом написания программ, потому что результирующий низкоуровневый код может быть исправлен откуда-то извне, когда ваше приложение работает. Тогда каков надежный документированный способ обеспечения инкапсуляции? Можем ли мы спрятать данные где-нибудь в оперативной памяти и запретить доступ к ним ничего, кроме нашего кода? Единственная идея, которую я имею, - это зашифровать частных участников и сделать их резервную копию, потому что что-то может повредить этих участников. Извините, если мой ответ слишком груб, я не хотел никого обидеть, но я действительно не думаю, что это утверждение является мудрым.

поскольку у вас есть объект требуемого класса, я предполагаю, что у вас есть объявление класса.Теперь, что вы можете сделать, это объявить другой класс с теми же членами, но сохранить все существующие спецификаторы доступа как общедоступные.

Например, предыдущий класс - это:

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

Ссылаясь на * this , вы включаете бэкдор для всех личных данных в объекте.

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

Довольно часто класс предоставляет методы-мутаторы для закрытых данных (геттеры и сеттеры).

Если класс предоставляет метод получения, который возвращает константную ссылку (но не метод установки), тогда вы можете просто 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 ++.
Единственное условие - вы можете наследовать от класса, к которому хотите получить доступ.
Затем весь кредит переходит к reinterpret_cast & Lt; & Gt; () .

Возможная проблема заключается в том, что она не будет работать, если вы вставите виртуальную функцию, которая изменит виртуальную таблицу и, следовательно, размер / выравнивание объекта.

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 (среди других внутренних структур).
Я использовал предложенное решение для переопределения (с копированием и вставкой) этих методов в 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 - 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 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 изменился, магическое число стало источником большой ошибки.

метод 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 complier.

, поэтому можно сделать вывод: Хозяин строит весь черный ход. у вора всегда есть грубый и плохой способ взломать. случайный хакер имеет элегантный и автоматизированный способ взломать.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top