Pregunta

Descargo de responsabilidad

Sí, soy plenamente consciente de que lo que estoy preguntando es totalmente estúpido y que cualquiera que desee intentar algo así en código de producción debería ser despedido y/o fusilado.Estoy principalmente buscando para ver si poder estar hecho.

Ahora que eso está fuera del camino, ¿hay alguna forma de acceder a miembros de una clase privada en C++ desde fuera de la clase?Por ejemplo, ¿hay alguna forma de hacer esto con compensaciones de puntero?

(Se aceptan técnicas ingenuas y que no estén listas para producción)

Actualizar

Como se indicó en los comentarios, hice esta pregunta porque quería escribir una publicación de blog sobre la sobreencapsulación (y cómo afecta a TDD).Quería ver si había una manera de decir "usar variables privadas no es una forma 100% confiable de hacer cumplir la encapsulación, incluso en C ++". Al final, decidí concentrarme más en cómo resolver el problema en lugar de por qué es un problema, por lo que no presenté algunas de las cosas mencionadas aquí tan prominentemente como había planeado, pero aún así dejé un enlace.

En cualquier caso, si alguien está interesado en saber cómo quedó, aquí lo tiene: Enemigos del desarrollo basado en pruebas, parte I:encapsulación (Te sugiero leerlo antes de que decidas que estoy loco).

¿Fue útil?

Solución

Si la clase contiene funciones miembro de plantilla, puede especializar esa función miembro para satisfacer sus necesidades. Incluso si el desarrollador original no lo pensó.

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

Salida:

900000
800000

Otros consejos

He agregado una entrada a mi blog (ver más abajo) que muestra cómo se puede hacer. Aquí hay un ejemplo de cómo lo usa para la siguiente clase

struct A {
private:
  int member;
};

Simplemente declare una estructura para él donde la describa e instancia la clase de implementación utilizada para el robo

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

La plantilla de clase Rob se define así, y solo necesita definirse una vez, independientemente de cuántos miembros privados tenga previsto acceder

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

Sin embargo, esto no muestra que las reglas de acceso de c ++ no sean confiables. Las reglas de idioma están diseñadas para proteger contra errores accidentales: si intenta robar datos de un objeto, el idioma by-design no demorará mucho en evitarlo.

Lo siguiente es astuto, ilegal, depende del compilador y puede no funcionar dependiendo de varios detalles de implementación.

#define private public
#define class struct

Pero es una respuesta a su OP, en la que invita explícitamente a una técnica que, y cito, es " totalmente estúpida y que cualquiera que desee probar algo así en el código de producción debería ser despedido y / o disparó " ;.


Otra técnica es acceder a los datos privados de los miembros, mediante la construcción de punteros utilizando desplazamientos codificados a mano / codificados desde el principio del objeto.

Hmmm, no sé si esto funcionaría, pero valdría la pena intentarlo. Cree otra clase con el mismo diseño que el objeto con miembros privados pero con privado cambiado a público. Cree una variable de puntero a esta clase. Use un elenco simple para señalar esto a su objeto con miembros privados e intente llamar a una función privada.

Espere chispas y tal vez un bloqueo;)

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

union 
{ 
    A a; 
    B b; 
};

Deberias hacer eso.

Hora estimada de llegada:Funcionará para este tipo de clase trivial, pero en general no.

TC++PL Sección C.8.3:"Una clase con un constructor, destructor u operación de copia no puede ser del tipo de miembro de una unión...porque el compilador no sabría qué miembro destruir".

Así que lo mejor que podemos hacer es declarar class B para igualar ADiseño y truco para ver los privados de una clase.

Si puede obtener un puntero a un miembro de una clase, puede usar el puntero sin importar cuáles sean los especificadores de acceso (incluso métodos).

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

Por supuesto, mi pequeño truco favorito es la puerta trasera de la plantilla de amigo.

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

Suponiendo que el creador de lo anterior ha definido backDoor para sus usos normales. Pero desea acceder al objeto y mirar las variables de miembros privados. Incluso si la clase anterior se ha compilado en una biblioteca estática, puede agregar su propia especialización de plantilla para backDoor y así acceder a los miembros.

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

Definitivamente es posible acceder a miembros privados con un desplazamiento de puntero en C ++. Supongamos que tenía la siguiente definición de tipo a la que quería acceder.

class Bar {
  SomeOtherType _m1;
  int _m2;
};

Suponiendo que no hay métodos virtuales en Bar, el caso fácil es _m1. Los miembros en C ++ se almacenan como compensaciones de la ubicación de la memoria del objeto. El primer objeto está en el desplazamiento 0, el segundo objeto en el desplazamiento de sizeof (primer miembro), etc. ...

Entonces, aquí hay una forma de acceder a _m1.

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

Ahora _m2 es un poco más difícil. Necesitamos mover el tamaño del puntero original de bytes (SomeOtherType) del original. El reparto a char es para asegurarme de que estoy incrementando en un desplazamiento de bytes

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

pregunta genial por cierto ... aquí está mi pieza:

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

Espero que esto ayude.

Esta respuesta se basa en el concepto exacto demostrado por @ respuesta / blog de Johannes , ya que parece ser el único " legítimo " camino. He convertido ese código de ejemplo en una práctica utilidad. Es fácilmente compatible con C ++ 03 (implementando std::remove_reference & Amp; reemplazando nullptr).

Biblioteca

#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>);

Uso

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

Ejemplo

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

Si sabe cómo su compilador de C ++ manipula los nombres, sí.

A menos que, supongo, sea una función virtual. Pero entonces, si sabes cómo tu compilador de C ++ construye el VTABLE ...

Editar: mirando las otras respuestas, me doy cuenta de que leí mal la pregunta y pensé que se trataba de funciones de miembros, no de datos de miembros. Sin embargo, el punto sigue en pie: si sabe cómo su compilador presenta los datos, puede acceder a esos datos.

En realidad es bastante fácil:

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

Como alternativa al método de puerta trasera de plantilla, puede usar la clase de puerta trasera de plantilla. La diferencia es que no necesita poner esta clase de puerta trasera en el área pública de la clase que va a probar. Utilizo el hecho de que muchos compiladores permiten que las clases anidadas accedan al área privada de la clase adjunta (que no es exactamente el estándar de 1998, pero se considera que es & Quot; right & Quot ;comportamiento). Y, por supuesto, en C ++ 11 esto se convirtió en un comportamiento legal.

Vea este ejemplo:

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

Además de #define private public también puede #define private protected y luego definir alguna clase foo como descendiente de la clase deseada para tener acceso a sus métodos (ahora protegidos) a través de tipo de fundición.

simplemente cree su propia función de miembro de acceso para extender la clase.

A todas las personas que sugieren " #define private public " ;:

Este tipo de cosas es ilegal . El estándar prohíbe definir / recuperar macros que son léxicamente equivalentes a palabras clave de lenguaje reservado. Si bien su compilador probablemente no se quejará (todavía no he visto un compilador que lo haga), no es algo que sea & "; ¡Cosa buena!" hacer.

" el uso de variables privadas no es una forma 100% confiable de imponer la encapsulación, incluso en C ++. " De Verdad? Puede desmontar la biblioteca que necesita, encontrar todas las compensaciones necesarias y usarlas. Eso te dará la posibilidad de cambiar cualquier miembro privado que quieras ... ¡PERO! No puedes acceder a miembros privados sin algún hackeo sucio. Digamos que escribir const no hará que tu constante sea realmente constante, porque puedes deseche const o simplemente use su dirección para invalidarlo. Si utiliza MSVC ++ y especificó & Quot; -merge: .rdata = .data & Quot; para un enlazador, el truco funcionará sin fallas de acceso a la memoria. Incluso podemos decir que escribir aplicaciones en C ++ no es una forma confiable de escribir programas, porque el código de bajo nivel resultante puede ser parcheado desde algún lugar externo cuando su aplicación está en ejecución. Entonces, ¿cuál es la forma documentada confiable de imponer la encapsulación? ¿Podemos ocultar los datos en algún lugar de la RAM y evitar que algo acceda a ellos excepto nuestro código? La única idea que tengo es cifrar a los miembros privados y hacer una copia de seguridad de ellos, porque algo puede corromper a esos miembros. Lo siento si mi respuesta es demasiado grosera, no quise ofender a nadie, pero realmente no creo que esa declaración sea sabia.

Como tienes un objeto de clase requerida, supongo que tienes una declaración de clase.Ahora lo que puede hacer es declarar otra clase con los mismos miembros pero mantener todos sus especificadores de acceso como públicos.

Por ejemplo la clase anterior es:

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

public:
    somefunctions
}

puedes declarar una clase como

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

    somefunctions
};

Ahora todo lo que necesitas hacer es lanzar el puntero de clase. Iamcompprivate en un puntero de clase NowIampublic y úsalos como desees.

Ejemplo:

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

Al hacer referencia a * this , habilita una puerta trasera para todos los datos privados dentro de un objeto.

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

Muy a menudo, una clase proporciona métodos mutantes a datos privados (captadores y establecedores).

Si una clase proporciona un captador que devuelve una referencia constante (pero no establece), entonces puede const_cast el valor de retorno del captador y usarlo como un valor 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;

He usado otro enfoque útil (y solución) para acceder a un miembro privado / protegido de c ++.
La única condición es que pueda heredar de la clase a la que desea acceder.
Entonces todo el crédito va a reinterpret_cast & Lt; & Gt; () .

Un posible problema es que no funcionará si inserta una función virtual, que modificará la tabla virtual y, por lo tanto, el tamaño / alineación del objeto.

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

Entonces solo necesita usar la clase de la siguiente manera:

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

Mi problema original era el siguiente: necesitaba una solución que no implicara recompilar bibliotecas QT.
Hay 2 métodos en QObject , dumpObjectInfo () y dumpObjectTree (), que solo funciona si las bibliotecas QT se compilan en modo de depuración y, por supuesto, necesitan acceso al miembro protegido d_ptr (entre otras estructuras internas).
Lo que hice fue usar la solución propuesta para reimplementar (con copiar y pegar) esos métodos en dumpObjectInfo2 () y dumpObjectTree2 () en mi propia clase ( QObjectWrapper ) eliminar esos protectores de preprocesador de depuración.

El siguiente código accede y modifica un miembro privado de la clase utilizando un puntero a esa clase.

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

solo propósito de estudio .... intente esto ... puede ser útil, supongo ..... este programa puede acceder a los datos privados simplemente conociendo los valores ...

//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.....";
}

Inspirado por @Johannes Schaub - litb, el siguiente código puede ser un poco más fácil de digerir.

    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;

método A: estado de ánimo intrusivo. Como podemos acceder al código fuente y volver a aplicarlo, podemos usar de muchas otras maneras, como clase de amigo para acceder a un miembro privado, todos son puerta trasera legal.

método B: estado de ánimo bruto.

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

usamos un número mágico (20), y no siempre es correcto. Cuando el diseño de la prueba de clase cambió, el número mágico es una gran fuente de errores.

método C: estado de ánimo super hacker. ¿Hay algún estado de ánimo no intrusivo y no bruto? dado que la información de diseño de la prueba de clase se oculta por el cumplidor, No podemos obtener información de compensación de la boca del complie. ex.

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

tampoco podemos obtener el puntero de miembro de la clase Test. ex.

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

@Johannes Schaub - litb tiene un blog, encontró una manera de robar el puntero de un miembro privado. pero pensé que esto debería ser un error del compilador o un error de idioma. puedo compilarlo en gcc4.8, pero no en vc8 complier.

por lo que la conclusión puede ser: El propietario construye todas las puertas traseras. El ladrón siempre tiene una forma brutal y mala de entrar. El hacker accidental tiene una forma elegante y automatizada de entrar.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top