web,forensics A cracked tails partition, a packet...

創建於:2026年4月12日

使用 GPT-5.4 Thinking 回答 Chat01

問題

Brick_Road.zip

web,forensics

A cracked tails partition, a packet capture, and a darkweb criminal named purple. FYI, you'll need TOR to complete this challenge; keep legal and institutional regulations in mind.
这是一道ctf题目

Python

python
import os, sqlite3, json, textwrap, re, struct, sys, hashlib, base64 paths = { "sqlite": "/mnt/data/brickroad/tails/enc/bookmarks/places.sqlite", "wal": "/mnt/data/brickroad/tails/enc/bookmarks/places.sqlite-wal", } for k,p in paths.items(): print(k, os.path.exists(p), os.path.getsize(p)) # connect readonly maybe WAL automatically? conn = sqlite3.connect(paths["sqlite"]) cur=conn.cursor() cur.execute("SELECT name FROM sqlite_master WHERE type='table'") print(cur.fetchall()[:20])

Python

python
import os, sqlite3, json, re, textwrap, binascii for p in ["/mnt/data/places.sqlite", "/mnt/data/places.sqlite-wal", "/mnt/data/creds.hex"]: print(p, os.path.exists(p), os.path.getsize(p)) conn = sqlite3.connect("/mnt/data/places.sqlite") cur=conn.cursor() cur.execute("PRAGMA journal_mode") print("journal_mode", cur.fetchone()) cur.execute("SELECT name FROM sqlite_master WHERE type='table' ORDER BY 1") tables = [r[0] for r in cur.fetchall()] print(tables[:50], "count", len(tables))

Python

python
conn = sqlite3.connect("/mnt/data/places.sqlite") cur=conn.cursor() # recent places cur.execute(""" SELECT id, url, title, visit_count, last_visit_date FROM moz_places ORDER BY COALESCE(last_visit_date,0) DESC LIMIT 50 """) rows = cur.fetchall() rows[:10], len(rows) ([(1, 'http://2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion/', None, 0, None), (2, 'http://pzhdfe7jraknpj2qgu5cz2u3i4deuyfwmonvzu5i3nyw4t4bmg7o5pad.onion/', None, 0, None), (3, 'http://rzuwtpc4wb3xdzrj3yeajsvm3fkq4vbeubm2tdxaqruzzzgs5dwemlad.onion/', None, 0, None), (4, 'about:manual', None, 0, None), (5, 'http://xmrhfasfg5suueegrnc4gsgyi2tyclcy5oz7f5drnrodmdtob6t2ioyd.onion/', None, 0, None), (6, 'http://v236xhqtyullodhf26szyjepvkbv6iitrhjgrqj4avaoukebkk6n6syd.onion/', None, 0, None), (7, 'https://donate.torproject.org/', None, 0, None), (8, 'http://yq5jjvr7drkjrelzhut7kgclfuro65jjlivyzfmxiq2kyv5lickrl4qd.onion/', None, 0, None), (9, 'http://yfudbrarr7smhd6x7hd7xkkdmcijy5d2i4soar3s2qoow7fg2n5l3dyd.onion/', None, 0, None)], 9)

Python

python
import importlib, sys mods = ["scapy.all", "dpkt", "pyshark"] for m in mods: try: mod = importlib.import_module(m) print(m, "OK", mod) except Exception as e: print(m, "ERR", type(e).__name__, e)

Python

python
import struct, collections, ipaddress, os, binascii, math, re, json pcap = "/mnt/data/brickroad/dist/packets.pcapng" # make readable copy maybe root file? let's just open; should be readable? use os.stat import os print(oct(os.stat(pcap).st_mode), os.path.getsize(pcap))

Python

python
import os, struct, collections, ipaddress, binascii, re, json pcap = "/mnt/data/packets.pcapng" print(os.path.getsize(pcap)) # Pcapng parser SHB = 0x0A0D0D0A IDB = 0x00000001 EPB = 0x00000006 SPB = 0x00000003 ISB = 0x00000005 NRB = 0x00000004 def align4(n): return (n + 3) & ~3 interfaces = [] packets = [] with open(pcap, 'rb') as f: data = f.read() off=0 endian = '<' block_counts=collections.Counter() while off + 12 <= len(data): btype = struct.unpack_from('<I', data, off)[0] blen = struct.unpack_from('<I', data, off+4)[0] if blen < 12 or off+blen > len(data): # maybe wrong endian? break block = data[off:off+blen]

Python

python
import struct, collections, ipaddress, binascii, re, json, math def parse_eth(pkt): if len(pkt) < 14: return None dst, src, etype = pkt[:6], pkt[6:12], struct.unpack('!H', pkt[12:14])[0] return {'dst':':'.join(f'{b:02x}' for b in dst), 'src':':'.join(f'{b:02x}' for b in src), 'etype':etype, 'payload':pkt[14:]} def parse_ipv4(buf): if len(buf)<20: return None vihl=buf[0] ver=vihl>>4; ihl=(vihl&0xf)*4 if ver!=4 or len(buf)<ihl: return None total=struct.unpack('!H', buf[2:4])[0] proto=buf[9] src=str(ipaddress.IPv4Address(buf[12:16])) dst=str(ipaddress.IPv4Address(buf[16:20])) return {'proto':proto,'src':src,'dst':dst,'payload':buf[ihl:

Python

python
# Summarize flows from collections import Counter flowc = Counter(flows) for flow, cnt in flowc.most_common(30): print(cnt, flow)

Python

python
hexs=open('/mnt/data/creds.hex').read().strip() b=bytes.fromhex(hexs) b, len(b) (b'\n\x07user_10\x12D\n \x8d\x14(Y\x0c\xb49\x06xoD\xc0l\xc01T9\xa6\x12\xca\xb6\xbeJ\xbe*:98\xe5|\x0f\xc8\x12 \x04\xe6?\xfdP\xe9eDmh7nG\xe4F\x0f\xe9\xe7U\xd6\xdd\x87\x8c\x04ZH\x8a\xedvNL\xd2', 79)

Python

python
# extract ascii payloads with printable content from tcp flows interesting=[] for iface_id,tsh,tsl,p in packets: eth=parse_eth(p) if not eth: continue net=parse_ipv4(eth['payload']) if eth['etype']==0x0800 else None if not net or net['proto']!=6: continue tcp=parse_tcp(net['payload']) if not tcp or not tcp['payload']: continue payload=tcp['payload'] if b'SSH-' in payload or b'HTTP' in payload or b'GET ' in payload or b'POST ' in payload or b'USER ' in payload or b'PASS ' in payload: interesting.append((net['src'],tcp['sport'],net['dst'],tcp['dport'],payload[:200])) interesting[:20], len(interesting) ([('2.57.122.192', 36516, '139.84.236.244', 22, b'SSH-2.0-PUTTY\r\n'), ('139.84.236.244', 22, '2.57.122.192', 36516, b'SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13.15\r\n'), ('139.84.236.244', 37224, '70.34.203.113', 1984, b'GET /1afaa1e32f63623160046882266183e1d134f008456ea99031ffdee773e1721c HTTP/1.1\r\nHost: 70.34.203.113:1984\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Extensions: '), ('139.84.236.244', 37240, '70.34.203.113', 1984, b'GET /5b7197269725f041fa094bbf763cca8b3104a0902f2b984da61b0cbc6775a997 HTTP/1.1\r\nHost: 70.34.203.113:1984\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Extensions: '), ('70.34.203.113', 1984, '139.84.236.244', 37224, b'HTTP/1.1 101 Switching Protocols\r\nconnection: upgrade\r\nupgrade: websocket\r\nsec-websocket-accept: 0UyQnU1XEqHk8PbIy0OaHAkP8Pw=\r\ndate: Sat, 11 Apr 2026 02:20:35 GMT\r\n\r\n'), ('70.34.203.113', 1984, '139.84.236.244', 37240, b'HTTP/1.1 101 Switching Protocols\r\nconnection: upgrade\r\nupgrade: websocket\r\nsec-websocket-accept: GfrBo/6GAxz6qZ4nOEvj9i+uRrc=\r\ndate: Sat, 11 Apr 2026 02:20:35 GMT\r\n\r\n'), ('139.84.236.244', 37246, '70.34.203.113', 1984, b'GET /8346251b1c5e53795cfa5aa4a14997365bcba0f1e1835e1fde09a0f663d25416 HTTP/1.1\r\nHost: 70.34.203.113:1984\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Extensions: '), ('70.34.203.113', 1984, '139.84.236.244', 37246, b'HTTP/1.1 101 Switching Protocols\r\nconnection: upgrade\r\nupgrade: websocket\r\nsec-websocket-accept: SgxpLn4K5q4xxdqK3p7L56Tdq0I=\r\ndate: Sat, 11 Apr 2026 02:20:36 GMT\r\n\r\n'), ('139.84.236.244', 50294, '70.34.203.113', 1984, b'GET /1afaa1e32f63623160046882266183e1d134f008456ea99031ffdee773e1721c HTTP/1.1\r\nHost: 70.34.203.113:1984\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Extensions: '), ('139.84.236.244', 50298, '70.34.203.113', 1984, b'GET /5517253bc07165c5b7878dc96debf38c06fed1c126c22925d28a2ac79cb89352 HTTP/1.1\r\nHost: 70.34.203.113:1984\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Extensions: '), ('70.34.203.113', 1984, '139.84.236.244', 50294, b'HTTP/1.1 101 Switching Protocols\r\nconnection: upgrade\r\nupgrade: websocket\r\nsec-websocket-accept: zrf56977W7fmiFlyP75S7C8ehxo=\r\ndate: Sat, 11 Apr 2026 02:22:21 GMT\r\n\r\n'), ('70.34.203.113', 1984, '139.84.236.244', 50298, b'HTTP/1.1 101 Switching Protocols\r\nconnection: upgrade\r\nupgrade: websocket\r\nsec-websocket-accept: vJJB70argfCKh0RGb7ti/yB7foY=\r\ndate: Sat, 11 Apr 2026 02:22:21 GMT\r\n\r\n'), ('139.84.236.244', 50312, '70.34.203.113', 1984, b'GET /8346251b1c5e53795cfa5aa4a14997365bcba0f1e1835e1fde09a0f663d25416 HTTP/1.1\r\nHost: 70.34.203.113:1984\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Extensions: '), ('70.34.203.113', 1984, '139.84.236.244', 50312, b'HTTP/1.1 101 Switching Protocols\r\nconnection: upgrade\r\nupgrade: websocket\r\nsec-websocket-accept: B7WNVJN0RmpXeWv+40f+D/VLWDA=\r\ndate: Sat, 11 Apr 2026 02:22:22 GMT\r\n\r\n'), ('195.178.110.15', 57186, '139.84.236.244', 22, b'SSH-2.0-PUTTY\r\n'), ('139.84.236.244', 22, '195.178.110.15', 57186, b'SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13.15\r\n'), ('139.84.236.244', 22, '80.94.92.165', 34144, b'SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13.15\r\n'), ('139.84.236.244', 22, '80.94.92.187', 44078, b'SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13.15\r\n'), ('80.94.92.187', 44078, '139.84.236.244', 22, b'SSH-2.0-Go\r\n'), ('2.57.121.118', 40982, '139.84.236.244', 22, b'SSH-2.0-PUTTY\r\n')], 64)

Python

python
conn = sqlite3.connect("/mnt/data/places.sqlite") cur = conn.cursor() # join bookmarks cur.execute(""" SELECT b.id, b.title, p.url, b.dateAdded, b.lastModified, b.type, b.parent, b.position FROM moz_bookmarks b LEFT JOIN moz_places p ON b.fk = p.id ORDER BY b.id """) rows = cur.fetchall() len(rows), rows[:20] (16, [(1, '', None, 1775925886077000, 1775926382735000, 2, 0, 0), (2, 'menu', None, 1775925886077000, 1775925888724000, 2, 1, 0), (3, 'toolbar', None, 1775925886077000, 1775926382735000, 2, 1, 1), (4, 'tags', None, 1775925886077000, 1775925886077000, 2, 1, 2), (5, 'unfiled', None, 1775925886077000, 1775925888714000, 2, 1, 3), (6, 'mobile', None, 1775925888515000, 1775925888714000, 2, 1, 4), (7, 'Tor Project Bookmarks', None, 1775925888724000, 1775925888724000, 2, 2, 0), (8, 'Learn more about Tor', 'http://2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion/', 1775925888724000, 1775925888724000, 1, 7, 0), (9, 'The Tor Blog', 'http://pzhdfe7jraknpj2qgu5cz2u3i4deuyfwmonvzu5i3nyw4t4bmg7o5pad.onion/', 1775925888724000, 1775925888724000, 1, 7, 1), (10, 'Tor Support portal - Get help', 'http://rzuwtpc4wb3xdzrj3yeajsvm3fkq4vbeubm2tdxaqruzzzgs5dwemlad.onion/', 1775925888724000, 1775925888724000, 1, 7, 2), (11, 'Tor Browser Manual', 'about:manual', 1775925888724000, 1775925888724000, 1, 7, 3), (12, 'Tor Community - Get Involved', 'http://xmrhfasfg5suueegrnc4gsgyi2tyclcy5oz7f5drnrodmdtob6t2ioyd.onion/', 1775925888724000, 1775925888724000, 1, 7, 4), (13, 'Tor Project Forum', 'http://v236xhqtyullodhf26szyjepvkbv6iitrhjgrqj4avaoukebkk6n6syd.onion/', 1775925888724000, 1775925888724000, 1, 7, 5), (14, 'Donate - Keep Tor Strong', 'https://donate.torproject.org/', 1775925888724000, 1775925888724000, 1, 7, 6), (15, 'Circumvent Censorship, Get Bridges', 'http://yq5jjvr7drkjrelzhut7kgclfuro65jjlivyzfmxiq2kyv5lickrl4qd.onion/', 1775925888724000, 1775925888724000, 1, 7, 7), (16, 'Brick Road', 'http://yfudbrarr7smhd6x7hd7xkkdmcijy5d2i4soar3s2qoow7fg2n5l3dyd.onion/', 1775926382735000, 1775926382735000, 1, 3, 0)])

Python

python
conn = sqlite3.connect("/mnt/data/places.sqlite") cur = conn.cursor() for q in [ "SELECT id, url, title, visit_count, frecency, last_visit_date FROM moz_places ORDER BY id", "SELECT id, place_id, visit_date, visit_type, from_visit, opener_visit FROM moz_historyvisits ORDER BY id LIMIT 50", "SELECT * FROM moz_inputhistory LIMIT 50", "SELECT * FROM moz_keywords LIMIT 50", "SELECT * FROM moz_meta LIMIT 50", ]: print("QUERY", q) try: cur.execute(q) rows=cur.fetchall() for r in rows[:20]: print(r) print("count", len(rows)) except Exception as e: print("ERR", e) print()

Python

python
for table in ['moz_historyvisits','moz_historyvisits_extra','moz_places_metadata','moz_places_extra']: print(table) cur.execute(f"PRAGMA table_info({table})") print(cur.fetchall()) cur.execute(f"SELECT * FROM {table} LIMIT 5") rows=cur.fetchall() for r in rows: print(r) print()

Python

python
# Collect payload bytes per tcp flow for 70.34.203.113:1984 from collections import defaultdict streams = defaultdict(bytearray) for iface_id,tsh,tsl,p in packets: eth=parse_eth(p) if not eth: continue net=parse_ipv4(eth['payload']) if eth['etype']==0x0800 else None if not net or net['proto']!=6: continue tcp=parse_tcp(net['payload']) if not tcp or not tcp['payload']: continue if tcp['sport']==1984 or tcp['dport']==1984: key=(net['src'],tcp['sport'],net['dst'],tcp['dport']) streams[key].extend(tcp['payload']) for k,v in streams.items(): print(k, len(v), repr(bytes(v[:200]))) print()

Python

python
import datetime, math def moz_to_dt(us): return datetime.datetime(1970,1,1)+datetime.timedelta(microseconds=us) for val in [1775925888724000,1775926382735000]: print(val, moz_to_dt(val))

Python

python
# show full http requests/responses for 1984 for k,v in streams.items(): if k[1]!=1984 and k[3]!=1984: continue try: txt = bytes(v).decode('latin1', errors='replace') except: txt = repr(bytes(v[:500])) print("FLOW",k) # print until end headers idx = txt.find('\r\n\r\n') print(txt[:idx+4 if idx!=-1 else 500]) print('-'*60)

Python

python
def websocket_frames(data, from_client=False, max_frames=20): i=0; out=[] while i+2<=len(data) and len(out)<max_frames: b1=data[i]; b2=data[i+1] fin=b1>>7; rsv=(b1>>4)&0x7; opcode=b1&0x0f mask=b2>>7; plen=b2&0x7f; i+=2 if plen==126: if i+2>len(data): break plen=struct.unpack('!H', data[i:i+2])[0]; i+=2 elif plen==127: if i+8>len(data): break plen=struct.unpack('!Q', data[i:i+8])[0]; i+=8 mkey=None if mask: if i+4>len(data): break mkey=data[i:i+4]; i+=4 if i+plen>len(data

思考

undefined

Python

python
import hashlib, itertools, base64, binascii vals = { 'user_10': b'user_10', 'Brick Road': b'Brick Road', 'brick road': b'brick road', 'purple': b'purple', 'yfud...': b'yfudbrarr7smhd6x7hd7xkkdmcijy5d2i4soar3s2qoow7fg2n5l3dyd.onion', 'hash1': bytes.fromhex('8d1428590cb43906786f44c06cc0315439a612cab6be4abe2a3a3938e57c0fc8'), 'hash2': bytes.fromhex('04e63ffd50e965446d68376e47e4460fe9e755d6dd878c045a488aed764e4cd2'), } targets = [ '1afaa1e32f63623160046882266183e1d134f008456ea99031ffdee773e1721c', '5b7197269725f041fa094bbf763cca8b3104a0902f2b984da61b0cbc6775a997', '8346251b1c5e53795cfa5aa4a14997365bcba0f1e1835e1fde09a0f663d25416', '5517253bc07165c5b7878dc96debf38c06fed1c126c22925d28a2ac79cb89352', ] for name,v in vals.items(): print(name, 'sha256', hashlib.sha256(v).hexdigest()) print(name, 'sha256hexstr', hashlib.sha256(v.hex().encode()).hexdigest())

思考

undefined

Python

python
from collections import Counter macs=Counter() etypes=Counter() for _,_,_,p in packets: eth=parse_eth(p) if not eth: continue macs[eth['src']]+=1; macs[eth['dst']]+=1 etypes[eth['etype']]+=1 print("MACs", macs.most_common()) print("etypes", etypes)

Python

python
import importlib, os, sys for m in ['nacl.bindings','cryptography.hazmat.primitives.asymmetric.x25519','ed25519']: try: mod=importlib.import_module(m) print("OK",m) except Exception as e: print("ERR",m,e)

Python

python
from cryptography.hazmat.primitives.asymmetric import x25519 from cryptography.hazmat.primitives import serialization b=bytes.fromhex(open('/mnt/data/creds.hex').read().strip()) # parse protobuf manually assert b[0]==0x0a and b[1]==7 user=b[2:9].decode() inner=b[11:11+68] # maybe field2 print(user, inner.hex()) assert inner[0]==0x0a and inner[1]==0x20 and inner[34]==0x12 and inner[35]==0x20 a=inner[2:34]; c=inner[36:68] print(len(a),len(c),a.hex(),c.hex()) try: priv = x25519.X25519PrivateKey.from_private_bytes(a) pub = priv.public_key().public_bytes(encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw)

Python

python
from cryptography.hazmat.primitives.asymmetric import ed25519 from cryptography.hazmat.primitives import serialization a=bytes.fromhex('8d1428590cb43906786f44c06cc0315439a612cab6be4abe2a3a3938e57c0fc8') c=bytes.fromhex('04e63ffd50e965446d68376e47e4460fe9e755d6dd878c045a488aed764e4cd2') try: priv=ed25519.Ed25519PrivateKey.from_private_bytes(a) pub=priv.public_key().public_bytes(encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw) print(pub.hex(), pub==c) except Exception as e: print("ed25519 fail", e)

Python

python
# Re-parse packets with seq numbers for port 1984 flow def parse_tcp_full(buf): if len(buf)<20: return None sport,dport,seq,ack,offres,flags,win,chk,urg = struct.unpack('!HHIIBBHHH', buf[:20]) off=(offres>>4)*4 return {'sport':sport,'dport':dport,'flags':flags,'payload':buf[off:], 'off':off, 'seq':seq, 'ack':ack} flow_packets=[] for iface_id,tsh,tsl,p in packets: eth=parse_eth(p) if not eth: continue net=parse_ipv4(eth['payload']) if eth['etype']==0x0800 else None if not net or net['proto']!=6: continue tcp=parse_tcp_full(net['payload']) if not tcp: continue if (net['src'],tcp['sport'],net['dst'],tcp['dport']) == ('139.84.236.244',50298,'70.34.203.113',1984) or \ (net['src'],tcp['sport'],net['dst'],tcp['dport']) == ('70.34.203.113',1984,'139.84.236.244',50298): flow_packets.append((net['src'],tcp['sport'],net['dst'],tcp['dport'],tcp['seq'],tcp['ack'],tcp['flags'],tcp['payload'])) len(flow_packets), flow_packets[:5] (74, [('139.84.236.244', 50298, '70.34.203.113', 1984, 4156248989, 0, 2, b''), ('70.34.203.113', 1984, '139.84.236.244', 50298, 1235898971, 4156248990, 18, b''), ('139.84.236.244', 50298, '70.34.203.113', 1984, 4156248990, 1235898972, 16, b''), ('139.84.236.244', 50298, '70.34.203.113', 1984, 4156248990, 1235898972, 24, b'GET /5517253bc07165c5b7878dc96debf38c06fed1c126c22925d28a2ac79cb89352 HTTP/1.1\r\nHost: 70.34.203.113:1984\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Extensions: permessage-deflate; client_max_window_bits\r\nSec-WebSocket-Key: 4eV1omnBT1qD3Wyv6k/NVQ==\r\n\r\n'), ('70.34.203.113', 1984, '139.84.236.244', 50298, 1235898972, 4156249281, 16, b'')])

Python

python
from collections import defaultdict def reassemble(direction_packets): # direction_packets: list of (seq, payload) segs={} for seq,payload in direction_packets: if not payload: continue # keep longest for same seq if seq not in segs or len(payload) > len(segs[seq]): segs[seq]=payload # naive merge based on increasing seq starting from min out=bytearray() if not segs: return bytes(out) sorted_items=sorted(segs.items()) cur=None for seq,payload in sorted_items: if cur is None: out.extend(payload); cur=seq+len(payload); start=seq else:

Python

python
idx=client_data.find(b'\r\n\r\n') body=client_data[idx+4:] frames=websocket_frames(body,max_frames=30) print("frames", [(f['opcode'],f['len'],f['mask']) for f in frames[:15]]) for i,f in enumerate(frames[:10]): print(i, f['len'], f['payload'][:16].hex(), repr(f['payload'][:60]))

思考

undefined

思考

undefined

思考

Crafting final response with care

I think parsing the sqlite-wal might be unnecessary unless the website’s title page is helpful. I could focus on providing the answer in Chinese, just in case the user prefers it. I’ll also include a clear note to avoid using institutional networks due to legal concerns and cite official Tor sources regarding Tor's safety and limitations with tor2web.

Clarifying note on Tor usage

I should mention the challenge's note about Tor aligns with the official Tor docs, which emphasize that onion services are only accessible via Tor, and tor2web is less safe. I'll cite official sources for that. No need to discuss specific extracted analysis details here, as they don’t need citations.

Thought for 7m 28s

回答

我先把这题里离线就能确定的关键线索整理出来。

1. Tails 持久化分区里最关键的异常项

  • Firefox 书签库里,前 8 个都是 Tails/Tor 默认书签。
  • 唯一后加的可疑书签是:
    • 标题:Brick Road
    • 地址:http://yfudbrarr7smhd6x7hd7xkkdmcijy5d2i4soar3s2qoow7fg2n5l3dyd.onion/
  • 这个书签是后面手动加进去的;默认 Tor 书签时间约是 2026-04-11 16:44:48 UTC,而 Brick Road 书签时间约是 2026-04-11 16:53:02 UTC,晚了大约 8 分钟。

这基本可以当成题目的主入口。

2. creds.hex 不是明文密码,而是一个 protobuf 风格的凭据结构 我把它解出来后,能稳定拿到:

  • 用户名:user_10
  • 后面跟着两段 32 字节二进制值:

8d1428590cb43906786f44c06cc0315439a612cab6be4abe2a3a3938e57c0fc8

04e63ffd50e965446d68376e47e4460fe9e755d6dd878c045a488aed764e4cd2

也就是说,这更像是站点登录所需的密钥材料/设备凭据,不像传统的 username:password

3. 抓包里最值得盯的不是 SSH 扫描,而是一个 WebSocket 服务 packets.pcapng 里最有价值的应用层明文握手是:

  • 139.84.236.244 -> 70.34.203.113:1984
  • HTTP Upgrade 到 WebSocket
  • 路径是 64 位 hex,例如:
    • 1afaa1e32f63623160046882266183e1d134f008456ea99031ffdee773e1721c
    • 5b7197269725f041fa094bbf763cca8b3104a0902f2b984da61b0cbc6775a997
    • 8346251b1c5e53795cfa5aa4a14997365bcba0f1e1835e1fde09a0f663d25416
    • 5517253bc07165c5b7878dc96debf38c06fed1c126c22925d28a2ac79cb89352

另外还有到 213.32.23.113:9100176.123.1.217:9001 的连接,看起来很像 Tor 相关中继流量。大量对 22 端口的 SSH 连接更像背景噪声/暴露主机常见扫描,不像这题真正的主线。

4. 目前最像正解的路线 这题的主线大概率是:

  1. 用 Tails 持久化分区恢复出 Brick Road 的 onion 地址
  2. creds.hex 拿到 user_10 和对应的二进制凭据。
  3. 通过 Tor 打开 Brick Road,用这些凭据登录或恢复会话。
  4. 在站内再去找题面说的那个 darkweb 犯罪分子 purple,最后拿 flag。

我在离线证据里没有直接找到 purple 这个字符串,所以它大概率不在 zip 里明文出现,而是在你连上 Brick Road 之后、站内页面/账户内容里。

5. 你本地接下来该怎么做 按照 Tor Project,.onion 服务本来就只能通过 Tor 访问;Tor Project 也明确说 Tor2web 会移除 Tor 的保护,不如直接用 Tor Browser。(Tor Community)

所以建议你在隔离环境里做下面几步:

  • Tor Browser 打开
    http://yfudbrarr7smhd6x7hd7xkkdmcijy5d2i4soar3s2qoow7fg2n5l3dyd.onion/
  • 先试最直接的登录元素:
    • 用户名:user_10
    • 看站点是否要求导入/粘贴凭据、恢复 seed、key file、token
  • 如果页面出现“恢复账户 / import key / paste credential / device secret”之类入口,就把那两段 32-byte 值作为恢复材料去试,而不是只当密码。

6. 我对这题目前的结论 你现在已经拿到了这题最关键的三个 IOC:

  • onion 入口yfudbrarr7smhd6x7hd7xkkdmcijy5d2i4soar3s2qoow7fg2n5l3dyd.onion
  • 用户名user_10
  • 二进制凭据:上面那两段 32-byte hex

这通常已经足够把题推进到 Tor 站内阶段了。

把你打开后的登录页、页面源码,或者站内和 purple 相关的截图贴上来,我就继续帮你把后半段走完。

分享這個問答