Closed-source binary reverse-engineered autonomously β authentication flow, session tokens, all 7 commands, and 5 vulnerabilities including a critical stack overflow discovered in a single ~4-minute run. Zero source code. Zero human guidance.
PacketHammer runs a 7-step cycle that continuously probes, instruments, decompiles, and models the target protocol β with no human guidance. The diagram below animates the live cycle. Steps 5β7 are conditional: they fire only when their trigger condition is met.
Line-delimited ASCII authentication protocol on TCP/2121. Clients authenticate with a username + password pair, receive a fixed session token, then issue computation commands (sum, concat, greet, multiply) β each requiring the token as a first argument.
Every message exchanged between client and server β in the exact order the protocol requires β discovered purely through live instrumentation. No source code was read. The packet animation above the table represents the continuous bidirectional exchange.
Every command the server accepts β syntax, authentication requirement, behaviour, and any security flags identified during live instrumentation and Ghidra decompilation.
Initiates the login sequence. Username is read via std::istream::operator>> and stored in a local variable. The server always responds 331 regardless of whether the username exists β username enumeration is not possible from response codes alone.
Completes login. Server simultaneously checks stored username against config['login'] and supplied password against config['password'] via std::operator==. Both must match. Success: 230 TOKEN TOKEN-LOCAL-12345, session continues. Failure: 530 Authentication failed, connection closed immediately.
Graceful disconnect. Server responds 221 Bye and closes the TCP connection. Available both before and after authentication. Sets internal flag iVar8=1 which terminates the dispatch loop.
Returns the integer sum of a and b as 200 ST <a+b>. Both operands parsed via std::istream::operator>> into signed int variables. Implemented at command_st (0x103fe4). No overflow concern β result formatted via std::to_string.
Concatenates two strings and returns 200 CT <s1s2>. Uses std::operator+ on std::string objects β heap-allocated, no buffer overflow risk. Implemented at command_ct (0x10409a).
Greets with name: returns 200 HI Hello <name>. The name argument (no length limit from istream) is passed to strcpy into a 24-byte stack buffer β any name > 23 bytes overwrites the saved return address. Stack canary present but bypassable via info-leak.
β Critical overflowReturns signed int32 product: 200 IV product=<a*b>. The product is used directly as the length argument to memset on a 64-byte scratch buffer. If a*b overflows and wraps to [0,63], memset fires with the wrapped value β unexpected but bounded. Implemented at command_iv (0x104342).
β Integer overflowPacketHammer's code-analyzer agent flagged these dangerous patterns by combining Ghidra static decompilation with live Frida instrumentation β every finding below was produced without a human reading a single line of assembly.
The user-supplied name argument (read via std::istream::operator>> with no length limit) is
passed directly to strcpy(name_buffer, src) where
name_buffer is a 24-byte stack array.
Any name longer than 23 bytes overflows the buffer and overwrites the saved RBP and return address.
A stack canary is present (__stack_chk_fail @ 0x103790),
providing partial mitigation β a direct overflow triggers abort. However, if an attacker can leak
the canary value (e.g. through a format-string or memory read primitive), full RIP control is
achievable. The overflow is reachable after successful authentication: USER admin β PASS secret β HI
TOKEN-LOCAL-12345 <payload>.
The line-reading function at 0x103eff loops recv(fd,&ch,1,0)
and appends each byte to a std::string via
push_back() until '\n'
is received. There is no maximum line length, no timeout, and no iteration cap.
An attacker sending a continuous stream of non-newline bytes forces the string to grow without bound. Each reallocation doubles capacity, leading to exponential memory consumption. The server process is OOM-killed by the OS. This requires no authentication and affects all concurrent sessions β a single TCP connection causes a reliable denial of service.
The product __val = a * b is computed as a signed 32-bit
multiplication with no overflow check. If the mathematical product overflows int32 and wraps to a
value in [0, 63], the code executes
memset(scratch, 0x41, (long)__val) on a 64-byte scratch
buffer β writing exactly __val bytes of 'A'.
No out-of-bounds write occurs (scratch is 64 bytes, __val < 64), but the unexpected control flow may interact with adjacent memory depending on stack layout. Exploiting this requires engineering a*b mod 2Β³Β² β [0,63] β mathematically constrained but achievable with chosen large operands.
Ghidra's decompilation of handle_client (0x1043aa, 2637 bytes) shows manual destructor call
sequences β basic_string() constructor, send_line(),
~string(), ~__new_allocator() β
interleaved with complex branch chains. If send_line() throws
or control flow is interrupted mid-sequence, a string object could be left in an inconsistent state.
Most likely this is a Ghidra decompilation artefact from inlined std::string code β actual RAII semantics in the binary are likely intact. Not confirmed exploitable. Recorded because the pattern warrants fuzzing attention, particularly under exception-triggering conditions.
snprintf(result, 0x28, "Hello %s", name_buffer)
uses a hardcoded format string with user data supplied as the %s
argument β this is safe as currently decompiled. The format string
"Hello %s" is a constant literal, not attacker-controlled.
Recorded because a decompile discrepancy (e.g. Ghidra misidentifying the argument order) or an alternate code path that passes user input directly as the format argument would make this a critical vector. Current static analysis confirms the format string is constant. Verify manually in the assembled binary if this target is high-value.
Same target. Same vulnerabilities. The difference is whether you find them in minutes or days β and whether the documentation writes itself or stays in someone's head.
| Capability | π€ Manual testing | π€ PacketHammer |
|---|---|---|
| Protocol structure discovery | β Manual reverse engineering | β Automated (Ghidra + Frida) |
| Seed / credential harvesting | β Source code or deep RE required | β Runtime comparison hooks |
| Vulnerability pattern scanning | β Analyst code review | β Static + live dynamic analysis |
| Session flow documentation | β Ad-hoc notes, often lost | β Structured JSON + visual HTML |
| Zero-day seeds (hidden comparisons) | β Frequently missed without source | β Tier 1 + Tier 2 oracle capture |
| Branch coverage tracking | β Estimated / gut feel | β Branch-level Frida instrumentation |
| Scales with binary complexity | β Analyst time scales linearly | β Consistent regardless of binary size |
| Cost per rerun | β Full analyst time at hourly rate | β Compute cost only |
| Time to first vulnerability | β Hours to days | β Minutes |
Static analysis addresses from Ghidra. Live addresses after PIE rebase will differ β use
get_base() via frida-live to compute the
runtime offset, then add these values to get live addresses.
| Symbol | Static address |
|---|---|
| handle_client | 0x1043aa |
| command_dispatch gate | 0x1044d4 |
| recv_line | 0x103eff |
| trim_crlf | 0x103a89 |
| command_hi β strcpy vuln | 0x1041ba |
| command_st | 0x103fe4 |
| command_ct | 0x10409a |
| command_iv β int overflow | 0x104342 |
| __stack_chk_fail (canary) | 0x103790 |