← Back to Writeups
CUSTOMMediumMisc

Хеш-станция (Hash Station)

XESXOR9/2/20263 min read
#misc#custom#medium#rainbow-table-/-precomputed-ha#sqlite-for-fast-hash-to-passwo#socket-programming-with-timeou#rockyou.txt-wordlist-usage

Хеш-станция (Hash Station)

Platform: hackerlab | Category: Misc | Type: Challenge | Difficulty: Medium | OS: NA | Author: XESXOR | Date: 2025-12-26 | Status: Solved Techniques: Rainbow table / precomputed hash lookup, SQLite for fast hash-to-password mapping, Socket programming with timeout handling, rockyou.txt wordlist usage

Summary

Service: 192.169.2.100:10300

Хеш-станция (Hash Station) — hackerlab

Description

"Как обычно ломают хеш? Сверяют с хешами паролей из популярного словаря пока не найдется совпадение"

Service: 192.169.2.100:10300

Analysis

When connecting to the service:

  1. Need to send "start" command to begin the game
  2. Service sends an MD5 hash
  3. Need to send the original password within 5 seconds
  4. Repeat 100 times to get the flag

Key problem: 5 seconds per hash is too short for online bruteforce. Pre-computation is required.

Solution

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Step 1: Preparing the Rainbow Table

Download rockyou.txt (14+ million passwords) and create an SQLite database with precomputed hashes:

python
#!/usr/bin/env python3
import sqlite3
import hashlib

# Create database
conn = sqlite3.connect('rainbow.db')
cursor = conn.cursor()
cursor.execute('''
    CREATE TABLE IF NOT EXISTS hashes (
        hash TEXT PRIMARY KEY,
        password TEXT
    )
''')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_hash ON hashes(hash)')

# Load rockyou.txt
with open('/usr/share/wordlists/rockyou.txt', 'r', encoding='latin-1') as f:
    batch = []
    for i, line in enumerate(f):
        password = line.rstrip('\n')
        md5_hash = hashlib.md5(password.encode('latin-1')).hexdigest()
        batch.append((md5_hash, password))
        
        if len(batch) >= 100000:
            cursor.executemany('INSERT OR IGNORE INTO hashes VALUES (?, ?)', batch)
            conn.commit()
            batch = []
            print(f"Processed {i+1} passwords...")
    
    if batch:
        cursor.executemany('INSERT OR IGNORE INTO hashes VALUES (?, ?)', batch)
        conn.commit()
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

conn.close()
print("Rainbow table created!")

Step 2: Adding Additional Patterns

Add short words, dates, and numeric combinations:

python
#!/usr/bin/env python3
import sqlite3
import hashlib
from itertools import product
import string

conn = sqlite3.connect('rainbow.db')
cursor = conn.cursor()

def add_hash(password):
    md5_hash = hashlib.md5(password.encode()).hexdigest()
    cursor.execute('INSERT OR IGNORE INTO hashes VALUES (?, ?)', (md5_hash, password))

# Short words (1-4 characters)
for length in range(1, 5):
    for combo in product(string.ascii_lowercase, repeat=length):
        add_hash(''.join(combo))
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# Numbers (0-999999)
for i in range(1000000):
    add_hash(str(i))

# Dates in various formats
for year in range(1950, 2025):
    for month in range(1, 13):
        for day in range(1, 32):
            # DDMMYYYY
            add_hash(f"{day:02d}{month:02d}{year}")
            # MMDDYYYY
            add_hash(f"{month:02d}{day:02d}{year}")
            # YYYYMMDD
            add_hash(f"{year}{month:02d}{day:02d}")

conn.commit()
conn.close()

Step 3: Solution Script

python
#!/usr/bin/env python3
import socket
import sqlite3
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

def solve():
    # Connect to database
    conn = sqlite3.connect('rainbow.db')
    cursor = conn.cursor()
    
    # Connect to service
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.settimeout(10)
    sock.connect(('192.169.2.100', 10300))
    
    # Read greeting
    data = sock.recv(4096).decode()
    print(data)
    
    # Send start
    sock.send(b'start\n')
    
    cracked = 0
    while True:
        try:
            data = sock.recv(4096).decode().strip()
            print(f"Received: {data}")
            
            if 'CODEBY{' in data:
                print(f"\n[+] FLAG: {data}")
                break
            
            # Extract hash (32 hex characters)
            md5_hash = None
            for word in data.split():
                if len(word) == 32 and all(c in '0123456789abcdef' for c in word.lower()):
                    md5_hash = word.lower()
                    break
            
            if not md5_hash:
                continue
            
            # Look up in database
            cursor.execute('SELECT password FROM hashes WHERE hash = ?', (md5_hash,))
            result = cursor.fetchone()
            
            if result:
                password = result[0]
                cracked += 1
                print(f"[{cracked}/100] {md5_hash} -> {password}")
                sock.send(f"{password}\n".encode())
            else:
                print(f"[-] Hash not found: {md5_hash}")
                break
                
        except socket.timeout:
            print("Timeout!")
            break
        except Exception as e:
            print(f"Error: {e}")
            break
    
    conn.close()
    sock.close()
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

if __name__ == '__main__':
    solve()

Result

[1/100] 5f4dcc3b5aa765d61d8327deb882cf99 -> password
[2/100] e10adc3949ba59abbe56e057f20f883e -> 123456
...
[99/100] 827ccb0eea8a706c4c34a16891f84e7b -> 12345
[100/100] 25d55ad283aa400af464c76d713c07ad -> 12345678

[+] FLAG: CODEBY{REDACTED}

Alternative Approaches

  1. Hashcat with GPU — faster for online bruteforce, but requires GPU
  2. Pre-built rainbow tables — can download ready-made tables for MD5
  3. Online services — crackstation.net, but slow for 100 hashes ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Optimizations

  • SQLite with index on hash — O(log n) lookup
  • Batch insert when creating the table — speeds up loading by 10x
  • Preloading database into memory (:memory:) — even faster ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍