<?xml version="1.0" encoding="utf-8" ?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:tt="http://teletype.in/" xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/"><title>Beavers0</title><subtitle>CTF Team</subtitle><author><name>Beavers0</name></author><id>https://teletype.in/atom/beaverszero</id><link rel="self" type="application/atom+xml" href="https://teletype.in/atom/beaverszero?offset=0"></link><link rel="alternate" type="text/html" href="https://teletype.in/@beaverszero?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=beaverszero"></link><link rel="next" type="application/rss+xml" href="https://teletype.in/atom/beaverszero?offset=10"></link><link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></link><updated>2026-04-17T16:58:32.102Z</updated><entry><id>beaverszero:NYCTFCrypto2026</id><link rel="alternate" type="text/html" href="https://teletype.in/@beaverszero/NYCTFCrypto2026?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=beaverszero"></link><title>Crypto Writeups</title><published>2026-01-16T23:35:17.586Z</published><updated>2026-01-16T23:35:17.586Z</updated><summary type="html">Write-ups for Crypto tasks by Kata. English is not my main language, so I might have problems with explanations.</summary><content type="html">
  &lt;h2 id=&quot;NWZW&quot;&gt;Preview&lt;/h2&gt;
  &lt;p id=&quot;zyB9&quot;&gt;Write-ups for Crypto tasks by Kata. English is not my main language, so I might have problems with explanations.&lt;/p&gt;
  &lt;p id=&quot;vmkE&quot;&gt;&lt;/p&gt;
  &lt;h1 id=&quot;XaCx&quot;&gt;babyCrypto&lt;/h1&gt;
  &lt;pre id=&quot;oFGN&quot;&gt;We have many messages with short MAC-signatures.&lt;/pre&gt;
  &lt;pre id=&quot;m3uE&quot;&gt;Recover the salt and find the flag.&lt;/pre&gt;
  &lt;p id=&quot;FNDk&quot;&gt;&lt;/p&gt;
  &lt;pre id=&quot;bD5c&quot;&gt;&amp;quot;flag_enc&amp;quot;: &amp;quot;641ca46700a47839aa6f1aae7131bb761a947a01be7131b86202bf5c0fbc621794540fa7770bb97e&amp;quot;,
  &amp;quot;params&amp;quot;: {
    &amp;quot;hmac&amp;quot;: &amp;quot;HMAC-SHA256&amp;quot;,
    &amp;quot;trunc_bytes&amp;quot;: 4,
    &amp;quot;salt_len_bytes&amp;quot;: 3,
    &amp;quot;note&amp;quot;: &amp;quot;salt is secret and same for all messages; flag_enc is XOR(flag, salt repeated)&amp;quot;
  }
&lt;/pre&gt;
  &lt;p id=&quot;9Fdn&quot;&gt;Here is the most important part of the file.&lt;/p&gt;
  &lt;p id=&quot;ESTg&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;0Mjw&quot;&gt;Salt is only 3 bytes (2²⁴ possibilities) and it’s reused everywhere. Truncating HMAC to 4 bytes gives us a cheap verification oracle we can brute-force the 3-byte salt, checking that the truncated HMACs for &lt;strong&gt;all&lt;/strong&gt; known messages match the provided MACs. Once salt is known, XORing &lt;code&gt;flag_enc&lt;/code&gt; with repeated salt yields the flag.&lt;/p&gt;
  &lt;p id=&quot;6tp9&quot;&gt;Salt length = 3 bytes -&amp;gt; at most 16777216 candidates. That’s easily brute-forceable.&lt;/p&gt;
  &lt;p id=&quot;7Pu7&quot;&gt;So the attack is iterate over all 2²⁴ salts, compute truncated HMAC for a small set of known messages quickly to filter, then confirm on the rest. When the salt matches every pair, XOR-decrypt &lt;code&gt;flag_enc&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;1uob&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;A6vz&quot;&gt;Decoder:&lt;/p&gt;
  &lt;pre id=&quot;wSLN&quot;&gt;import json, hmac, hashlib
from binascii import unhexlify, hexlify

data = {
  &amp;quot;dataset&amp;quot;: [
    {&amp;quot;message&amp;quot;:&amp;quot;note_0:1d7b4557164378a4&amp;quot;,&amp;quot;mac&amp;quot;:&amp;quot;ceaedcf7&amp;quot;},...  ],
  &amp;quot;flag_enc&amp;quot;:&amp;quot;641ca46700a47839aa6f1aae7131bb761a947a01be7131b86202bf5c0fbc621794540fa7770bb97e&amp;quot;
}

pairs = [(d[&amp;#x27;message&amp;#x27;], d[&amp;#x27;mac&amp;#x27;]) for d in data[&amp;#x27;dataset&amp;#x27;]]
flag_enc = data[&amp;#x27;flag_enc&amp;#x27;]

def truncated_hmac_hex(salt_bytes, message):
    return hmac.new(salt_bytes, message.encode(), hashlib.sha256).digest()[:4].hex()

filter_pairs = pairs[:6]
full_pairs = pairs

found_salt = None
print(&amp;quot;Starting brute force over 2^24 salts (3 bytes). This will take some time but is feasible.&amp;quot;)
for k in range(0, 1&amp;lt;&amp;lt;24):
    salt = k.to_bytes(3, &amp;#x27;big&amp;#x27;)
    good = True
    for m, mac in filter_pairs:
        if truncated_hmac_hex(salt, m) != mac:
            good = False
            break
    if not good:
        continue
    # full verification
    for m, mac in full_pairs:
        if truncated_hmac_hex(salt, m) != mac:
            good = False
            break
    if good:
        found_salt = salt
        break

if found_salt is None:
    print(&amp;quot;No salt found with key=salt (big-endian). Try other interpretations (little-endian) or alternate HMAC usage.&amp;quot;)
else:
    print(&amp;quot;Found salt (hex):&amp;quot;, found_salt.hex())
    # decode flag_enc by repeating salt
    enc = unhexlify(flag_enc)
    dec = bytes([enc[i] ^ found_salt[i % len(found_salt)] for i in range(len(enc))])
    try:
        print(&amp;quot;Recovered flag:&amp;quot;, dec.decode())
    except UnicodeDecodeError:
        print(&amp;quot;Recovered bytes (hex):&amp;quot;, dec.hex())
&lt;/pre&gt;
  &lt;p id=&quot;b1EH&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;wPrA&quot;&gt;Flag: &lt;strong&gt;grodno{Walter_put_your_salt_away_Walter}&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;kv8P&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;IqVf&quot;&gt;&lt;/p&gt;
  &lt;h1 id=&quot;Zog9&quot;&gt;CryBaby&lt;/h1&gt;
  &lt;blockquote id=&quot;H0CS&quot;&gt;The only thing I want to do is cry.&lt;br /&gt;Flag format: grodnogrodno{}&lt;/blockquote&gt;
  &lt;p id=&quot;RhiT&quot;&gt;&lt;/p&gt;
  &lt;pre id=&quot;cR7I&quot;&gt;import random
import binascii
import base64
 def xor_bytes(a: bytes, b: bytes) -&amp;gt; bytes:
length = min(len(a), len(b))
return bytes([a[i] ^ b[i] for i in range(length)])
 def encrypt_round(s: str) -&amp;gt; str:
b = s.encode(&amp;#x27;utf-8&amp;#x27;)
if random.randrange(2) == 0:
return base64.encodebytes(b).decode(&amp;#x27;ascii&amp;#x27;)
else:
return binascii.hexlify(b).decode(&amp;#x27;ascii&amp;#x27;)
 def main():
flag = b&amp;quot;REDACTED&amp;quot;
assert len(flag) == 28
  l = flag[:14]
r = flag[14:]
xored = xor_bytes(l, r) 
c = binascii.hexlify(xored).decode(&amp;#x27;ascii&amp;#x27;)
rounds = random.randint(1, 20)
  for _ in range(rounds):
c = encrypt_round(c)
  with open(&amp;#x27;chall.txt&amp;#x27;, &amp;#x27;w&amp;#x27;, encoding=&amp;#x27;utf-8&amp;#x27;) as f:
f.write(c)
 if __name__ == &amp;quot;__main__&amp;quot;:
main()&lt;/pre&gt;
  &lt;p id=&quot;7U2I&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;6Puo&quot;&gt;&lt;strong&gt;What the challenge does&lt;/strong&gt;&lt;/p&gt;
  &lt;ol id=&quot;adTP&quot;&gt;
    &lt;li id=&quot;sQMR&quot;&gt;&lt;code&gt;flag&lt;/code&gt; is 28 bytes long and split into two halves &lt;code&gt;L&lt;/code&gt; and &lt;code&gt;R&lt;/code&gt; of 14 bytes each.&lt;/li&gt;
    &lt;li id=&quot;yJxT&quot;&gt;They compute &lt;code&gt;xored = L XOR R&lt;/code&gt; (14 bytes).&lt;/li&gt;
    &lt;li id=&quot;0KK8&quot;&gt;&lt;code&gt;xored&lt;/code&gt; is hexlified once (so we have an ascii hex string).&lt;/li&gt;
    &lt;li id=&quot;K2k0&quot;&gt;Then the program applies a &lt;em&gt;random&lt;/em&gt; number (1..20) of rounds; each round chooses randomly between:&lt;/li&gt;
    &lt;ul id=&quot;c6SG&quot;&gt;
      &lt;li id=&quot;8m0U&quot;&gt;&lt;code&gt;base64.encodebytes(...)&lt;/code&gt; (note: this inserts newline bytes), or&lt;/li&gt;
      &lt;li id=&quot;3cYn&quot;&gt;&lt;code&gt;binascii.hexlify(...)&lt;/code&gt; (pure hex)&lt;/li&gt;
    &lt;/ul&gt;
    &lt;li id=&quot;GXWF&quot;&gt;Final output is written to &lt;code&gt;chall.txt&lt;/code&gt;.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;x5ih&quot;&gt;So the final file is just a chain of alternating hex/base64 encodings of the original 14-byte XOR buffer.&lt;/p&gt;
  &lt;p id=&quot;hTdI&quot;&gt;&lt;strong&gt;Attack:&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;1F2E&quot;&gt;Reversing this is straightforward: repeatedly try to decode the blob as hex &lt;strong&gt;and&lt;/strong&gt; as base64. Because each round deterministically maps bytes→ascii→bytes, we can do a branching search (BFS/DFS) over decode choices until we reach a 14-byte result. Once we have the 14 bytes &lt;code&gt;xored&lt;/code&gt;, we reconstruct &lt;code&gt;L&lt;/code&gt; and &lt;code&gt;R&lt;/code&gt; from the known flag format:&lt;/p&gt;
  &lt;ul id=&quot;iLhT&quot;&gt;
    &lt;li id=&quot;PhUs&quot;&gt;We know &lt;code&gt;L&lt;/code&gt; begins with &lt;code&gt;b&amp;quot;grodnogrodno{&amp;quot;&lt;/code&gt; (13 bytes).&lt;/li&gt;
    &lt;li id=&quot;SNwn&quot;&gt;We know the last byte of &lt;code&gt;R&lt;/code&gt; is &lt;code&gt;b&amp;#x27;}&amp;#x27;&lt;/code&gt;.&lt;br /&gt; Given &lt;code&gt;L[i]&lt;/code&gt; (for i=0..12) and &lt;code&gt;xored[i]&lt;/code&gt;, compute &lt;code&gt;R[i] = L[i] ^ xored[i]&lt;/code&gt;. Set &lt;code&gt;R[13] = ord(&amp;#x27;}&amp;#x27;)&lt;/code&gt; and compute &lt;code&gt;L[13] = xored[13] ^ R[13]&lt;/code&gt;. That gives full &lt;code&gt;L&lt;/code&gt; and &lt;code&gt;R&lt;/code&gt;. The original flag is &lt;code&gt;L || R&lt;/code&gt;.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;7KGJ&quot;&gt;There is no further brute force: once &lt;code&gt;xored&lt;/code&gt; is recovered the flag is unique.&lt;/p&gt;
  &lt;p id=&quot;qc1f&quot;&gt;&lt;strong&gt;&lt;br /&gt;Solver:&lt;/strong&gt;&lt;/p&gt;
  &lt;pre id=&quot;p09k&quot;&gt;import sys
import binascii
import base64
from collections import deque

def try_hex(b: bytes):
    s = b.replace(b&amp;#x27;\n&amp;#x27;, b&amp;#x27;&amp;#x27;).replace(b&amp;#x27;\r&amp;#x27;, b&amp;#x27;&amp;#x27;).replace(b&amp;#x27; &amp;#x27;, b&amp;#x27;&amp;#x27;)
    try:
        return binascii.unhexlify(s)
    except (binascii.Error, ValueError):
        return None

def try_b64(b: bytes):
    try:
        return base64.b64decode(b, validate=False)
    except (binascii.Error, ValueError):
        return None

def reconstruct_flag_from_xored(xored: bytes, prefix: bytes = b&amp;quot;grodnogrodno{&amp;quot;, suffix_last: int = ord(&amp;#x27;}&amp;#x27;)):
    if len(xored) != 14:
        raise ValueError(&amp;quot;xored must be 14 bytes&amp;quot;)

    L = [None] * 14
    R = [None] * 14

    for i in range(min(len(prefix), 14)):
        L[i] = prefix[i]

    for i in range(min(len(prefix), 14)):
        R[i] = L[i] ^ xored[i]

    R[13] = suffix_last
    L[13] = xored[13] ^ R[13]

    L_bytes = bytes([x if x is not None else 0 for x in L])
    R_bytes = bytes([x if x is not None else 0 for x in R])
    return L_bytes + R_bytes, L_bytes, R_bytes

def printable(b: bytes):
    try:
        s = b.decode(&amp;#x27;utf-8&amp;#x27;)
        return all(32 &amp;lt;= ord(c) &amp;lt; 127 for c in s)
    except:
        return False

def find_xored_candidates(start_bytes: bytes, max_nodes=2000000):
    seen = set()
    q = deque()
    q.append(start_bytes)
    seen.add(start_bytes)
    found = set()
    nodes = 0

    while q:
        nodes += 1
        if nodes &amp;gt; max_nodes:
            print(f&amp;quot;[!] hit max node limit ({max_nodes}), aborting search.&amp;quot;)
            break

        cur = q.popleft()

        if len(cur) == 14:
            found.add(cur)
            continue

        h = try_hex(cur)
        if h is not None and h not in seen:
            seen.add(h)
            q.append(h)

        b = try_b64(cur)
        if b is not None and b not in seen:
            seen.add(b)
            q.append(b)

    return found

def main():
    path = sys.argv[1] if len(sys.argv) &amp;gt; 1 else &amp;quot;chall.txt&amp;quot;
    raw = open(path, &amp;quot;rb&amp;quot;).read().strip(b&amp;quot;\n\r&amp;quot;)

    print(f&amp;quot;Loaded {path} ({len(raw)} bytes). Starting BFS decode...&amp;quot;)
    candidates = find_xored_candidates(raw)

    if not candidates:
        print(&amp;quot;No 14-byte candidates found. Try increasing max_nodes or check input.&amp;quot;)
        return

    print(f&amp;quot;Found {len(candidates)} candidate(s) of length 14.&amp;quot;)
    for i, x in enumerate(candidates, 1):
        print(f&amp;quot;\nCandidate #{i}: xored = {binascii.hexlify(x).decode()}&amp;quot;)
        flag, L, R = reconstruct_flag_from_xored(x, prefix=b&amp;quot;grodnogrodno{&amp;quot;, suffix_last=ord(&amp;#x27;}&amp;#x27;))
        print(&amp;quot;Left  (14):&amp;quot;, L)
        print(&amp;quot;Right (14):&amp;quot;, R)
        try:
            print(&amp;quot;Flag bytes:&amp;quot;, flag)
            print(&amp;quot;Flag string:&amp;quot;, flag.decode(&amp;#x27;utf-8&amp;#x27;))
        except Exception:
            print(&amp;quot;Flag (hex):&amp;quot;, binascii.hexlify(flag).decode())

if __name__ == &amp;quot;__main__&amp;quot;:
    main()&lt;/pre&gt;
  &lt;p id=&quot;0T5H&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;JFTF&quot;&gt;Flag:&lt;strong&gt; grodnogrodno{new_year_xor26}&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;Gx9n&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;cgSe&quot;&gt;&lt;/p&gt;
  &lt;h1 id=&quot;uann&quot;&gt;ChristmasRSA&lt;/h1&gt;
  &lt;blockquote id=&quot;1Rk7&quot;&gt;How can you celebrate the New Year without RSA?&lt;/blockquote&gt;
  &lt;p id=&quot;PeO5&quot;&gt;&lt;/p&gt;
  &lt;pre id=&quot;iC3v&quot;&gt;from Crypto.Util.number import getPrime, bytes_to_long
from typing import Tuple
 E = 4
 def choose_prime_with_quadratic_residue(message_int: int, bits: int = 512) -&amp;gt; Tuple[int, int]:
attempts = 0
while True:
attempts += 1
p = getPrime(bits)
val = pow(message_int, E, p)
if pow(val, (p - 1) // 2, p) == 1:
return p, attempts
 def main() -&amp;gt; None:
FLAG = b&amp;quot;REDACTED&amp;quot;
m_int = bytes_to_long(FLAG)
  prime_n, tries = choose_prime_with_quadratic_residue(m_int, bits=512)
ciphertext = pow(m_int, E, prime_n)
  print(&amp;quot;n =&amp;quot;, prime_n)
print(&amp;quot;c =&amp;quot;, ciphertext)
 if __name__ == &amp;quot;__main__&amp;quot;:
main()&lt;/pre&gt;
  &lt;p id=&quot;zikV&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;GgGL&quot;&gt;Task work modulo prime &lt;code&gt;p&lt;/code&gt;. If &lt;code&gt;c = m^E mod p&lt;/code&gt; with &lt;code&gt;E = 2^k&lt;/code&gt;, then &lt;code&gt;m&lt;/code&gt; is an E-th root of &lt;code&gt;c&lt;/code&gt;. To find all E-th roots we do:&lt;/p&gt;
  &lt;ul id=&quot;lCp4&quot;&gt;
    &lt;li id=&quot;Vnff&quot;&gt;If &lt;code&gt;E = 1&lt;/code&gt; answer is &lt;code&gt;m = c&lt;/code&gt;.&lt;/li&gt;
    &lt;li id=&quot;itNq&quot;&gt;If &lt;code&gt;E = 2^k&lt;/code&gt; and &lt;code&gt;k &amp;gt; 0&lt;/code&gt;, then compute modular square roots of &lt;code&gt;c&lt;/code&gt;. For each square root &lt;code&gt;r&lt;/code&gt; we recursively compute all &lt;code&gt;2^{k-1}&lt;/code&gt; roots of &lt;code&gt;r&lt;/code&gt; for exponent &lt;code&gt;2^{k-1}&lt;/code&gt;. We obtain up to &lt;code&gt;2^k&lt;/code&gt; candidates. Tonelli–Shanks solves modular sqrt &lt;code&gt;r^2 ≡ x (mod p)&lt;/code&gt; for prime &lt;code&gt;p&lt;/code&gt; efficiently (fast for 512-bit primes). Then test the candidates for the flag pattern.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;zFt2&quot;&gt;&lt;strong&gt;Attack:&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;2kcs&quot;&gt;Parse &lt;code&gt;n&lt;/code&gt;, &lt;code&gt;c&lt;/code&gt;, and &lt;code&gt;E&lt;/code&gt; (verify &lt;code&gt;E&lt;/code&gt; is a power of two; if not — beware).&lt;/p&gt;
  &lt;p id=&quot;slTa&quot;&gt;Check &lt;code&gt;c&lt;/code&gt; is a quadratic residue modulo &lt;code&gt;n&lt;/code&gt; before taking sqrt (Tonelli–Shanks requires that).&lt;/p&gt;
  &lt;p id=&quot;a7RR&quot;&gt;Recursively compute all &lt;code&gt;E&lt;/code&gt;-th roots of &lt;code&gt;c&lt;/code&gt; using Tonelli–Shanks for each square-root step.&lt;/p&gt;
  &lt;p id=&quot;yRxM&quot;&gt;For each root &lt;code&gt;r&lt;/code&gt;, convert to bytes (&lt;code&gt;long_to_bytes&lt;/code&gt;) and test for the flag format &lt;code&gt;grodnogrodno{...}&lt;/code&gt; (or any other validation you know).&lt;/p&gt;
  &lt;p id=&quot;fDDy&quot;&gt;One of candidates should decode to the flag.&lt;/p&gt;
  &lt;p id=&quot;Mym4&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;MGAe&quot;&gt;&lt;strong&gt;Solver:&lt;br /&gt;&lt;/strong&gt;&lt;/p&gt;
  &lt;pre id=&quot;mStk&quot;&gt;from Crypto.Util.number import long_to_bytes
from typing import List, Optional
 MOD = 0x00
CIPHER = 0x00
E = 5 
 def legendre_symbol(a: int, p: int) -&amp;gt; int:
return pow(a % p, (p - 1) // 2, p)
 def sqrt_mod_prime(n: int, p: int) -&amp;gt; Optional[int]:
n %= p
if n == 0:
return 0
if legendre_symbol(n, p) != 1:
return None
  if p % 4 == 3:
r = pow(n, (p + 1) // 4, p)
return r
  q = p - 1
s = 0
while q % 2 == 0:
q //= 2
s += 1
  z = 2
while legendre_symbol(z, p) != p - 1:
z += 1
  c = pow(z, q, p)
r = pow(n, (q + 1) // 2, p)
t = pow(n, q, p)
m = s
  while t % p != 1:
i = 1
t2i = (t * t) % p
while t2i % p != 1:
t2i = (t2i * t2i) % p
i += 1
if i &amp;gt;= m:
raise RuntimeError(&amp;quot;Tonelli-Shanks failed to find i&amp;quot;)
b = pow(c, 1 &amp;lt;&amp;lt; (m - i - 1), p)
r = (r * b) % p
c = (b * b) % pt = (t * c) % p
m = i
return r
 def all_e_th_roots(value: int, e: int, p: int) -&amp;gt; List[int]:
if e &amp;lt; 1:
return []
  if e == 1:
return [value % p]
  if legendre_symbol(value, p) != 1:
return []
  r1 = sqrt_mod_prime(value, p)
if r1 is None:
return []
  r2 = p - r1
roots = []
roots += all_e_th_roots(r1, e // 2, p)
if r2 != r1:
roots += all_e_th_roots(r2, e // 2, p)
uniq = sorted({r % p for r in roots})
return uniq
 def try_print_candidates(roots: List[int]):
if not roots:
return
  for idx, r in enumerate(roots, 1):
b = long_to_bytes(r)
hx = r.to_bytes((r.bit_length() + 7) // 8, &amp;#x27;big&amp;#x27;).hex() or &amp;quot;00&amp;quot;
try:
s = b.decode(&amp;#x27;utf-8&amp;#x27;)
dec = f&amp;quot;utf8: {s}&amp;quot;
except Exception:
dec = &amp;quot;utf8: &amp;lt;non-utf8 bytes&amp;gt;&amp;quot;
 if __name__ == &amp;quot;__main__&amp;quot;:
ls = legendre_symbol(CIPHER, MOD)
if ls != 1:
print(&amp;quot;[!] CIPHER is not a quadratic residue modulo MOD — no solutions by this method.&amp;quot;)
else:
roots = all_e_th_roots(CIPHER, E, MOD)
try_print_candidates(roots)&lt;/pre&gt;
  &lt;p id=&quot;CsTK&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;4oY9&quot;&gt;Flag: &lt;strong&gt;grodno{ohhhh_u_solved_this}&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;79hn&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;PCoh&quot;&gt;&lt;/p&gt;
  &lt;h1 id=&quot;PQ7U&quot;&gt;NewYearsAES&lt;/h1&gt;
  &lt;blockquote id=&quot;66RB&quot;&gt;Santa tried to upgrade the gift-protection system by creating their own “New Year’s AES” based on ternary arithmetic.&lt;br /&gt;But something went wrong — an important holiday message ended up completely encrypted.&lt;br /&gt;Can you help him decode the message?&lt;/blockquote&gt;
  &lt;p id=&quot;16WK&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;aBNM&quot;&gt;Given &lt;code&gt;all_enc&lt;/code&gt; — the list of ciphertexts for all &lt;code&gt;3^9&lt;/code&gt; plaintexts under a custom tryte-based block cipher (A3S), and &lt;code&gt;enc_flag&lt;/code&gt; = &lt;code&gt;sha512(key) XOR pad(flag)&lt;/code&gt;, recover the secret key (and therefore the flag). The author leaked a massive dataset that makes a linear / differential attack feasible: recover last round key trytes by statistics, build linear equations from the round expansion &amp;amp; checkpoints, solve the GF(3) linear system for the whole round-key schedule, reconstruct the master key and recover the flag.&lt;/p&gt;
  &lt;h2 id=&quot;DftC&quot;&gt;What I assumed / what is given&lt;/h2&gt;
  &lt;ul id=&quot;9tVX&quot;&gt;
    &lt;li id=&quot;NuGu&quot;&gt;You have &lt;code&gt;all_enc&lt;/code&gt; — encryption of every plaintext (0..3^9−1) under the unknown key (produced by &lt;code&gt;gen()&lt;/code&gt;).&lt;/li&gt;
    &lt;li id=&quot;cxZ2&quot;&gt;You have &lt;code&gt;enc_flag&lt;/code&gt; = &lt;code&gt;sha512(key) XOR pad(flag)&lt;/code&gt; and &lt;code&gt;secret.txt&lt;/code&gt; structure is &lt;code&gt;(flag, key)&lt;/code&gt;. In practice the challenge supplies &lt;code&gt;all_enc&lt;/code&gt; and &lt;code&gt;enc_flag&lt;/code&gt;.&lt;/li&gt;
    &lt;li id=&quot;k2hG&quot;&gt;The cipher operates over GF(3), trytes (3-trit groups), with &lt;code&gt;sizeT = 3&lt;/code&gt;, &lt;code&gt;sizeW = 3&lt;/code&gt;, &lt;code&gt;len = 8&lt;/code&gt; round keys, and the provided &lt;code&gt;mini.py&lt;/code&gt; contains the &lt;code&gt;a3s()&lt;/code&gt; encryptor / key schedule implementation (we used it heavily).&lt;/li&gt;
    &lt;li id=&quot;TAun&quot;&gt;We can run &lt;code&gt;a3s(pt,key)&lt;/code&gt; or symbolic variants and we can mutate functions to produce affine / linear descriptions.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;CTDa&quot;&gt;Important overall observation: most of the cipher is linear (over GF(3)) except for SBOX usages. That means we can treat the forward transform up to selected checkpoints as an affine map in &lt;code&gt;{pt, kr}&lt;/code&gt;:&lt;br /&gt; &lt;code&gt;ct = ptmat * pt + ptconst + kptmat * kr&lt;/code&gt;&lt;br /&gt; where &lt;code&gt;kr&lt;/code&gt; are flattened round-key trytes (unknown). Once we can produce &lt;code&gt;ptmat&lt;/code&gt;, &lt;code&gt;ptconst&lt;/code&gt;, &lt;code&gt;kptmat&lt;/code&gt; we can combine known plaintext/ciphertext pairs into linear equations about &lt;code&gt;kr&lt;/code&gt;. By also recovering the last round key trytes via statistical differentials we reduce the unknowns drastically and get a solvable linear system.&lt;/p&gt;
  &lt;hr /&gt;
  &lt;h2 id=&quot;3Kox&quot;&gt;High-level attack plan (summary)&lt;/h2&gt;
  &lt;ol id=&quot;pNzd&quot;&gt;
    &lt;li id=&quot;k5OU&quot;&gt;Convert &lt;code&gt;all_enc&lt;/code&gt; into the internal 3×3 tryte format and flatten to 27-trit vectors (GF(3) vectors).&lt;/li&gt;
    &lt;li id=&quot;pnVh&quot;&gt;Produce symbolic affine model of the round function up to checkpoints (two checkpoints used):&lt;br /&gt; &lt;code&gt;ct = ptmat * pt + ptconst + kptmat * kr&lt;/code&gt;.&lt;/li&gt;
    &lt;li id=&quot;WyaB&quot;&gt; Using the affine model for a later checkpoint, compute &lt;code&gt;rhs_prob = server_ctv.T - (ptmat * server_ptv + ptconst)&lt;/code&gt; and select the most frequent trit per coordinate to form an approximate RHS where &lt;code&gt;M*kr = rhs&lt;/code&gt;.&lt;/li&gt;
    &lt;li id=&quot;sB3R&quot;&gt;Assemble all linear constraints:&lt;/li&gt;
    &lt;ul id=&quot;qAzN&quot;&gt;
      &lt;li id=&quot;SvaE&quot;&gt;27 equations from known last_key positions,&lt;/li&gt;
      &lt;li id=&quot;TyJf&quot;&gt;27 from 3rd step &lt;code&gt;M*kr = rhs&lt;/code&gt;,&lt;/li&gt;
      &lt;li id=&quot;UhmD&quot;&gt;linear constraints from the key expansion that are purely linear,&lt;/li&gt;
      &lt;li id=&quot;Vvm0&quot;&gt;optional key-expansion rows that touch the S-box (we try include them, but can drop small blocks if they conflict).&lt;/li&gt;
    &lt;/ul&gt;
    &lt;li id=&quot;hDsi&quot;&gt;Attempt to solve the GF(3) linear system. If non-unique or failures appear, try removing each small 3-row SBOX block (a handful of variants) — one variant gives a unique solution.&lt;/li&gt;
    &lt;li id=&quot;8NCH&quot;&gt;Convert solved &lt;code&gt;kr&lt;/code&gt; back to canonical key bytes, derive &lt;code&gt;sha512(key)&lt;/code&gt;, XOR with &lt;code&gt;enc_flag&lt;/code&gt; to obtain &lt;code&gt;flag&lt;/code&gt;.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;EdMe&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;Ahcv&quot;&gt;My Solver:&lt;/p&gt;
  &lt;pre id=&quot;QHjh&quot;&gt;import time
import numpy as np
t = time.time()
import os, sys

cwd = os.getcwd()
if cwd not in sys.path:
    sys.path.insert(0, cwd)

import AES as orig 
from AES import *  
from output import *   

F = GF(3) # Field we are working in

server_ct = [up(int_to_tyt(byt_to_int(ct)), W_SIZE ** 2, int_to_tyt(0)[0])[-1] for ct in all_enc]
server_ctv = [vector(F, [i for j in ct for i in j]) for ct in server_ct]
server_pt = [up(int_to_tyt(pt), W_SIZE ** 2, int_to_tyt(0)[0])[-1] for pt in range(3^9)]
server_ptv = [vector(F, [i for j in pt for i in j]) for pt in server_pt]

ptkr = PolynomialRing(F, [&amp;#x27;pt%d&amp;#x27;%i for i in range(9*3)] + [&amp;#x27;kr%d&amp;#x27;%i for i in range(9*3*8)]).gens()

pt_v = ptkr[:9*3]
pt = tuple(tuple(pt_v[i*3:i*3+3]) for i in range(9))
kr_v = ptkr[9*3:]
kr = tuple(tuple(tuple(kr_v[i*9*3:i*9*3+9*3][j*3:j*3+3]) for j in range(9)) for i in range(8))

xor = lambda a,b: a+b
uxor = lambda a,b: a-b
t_xor = lambda a,b: tuple(x+y for x,y in zip(a,b))
T_xor = lambda a,b: tuple(t_xor(i,j) for i,j in zip(a,b))
t_uxor = lambda a,b: tuple(x-y for x,y in zip(a,b))
T_uxor = lambda a,b: tuple(t_uxor(i,j) for i,j in zip(a,b))

SBOX_TYT = dict((int_to_tyt(i)[0], int_to_tyt(s)[0]) for i,s in enumerate(SBOX))
ISBOX = tuple(SBOX.index(i) for i in range(27))
ISBOX_TYT = dict((int_to_tyt(i)[0], int_to_tyt(s)[0]) for i,s in enumerate(ISBOX))

def sbox_affine(i:tuple):
    return (
        (0 + i[0]*1 + i[1]*1 + i[2]*0),
        (0 + i[0]*0 + i[1]*0 + i[2]*1),
        (1 + i[0]*0 + i[1]*2 + i[2]*2)
    )

def expand(tyt):
    words   = tyt_to_wrd(tyt) 
    size    = len(words)
    rnum    = size + 3
    rcons   = rcon(rnum * 3 // size)

    for i in range(size, rnum * 3):
        k   = words[i - size]
        l   = words[i - 1]
        if i % size == 0:
            s = tuple(sbox_affine(i) for i in rot_wrd(l))
            k = T_xor(k, s)
            k = (t_xor(k[0], rcons[i // size - 1]),) + k[1:]
        else:
            k = T_xor(k, l)
        words = words + (k,)

    return up(down(words[:rnum * 3]), W_SIZE ** 2, int_to_tyt(0)[0])

def tri_mulmod(A, B, mod=POLY):
    c = [0] * (len(mod) - 1)
    for a in A[::-1]:
        c = [0] + c
        x = tuple(b * a for b in B)
        c[:len(x)] = t_xor(c, x)
        n = -c[-1]*mod[-1]
        c[:] = [x+y*n for x,y in zip(c,mod)]
        c.pop()
    return tuple(c)

def tyt_mulmod(A, B, mod=POLY2, mod2=POLY):
    fil = [(0,) * T_SIZE]
    C = fil * (len(mod) - 1)
    for a in A[::-1]:
        C = fil + C
        x = tuple(tri_mulmod(b, a, mod2) for b in B)
        C[:len(x)] = T_xor(C, x)
        num = modinv(mod[-1], mod2)
        num2 = tri_mulmod(num, C[-1], mod2)
        x = tuple(tri_mulmod(m, num2, mod2) for m in mod)
        C[:len(x)] = T_uxor(C, x)

        C.pop()
    return C

def add(a,b):
    return tuple(
        tuple(x+y for x,y in zip(i,j)) for i,j in zip(a,b)
    )

def sub(a):
    return tuple(
        sbox_affine(x) for x in a
    )

def shift(a):
    return [
        a[i] for i in SHIFT_ROWS
    ]

def mix(tyt):
    tyt = list(tyt)
    for i in range(W_SIZE):
        tyt[i::W_SIZE] = tyt_mulmod(tyt[i::W_SIZE], CONS)
    return tuple(tyt)

xkey_mat1 = []   # An array of 135 x 216 numbers
xkey_const1 = [] # A vector of 135 numbers

for i in range(6,24):
    if i%5 == 0: continue
    for j in range(9):
        r = vector([0]*3*8*9)
        r[i*9+j] = 1; r[i*9-9+j] = 2; r[i*9-9*5+j] = 2
        xkey_mat1.append(r)
        xkey_const1.append(0)

xkey_eqns = []
for i in range(5,25,5):
    rcons = ((1, 0, 0), (0, 1, 0), (0, 0, 1), (1, 0, 2))
    k  = [tuple(kr_v[(i-5)*9:(i-5)*9+9][j*3:j*3+3]) for j in range(3)]
    l  = [tuple(kr_v[(i-1)*9:(i-1)*9+9][j*3:j*3+3]) for j in range(3)]
    s = sub(rot_wrd(l))
    k0 = T_xor(k, s)
    k0 = (t_xor(k0[0], rcons[i // 5 - 1]),) + k0[1:]
    k0 = [i for j in k0 for i in j]
    k0 = [i-j for i,j in zip(k0,kr_v[i*9:i*9+9])]
    xkey_eqns.extend(k0)

xkey_mat2 = []
xkey_const2 = []
for k in xkey_eqns:
    r = vector([0]*3*8*9)
    s = 0
    for v,c in k.dict().items():
        if 1 not in v:
            s -= c
            continue
        else:
            vi = list(v).index(1)
            r[vi - 9*3] += c
    xkey_mat2.append(r)
    xkey_const2.append(s)

def forward_to_checkpoint(ctt, keys, checkpoint):
    ctt = add(ctt, keys[0])

    for r in range(1, len(keys) - 1):
        ctt = sub(ctt)
        ctt = shift(ctt)
        ctt = mix(ctt)
        ctt = add(ctt, keys[r])

    if checkpoint==0: return ctt
    ctt  = sub(ctt)
    ctt  = shift(ctt)
    if checkpoint==1: return ctt
    ctt  = add(ctt, keys[-1])

    return ctt

def gen_mat(checkpoint):
    ctt = forward_to_checkpoint(pt, kr, checkpoint)

    ptmat = []
    kptmat = []
    ptconst = []
    for w in ctt:
        for t in w:
            rpt = vector([0]*9*3)
            rkpt = vector([0]*9*3*8)
            s = 0
            for v,c in t.dict().items():
                if 1 not in v: 
                    s += c; continue
                vi = list(v).index(1)
                if vi&amp;gt;=9*3: 
                    rkpt[vi-9*3] += c
                else:    
                    rpt[vi] += c
            ptmat.append(rpt)
            ptconst.append(s)
            kptmat.append(rkpt)
    
    return matrix(F, ptmat), vector(F, ptconst), matrix(F, kptmat)

print(&amp;quot;[*] Exploiting checkpoint 1...&amp;quot;)

ptmat, _, _ = gen_mat(0)
spt = ptmat * matrix(F, server_ptv).T
spt = spt.T

dspt = []
for offset in [1,3,9,27,81,243,729,2187]:
    dspt += [(spt[i]-spt[(i+offset)%3^9], (i,(i+offset)%3^9)) for i in range(3^9)]

all_kscore = []

for cidx in range(9): # enumerate all trytes of &amp;#x60;last_key&amp;#x60;

    pidx = SHIFT_ROWS[cidx]
    kscore = [0]*27
    for dptidx in range(len(dspt)):

        dpt,(i0,i1) = dspt[dptidx]
        dct = (server_ct[i0], server_ct[i1])

        c = (dct[0][cidx], dct[1][cidx])
        p = tuple(dpt[pidx*3:pidx*3+3])

        for k in range(27): 
 
            kt = orig.int_to_tyt(k)[0]
            ci = (orig.t_uxor(c[0],kt), orig.t_uxor(c[1],kt)) 
            ci = (ISBOX_TYT[ci[0]], ISBOX_TYT[ci[1]]) 
            ci = orig.t_uxor(ci[0], ci[1])

            if ci == p: # if matches, add 1 to score
                kscore[k] += 1

        print(dptidx, end=&amp;quot;\r&amp;quot;)
        
    all_kscore.append(kscore)
    print(&amp;quot;[-]&amp;quot;, cidx, &amp;#x27;done!&amp;#x27;)

last_key = [int_to_tyt(all_kscore[i].index(max(all_kscore[i])))[0] for i in range(9)]
last_key = [i for j in last_key for i in j]

ptmat, ptconst, kptmat = gen_mat(1)

spt = ptmat * matrix(F, server_ptv).T + matrix(F, [list(ptconst)]*3^9).T
rhs_prob = matrix(F, server_ctv).T - spt

from collections import Counter
rhs = [sorted(Counter(i).items(), key=lambda x:x[1])[-1][0] for i in rhs_prob]

M = np.zeros((27,216))
for i in range(27):
    M[i][216-27+i] = 1
M = matrix(F,M) + kptmat

def build_solve_mat(xkey_mat2, xkey_const2):
    
    a3s_lhs = [[0]*216 for _ in range(27)]
    a3s_rhs = last_key.copy()
    for i in range(27):
        a3s_lhs[i][216-27+i] = 1
        
    a3s_lhs.extend(list(M))
    a3s_rhs.extend(rhs)    
    a3s_lhs.extend(list(xkey_mat1))
    a3s_rhs.extend(list(xkey_const1))
    
    a3s_lhs.extend(list(xkey_mat2))
    a3s_rhs.extend(list(xkey_const2))

    a3s_lhs = matrix(F, a3s_lhs)
    a3s_rhs = vector(F, a3s_rhs)
    return a3s_lhs, a3s_rhs


for i in range(12): # Try all 12 possible substitutions
    a = xkey_mat2.copy()
    b = xkey_const2.copy()
    a = [p for j,p in enumerate(a) if j not in range(i*3,i*3+3)]
    b = [p for j,p in enumerate(b) if j not in range(i*3,i*3+3)]
    l,r = build_solve_mat(a,b)
    try:
        key_recovered = l.solve_right(r)
        print(&amp;quot;[*] kr found! ^-^&amp;quot;)
        break
    except: # Crashes if l.solve_right has no solutions
        pass
    
assert len(l.right_kernel().basis()) == 0, &amp;quot;Not the only solution!&amp;quot;

key_recovered = [int(i) for i in key_recovered]
key_recovered = tyt_to_int(tuple(tuple(key_recovered[i*3:i*3+3]) for i in range(15)))
key_recovered = int_to_byt(key_recovered)
print(&amp;quot;[*] Original Key:&amp;quot;, key_recovered.hex())

from hashlib import sha512

hsh = sha512(key_recovered).digest()
flag = byte_xor(hsh, enc_flag)
print(flag.decode())
&lt;/pre&gt;
  &lt;p id=&quot;cDQZ&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;1xDS&quot;&gt;Flag: &lt;strong&gt;grodno{th1s_1s_my_m@gnum_0pu5_d0n1_kn0w_how_u_s0lv3d_th1s}&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;0LVT&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;x6Dw&quot;&gt;&lt;/p&gt;
  &lt;h1 id=&quot;d732&quot;&gt;Esoteric Language?&lt;/h1&gt;
  &lt;blockquote id=&quot;U1oB&quot;&gt;Can the programming language of the 1C:Enterprise system be called an esoteric language?&lt;/blockquote&gt;
  &lt;p id=&quot;TOGO&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;O5VH&quot;&gt;The app encrypts notes with a tiny, custom stream cipher built from an LCG and byte rotations, then permutes bytes in 3-byte blocks and Base64-encodes the result. With (almost) full key material available for several records we can reproduce the PRNG keystream and fully decrypt every note. The task is: recover missing masked parts (if any), reconstruct the LCG seed, reverse block-permutation, rotate and XOR back the keystream → assemble the flag.&lt;/p&gt;
  &lt;p id=&quot;aagu&quot;&gt;The cipher is entirely deterministic and entirely keyed by three fields: &lt;code&gt;Code&lt;/code&gt;, &lt;code&gt;Ref&lt;/code&gt;, &lt;code&gt;Created&lt;/code&gt;. If you know these, you can deterministically compute the seed and therefore the keystream and decrypt the note.&lt;/p&gt;
  &lt;p id=&quot;GT95&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;BU0J&quot;&gt;Solver:&lt;/p&gt;
  &lt;pre id=&quot;lIPQ&quot;&gt;# organizer_decoder.py
import base64

RECORDS = [
    {
        &amp;quot;name&amp;quot;: &amp;quot;Record1&amp;quot;,
        &amp;quot;Code&amp;quot;: &amp;quot;ARC-01&amp;quot;,
        &amp;quot;RefFull&amp;quot;: &amp;quot;aa11bb22-33cc-44dd-55ee-66ff778899aa&amp;quot;,
        &amp;quot;CreatedFull&amp;quot;: &amp;quot;20251007122345&amp;quot;,
        &amp;quot;NoteEncrypted&amp;quot;: &amp;quot;8yR1aju+AAAX&amp;quot;
    },
    {
        &amp;quot;name&amp;quot;: &amp;quot;Record2&amp;quot;,
        &amp;quot;Code&amp;quot;: &amp;quot;ARC-02&amp;quot;,
        &amp;quot;RefFull&amp;quot;: &amp;quot;bb22cc33-44dd-55ee-66ff-778899aabbcc&amp;quot;,
        &amp;quot;CreatedFull&amp;quot;: &amp;quot;20251007121630&amp;quot;,
        &amp;quot;NoteEncrypted&amp;quot;: &amp;quot;RM5bcxPzAABi&amp;quot;
    },
    {
        &amp;quot;name&amp;quot;: &amp;quot;Record3&amp;quot;,
        &amp;quot;Code&amp;quot;: &amp;quot;ARC-03&amp;quot;,
        &amp;quot;RefFull&amp;quot;: &amp;quot;cc33dd44-55ee-66ff-7788-99aabbccdd11&amp;quot;,
        &amp;quot;CreatedFull&amp;quot;: &amp;quot;20251007103005&amp;quot;,
        &amp;quot;NoteEncrypted&amp;quot;: &amp;quot;R1YQdzaRAABI&amp;quot;
    },
    {
        &amp;quot;name&amp;quot;: &amp;quot;Record4&amp;quot;,
        &amp;quot;Code&amp;quot;: &amp;quot;ARC-04&amp;quot;,
        &amp;quot;RefFull&amp;quot;: &amp;quot;dd44ee55-66ff-7788-99aa-bbccddeeff22&amp;quot;,
        &amp;quot;CreatedFull&amp;quot;: &amp;quot;20251007104510&amp;quot;,
        &amp;quot;NoteEncrypted&amp;quot;: &amp;quot;jULmZg0V&amp;quot;
    },
    {
        &amp;quot;name&amp;quot;: &amp;quot;Record5&amp;quot;,
        &amp;quot;Code&amp;quot;: &amp;quot;ARC-05&amp;quot;,
        &amp;quot;RefFull&amp;quot;: &amp;quot;ee55ff66-7788-99aa-bbcc-ddeeff001133&amp;quot;,
        &amp;quot;CreatedFull&amp;quot;: &amp;quot;20251007143200&amp;quot;,
        &amp;quot;NoteEncrypted&amp;quot;: &amp;quot;FPZTlS0L&amp;quot;
    }
]

A = 1103515245
C = 12345
M = 2 ** 31

def ror(b, shift):
    shift &amp;amp;= 7
    return ((b &amp;amp; 0xFF) &amp;gt;&amp;gt; shift) | ((b &amp;lt;&amp;lt; (8 - shift)) &amp;amp; 0xFF)

def seed_from_key_material(code, ref, created):
    key_material = code.strip() + &amp;quot;|&amp;quot; + ref + &amp;quot;|&amp;quot; + created
    s = sum(ord(ch) for ch in key_material)
    return (s * 2654435761) % (2**31)

def reverse_block_permutation(blk_bytes):
    tmp = bytearray()
    n = len(blk_bytes)
    i = 0
    while i &amp;lt; n:
        g0 = blk_bytes[i] if i &amp;lt; n else 0
        g1 = blk_bytes[i+1] if (i+1) &amp;lt; n else 0
        g2 = blk_bytes[i+2] if (i+2) &amp;lt; n else 0
        tmp.extend(bytes([g2, g1, g0]))
        i += 3
    while tmp and tmp[-1] == 0:
        tmp.pop()
    return bytes(tmp)

def decrypt_record(record):
    code = record[&amp;quot;Code&amp;quot;]
    ref = record[&amp;quot;RefFull&amp;quot;]
    created = record[&amp;quot;CreatedFull&amp;quot;]
    note_b64 = record[&amp;quot;NoteEncrypted&amp;quot;]

    blk = base64.b64decode(note_b64)
    bytes_temp = reverse_block_permutation(blk)
    prev_plain = sum(ord(ch) for ch in ref) % 256

    seed = seed_from_key_material(code, ref, created)
    out = bytearray()
    for i, cb in enumerate(bytes_temp):
        seed = (A * seed + C) % M
        ks = (seed &amp;gt;&amp;gt; 17) &amp;amp; 0xFF
        shift = (len(code) * 3 + (prev_plain &amp;amp; 7) + i) % 8
        x = ror(cb, shift)
        pb = x ^ ks
        out.append(pb &amp;amp; 0xFF)
        prev_plain = pb &amp;amp; 0xFF

    try:
        plain = out.decode(&amp;#x27;utf-8&amp;#x27;)
    except Exception:
        plain = out.decode(&amp;#x27;utf-8&amp;#x27;, errors=&amp;#x27;replace&amp;#x27;)
    return plain

def main():
    fragments = []
    for rec in RECORDS:
        plain = decrypt_record(rec)
        print(f&amp;quot;{rec[&amp;#x27;name&amp;#x27;]}: {plain}&amp;quot;)
        fragments.append(plain)
    flag = &amp;quot;&amp;quot;.join(fragments)
    print(&amp;quot;\nAssembled flag:&amp;quot;)
    print(flag)

if __name__ == &amp;quot;__main__&amp;quot;:
    main()
&lt;/pre&gt;
  &lt;p id=&quot;1KnH&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;MJY2&quot;&gt;Flag: &lt;strong&gt;grodno{obviously_devils_language}&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;aKp9&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;HQcR&quot;&gt;&lt;/p&gt;
  &lt;h1 id=&quot;3XW9&quot;&gt;ChristmasRing&lt;/h1&gt;
  &lt;blockquote id=&quot;kFsM&quot;&gt;Santa Claus&amp;#x27;s workshop contains a magical ring of bells — the Christmas Ring. Every time someone pulls the string, the ring emits a long set of large random numbers, which are used to wrap and encrypt gifts.&lt;br /&gt;But the elf-helper was a bit lazy and used a standard Python random number generator instead of a cryptographically secure one.&lt;/blockquote&gt;
  &lt;p id=&quot;HAEB&quot;&gt;&lt;/p&gt;
  &lt;pre id=&quot;ue4O&quot;&gt;import time
import numpy as np
import random
  def interpolate(l):
for _ in range(624):
x = random.getrandbits(32*2**4)
print(x)
mo = random.getrandbits(32*2**4)
FR.&amp;lt;x&amp;gt; = PolynomialRing( IntegerModRing(mo) )
f = prod([(random.getrandbits(32*2**4)*x-1) for _ in range(1,l)])
return f, mo
 #hmm, maybe a bit slow
def evaluate(poly, points, mo):
evaluations = []

for point in points:
evaluations.append(poly(point))

return evaluations
 if __name__ == &amp;quot;__main__&amp;quot;:
with open(&amp;quot;flag.txt&amp;quot;,&amp;quot;r&amp;quot;) as f:
flag = f.read()

size =4096
poly, mo = interpolate(size)
R = Integers(mo)
points = [R(random.getrandbits(32*2**4)) for _ in range(size)]
ans = bytearray.fromhex(hex(prod(evaluate(poly,points,mo)))[2:-10])


ciphertext = bytearray(b&amp;quot;&amp;quot;)
for i, c in enumerate(flag):
ciphertext.append(ord(c)^^ans[i])

print(ciphertext)
print(ord(c)^^ans[i])&lt;/pre&gt;
  &lt;p id=&quot;atfK&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;QfcH&quot;&gt;The challenge prints many 512-bit outputs from Python’s &lt;code&gt;random&lt;/code&gt; (MT19937). Each printed 512-bit block contains 16 little-endian 32-bit MT outputs; by untempering the last 624 32-bit outputs we recover the full MT19937 internal state, reconstruct the PRNG, re-generate the exact sequence used to build the (broken) polynomial construction and derived mask, then XOR that mask with the ciphertext to get the flag.&lt;/p&gt;
  &lt;p id=&quot;80r0&quot;&gt;The generator is Python’s &lt;code&gt;random.Random()&lt;/code&gt; — MT19937 — which is not cryptographically secure and is fully recoverable from 624 consecutive 32-bit outputs.&lt;/p&gt;
  &lt;p id=&quot;uo8S&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;Bbkp&quot;&gt;Attack: &lt;/p&gt;
  &lt;p id=&quot;4zso&quot;&gt;Read the file of printed numbers. The file contains many 512-bit integers (one per line) and a final printed ciphertext line like &lt;code&gt;bytearray(b&amp;#x27;...&amp;#x27;)&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;e9mN&quot;&gt;Use the &lt;em&gt;last 39&lt;/em&gt; printed 512-bit numbers. Split each 512-bit integer into 16 32-bit words (word 0 = least significant 32 bits, word 1 next, …) — &lt;strong&gt;little-endian by words&lt;/strong&gt;. Concatenate the 16×39 words → 624 32-bit outputs.&lt;/p&gt;
  &lt;p id=&quot;m1Sx&quot;&gt;Undo MT19937 tempering for each 32-bit output (standard invert tempering routine) to obtain the 624 internal &lt;code&gt;state&lt;/code&gt; words.&lt;/p&gt;
  &lt;p id=&quot;gNe0&quot;&gt;Create a &lt;code&gt;random.Random()&lt;/code&gt; instance and set the recovered state so that it resumes from the same place the challenge RNG was at (so future &lt;code&gt;getrandbits&lt;/code&gt; generate identical values).&lt;/p&gt;
  &lt;p id=&quot;EIWe&quot;&gt;Recompute the exact sequence the challenge generated:&lt;/p&gt;
  &lt;ul id=&quot;uamf&quot;&gt;
    &lt;li id=&quot;EhOm&quot;&gt;&lt;code&gt;mo = r.getrandbits(512)&lt;/code&gt;&lt;/li&gt;
    &lt;li id=&quot;D2kx&quot;&gt;&lt;code&gt;co = [ r.getrandbits(512) for _ in range(1, s) ]&lt;/code&gt; (coefficients)&lt;/li&gt;
    &lt;li id=&quot;Rxtv&quot;&gt;&lt;code&gt;p = [ r.getrandbits(512) % mo for _ in range(s) ]&lt;/code&gt; (points)&lt;/li&gt;
    &lt;li id=&quot;v1wU&quot;&gt;Evaluate the product &lt;code&gt;prod((am*pm - 1) % mo)&lt;/code&gt; for each &lt;code&gt;pm&lt;/code&gt; and collect values &lt;code&gt;v&lt;/code&gt;.&lt;/li&gt;
    &lt;li id=&quot;dkC9&quot;&gt;Multiply those &lt;code&gt;v&lt;/code&gt; values modulo &lt;code&gt;mo&lt;/code&gt; → &lt;code&gt;bp&lt;/code&gt;.&lt;/li&gt;
    &lt;li id=&quot;YU8F&quot;&gt;Convert &lt;code&gt;hex(bp)[2:-10]&lt;/code&gt; into bytes (the original program slices off the last 10 hex digits — replicate that behavior).&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;XDgZ&quot;&gt;XOR &lt;code&gt;bp&lt;/code&gt;-derived bytes with the ciphertext bytes to get plaintext; decode the flag.&lt;/p&gt;
  &lt;p id=&quot;vYvw&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;yVQt&quot;&gt;Solver: &lt;/p&gt;
  &lt;pre id=&quot;61kS&quot;&gt;import random
import re
import ast

MASK32 = (1 &amp;lt;&amp;lt; 32) - 1

def undo_right(y, s):
    x = y
    for _ in range(10):
        x = y ^ (x &amp;gt;&amp;gt; s)
    return x &amp;amp; MASK32

def undo_left(y, s, mask):
    x = y
    for _ in range(10):
        x = y ^ ((x &amp;lt;&amp;lt; s) &amp;amp; mask)
    return x &amp;amp; MASK32

def untemper(y):
    y = undo_right(y, 18)
    y = undo_left(y, 15, 0xEFC60000)
    y = undo_left(y, 7,  0x9D2C5680)
    y = undo_right(y, 11)
    return y &amp;amp; MASK32

def split512_le(x):
    return [(x &amp;gt;&amp;gt; (32 * i)) &amp;amp; MASK32 for i in range(16)]

def recover_rng_after_print(nums_512):
    outs = []
    for n in nums_512[-39:]:
        outs.extend(split512_le(n))
    state_words = [untemper(o) for o in outs]

    r = random.Random()
    r.setstate((3, tuple(state_words) + (624,), None))
    return r

def prod_mod(lst, mod):
    res = 1
    for x in lst:
        res = (res * x) % mod
    return res

def main():
    with open(&amp;quot;ChristmasRing1.txt&amp;quot;, &amp;quot;r&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;) as f:
        lines = [ln.strip() for ln in f.readlines() if ln.strip()]

    nums = list(map(int, lines[:-1]))

    m = re.match(r&amp;quot;bytearray\((b[&amp;#x27;\&amp;quot;].*[&amp;#x27;\&amp;quot;])\)&amp;quot;, lines[-1])
    if not m:
        raise ValueError(&amp;quot;Bad ciphertext line&amp;quot;)
    ct = bytearray(ast.literal_eval(m.group(1)))

    r = recover_rng_after_print(nums)

    s = 4096
    mo = r.getrandbits(512)
    co = [r.getrandbits(512) for _ in range(1, s)]
    coeffs_mod = [c % mo for c in co]
    p = [r.getrandbits(512) % mo for _ in range(s)]

    # evaluate
    v = []
    for pm in p:
        val = 1
        for am in coeffs_mod:
            t = (am * pm) % mo
            t = (t - 1) % mo
            val = (val * t) % mo
        v.append(val)

    bp = prod_mod(v, mo)

    hs = hex(bp)[2:-10]
    if len(hs) % 2 == 1:
        hs = &amp;quot;0&amp;quot; + hs
    a = bytearray.fromhex(hs)

    pt = bytes(ct[i] ^ a[i] for i in range(len(ct)))
    print(pt.decode())

if __name__ == &amp;quot;__main__&amp;quot;:
    main()
&lt;/pre&gt;
  &lt;p id=&quot;R7rb&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;gRGm&quot;&gt;Flag: &lt;strong&gt;grodno{You_are_the_lord_of_the_chocolate_ring}&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;N1Pz&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;vZ2l&quot;&gt;&lt;/p&gt;
  &lt;h1 id=&quot;29S0&quot;&gt;RSA?&lt;/h1&gt;
  &lt;blockquote id=&quot;zNOj&quot;&gt;On New Year&amp;#x27;s Eve, Santa Claus makes a wish to give flags to all good hackers. But in his haste, he mixed up the operators in his magical encryption program, and instead of the reliable RSA, he got some strange code.&lt;/blockquote&gt;
  &lt;p id=&quot;v6rr&quot;&gt;&lt;/p&gt;
  &lt;pre id=&quot;H0DK&quot;&gt;A = __import__(&amp;#x27;random&amp;#x27;).SystemRandom().randint
 def R():
while True:
S = (ZZ^2).0
while S.norm() &amp;lt; 5^55:
a,b = ((-1)^A(0,1)*A(1,7^y) for y in (2,1))
S *= matrix([[a,b],[123*b,-a]])
S += (ZZ^2).0
S *= diagonal_matrix((1,123)) * S
if is_pseudoprime(S): return S
 n = prod(R() for _ in &amp;#x27;yyyyyyy&amp;#x27;)
print(f&amp;#x27;{n = :#x}&amp;#x27;)
 m = int.from_bytes(open(&amp;#x27;flag.txt&amp;#x27;,&amp;#x27;rb&amp;#x27;).read().strip(), &amp;#x27;big&amp;#x27;)
c = int(pow(m, 1|2^2^2^2, n))
print(f&amp;#x27;{c = :#x}&amp;#x27;&lt;/pre&gt;
  &lt;p id=&quot;3uYM&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;V0Vx&quot;&gt;The challenge builds an RSA-like modulus &lt;code&gt;n&lt;/code&gt; as a product of several specially constructed pseudoprimes coming from a quadratic-form / complex multiplication (CM) construction, then raises the message &lt;code&gt;m&lt;/code&gt; to some weird exponent modulo &lt;code&gt;n&lt;/code&gt;. Because the modulus factors are chosen with structure amenable to &lt;strong&gt;CM-style elliptic curve factorization&lt;/strong&gt;, we can factor &lt;code&gt;n&lt;/code&gt; efficiently by constructing elliptic curves with prescribed CM (via Hilbert class polynomials) and doing scalar multiplications with large smooth exponents until a division-by-zero occurs modulo a composite — that failure reveals a nontrivial gcd and yields a factor. After factoring we compute the private exponent (inverse of the public exponent modulo ∏(p_i − 1) or lcm) and recover the flag.&lt;/p&gt;
  &lt;p id=&quot;Co6t&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;BCgm&quot;&gt;Attack:&lt;/p&gt;
  &lt;p id=&quot;7DyC&quot;&gt;&lt;strong&gt;ECM / CM principle&lt;/strong&gt;: Elliptic-curve methods factor a composite &lt;code&gt;m&lt;/code&gt; by performing group operations on elliptic curves defined modulo &lt;code&gt;m&lt;/code&gt;. If during the group arithmetic you need to invert a denominator which is not invertible modulo &lt;code&gt;m&lt;/code&gt;, then that denominator shares a nontrivial factor with &lt;code&gt;m&lt;/code&gt;. Taking &lt;code&gt;gcd(denominator, m)&lt;/code&gt; yields a factor. Practical ECMs try many random curves and multiply by a large smooth scalar until failure.&lt;/p&gt;
  &lt;p id=&quot;C6HB&quot;&gt;&lt;strong&gt;Hilbert class polynomials&lt;/strong&gt;: Curves with complex multiplication by orders of imaginary quadratic fields have known j-invariants given by roots of Hilbert class polynomials. Building curves via Hilbert class polynomials gives curves whose orders are constrained — we can choose a smooth exponent &lt;code&gt;L&lt;/code&gt; such that the (putative) group order on a prime factor divides &lt;code&gt;L&lt;/code&gt;. Multiplying a random point by &lt;code&gt;L&lt;/code&gt; reduces it to the identity modulo a prime factor; on the composite modulus the calculation often triggers a division failure revealing a factor.&lt;/p&gt;
  &lt;p id=&quot;uXhY&quot;&gt;The solver uses that trick: for an appropriate discriminant &lt;code&gt;-d&lt;/code&gt; it constructs the Hilbert class polynomial &lt;code&gt;H_{-d}&lt;/code&gt; modulo &lt;code&gt;m&lt;/code&gt; (working in &lt;code&gt;Z/mZ[x]&lt;/code&gt; and then in &lt;code&gt;S = R / (H_{-d})&lt;/code&gt;) and picks random symbolic points; then it repeatedly multiplies those points by large smooth exponents &lt;code&gt;e = L^i&lt;/code&gt;. When a denominator becomes non-invertible during these scalar multiplications it raises &lt;code&gt;ZeroDivisionError&lt;/code&gt; — the exception embeds values that let us compute &lt;code&gt;gcd&lt;/code&gt; with the composite &lt;code&gt;m&lt;/code&gt; and thus factor it.&lt;/p&gt;
  &lt;p id=&quot;k0Yc&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;F6Nt&quot;&gt;Solver:&lt;/p&gt;
  &lt;pre id=&quot;LWN7&quot;&gt;import re
proof.all(False)

d = 123
n,c = map(ZZ, re.findall(&amp;#x27;0x[0-9a-f]+&amp;#x27;, open(&amp;#x27;output.txt&amp;#x27;).read()))

def inv(f):
    ff = f.lift().change_ring(QQ)
    gg = f.parent().modulus().change_ring(QQ)
    hh = xgcd(ff,gg)           
    assert hh[0] == 1
    return f.parent()(hh[1])   

def xDBLADD(a,b,X1,X2,X3):
    Z1 = Z2 = Z3 = 1
    if X1 == (): X1,Z1 = 1,0
    if X2 == (): X2,Z2 = 1,0
    if X3 == (): X3,Z3 = 1,0
    X4 = (X2^2-a*Z2^2)^2-8*b*X2*Z2^3
    Z4 = 4*(X2*Z2*(X2^2+a*Z2^2)+b*Z2^4)
    R = 2*(X2*Z3+X3*Z2)*(X2*X3+a*Z2*Z3)+4*b*Z2^2*Z3^2
    S = (X2*Z3-X3*Z2)^2
    X5 = R-S*X1
    Z5 = S
    return (X4*inv(Z4) if Z4 else ()), (X5*inv(Z5) if Z5 else ())

def xMUL(a,b,n,P):
    Q,PQ = (),P
    for bit in map(int,f&amp;#x27;{n:b}&amp;#x27;):
        if bit:
            P,Q = xDBLADD(a,b,PQ,P,Q)
        else:
            Q,P = xDBLADD(a,b,PQ,Q,P)
    return Q


ls = list(sorted({u^2+d*v^2 for u in range(49+1) for v in range(7+1)} - {0}))
L = lcm(ls)     

def fac(m, path=&amp;#x27;&amp;#x27;):

    print(f&amp;#x27;\x1b[33m    {&amp;quot;[&amp;quot;+path+&amp;quot;]&amp;quot;:9} ({int(m).bit_length()} bits)\x1b[0m&amp;#x27;)

    m = ZZ(m)
    if is_pseudoprime(m):                
        print(f&amp;#x27;--&amp;gt; \x1b[36m{m:#x}\x1b[0m&amp;#x27;); print()
        return [m]

    R.&amp;lt;x&amp;gt; = Zmod(m)[]
    S.&amp;lt;J&amp;gt; = R.quotient(hilbert_class_polynomial(-d))               
    a0, b0 = (-3*J^2+3*1728*J), (-2*J^3+4*1728*J^2-2*1728^2*J)    
    for i in range(1,20):

        e = L^i                           
        P = J.parent().random_element()     
        try:
            Q = xMUL(a0,b0,e, P)
            print(f&amp;#x27;{i:2} nope&amp;#x27;)
            continue
        except ZeroDivisionError as e:     
            vs = list(map(ZZ, re.findall(&amp;#x27;[0-9]+&amp;#x27;, e.args[0])))

        f = gcd(vs[0],m)
        if 1 &amp;lt; f &amp;lt; m:
            print(f&amp;#x27;\x1b[34m{m:#x} = {f:#x} * {m//f:#x}\x1b[0m&amp;#x27;); print()
            return fac(f, path+&amp;#x27;L&amp;#x27;) + fac(m//f, path+&amp;#x27;R&amp;#x27;) 

    print(&amp;#x27;\x1b[31mFAILED\x1b[0m&amp;#x27;)
    exit(1)


fs = fac(n)

print(&amp;#x27;    \x1b[1;4mfactorization\x1b[0m&amp;#x27;)
for f in fs:
    print(f&amp;#x27;\x1b[36m{f:#x}\x1b[0m&amp;#x27;)
print()

privkey = int(~Mod(65537, prod(f-1 for f in fs)))
print(f&amp;#x27;\x1b[35md = {privkey:#x}\x1b[0m&amp;#x27;); print()

flag = ZZ(pow(c, privkey, n))
flag = bytes.fromhex(f&amp;#x27;{flag:x}&amp;#x27;).decode(errors=&amp;#x27;replace&amp;#x27;)
print(f&amp;#x27;--&amp;gt; \x1b[1;32m{flag}\x1b[0m&amp;#x27;); print()&lt;/pre&gt;
  &lt;p id=&quot;HoJq&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;CgPj&quot;&gt;Flag: &lt;strong&gt;grodno{S1mpl3_s@g3_scr1pt_c@n_sc@r3_u}&lt;/strong&gt;&lt;/p&gt;

</content></entry><entry><id>beaverszero:NYCTFStegano2026</id><link rel="alternate" type="text/html" href="https://teletype.in/@beaverszero/NYCTFStegano2026?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=beaverszero"></link><title>Stegano Writeups</title><published>2026-01-13T21:46:53.744Z</published><updated>2026-01-13T21:47:28.933Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img1.teletype.in/files/8c/be/8cbecfc8-f1a3-4de2-9457-bc1c0daabb73.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img2.teletype.in/files/5e/0f/5e0f7fdc-c6e5-465a-97c2-1886713c585a.png&quot;&gt;Write-ups for Stegano tasks by Kata. English is not my main language, so I might have problems with explanations.</summary><content type="html">
  &lt;h2 id=&quot;8eSy&quot;&gt;Preview&lt;/h2&gt;
  &lt;p id=&quot;zyB9&quot;&gt;Write-ups for Stegano tasks by Kata. English is not my main language, so I might have problems with explanations.&lt;/p&gt;
  &lt;p id=&quot;U6MD&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;5UKP&quot;&gt;babyStegano&lt;/h2&gt;
  &lt;blockquote id=&quot;xSx3&quot;&gt;There are snowflakes on the window, but I think there is something behind them.&lt;/blockquote&gt;
  &lt;figure id=&quot;Xy1b&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/5e/0f/5e0f7fdc-c6e5-465a-97c2-1886713c585a.png&quot; width=&quot;900&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;P16x&quot;&gt;babyStegano implies that this is indeed a simple task, so let&amp;#x27;s go to the zsteg and see the flag:&lt;/p&gt;
  &lt;figure id=&quot;K9qA&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/c1/c1/c1c1b4b0-87f7-48c4-8525-1140e9304d7b.png&quot; width=&quot;1219&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;NhVW&quot;&gt;Flag:&lt;strong&gt; grodno{happy_new_year_ctfers}&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;vFI2&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;PJva&quot;&gt;&lt;/p&gt;
  &lt;h1 id=&quot;iA92&quot;&gt;UltraLastChristmas&lt;/h1&gt;
  &lt;blockquote id=&quot;8139&quot;&gt;Did you know that elves can even hear ultrasound?&lt;/blockquote&gt;
  &lt;p id=&quot;pj4m&quot;&gt;This is also a very simple chall. The first thing I personally do when I see files in audio format is go to SonicVisualizer and look at the spectrogram.&lt;/p&gt;
  &lt;figure id=&quot;VmdB&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/0e/eb/0eebfc06-41f5-43ee-9eb2-b1f6a09ed735.png&quot; width=&quot;1440&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;6ktZ&quot;&gt;Flag: &lt;strong&gt;grodno{laaaast_christmaaas_i_geiv_u_ma_ha}&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;FDOx&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;LMsH&quot;&gt;&lt;/p&gt;
  &lt;h1 id=&quot;IkTG&quot;&gt;Santa’s Report&lt;/h1&gt;
  &lt;blockquote id=&quot;gfrs&quot;&gt;We intercepted an encrypted report on Santa Claus’s activities.&lt;br /&gt;All that remains is to determine his favorite day of the year.&lt;/blockquote&gt;
  &lt;p id=&quot;x2BB&quot;&gt;We have an attached docs file that looks like the encoding is broken.&lt;/p&gt;
  &lt;figure id=&quot;uFTs&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/45/fa/45faa2e0-cb74-44e9-9023-f1b57583793c.png&quot; width=&quot;1032&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;TvpE&quot;&gt;There is a row in the document highlighted in bold that looks like a flag.&lt;/p&gt;
  &lt;p id=&quot;stLU&quot;&gt;I, as the author of the сhal, will not fix the encoding, since this line and the entire file is a Wikipedia article about Santa Claus and was left to distract you.&lt;/p&gt;
  &lt;p id=&quot;69Zd&quot;&gt;So let&amp;#x27;s move straight to the correct solution.&lt;/p&gt;
  &lt;p id=&quot;rMEU&quot;&gt;Some people may not know this, but DOCS and ODF formats are zip archives of XML files.&lt;br /&gt;So let&amp;#x27;s just change the file extension to zip and see what&amp;#x27;s inside.&lt;/p&gt;
  &lt;figure id=&quot;XaHc&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/4a/96/4a96f1ff-e0b5-4368-af52-7557946f0a7a.png&quot; width=&quot;1095&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;hX5v&quot;&gt;After searching through the files we can&amp;#x27;t find the flag.&lt;/p&gt;
  &lt;p id=&quot;ORrE&quot;&gt;We have only one image left in the folder Thumbnails &lt;/p&gt;
  &lt;figure id=&quot;rspJ&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/07/31/0731857b-6d4d-4f2b-ac0a-b4909aef37a3.png&quot; width=&quot;273&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;5LQs&quot;&gt;Let&amp;#x27;s open a hex editor and examine the image.&lt;/p&gt;
  &lt;figure id=&quot;bSHm&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/03/c5/03c5763e-c0ed-4518-be08-28ba067aa028.png&quot; width=&quot;1123&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;aYif&quot;&gt;Flag: &lt;strong&gt;grodno{my_lovley_06_12}&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;eoHR&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;LZzU&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;0ADp&quot;&gt;Admin Vibes&lt;/h2&gt;
  &lt;blockquote id=&quot;xjam&quot;&gt;I always try to be serious while creating tasks. Right?&lt;/blockquote&gt;
  &lt;p id=&quot;GNmL&quot;&gt;Attached to the task is an audio file (my favorite song, I recommend everyone listen to it in full)&lt;/p&gt;
  &lt;p id=&quot;I4QP&quot;&gt;&lt;em&gt;Danika House on YouTube)&lt;/em&gt;&lt;/p&gt;
  &lt;p id=&quot;ACfO&quot;&gt;This audio has an embedded image signature using LSB that can be extracted using a simple Python script.&lt;/p&gt;
  &lt;p id=&quot;6Wrh&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;zGJz&quot;&gt;Let&amp;#x27;s extract the image using binwalk: &lt;/p&gt;
  &lt;figure id=&quot;Di4B&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/0a/2f/0a2fa902-16fe-44e7-8985-0e4e83cb1b85.png&quot; width=&quot;1670&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;tONo&quot;&gt;Here is the photo itself:&lt;/p&gt;
  &lt;figure id=&quot;l2CD&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/f1/d0/f1d0a423-b40b-46b0-a059-b88c0208c569.png&quot; width=&quot;648&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;qO1r&quot;&gt;Let&amp;#x27;s try zsteg again:&lt;/p&gt;
  &lt;figure id=&quot;7Alc&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/da/2d/da2d98f0-a0a1-4d7c-8ae4-81e9b19d6eca.png&quot; width=&quot;884&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;R76p&quot;&gt;Flag: &lt;strong&gt;grodno{1&amp;#x27;m_s@n3_!_pr0m1s3}&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;B9dw&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;mrBR&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;F2gD&quot;&gt;BinaryChess&lt;/h2&gt;
  &lt;blockquote id=&quot;f3Tn&quot;&gt;In this chess game, a smart guy hid a flag. Find it. Pay attention to the moves. 0 and 1 are significant.&lt;/blockquote&gt;
  &lt;p id=&quot;cAlC&quot;&gt;This chall was made using &lt;a href=&quot;https://github.com/WintrCat/chessencryption&quot; target=&quot;_blank&quot;&gt;https://github.com/WintrCat/chessencryption&lt;/a&gt;&lt;br /&gt;The encoding script reads the file as a bit stream and encodes them into chess moves.&lt;br /&gt;In each position, it takes all legal moves, numbers them, and assigns each a binary number. The next portion of bits from the file selects which move to make. The sequence of moves is written to the PGN—this is the encoded data.&lt;/p&gt;
  &lt;p id=&quot;91wP&quot;&gt;Decoder:&lt;/p&gt;
  &lt;pre id=&quot;8pxf&quot;&gt;from time import time&lt;/pre&gt;
  &lt;pre id=&quot;nwK0&quot;&gt;from math import log2&lt;/pre&gt;
  &lt;pre id=&quot;A86e&quot;&gt;from chess import pgn, Board&lt;/pre&gt;
  &lt;pre id=&quot;UJMU&quot;&gt;from util import get_pgn_games&lt;/pre&gt;
  &lt;pre id=&quot;gEmn&quot;&gt;def decode(pgn_string: str, output_file_path: str):&lt;/pre&gt;
  &lt;pre id=&quot;Y2AP&quot;&gt;start_time = time()&lt;/pre&gt;
  &lt;pre id=&quot;rewf&quot;&gt;total_move_count = 0&lt;/pre&gt;
  &lt;pre id=&quot;Kae4&quot;&gt;games: list[pgn.Game] = get_pgn_games(pgn_string)&lt;/pre&gt;
  &lt;pre id=&quot;xYI0&quot;&gt;with open(output_file_path, &amp;quot;w&amp;quot;) as output_file:&lt;/pre&gt;
  &lt;pre id=&quot;YJMS&quot;&gt;output_file.write(&amp;quot;&amp;quot;)&lt;/pre&gt;
  &lt;pre id=&quot;TEej&quot;&gt;output_file = open(output_file_path, &amp;quot;ab&amp;quot;)&lt;/pre&gt;
  &lt;pre id=&quot;7uoW&quot;&gt;output_data = &amp;quot;&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;UoV1&quot;&gt;for game_index, game in enumerate(games):&lt;/pre&gt;
  &lt;pre id=&quot;h08r&quot;&gt;chess_board = Board()&lt;/pre&gt;
  &lt;pre id=&quot;56w1&quot;&gt;game_moves = list(game.mainline_moves())&lt;/pre&gt;
  &lt;pre id=&quot;gqOy&quot;&gt;total_move_count += len(game_moves)&lt;/pre&gt;
  &lt;pre id=&quot;VyqJ&quot;&gt;for move_index, move in enumerate(game_moves):&lt;/pre&gt;
  &lt;pre id=&quot;4McS&quot;&gt;legal_move_ucis = [&lt;/pre&gt;
  &lt;pre id=&quot;xeFa&quot;&gt;legal_move.uci()&lt;/pre&gt;
  &lt;pre id=&quot;qoS8&quot;&gt;for legal_move in list(chess_board.generate_legal_moves())&lt;/pre&gt;
  &lt;pre id=&quot;EWyq&quot;&gt;]&lt;/pre&gt;
  &lt;pre id=&quot;oL4y&quot;&gt;move_binary = bin(&lt;/pre&gt;
  &lt;pre id=&quot;qbLR&quot;&gt;legal_move_ucis.index(move.uci())&lt;/pre&gt;
  &lt;pre id=&quot;Nx5Q&quot;&gt;)[2:]&lt;/pre&gt;
  &lt;pre id=&quot;DQUb&quot;&gt;if (&lt;/pre&gt;
  &lt;pre id=&quot;goVS&quot;&gt;game_index == len(games) - 1&lt;/pre&gt;
  &lt;pre id=&quot;q4Ko&quot;&gt;and move_index == len(game_moves) - 1&lt;/pre&gt;
  &lt;pre id=&quot;ghmN&quot;&gt;):&lt;/pre&gt;
  &lt;pre id=&quot;YJqP&quot;&gt;max_binary_length = min(&lt;/pre&gt;
  &lt;pre id=&quot;UQPF&quot;&gt;int(log2(&lt;/pre&gt;
  &lt;pre id=&quot;CQwd&quot;&gt;len(legal_move_ucis)&lt;/pre&gt;
  &lt;pre id=&quot;CK5N&quot;&gt;)),&lt;/pre&gt;
  &lt;pre id=&quot;SAxm&quot;&gt;8 - (len(output_data) % 8)&lt;/pre&gt;
  &lt;pre id=&quot;oHC8&quot;&gt;)&lt;/pre&gt;
  &lt;pre id=&quot;lCgA&quot;&gt;else:&lt;/pre&gt;
  &lt;pre id=&quot;sOY4&quot;&gt;max_binary_length = int(log2(&lt;/pre&gt;
  &lt;pre id=&quot;r9bS&quot;&gt;len(legal_move_ucis)&lt;/pre&gt;
  &lt;pre id=&quot;efc9&quot;&gt;))&lt;/pre&gt;
  &lt;pre id=&quot;AZ6l&quot;&gt;required_padding = max(0, max_binary_length - len(move_binary))&lt;/pre&gt;
  &lt;pre id=&quot;1xdL&quot;&gt;move_binary = (&amp;quot;0&amp;quot; * required_padding) + move_binary&lt;/pre&gt;
  &lt;pre id=&quot;kOZ2&quot;&gt;chess_board.push_uci(move.uci())&lt;/pre&gt;
  &lt;pre id=&quot;wf6k&quot;&gt;output_data += move_binary&lt;/pre&gt;
  &lt;pre id=&quot;ZA5r&quot;&gt;if len(output_data) % 8 == 0:&lt;/pre&gt;
  &lt;pre id=&quot;YgDM&quot;&gt;output_file.write(&lt;/pre&gt;
  &lt;pre id=&quot;pZ9i&quot;&gt;bytes([&lt;/pre&gt;
  &lt;pre id=&quot;WSnV&quot;&gt;int(output_data[i * 8 : i * 8 + 8], 2)&lt;/pre&gt;
  &lt;pre id=&quot;zw91&quot;&gt;for i in range(len(output_data) // 8)&lt;/pre&gt;
  &lt;pre id=&quot;VpxR&quot;&gt;])&lt;/pre&gt;
  &lt;pre id=&quot;ETLO&quot;&gt;)&lt;/pre&gt;
  &lt;pre id=&quot;Tccu&quot;&gt;output_data = &amp;quot;&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;ANhe&quot;&gt;print(&lt;/pre&gt;
  &lt;pre id=&quot;IF3Z&quot;&gt;&amp;quot;\nsuccessfully decoded pgn with &amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;ocSm&quot;&gt;+ f&amp;quot;{len(games)} game(s), {total_move_count} total move(s)&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;o8au&quot;&gt;+ f&amp;quot;({round(time() - start_time, 3)}s).&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;TvOc&quot;&gt;)&lt;/pre&gt;
  &lt;pre id=&quot;LnSc&quot;&gt;if __name__ == &amp;quot;__main__&amp;quot;:&lt;/pre&gt;
  &lt;pre id=&quot;wp9M&quot;&gt;PGN_FILE_PATH = &amp;quot;output.pgn&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;9hQF&quot;&gt;OUTPUT_FILE_PATH = &amp;quot;decoded_output.bin&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;hOT8&quot;&gt;with open(PGN_FILE_PATH, &amp;quot;r&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;) as pgn_file:&lt;/pre&gt;
  &lt;pre id=&quot;BpUM&quot;&gt;pgn_text = pgn_file.read()&lt;/pre&gt;
  &lt;pre id=&quot;Rxub&quot;&gt;decode(pgn_text, OUTPUT_FILE_PATH)&lt;/pre&gt;
  &lt;pre id=&quot;nLmH&quot;&gt;print(f&amp;quot;Decoded binary saved to: {OUTPUT_FILE_PATH} (from: {PGN_FILE_PATH})&amp;quot;)&lt;/pre&gt;
  &lt;p id=&quot;x6zJ&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;IX3R&quot;&gt;Аnd we get the flag: &lt;strong&gt;grodno{wh@t_d0_u_me@n_st3g0_1n_ch3ss}&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;hGuU&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;NuE7&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;fD5E&quot;&gt;Chupakabra&lt;/h2&gt;
  &lt;blockquote id=&quot;1Xpu&quot;&gt;Under the influence of various New Year&amp;#x27;s treats and drinks, I came up with the idea to write my own implementation of /dev/urandom/. But I recorded some very important data with it, and now I can&amp;#x27;t get it back. Flag format inside the grodno file{a-z0-9}&lt;/blockquote&gt;
  &lt;p id=&quot;fhhe&quot;&gt;The most terrifying task in my opinion. You can try typing &lt;/p&gt;
  &lt;p id=&quot;QjAZ&quot;&gt;&lt;strong&gt;&lt;em&gt;cat /dev/urandom&lt;/em&gt;&lt;/strong&gt; in your terminal and see what happens.&lt;/p&gt;
  &lt;p id=&quot;FTuo&quot;&gt;I can&amp;#x27;t say what the author intended for this task, but I solved it with the help of AI.&lt;/p&gt;
  &lt;p id=&quot;k7Wj&quot;&gt;The gist of my solver:&lt;/p&gt;
  &lt;p id=&quot;DQtb&quot;&gt;The program reads the file in chunks (block_size = 1 MiB) and calculates the MD5 hash for each block.&lt;/p&gt;
  &lt;p id=&quot;x0FS&quot;&gt;For each hash, it counts how many times it appears (counts) and stores up to four positions where the hash appears (positions).&lt;/p&gt;
  &lt;p id=&quot;JLY1&quot;&gt;It finds all hashes that appear exactly once—these are &amp;quot;singleton&amp;quot; blocks. The first such block is considered flag_hash (the supposed block with the flag) and its position is taken as flag_pos.&lt;/p&gt;
  &lt;p id=&quot;c0iC&quot;&gt;It looks for a &amp;quot;clean&amp;quot; block (clean_hash)—the most frequent hash in the file, different from flag_hash. It takes its position as clean_pos. The idea is that the file contains many identical (original) blocks and one different block, which contains the flag.&lt;/p&gt;
  &lt;p id=&quot;XTKA&quot;&gt;It reads the bytes of both blocks (read_block reads the block by index) and XORs them bitwise: xor_bytes = flag_block XOR clean_block.&lt;/p&gt;
  &lt;p id=&quot;itWw&quot;&gt;The result is searched using a regular expression for the byte pattern b&amp;quot;grodno\{[a-z0-9]+\}&amp;quot;—that is, a string like grodno{...} with lowercase letters and numbers. If found, a flag is printed.&lt;/p&gt;
  &lt;pre id=&quot;kg1M&quot;&gt;import hashlib
import os
import re
import sys


def read_block(path, index, size):
    with open(path, &amp;quot;rb&amp;quot;) as f:
        f.seek(index * size)
        return f.read(size)


def main():
    path = sys.argv[1] if len(sys.argv) &amp;gt; 1 else &amp;quot;/home/marcus/Загрузки/challenge.bin&amp;quot;
    block_size = 1024 * 1024
    counts = {}
    positions = {}
    total_blocks = 0
    with open(path, &amp;quot;rb&amp;quot;) as f:
        while True:
            data = f.read(block_size)
            if not data:
                break
            h = hashlib.md5(data).digest()
            counts[h] = counts.get(h, 0) + 1
            if len(positions.get(h, [])) &amp;lt; 4:
                positions.setdefault(h, []).append(total_blocks)
            total_blocks += 1
    singles = [h for h, c in counts.items() if c == 1]
    if not singles:
        print(&amp;quot;no singleton block found&amp;quot;)
        return
    flag_hash = singles[0]
    flag_pos = positions[flag_hash][0]
    clean_hash = max((h for h in counts if h != flag_hash), key=lambda k: counts[k])
    clean_pos = positions[clean_hash][0]
    flag_block = read_block(path, flag_pos, block_size)
    clean_block = read_block(path, clean_pos, block_size)
    xor_bytes = bytes(a ^ b for a, b in zip(flag_block, clean_block))
    m = re.search(b&amp;quot;grodno\\{[a-z0-9]+\\}&amp;quot;, xor_bytes)
    if not m:
        print(&amp;quot;flag pattern not found&amp;quot;)
        return
    print(m.group(0).decode())


if __name__ == &amp;quot;__main__&amp;quot;:
    main()
&lt;/pre&gt;
  &lt;p id=&quot;m6sr&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;95x5&quot;&gt;Let&amp;#x27;s wait a little and get the flag:&lt;br /&gt;&lt;strong&gt;grodno{deadlyparkourkillerdarkbrawlstarsassasinstalkersniper1998rus}&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;zsN0&quot;&gt;&lt;/p&gt;
  &lt;h1 id=&quot;dV2E&quot;&gt;New Year&amp;#x27;s Quantization&lt;/h1&gt;
  &lt;blockquote id=&quot;4ZG6&quot;&gt;Think beyond pixels, sometimes the storage format of an image matters.&lt;/blockquote&gt;
  &lt;figure id=&quot;G9E2&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/a1/ba/a1bac4aa-ddc6-4ec6-b36c-b6f96a3c2e25.png&quot; width=&quot;261&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;Q6Ry&quot;&gt;Now let&amp;#x27;s move on to my Magnum Opus.&lt;br /&gt;The idea for this task came to me about six months ago when I came across an article about the JPEG format on Wikipedia.&lt;/p&gt;
  &lt;p id=&quot;wBa6&quot;&gt;First, I&amp;#x27;ll tell you what the task about, and then I&amp;#x27;ll move on to the solution itself.&lt;/p&gt;
  &lt;p id=&quot;7NMH&quot;&gt;The script hides the payload bitwise in the parity bit (LSB) of the quantized DCT coefficients of the luminance (Y) component of a JPEG. It:&lt;/p&gt;
  &lt;ul id=&quot;OLZ1&quot;&gt;
    &lt;li id=&quot;RMvd&quot;&gt;Divides the image into 8x8 blocks,&lt;/li&gt;
    &lt;li id=&quot;EkTa&quot;&gt;performs an orthonormal DCT on each block,&lt;/li&gt;
    &lt;li id=&quot;EeLv&quot;&gt;divides the coefficients by the quantization table entries (rounds them) to obtain integer quantized coefficients,&lt;/li&gt;
    &lt;li id=&quot;mQlG&quot;&gt;changes the parity (±1) of the required AC coefficients so that it matches the payload bit,&lt;/li&gt;
    &lt;li id=&quot;27s2&quot;&gt;restores (multiply by the quantization table → inverse DCT → level shift), and saves the image as a JPEG.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;3dnr&quot;&gt;The hint says that the flag itself is located in the Y component.&lt;/p&gt;
  &lt;pre id=&quot;XWAN&quot;&gt;import sys
from PIL import Image
import numpy as np
import math

def build_dct_matrix(N=8):
    C = np.zeros((N, N), dtype=np.float64)
    for k in range(N):
        for n in range(N):
            if k == 0:
                a = math.sqrt(1.0 / N)
            else:
                a = math.sqrt(2.0 / N)
            C[k, n] = a * math.cos(math.pi * (2*n + 1) * k / (2.0 * N))
    return C

def block_dct(block):
    return C8 @ block @ C8_T

def quality_scale_table(q):
    q = max(1, min(100, q))
    if q &amp;lt; 50:
        scale = 5000.0 / q
    else:
        scale = 200.0 - 2.0 * q
    qt = ((STD_QT * scale) + 50.0) // 100.0
    qt[qt &amp;lt; 1] = 1
    return qt

ZIGZAG = [
 0,  1,  5,  6, 14, 15, 27, 28,
 2,  4,  7, 13, 16, 26, 29, 42,
 3,  8, 12, 17, 25, 30, 41, 43,
 9, 11, 18, 24, 31, 40, 44, 53,
10, 19, 23, 32, 39, 45, 52, 54,
20, 22, 33, 38, 46, 51, 55, 60,
21, 34, 37, 47, 50, 56, 59, 61,
35, 36, 48, 49, 57, 58, 62, 63
]
ZIGZAG_RC = [(idx // 8, idx % 8) for idx in ZIGZAG]

STD_QT = np.array([
 [16,11,10,16,24,40,51,61],
 [12,12,14,19,26,58,60,55],
 [14,13,16,24,40,57,69,56],
 [14,17,22,29,51,87,80,62],
 [18,22,37,56,68,109,103,77],
 [24,35,55,64,81,104,113,92],
 [49,64,78,87,103,121,120,101],
 [72,92,95,98,112,100,103,99]
], dtype=np.float64)

C8 = build_dct_matrix(8)
C8_T = C8.T

def extract_bits_for_quality(stego_path, q):
    im = Image.open(stego_path).convert(&amp;#x27;YCbCr&amp;#x27;)
    y, cb, cr = im.split()
    y_arr = np.array(y, dtype=np.float64)
    h0, w0 = y_arr.shape
    qt = quality_scale_table(q)
    H = ((h0 + 7) // 8) * 8
    W = ((w0 + 7) // 8) * 8
    y_p = np.pad(y_arr, ((0, H - h0), (0, W - w0)), mode=&amp;#x27;edge&amp;#x27;)
    blocks_h = H // 8
    blocks_w = W // 8
    bits = []
    for br in range(blocks_h):
        for bc in range(blocks_w):
            r0 = br * 8
            c0 = bc * 8
            block = y_p[r0:r0+8, c0:c0+8] - 128.0
            D = block_dct(block)
            Q = np.rint(D / qt).astype(int)
            for zind in range(1,64):
                rr, cc = ZIGZAG_RC[zind]
                bits.append(int(Q[rr,cc]) &amp;amp; 1)
    return bits

def bits_to_bytes(bits):
    if len(bits) &amp;lt; 32:
        return None
    blen = 0
    for i in range(32):
        blen = (blen &amp;lt;&amp;lt; 1) | bits[i]
    need = 32 + blen*8
    if need &amp;gt; len(bits):
        return None
    out = bytearray()
    for i in range(32, need):
        if (i-32) % 8 == 0:
            out.append(0)
        out[-1] = (out[-1] &amp;lt;&amp;lt; 1) | bits[i]
    return bytes(out)

def try_all_qualities(stego_path, qmin=40, qmax=95):
    for q in range(qmin, qmax+1):
        try:
            bits = extract_bits_for_quality(stego_path, q)
            payload = bits_to_bytes(bits)
            if payload is None:
                continue
            if b&amp;#x27;flag{&amp;#x27; in payload or all(32 &amp;lt;= b &amp;lt; 127 for b in payload[:min(50,len(payload))]):
                print(f&amp;quot;[+] Possibly quality={q}, payload_len={len(payload)} bytes&amp;quot;)
                print(payload[:200])
                open(f&amp;quot;recovered_q{q}.bin&amp;quot;,&amp;quot;wb&amp;quot;).write(payload)
        except Exception as e:
            pass

if __name__ == &amp;quot;__main__&amp;quot;:
    if len(sys.argv) &amp;lt; 2:
        print(&amp;quot;Usage: try_bruteforce_quality.py stego.jpg&amp;quot;)
        sys.exit(1)
    try_all_qualities(sys.argv[1], 40, 95)
&lt;/pre&gt;
  &lt;p id=&quot;egLO&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;m6ex&quot;&gt;The script isn&amp;#x27;t very pretty and it could be optimized and made better.&lt;/p&gt;
  &lt;p id=&quot;Q4sp&quot;&gt;The decoder tries to extract hidden bits from the quantized DCT coefficients of the luminance (Y) channel, assuming that:&lt;/p&gt;
  &lt;ul id=&quot;1DmH&quot;&gt;
    &lt;li id=&quot;Xnav&quot;&gt;the data is hidden in the LSB (least significant bit) of the coefficients,&lt;/li&gt;
    &lt;li id=&quot;4riD&quot;&gt;the standard JPEG quantization table is used,&lt;/li&gt;
    &lt;li id=&quot;zsf1&quot;&gt;the JPEG quality is unknown, so it is brute-forced,&lt;/li&gt;
    &lt;li id=&quot;I86G&quot;&gt;the first 4 bytes (32 bits) are the payload length (big-endian).&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;loYZ&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;7UKH&quot;&gt;This is how we get a flag: &lt;strong&gt;grodno{Qu@ntizat10n_1nfel1c1tq_m@tters}&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;yZXv&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;lXvg&quot;&gt;&lt;/p&gt;
  &lt;h1 id=&quot;uube&quot;&gt;NorthWind Secrets&lt;/h1&gt;
  &lt;blockquote id=&quot;kZiS&quot;&gt;While investigating the coordinator&amp;#x27;s Telegram account, we discovered that he is a member of a group with an unusual name: &lt;strong&gt;NYHEIST2026&lt;/strong&gt;. A very strange base64 file was found in it. Decrypt the group&amp;#x27;s hidden plans.&lt;/blockquote&gt;
  &lt;p id=&quot;v4Fy&quot;&gt;After going to the group, we find a TXT file with 2.1 million characters in base64.&lt;/p&gt;
  &lt;p id=&quot;RhhK&quot;&gt;With the help of CyberChef, we find out that there&amp;#x27;s a hidden zip archive in this base64.&lt;/p&gt;
  &lt;p id=&quot;gwhP&quot;&gt;By writing a simple Python script, we will restore the archive itself and see that it is password-protected.&lt;/p&gt;
  &lt;p id=&quot;zybD&quot;&gt;Lovley John the Ripper quickly cracks the password (654321) and we see 512 pieces of the image.&lt;/p&gt;
  &lt;p id=&quot;MZqF&quot;&gt;Using Chatagpt, let&amp;#x27;s write a script to restore the full image.&lt;/p&gt;
  &lt;pre id=&quot;cH46&quot;&gt;
import os
import argparse
from PIL import Image

def parse_args():
    p = argparse.ArgumentParser(description=&amp;quot;Rebuild image from 512 parts.&amp;quot;)
    p.add_argument(&amp;quot;parts_dir&amp;quot;, help=&amp;quot;Directory with part1..part512 images&amp;quot;)
    p.add_argument(&amp;quot;output&amp;quot;, help=&amp;quot;Output image path&amp;quot;)
    p.add_argument(&amp;quot;--cols&amp;quot;, type=int, default=32)
    p.add_argument(&amp;quot;--rows&amp;quot;, type=int, default=16)
    p.add_argument(&amp;quot;--prefix&amp;quot;, default=&amp;quot;part&amp;quot;)
    p.add_argument(&amp;quot;--ext&amp;quot;, default=&amp;quot;png&amp;quot;)
    return p.parse_args()

def main():
    args = parse_args()

    cols = args.cols
    rows = args.rows
    prefix = args.prefix
    ext = args.ext
    parts_dir = args.parts_dir

    total = cols * rows

    first_path = os.path.join(parts_dir, f&amp;quot;{prefix}1.{ext}&amp;quot;)
    if not os.path.exists(first_path):
        raise FileNotFoundError(f&amp;quot;Missing {first_path}&amp;quot;)

    first = Image.open(first_path)
    tile_w, tile_h = first.size
    mode = first.mode

    out = Image.new(mode, (tile_w * cols, tile_h * rows))

    index = 1
    for r in range(rows):
        for c in range(cols):
            part_name = f&amp;quot;{prefix}{index}.{ext}&amp;quot;
            part_path = os.path.join(parts_dir, part_name)

            if not os.path.exists(part_path):
                raise FileNotFoundError(f&amp;quot;Missing {part_path}&amp;quot;)

            tile = Image.open(part_path)
            out.paste(tile, (c * tile_w, r * tile_h))
            index += 1

    out.save(args.output)
    print(f&amp;quot;[+] Rebuilt image saved as {args.output}&amp;quot;)

if __name__ == &amp;quot;__main__&amp;quot;:
    main()
&lt;/pre&gt;
  &lt;p id=&quot;MXoL&quot;&gt;Afterwards, we open the resulting image:&lt;/p&gt;
  &lt;figure id=&quot;ZB3c&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/bd/e7/bde78b36-5e53-43ce-bf51-5e5200bfe7f5.png&quot; width=&quot;1280&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;RTgV&quot;&gt;Flag: &lt;strong&gt;grodno{1_knew_th@t_th1s_w0uldn&amp;#x27;t_w0rk}&lt;/strong&gt;&lt;/p&gt;

</content></entry><entry><id>beaverszero:NYCTFOsint2026</id><link rel="alternate" type="text/html" href="https://teletype.in/@beaverszero/NYCTFOsint2026?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=beaverszero"></link><title>OSINT Writeups</title><published>2026-01-13T18:59:06.156Z</published><updated>2026-01-13T21:56:28.067Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img4.teletype.in/files/73/63/73634b3f-3a33-4ee1-8947-c9a53a71ef82.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img1.teletype.in/files/08/88/088882f6-13a1-414a-924f-4eddaf97eb8b.png&quot;&gt;Write-ups for Osint tasks by Kata. English is not my main language, so I might have problems with explanations.</summary><content type="html">
  &lt;h2 id=&quot;74Kd&quot;&gt;Preview&lt;/h2&gt;
  &lt;p id=&quot;ctOE&quot;&gt;Write-ups for Osint tasks by Kata. English is not my main language, so I might have problems with explanations.&lt;/p&gt;
  &lt;h2 id=&quot;zi7v&quot;&gt;&lt;/h2&gt;
  &lt;h1 id=&quot;ZgWJ&quot;&gt;Admin&amp;#x27;s Fuel&lt;/h1&gt;
  &lt;pre id=&quot;UeJf&quot;&gt;Rumor has it that our admins can survive solely on this wonderful drink.&lt;/pre&gt;
  &lt;pre id=&quot;JrUV&quot;&gt;This blend of malt and hops is known throughout Belarus.&lt;/pre&gt;
  &lt;pre id=&quot;NZK8&quot;&gt;Can you find the place where our fuel is produced?&lt;/pre&gt;
  &lt;pre id=&quot;yN9y&quot;&gt;​&lt;/pre&gt;
  &lt;pre id=&quot;niYS&quot;&gt;Flag example: grodno{grodno{Сity_Main_Square_1}&lt;/pre&gt;
  &lt;p id=&quot;Aufg&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;RW4x&quot;&gt;From the description it becomes clear that we are talking about a brewery. There is also an interesting pinned message in the Telegram group: &lt;/p&gt;
  &lt;blockquote id=&quot;iEbF&quot;&gt;‼️Аліварыя - лепшае беларускае піва‼️&lt;/blockquote&gt;
  &lt;p id=&quot;7LYT&quot;&gt;which translates as &lt;strong&gt;Olivariya - the best Belarusian beer&lt;/strong&gt;&lt;/p&gt;
  &lt;figure id=&quot;7GJv&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/08/88/088882f6-13a1-414a-924f-4eddaf97eb8b.png&quot; width=&quot;1162&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;afCI&quot;&gt;Flag format is grodno{Сity_Main_Square_1} &lt;br /&gt;So the flag is grodno{Minsk_Kiseleva_Street_30}&lt;/p&gt;
  &lt;p id=&quot;KIQ4&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;gB9L&quot;&gt;&lt;/p&gt;
  &lt;h1 id=&quot;PK6p&quot;&gt;Regular GoogleMaps&lt;/h1&gt;
  &lt;pre id=&quot;S5Y2&quot;&gt;I was browsing Google Maps looking for interesting views, when I suddenly saw THIS.&lt;/pre&gt;
  &lt;pre id=&quot;I3jn&quot;&gt;I wanted to share this find with friends, but I ABSOLUTELY accidentally closed the tab, cleared my history, and completely forgot where it was.&lt;/pre&gt;
  &lt;pre id=&quot;eC2W&quot;&gt;Can you help me find it?&lt;/pre&gt;
  &lt;pre id=&quot;joqW&quot;&gt;Flag format: grodno{State_City_Street}&lt;/pre&gt;
  &lt;figure id=&quot;AzO4&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/d4/0e/d40ee209-bc07-481f-8b67-0ebca15de345.png&quot; width=&quot;948&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;ebF5&quot;&gt;Using RIS (Reverse Image Search) we can see that this image is present in a large number of TikToks and Reels, in the comments of which I found the place itself: Moorpark, Arroyo Drive&lt;/p&gt;
  &lt;figure id=&quot;mLCh&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/3c/3c/3c3c60ad-a335-41da-b40c-40f8fc9c99f8.png&quot; width=&quot;783&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;VLdH&quot;&gt;The flag is &lt;strong&gt;grodno{California_Moorpark_Arroyo_Drive}&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;3QjC&quot;&gt;&lt;/p&gt;
  &lt;h1 id=&quot;HGLf&quot;&gt;New Year&amp;#x27;s Eve&lt;/h1&gt;
  &lt;blockquote id=&quot;L7pA&quot;&gt;This is the last photo I took in 2025.&lt;br /&gt;It&amp;#x27;s hard to describe in words how many emotions you can get from celebrating the New Year in such a stunning place.&lt;br /&gt;And you need to find the official website of this ski resort)&lt;/blockquote&gt;
  &lt;figure id=&quot;l3d7&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/28/9a/289a392f-c2ff-4289-ba34-5ad4dac07317.png&quot; width=&quot;706&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;eeWf&quot;&gt;By the way, this is really my last photo in 2025) But where was it taken?&lt;/p&gt;
  &lt;p id=&quot;0ruB&quot;&gt;The photo shows a ski resort, it remains to be seen which one&lt;/p&gt;
  &lt;p id=&quot;zhVa&quot;&gt;Our favorite Reverse Search shows: &lt;/p&gt;
  &lt;figure id=&quot;BxSs&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/6a/e7/6ae7cda2-a291-4e48-9e63-6433c4cf9eb3.png&quot; width=&quot;1166&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;GnUF&quot;&gt;So we learn that there are several ski resorts in Belarus, and we can simply look through their websites in the flag, but if we google the most popular ones, we can find the exact location where the photo was taken.&lt;/p&gt;
  &lt;figure id=&quot;BysK&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/0a/e7/0ae7aaba-6c20-4f66-a383-a71d2c3c7e9c.png&quot; width=&quot;2500&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;niou&quot;&gt;This is the Logoisk ski resort and their official website: &lt;a href=&quot;https://logoisk.by/&quot; target=&quot;_blank&quot;&gt;https://logoisk.by/&lt;/a&gt;&lt;br /&gt;And the flag: &lt;strong&gt;grodno {&lt;a href=&quot;https://logoisk.by&quot; target=&quot;_blank&quot;&gt;https://logoisk.by&lt;/a&gt;}&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;ZD6J&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;Gqoq&quot;&gt;&lt;/p&gt;
  &lt;h1 id=&quot;pXyi&quot;&gt;Flight of the grinch&lt;/h1&gt;
  &lt;p id=&quot;ErGx&quot;&gt;&lt;/p&gt;
  &lt;pre id=&quot;2qsg&quot;&gt;After Santa took off to deliver Christmas presents around the world, something strange appeared in the sky.&lt;/pre&gt;
  &lt;pre id=&quot;XKTl&quot;&gt;The Grinch decided to leave his own “holiday decoration”  not on a Christmas tree, but high above the ground, visible only to those who were watching the skies carefully.&lt;/pre&gt;
  &lt;pre id=&quot;XPnw&quot;&gt;The drawing was made by an aircraft, leaving a distinctive track that formed a recognizable shape.&lt;/pre&gt;
  &lt;pre id=&quot;vfiU&quot;&gt;You need is to determine the exact date when the pilot who created this sky drawing received their pilot certificate.&lt;/pre&gt;
  &lt;pre id=&quot;LIXk&quot;&gt;​&lt;/pre&gt;
  &lt;pre id=&quot;JOM6&quot;&gt;Flag format: grodno{MM.DD.YYYY}&lt;/pre&gt;
  &lt;p id=&quot;oEup&quot;&gt;&lt;/p&gt;
  &lt;figure id=&quot;ynLm&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/ca/8f/ca8f15d9-9c33-408f-8d3c-a615ef23ecf0.png&quot; width=&quot;1280&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;2IOG&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;VGRj&quot;&gt;And again, the reverse search gives us the result.&lt;/p&gt;
  &lt;figure id=&quot;yxC7&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/a5/ed/a5ed3422-aa27-455d-b086-391903a44ccc.png&quot; width=&quot;1206&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;X33x&quot;&gt;By entering the tail number on the website &lt;a href=&quot;https://www.flightaware.com/live/flight/N6914W&quot; target=&quot;_blank&quot;&gt;https://www.flightaware.com/live/flight/N6914W&lt;/a&gt; we can find out that it is registered to Timothy M. Pearson&lt;/p&gt;
  &lt;p id=&quot;QRxl&quot;&gt;The next step is to go to the FAA and search for the pilot by name:&lt;/p&gt;
  &lt;figure id=&quot;3TvT&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/7e/60/7e608bcb-c022-42e8-ac74-810bab043f4d.png&quot; width=&quot;1207&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;dRYR&quot;&gt;And we see the pilot we need TIMOTHY MARTIN PEARSON&lt;/p&gt;
  &lt;figure id=&quot;C5vs&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/96/2f/962fad21-2311-40c3-b4ae-e0bcc84efa0c.png&quot; width=&quot;1132&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;U1Mt&quot;&gt;Date of issue was 01.29.2021&lt;/p&gt;
  &lt;p id=&quot;XydS&quot;&gt;The flag is &lt;strong&gt;grodno{01.29.2021}&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;RMVL&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;uNv9&quot;&gt;&lt;/p&gt;
  &lt;h1 id=&quot;mcXl&quot;&gt;Visiting Belorussian Grandfather&lt;/h1&gt;
  &lt;pre id=&quot;DGQH&quot;&gt;I visited my grandfather this summer while he was on vacation. He started working in December, so I wanted to come visit him and help him out, but I forgot where his house is. I have a photo of his assistant, and I remember his house is somewhere on the border  in the forest.&lt;/pre&gt;
  &lt;pre id=&quot;oC4t&quot;&gt;Can you help me?&lt;/pre&gt;
  &lt;pre id=&quot;r3Hc&quot;&gt;​&lt;/pre&gt;
  &lt;pre id=&quot;eDvj&quot;&gt;Flag format: grodno{Name_of_the_Forest}&lt;/pre&gt;
  &lt;p id=&quot;A1w2&quot;&gt;&lt;/p&gt;
  &lt;figure id=&quot;y6oI&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/08/76/0876f8ae-2534-48c4-bbe2-1c87bca21ea6.png&quot; width=&quot;960&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;xNT0&quot;&gt;It&amp;#x27;s very easy chall so I don&amp;#x27;t want to waste your time or mine. RIS shows us that this place is in Belovezhskaya Pushcha.&lt;/p&gt;
  &lt;p id=&quot;csfv&quot;&gt;Flag:&lt;strong&gt; grodno{Belovezhskaya_Pushcha}&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;qnK8&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;svPb&quot;&gt;&lt;/p&gt;
  &lt;h1 id=&quot;yrli&quot;&gt;Beginning of the NorthWind&lt;/h1&gt;
  &lt;pre id=&quot;IVSA&quot;&gt;During preparations for the holiday, an unknown APT group called “NorthWind”  kidnapped Santa Claus, and now New Year&amp;#x27;s is in danger.&lt;/pre&gt;
  &lt;pre id=&quot;YBRc&quot;&gt;We have found the contact of the person coordinating the operation. His current nickname on Telegram is @vvanuss&lt;/pre&gt;
  &lt;pre id=&quot;9qmE&quot;&gt;Find the information hidden in his previous nickname; it should help us move forward with the investigation.&lt;/pre&gt;
  &lt;p id=&quot;Dbyy&quot;&gt;We have a Telegram account username @vvanuss&lt;/p&gt;
  &lt;p id=&quot;vDG6&quot;&gt;Telegram has a HUGE number of OSINT bots, so we&amp;#x27;ll use one of them for this solution: @Funstat_fbot&lt;br /&gt;&lt;br /&gt;Let&amp;#x27;s enter the user&amp;#x27;s ID and get our flag:&lt;/p&gt;
  &lt;figure id=&quot;D1l8&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/28/88/2888c61c-abe7-418a-b7bc-af58323b1dda.png&quot; width=&quot;682&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;TqCA&quot;&gt;Flag:&lt;strong&gt; grodno{f1rst_st3p_0f_1nvest1g@tion}&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;ZJdE&quot;&gt;&lt;/p&gt;
  &lt;h1 id=&quot;Pirh&quot;&gt;Admin&amp;#x27;s Setup&lt;/h1&gt;
  &lt;blockquote id=&quot;SeQb&quot;&gt;Author: @vvanuss&lt;/blockquote&gt;
  &lt;blockquote id=&quot;cqIW&quot;&gt;Many people already know almost everything about me, but can you find the name of my laptop processor?&lt;/blockquote&gt;
  &lt;blockquote id=&quot;YyVK&quot;&gt;Flag format: grodno{Pentium_4}&lt;/blockquote&gt;
  &lt;p id=&quot;b2S4&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;XTnF&quot;&gt;The author of this task has a Telegram channel, which you can scroll to find a screenshot from the btop.&lt;/p&gt;
  &lt;figure id=&quot;gwjq&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/d7/30/d7309f9e-5728-4e7b-a816-65cdccd8ee3f.png&quot; width=&quot;1280&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;dxp2&quot;&gt;And we see the full name of the processor: Ryzen 7 6800H&lt;br /&gt;&lt;br /&gt;I admit that there is a problem with the flag format, since based on it the flag will be: grodno{Ryzen_7} but the real flag is &lt;strong&gt;grodno{Ryzen_7_6800H}&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;MDnk&quot;&gt;&lt;/p&gt;
  &lt;h1 id=&quot;wXjP&quot;&gt;babyOSINT&lt;/h1&gt;
  &lt;pre id=&quot;ANb6&quot;&gt;On December 31st, an employee of one company posted a photo from a corporate party with the caption:&lt;/pre&gt;
  &lt;pre id=&quot;jiC4&quot;&gt;​&lt;/pre&gt;
  &lt;pre id=&quot;A1fE&quot;&gt;&amp;quot;Best New Year in 10 years 🎅&amp;quot; &lt;/pre&gt;
  &lt;pre id=&quot;GbuY&quot;&gt;​&lt;/pre&gt;
  &lt;pre id=&quot;QDuT&quot;&gt;The account was deleted a day later.&lt;/pre&gt;
  &lt;pre id=&quot;Mf6F&quot;&gt;​&lt;/pre&gt;
  &lt;pre id=&quot;lWcZ&quot;&gt;Find the address of this establishment:&lt;/pre&gt;
  &lt;pre id=&quot;E9ee&quot;&gt;Flag format: grodno{Main_Square_1A}&lt;/pre&gt;
  &lt;p id=&quot;smKd&quot;&gt;&lt;/p&gt;
  &lt;figure id=&quot;HhqP&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/16/81/1681aa4b-0c74-4a98-bb8a-2917176b94e0.png&quot; width=&quot;1169&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;Od1P&quot;&gt;RIS shows that this photo was taken at the bar named Spichki.&lt;/p&gt;
  &lt;figure id=&quot;BbkF&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/6b/cd/6bcd3b16-d995-4b28-a911-a179a216a9f1.png&quot; width=&quot;654&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;9oGi&quot;&gt;CTF is held in Grodno, so it&amp;#x27;s logical that the bar is also located in Grodno.&lt;/p&gt;
  &lt;figure id=&quot;KxpD&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/8d/a6/8da61112-f4d2-47b2-8d21-12a21e785158.png&quot; width=&quot;754&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;GlMu&quot;&gt;So the flag: grodno{Sovetskaya_Square_2A}&lt;/p&gt;

</content></entry></feed>