Rega's Town
Rega's Town
Platform: HackTheBox | Category: Reversing | Type: Challenge | Difficulty: Easy | OS: NA | Author: D3v0o0Nu11 | Date: 2026-02-28 | Status: Solved Techniques: regex_constraint_extraction, position_constraint_mapping, ascii_product_verification, string_analysis, leetspeak_decoding
Summary
ELF 64-bit LSB PIE executable, x86-64, dynamically linked, not stripped, with debug info. Rust binary rega_town (~19MB, typical for Rust). The program asks for a "secret passphrase" and validates input in two stages: first through 9 regex patterns, then through ASCII code product verification of s
Recon
Port scan
nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
| Port | Service | Version | Notes |
|---|---|---|---|
| <PORT> | <SVC> | <VER> | <notes> |
Enumeration highlights
- Event:
HackTheBox| ID:20260228_hackthebox_regas_town - Tags: elf64, leetspeak, regex, constraint_satisfaction, rust, ascii_arithmetic, string_validation
- Indicators: Rust binary with regex crate, embedded POSIX regex patterns in strings output, challenge name hints at regex (Rega = Regex), multi-stage input validation: regex + arithmetic check, character product comparison using u128 arithmetic
- Source:
20260228_hackthebox_regas_town.md
Foothold
Vulnerability / Misconfiguration
- Regex_constraint_extraction
- Position_constraint_mapping
- Ascii_product_verification
- String_analysis
- Leetspeak_decoding
<command>
Exploitation
- See original writeup content for detailed exploitation.
Privilege Escalation
Enumeration
sudo -l find / -perm -4000 2>/dev/null getcap -r / 2>/dev/null cat /etc/crontab ps aux
Exploitation
- N/A for challenge-type writeup; see exploitation above.
- Flag obtained via challenge solve.
<command>
Flags
| Flag | Location | Value |
|---|---|---|
| flag | REDACTED |
Key Takeaways / Lessons
- regex_constraint_extraction
- position_constraint_mapping
- ascii_product_verification
- string_analysis
- leetspeak_decoding
- Tags: elf64, leetspeak, regex, constraint_satisfaction, rust, ascii_arithmetic, string_validation
Original Writeup
<details><summary>Click to expand original content</summary>Description
Welcome to Rega Town, a quaint little place where everyone communicates through the magic of patterns and rules!
ELF 64-bit LSB PIE executable, x86-64, dynamically linked, not stripped, with debug info. Rust binary rega_town (~19MB, typical for Rust). The program asks for a "secret passphrase" and validates input in two stages: first through 9 regex patterns, then through ASCII code product verification of substrings.
Analysis
Initial Reconnaissance
$ file rega_town ELF 64-bit LSB PIE executable, x86-64, dynamically linked, with debug_info, not stripped $ strings rega_town | grep -E "Welcome|passphrase|secret" Welcome to our secret town! Enter secret passphrase:
The binary is not stripped and contains debug info — Rust symbols are fully available for analysis.
Key Functions
rega_town::main @ 0x58230 — entry point, prints greeting, reads input
rega_town::filter_input @ 0x578f0 — validation through 9 regex patterns
rega_town::check_input @ 0x57c00 — extracts substrings, verifies ASCII code products
rega_town::multiply_characters @ 0x57bd0 — computes product of ASCII values (u128)
Stage 1: Extracting Regex Patterns
Running strings on the binary revealed a block of 9 POSIX-style regex patterns located between called Result::unwrap() on an Err value and src/main.rs:
| # | Regex | Constraint |
|---|---|---|
| 1 | ^.{33}$ | String length = 33 characters |
| 2 | (?:^[\x48][\x54][\x42]).* | Starts with "HTB" (0x48=H, 0x54=T, 0x42=B) |
| 3 | ^.{3}(\x7b).*(\x7d)$ | Position 3 = { (0x7b), ends with } (0x7d) |
| 4 | ^[[:upper:]]{3}.[[:upper:]].{3}[[:upper:]].{3}[[:upper:]].{3}[[:upper:]].{4}[[:upper:]].{2}[[:upper:]].{3}[[:upper:]].{4}$ | Uppercase at positions 0,1,2,4,8,12,16,21,24,28 |
| 5 | (?:.*\x5f.*) | Contains at least one _ (0x5f) |
| 6 | (?:.[^0-9]*\d.*){5} | Minimum 5 digits |
| 7 | .{24}\x54.\x65.\x54.* | Position 24=T, 26=e (0x65), 28=T |
| 8 | ^.{4}[X-Z]\d._[A]\D\d.................[[:upper:]][n-x]{2}[n|c].$ | Constraints on positions 4-10 and 28-32 |
| 9 | .{11}_T[h|7]\d_[[:upper:]]\dn[a-h]_[O]\d_[[:alpha:]]{3}_.{5} | Constraints on positions 11-27 |
Stage 2: Position Constraint Mapping
Combining all 9 regex rules, each position (0-32) gets a set of constraints:
Pos Char Source
--- ---- ------
0 H Rule 2 (hex 0x48)
1 T Rule 2 (hex 0x54)
2 B Rule 2 (hex 0x42)
3 { Rule 3 (hex 0x7b)
4 Y Rule 8 [X-Z] + Rule 4 uppercase
5 0 Rule 8 \d
6 u Rule 8 any char
7 _ Rule 5 + Rule 8 separator
8 A Rule 8 [A] + Rule 4 uppercase
9 r Rule 8 \D (non-digit)
10 3 Rule 8 \d
11 _ Rule 9 separator
12 T Rule 9 T + Rule 4 uppercase
13 h Rule 9 [h|7]
14 3 Rule 9 \d
15 _ Rule 9 separator
16 K Rule 4 uppercase + Rule 9 [[:upper:]]
17 1 Rule 9 \d
18 n Rule 9 n
19 g Rule 9 [a-h]
20 _ Rule 9 separator
21 O Rule 9 [O] + Rule 4 uppercase
22 ? Rule 9 \d → digit, but WHICH one?
23 _ Rule 9 separator
24 T Rule 7 (hex 0x54) + Rule 4 uppercase
25 h Rule 9 [[:alpha:]]
26 e Rule 7 (hex 0x65)
27 _ separator
28 T Rule 7 + Rule 8 [[:upper:]]
29 o Rule 8 [n-x]
30 w Rule 8 [n-x]
31 n Rule 8 [n|c]
32 } Rule 3 (hex 0x7d)
Regex constraints give us: HTB{REDACTED?_The_Town} — position 22 remains undetermined (any digit 0-9).
Stage 3: ASCII Code Product Verification (check_input)
Reverse engineering the check_input function revealed additional validation. The binary extracts 7 substrings and computes the product of ASCII codes for each character (via multiply_characters, using u128 arithmetic), comparing the result with a hardcoded constant:
| Range | Substring | Expected Product |
|---|---|---|
| [4..7) | Y0u | 0x7a070 (499824) |
| [8..11) | Ar3 | 0x5c436 (377910) |
| [12..15) | Th3 | 0x6cc60 (445536) |
| [16..20) | K1ng | 0x27b5776 (41637750) |
| [21..23) | O? | 0x10f9 (4345) |
| [24..27) | The | 0xd76a0 (882336) |
| [28..32) | Town | 0x7465a58 (122051160) |
Key calculation for position 22:
- Substring
[21..23)= "O?" must yield product 4345 ord('O') = 794345 / 79 = 55 = ord('7')- Therefore, position 22 =
7
Solution
#!/usr/bin/env python3
"""
Rega's Town — HackTheBox Reverse Engineering
Recovering the flag from regex constraints + ASCII product check
"""
# 9 regex patterns extracted from the binary via strings
regexes = [
r'^.{33}$', # length 33
r'(?:^[\x48][\x54][\x42]).*', # starts with HTB
r'^.{3}(\x7b).*(\x7d)$', # pos3={, ends with }
r'^[[:upper:]]{3}.[[:upper:]].{3}[[:upper:]].{3}[[:upper:]].{3}[[:upper:]].{4}[[:upper:]].{2}[[:upper:]].{3}[[:upper:]].{4}$',
r'(?:.*\x5f.*)', # contains _
r'(?:.[^0-9]*\d.*){5}', # minimum 5 digits
r'.{24}\x54.\x65.\x54.*', # pos24=T, pos26=e, pos28=T
r'^.{4}[X-Z]\d._[A]\D\d.................[[:upper:]][n-x]{2}[n|c].$',
r'.{11}_T[h|7]\d_[[:upper:]]\dn[a-h]_[O]\d_[[:alpha:]]{3}_.{5}',
]
# From regex constraints: HTB{REDACTED?_The_Town}
# Position 22 is unknown — determine via ASCII product check
# Hardcoded product for substring [21..23) = "O?"
expected_product = 0x10f9 # 4345
o_ascii = ord('O') # 79
missing_char = expected_product // o_ascii # 4345 / 79 = 55
assert expected_product == o_ascii * missing_char
print(f"Position 22 = chr({missing_char}) = '{chr(missing_char)}'") # '7'
flag = "HTB{REDACTED}"
print(f"Flag: {flag}")
# Verification of all product checks
substrings = {
(4, 7): 0x7a070, # Y0u
(8, 11): 0x5c436, # Ar3
(12, 15): 0x6cc60, # Th3
(16, 20): 0x27b5776, # K1ng
(21, 23): 0x10f9, # O7
(24, 27): 0xd76a0, # The
(28, 32): 0x7465a58, # Town
}
for (start, end), expected in substrings.items():
substr = flag[start:end]
product = 1
for c in substr:
product *= ord(c)
assert product == expected, f"FAIL: {substr} product={product} expected={expected}"
print(f" [{start}..{end}) = '{substr}' → product = {product} ✓")
print(f"\nAll checks passed! Flag: {flag}")
</details>
Auto-tracked: saved to WriteUps; run
/xesor-reviseto fold lessons into XESXor_Methodology.md.
signed by XESXOR