wrong name
wrong name
Platform: pwn_spbctf | Category: pwn | Type: Challenge | Difficulty: Easy | OS: NA | Author: XESXOR | Date: 2026-06-17 | Status: Solved Techniques: glibc_rand_reimplementation, prng_seed_prediction, time_seed_attack
Summary
Task: 64-bit NON-PIE ELF picks a secret first name via srand(time(NULL)) + rand()%20 and gives the flag if you strncmp-match it. Solution: reimplement glibc's actual TYPE_3 additive-feedback rand() in Python, seed it with the current UNIX second at connect time, predict rand()%20, and send the matching name from the known 20-name table.
wrong name — pwn_spbctf
Description
Угадай имя или, если ты ленивый, сделай что-нибудь другое...
English: "Guess the name, or if you're lazy do something else..." The server
picks a random first name and only prints the flag if you send the correct one.
Remote: nc 109.233.56.90 11632. Author: Mikhail Kolesov (@crazylazyowl).
Analysis
wrong_name.elf is a 64-bit x86-64 ELF, NON-PIE (base 0x400000), NX,
Partial RELRO, NO stack canary.
Functions:
read_n@0x401222— reads exactly N bytes into a buffer.rand_int@0x401290—lo + rand() % (hi - lo).rand_name@0x4012b5—srand(time(NULL)); idx = rand_int(0,20) = rand()%20;returnsNAMES[idx], a pointer into a table of 20 first-name strings.print_flag@0x40136e—fopen("/flag")/fgets/puts.main@0x4013e5— prints a banner andWhat's your name:, callsread_n(buf@rbp-0x30, 0x30)(exactly0x30bytes, no overflow past the buffer), thenstrncmp(buf, secret, strlen(secret)). On equality →print_flag().
The 20 NAMES (indices 0..19):
Joseph, Jessica, Thomas, Sarah, Charles, Karen, Christopher, Nancy, Daniel,
Lisa, Matthew, Margaret, Tom, Betty, Donald, Sandra, Mark, Ashley, Paul, Jerry
There is no memory-corruption path here (the read is bounded, no canary but no
overflow either). The intended "lazy" path is the PRNG hint: the secret is
chosen by srand(time(NULL)) followed by a single rand()%20. The seed is the
current UNIX time — fully known to the client at connect time — so the "random"
choice is completely predictable.
Solution
Predict the secret name by replaying glibc's PRNG with seed = time():
- Reimplement glibc
rand(). It is not an LCG and not Mersenne Twister — it is the TYPE_3 additive-feedback generator (degree 31, separation 3). The state is seeded with a Park–Miller minimal-standard LCG for the first 31 words, then advanced byr[i] = r[i-31] + r[i-3], and each output is(r[i] >> 1) & 0x7fffffff. Verified against the canonical vectorsrand(1) -> rand() == 1804289383. - At connect, take
t = int(time.time()), computeidx = glibc_first(t) % 20, and sendNAMES[idx]. - For robustness retry adjacent seeds
t-1, t+1in case the server seeded a second earlier/later. In practice it matched on the first attempt withseed = t.
The strncmp only compares strlen(secret) bytes, but exact prediction of the
chosen name makes that detail irrelevant.
glibc_rand.py
python read-only • copy allowedclass GlibcRand: """Full glibc rand() stream (TYPE_3, deg=31, sep=3) after srand(seed).""" def __init__(self, seed): seed &= 0xffffffff r = [0]*344 r[0] = seed if seed != 0 else 1 for i in range(1, 31): hi = r[i-1] // 127773 lo = r[i-1] % 127773 word = 16807 * lo - 2836 * hi if word < 0: word += 2147483647 r[i] = word for i in range(31, 34): r[i] = r[i-31] for i in range(34, 344): r[i] = (r[i-31] + r[i-3]) & 0xffffffff self.r = r self.i = 344 def next(self): r = self.r val = (r[self.i-31] + r[self.i-3]) & 0xffffffff r.append(val) self.i += 1 return (val >> 1) & 0x7fffffff def glibc_srand_rand_first(seed): return GlibcRand(seed).next() if __name__ == '__main__': import sys seed = int(sys.argv[1]) print(glibc_srand_rand_first(seed) % 20)
exp_wrong_name.py
python read-only • copy allowed#!/usr/bin/env python3 from pwn import * import time from glibc_rand import glibc_srand_rand_first context.log_level = 'warn' NAMES = ["Joseph","Jessica","Thomas","Sarah","Charles","Karen","Christopher", "Nancy","Daniel","Lisa","Matthew","Margaret","Tom","Betty","Donald", "Sandra","Mark","Ashley","Paul","Jerry"] HOST, PORT = '109.233.56.90', 11632 def attempt(): io = remote(HOST, PORT) t = int(time.time()) # seed ~= server's time(NULL) at connect for seed in (t, t-1, t+1, t-2, t+2): idx = glibc_srand_rand_first(seed) % 20 name = NAMES[idx] io.recvuntil(b'name: ') io.sendline(name.encode()) data = io.recvall(timeout=3) return data, name, seed io.close() def main(): for tryn in range(8): try: data, name, seed = attempt() except Exception as e: log.warning('err %s' % e); continue s = data.decode(errors='replace') print('[try %d seed=%d name=%s]' % (tryn, seed, name)) if 'spbctf' in s: print(s); return time.sleep(0.3) if __name__ == '__main__': main()
Run it, read the flag from the response, and submit via the platform's POST
flag form. The task tile flips to "solved".