Saltar a contenido

Sistema de proteccion y friend (capitulo 32)

Resumen corto

Este capitulo explica como C++ permite romper el encapsulamiento de forma controlada usando friend.

  • Sin friend, una clase o funcion externa no puede tocar miembros private.
  • Con friend, habilitas una excepcion puntual.
  • Esa excepcion no se hereda ni se propaga automaticamente.

Reglas de amistad en C++

  • No es transitiva: si A es amiga de B y B de C, no implica que A sea amiga de C.
  • No es heredable: si A es amiga de B, no automaticamente de derivadas de B.
  • No es simetrica: si A es amiga de B, no implica que B sea amiga de A.

Caso 1: funcion amiga externa

Patron:

class A {
private:
    int a;
    friend void Ver(A);
};

Ver(A) no es metodo de A, pero puede leer/modificar A::a por la declaracion friend.

Lectura de reversing:

  • Si sacas friend, el codigo deja de compilar al acceder a Xa.a.
  • No cambia calling convention: sigue siendo funcion externa normal.

Caso 2: metodo amigo de otra clase

Patron:

class A; // forward declaration

class B {
public:
    bool EsMayor(A Xa);
};

class A {
private:
    int a;
    friend bool B::EsMayor(A Xa);
};

Puntos clave:

  • Hace falta forward declaration de A para declarar EsMayor(A).
  • B::EsMayor puede leer A::a por ser amiga.

Nota de reversing importante:

  • Si dos metodos/constructores quedan identicos en codigo maquina, el compilador puede plegarlos (COMDAT/ICF segun build).
  • Resultado: en ASM parece que "falta" una funcion, pero en realidad se reutiliza la misma implementacion.

Bajo nivel (caso A y B)

1) main crea ambos objetos en stack

Se ve Na y Nb como variables locales en el frame:

  • Na = A ptr -11Ch
  • Nb = B ptr -0FCh

Construccion:

mov edx, 0Ah
lea rcx, [rbp+...+Na]   ; this
call A::A(int)

mov edx, 0Ch
lea rcx, [rbp+...+Nb]   ; this
call B::B(int)

RCX lleva this y EDX el entero del constructor.

2) Llamada a B::EsMayor(A) y paso de parametro

La llamada relevante es:

mov edx, [rbp+...+Na.a] ; Xa
lea rcx, [rbp+...+Nb]   ; this
call B::EsMayor(A)

Punto importante: A en este ejemplo tiene solo un int, entonces MSVC lo pasa de forma efectiva como ese valor de 32 bits (no como objeto complejo en memoria).

3) Cuerpo de B::EsMayor(A) en asm

Dentro de la funcion:

mov rax, [rbp+...+this] ; B* this
mov ecx, [rbp+...+Xa.a] ; valor de A::a
cmp [rax], ecx          ; compara B::b vs Xa.a
jle ...

[rax] es B::b (offset 0), porque B tambien contiene solo un int.

4) Donde aparece realmente friend

friend no genera una instruccion especial en asm.
Su efecto es de compilacion: permite que exista codigo fuente como Xa.a dentro de B::EsMayor.

Si quitas friend, ese acceso no compila.
Si lo dejas, compila y en asm solo ves una lectura normal del campo.

5) A::Ver(void) y B::Ver(void) en este build

En tu dump, ambas funciones tienen la misma estructura:

mov rax, [rbp+...+this]
mov edx, [rax]      ; lee el int del objeto
call std::ostream::operator<<(int)

Interpretacion:

  • Tanto A como B tienen su int en offset +0.
  • Por eso ambos Ver() se ven casi iguales en asm, salvo simbolos y etiquetas.

Caso 3: clase amiga completa (friend class)

Ejemplo del capitulo: lista enlazada con Elemento y Lista.

class Elemento {
private:
    int tipo;
    Elemento *sig;
    friend class Lista;
};

Lista puede tocar Elemento::tipo y Elemento::sig aunque sean private.


Mapeo de estructuras en ASM (MSVC x64)

Con el codigo del capitulo:

  • Elemento ocupa 16 bytes:
  • +0x0 -> int tipo
  • +0x8 -> Elemento* sig (hay padding entre medio)
  • Lista ocupa 8 bytes:
  • +0x0 -> Elemento* Cabeza

Funciones observadas en el ASM:

  • Elemento::Tipo() -> lee [this+0]
  • Lista::Primero() -> devuelve [this+0] (Cabeza)
  • Lista::Siguiente(p) -> si p != 0, devuelve [p+8] (sig)
  • Elemento::Elemento(int t) -> guarda t en tipo y sig = 0

Patron de insercion (Lista::Nuevo)

Secuencia en ASM:

  1. operator new(16) para un Elemento.
  2. Llama Elemento::Elemento(tipo).
  3. p->sig = this->Cabeza.
  4. this->Cabeza = p.

Este patron inserta siempre al principio (LIFO).
Si insertas 4, luego 2, luego 1, al recorrer imprimes 1,2,4.


Patron de liberacion (Lista::LiberarLista)

Loop observado:

  1. p = Cabeza
  2. Cabeza = p->sig
  3. delete p
  4. repetir hasta Cabeza == NULL

Este metodo lo llama el destructor Lista::~Lista().


Checklist de reversing para friend

  • Busca accesos a campos privados desde clases/funciones externas.
  • Si compila, revisa si hay friend en headers/clase.
  • Mapea offsets de campos ([this+0], [this+8]) antes de renombrar.
  • En listas enlazadas, identifica rapido el patron:
  • node->next = head
  • head = node
  • loop de delete.