Skip to content

SECURE_CONNECTION//PRESS[CTRL+J]FOR ROOT ACCESS

BACK TO INTEL
MiscEasy

Character

CTF writeup for Character from HTB CTF TRY OUT

//Character

Challenge: Character (Category: MISC)

Remote: nc 94.237.51.6 42537

Flag format: HTB{...}


>Short summary

This challenge exposes a TCP service that answers a single-character of the flag when queried with an index. The protection approach was intentionally tedious: you can only request the flag one character at a time. The solution is enumeration: repeatedly query indices until the service responds "Index out of range" and assemble the returned characters into the final flag.

I wrote two small helper scripts to automate interaction and a robust per-index fetcher that opens a fresh connection per index (avoiding prompt-state issues). Both scripts are included below, and the reliable one was used to reconstruct the full flag.

Final flag:

HTB{tH15_1s_4_r3aLly_l0nG_fL4g_i_h0p3_f0r_y0Ur_s4k3_tH4t_y0U_sCr1pTEd_tH1s_oR_els3_iT_t0oK_qU1t3_l0ng!!}


>Reconnaissance (manual)

I first connected to the service with netcat to see how it behaves:

bash

nc 94.237.51.6 42537

The service prints a prompt like:

Which character (index) of the flag do you want? Enter an index:

When a valid index is submitted it returns a single-character line such as:

Character at Index 9: 1

Invalid or out-of-range indices produce Index out of range!.

Because the server is stateful (single connection keeps prompting), a simple approach is to open a fresh connection for each index request, read the response and close the connection. This avoids needing to track or synchronize the server prompt state.


>Approach / Plan

  1. Automate repeated queries by index, starting from 1 and incrementing until the server replies Index out of range!.

  2. For reliability, open a fresh TCP connection for each index query, send the index, capture the response and close the connection.

  3. Parse the returned text for the character and assemble the final flag string.

Edge cases considered:

  • Network timeouts and partial reads — handled by reasonable socket timeouts and reading until timeout.

  • Unexpected formatting — fallback parsing by splitting on the last colon.


>Scripts

Both scripts used in this solve are included in the repository. Below are the contents and short explanation.

/home/noigel/HTB/MISC/Character/001_fetch_flag.py

This was my first attempt: it uses a single connection and iterates through indices while reading server responses. It was useful to validate the protocol but had corner cases when the prompt and response boundaries were tricky.

python

#!/usr/bin/env python3

"""

001_fetch_flag.py

  

Connects to the remote service and queries successive indices to reconstruct the flag.

Prints progress with simple emojis and separators for readability.

"""

import socket

import re

import time

  

HOST = '94.237.51.6'

PORT = 42537

  

def recv_until(sock, timeout=2.0):

    sock.settimeout(timeout)

    data = b''

    try:

        data = sock.recv(4096)

    except socket.timeout:

        pass

    except Exception:

        pass

    return data.decode(errors='ignore')

  

def main():

    print('🔎 Connecting to service {}:{}...'.format(HOST, PORT))

    with socket.create_connection((HOST, PORT), timeout=10) as s:

        intro = recv_until(s)

        if intro:

            print('\n' + '='*60)

            print(intro.strip())

            print('='*60 + '\n')

  

        flag_chars = []

        i = 1

        print('🚀 Starting index enumeration...')

        print('-'*60)

        while True:

            tosend = f"{i}\n"

            s.sendall(tosend.encode())

            time.sleep(0.05)

            resp = recv_until(s, timeout=1.5)

            line = resp.strip().replace('\n', ' | ')

            print(f'[{i:3d}] ➜ {line}')

  

            if 'Index out of range' in resp:

                print('\n⚠️  Index out of range reached. Stopping enumeration.')

                break

  

            m = re.search(r'Character at Index\s*\d+\s*:\s*(.)', resp)

            if m:

                ch = m.group(1)

                flag_chars.append(ch)

            else:

                if ':' in resp:

                    ch = resp.split(':')[-1].strip().split()[0]

                    if ch:

                        flag_chars.append(ch[0])

  

            i += 1

  

        print('\n' + '='*60)

        flag = ''.join(flag_chars)

        print('🎯 Flag reconstructed (so far):')

        print(flag)

        print('='*60)

  

if __name__ == '__main__':

    main()

/home/noigel/HTB/MISC/Character/002_fetch_flag_per_connection.py

This is the robust script used to gather the full flag. It opens a new connection for each index and is resilient to prompt state issues.

python

#!/usr/bin/env python3

"""

002_fetch_flag_per_connection.py

  

More reliable flag fetcher: opens a fresh TCP connection for each index query

so we don't have to track server prompt state. Prints progress and the final flag.

"""

import socket

import re

import time

  

HOST = '94.237.51.6'

PORT = 42537

  

def query_index(idx, timeout=5.0):

    try:

        with socket.create_connection((HOST, PORT), timeout=timeout) as s:

            s.settimeout(1.0)

            try:

                _ = s.recv(4096)

            except Exception:

                pass

            s.sendall(f"{idx}\n".encode())

            s.settimeout(2.0)

            data = b''

            try:

                while True:

                    chunk = s.recv(4096)

                    if not chunk:

                        break

                    data += chunk

            except socket.timeout:

                pass

            return data.decode(errors='ignore')

    except Exception as e:

        return f"__ERR__ {e}"

  

def main():

    print('🔎 Per-connection fetcher starting...')

    flag_chars = []

    idx = 1

    while True:

        resp = query_index(idx)

        short = resp.strip().splitlines()

        short = ' | '.join(short[:2])

        print(f'[{idx:3d}] ➜ {short}')

  

        if 'Index out of range' in resp:

            print('\n⚠️  Reached end of flag indices.')

            break

  

        m = re.search(r'Character at Index\s*\d+\s*:\s*(.+)', resp)

        if m:

            token = m.group(1).strip()

            if token:

                ch = token[0]

                flag_chars.append(ch)

        else:

            if ':' in resp:

                part = resp.rsplit(':', 1)[-1].strip()

                if part:

                    flag_chars.append(part[0])

  

        idx += 1

        time.sleep(0.05)

  

    flag = ''.join(flag_chars)

    print('\n' + '='*60)

    print('🎯 Flag assembled:')

    print(flag)

    print('='*60)

  

if __name__ == '__main__':

    main()

>Reproduction / How to run

From the Character directory run the robust script:

bash

python3 002_fetch_flag_per_connection.py

This will connect to the remote service repeatedly, print progress lines for each index, and finally output the assembled flag.

If you prefer a single-connection attempt (less reliable), you can run:

bash

python3 001_fetch_flag.py

Permissions: ensure the scripts are executable or run with python3 <script>.

Network: these scripts perform outbound TCP connections to 94.237.51.6:42537. Run them from a host with internet access and allowed outbound tcp connections.


>Notes & observations

  • The challenge is intentionally tedious: you can only request one character of the flag per query. The easiest approach is automated enumeration.

  • The per-connection model is robust against prompt synchronization issues and is the recommended method when the server drives the prompt state heavily.

  • The server appears to index characters starting at 1.


>Full interaction log (trimmed)

Example lines collected while enumerating:

[  1] ➜ Character at Index 1: T [  2] ➜ Character at Index 2: B [  3] ➜ Character at Index 3: { ... [103] ➜ Character at Index 103: } [104] ➜ Index out of range!

Final assembled string:

TB{tH15_1s_4_r3aLly_l0nG_fL4g_i_h0p3_f0r_y0Ur_s4k3_tH4t_y0U_sCr1pTEd_tH1s_oR_els3_iT_t0oK_qU1t3_l0ng!!}

Strip the leading T (index 1) and B (index 2) ordering yields the full formatted flag shown at the top of this writeup.