比赛地址:UMDCTF 2025
比赛时间:26 Apr 2025 06:00 CST - 28 Apr 2025 06:00 CST
Misc
find the seeds
Challenge
can u help Alice find her seeds in the bin? She’s pretty sure the bin hasn’t been dumped since it was generated.
import randomimport time seed = int(time.time())random.seed(seed) plaintext = b"UMDCTF{REDACTED}"keystream = bytes([random.getrandbits(8) for _ in range(len(plaintext))])ciphertext = bytes([p ^ k for p, k in zip(plaintext, keystream)]) with open("secret.bin", "wb") as f: f.write(ciphertext)Solution
加密过程使用了 XOR 操作,将 plaintext 和一个由随机数生成器(random.getrandbits(8))生成的 keystream 进行逐字节异或操作,得到 ciphertext。
已知 XOR 的性质:如果 A ^ B = C,那么 C ^ B = A。因此,如果我们知道 ciphertext 和 keystream,可以通过 ciphertext ^ keystream 还原出 plaintext。
因此可以爆破加密时的时间戳,使用该时间戳重新生成 keystream,通过 ciphertext ^ keystream 还原出 plaintext。
import randomimport time # 读取密文文件with open("secret.bin", "rb") as f: ciphertext = f.read() # 已知明文的固定前缀KNOWN_PREFIX = b"UMDCTF{" # 尝试还原 plaintextdef recover_plaintext(ciphertext): # 获取当前时间戳 current_time = int(time.time()) # 时间设置长一点进行爆破 for seed in range(current_time - 360000, current_time + 1): random.seed(seed) # 重新生成 keystream keystream = bytes([random.getrandbits(8) for _ in range(len(ciphertext))]) # 还原 plaintext plaintext = bytes([c ^ k for c, k in zip(ciphertext, keystream)]) # 检查 plaintext 是否以已知前缀开头 if plaintext.startswith(KNOWN_PREFIX): print(f"Seed found: {seed}") return plaintext return None # 还原 plaintextplaintext = recover_plaintext(ciphertext)print(plaintext.decode('utf-8'))Seed found: 1745447710UMDCTF{pseudo_entropy_hidden_seed}UMDCTF{pseudo_entropy_hidden_seed}tiktok-ban
Challenge
Oh snap! Oh crap! TikTok is banned in Ohio!
nc challs.umdctf.io 32300
Solution
服务端运行了一个 dnsmasq 实例,配置了一个 值为 flag 的TXT 记录 tiktok.com 。
服务端首先读取 4 字节的长度信息(大端格式),然后根据该长度读取后续的数据。
如果数据中包含 tiktok\x03com(即 DNS 查询格式中的 tiktok.com),服务器会返回一段固定的错误消息,否则就返回 flag。
因此我们需要构造一个 DNS 请求,绕过检查查询 tiktok.com 的 TXT 记录,从而获取 flag。
注意到服务端使用 if b'tiktok\x03com' in req 进行检查,域名是不区分大小写的,然而这里的判断是区分大小写的,因此我们可以通过利用大小写绕过构造一个等价的域名。
from pwn import *import struct HOST = "challs.umdctf.io"PORT = 32300 # 构造 DNS 查询请求def create_dns_query(): # DNS Header (固定字段) transaction_id = b"\x12\x34" # 事务 ID,可以随意设置 flags = b"\x01\x00" # 标志位:标准查询 questions = b"\x00\x01" # 问题数:1 answer_rrs = b"\x00\x00" # 回答资源记录数:0 authority_rrs = b"\x00\x00" # 权威资源记录数:0 additional_rrs = b"\x00\x00" # 附加资源记录数:0 header = transaction_id + flags + questions + answer_rrs + authority_rrs + additional_rrs # DNS Question (查询部分) qname = b"\x06Tiktok\x03com\x00" # 域名:Tiktok.com(大小写绕过) qtype = b"\x00\x10" # 查询类型:TXT (16) qclass = b"\x00\x01" # 查询类:IN (1) question = qname + qtype + qclass # 组合完整的 DNS 请求 dns_query = header + question return dns_query def main(): conn = remote(HOST, PORT) dns_query = create_dns_query() # 发送长度信息和 DNS 请求 conn.send(struct.pack(">I", len(dns_query))) # 大端格式的长度 conn.send(dns_query) response = conn.recvall() print(response.decode()) conn.close() if __name__ == "__main__": main()OSINT
swag-like-ohio
Challenge
swag like ohio. down in ohio. swag like ohio. down in ohio. anyway we seem to be on a bridge. what’s the address of the bridge?
flag will look like: UMDCTF{Boulder Bridge, Washington, DC 20008}

Solution

先识图找ohio找到这篇文章Vacancies — Marietta Main Street
再用这篇文章里的图接着搜

再找到维基百科上的这张图File:Ohio - Marietta - Dime Bank.jpg - Wikimedia Commons,得知这个建筑是 Marietta Dime Bank

在这里200 Putnam Street in Historic Downtown Marietta, OH找到地址 200 Putnam Street, Marietta, OH 45750

到谷歌地图直接搜

UMDCTF{Putnam Bridge, Marietta, OH 45750}sunshine
Challenge
what a nice sunny day. what is the full address of house number 356?
flag will look like: UMDCTF{3983 Campus Dr, College Park, MD 20742}

Solution

搜索题目给定的这栋房子发现它是篮球篮球运动员 LeBron James 童年的住所
在第一个搜索结果Where LeBron James Lives: A Peek Into LeBron’s Homes Interbasket中发现 LeBron’s Childhood Home in Akron, Ohio
进一步搜索

在第一个搜索结果LeBron James’ childhood home in Akron, OH (Google Maps)中找到题目给定的地址(甚至街景都一模一样)

位于 356 Hillwood Dr, Akron, OH 44320美國
UMDCTF{356 Hillwood Dr, Akron, OH 44320}beauty
Challenge
truly a beautiful panorama. ohio is not always ugly. i really wanna know who made this pano tho. what’s their name?
flag will look like: UMDCTF{Darryll Pines}

Solution

搜到这篇百科List of tallest buildings in Ohio - Wikipedia

找到这个建筑AEP Building - Wikipedia,然后到谷歌地图上搜

Battelle Riverfront Park - Google 地圖
UMDCTF{Neil Larimore}the-master
Challenge
trust me bro, i know what im talking about. im the master when it comes to these things. what street are we on?
flag will look like:
UMDCTF{Campus Dr, College Park, MD 20742}

Solution

识图找到维基百科上的这张图片File:Lore City UMC.jpg - Wikimedia Commons
下面给出了拍摄这张图时所在的坐标GeoHack - File:Lore City UMC.jpg
打开谷歌地图[39°59’02.0”N 81°27’32.0”W - Google Maps](39°59’02.0”N 81°27’32.0”W - Google Maps)
最终可以定位到这里190 Main St - Google Maps
UMDCTF{Main St, Lore City, OH 43755}Nyt
the-mini
Challenge
Joel Fagliano has nothing on me. (flag is all caps)
Solution

这是个填字游戏

并且它的 solution 被锁住了
然而这里的 key 只有 4 位数,因此直接爆破
在 GitHub 上找了个处理 .puz 的 Python 库alexdej/puzpy,然后就是写脚本调用接口爆破了
import puz def brute_force_unlock(puzzle, max_key=10000): """ 尝试暴力破解锁定的谜题。 :param puzzle: puz.Puzzle 对象 :param max_key: 最大密钥值,默认为 10000 :return: 解锁后的谜题对象(如果成功),否则返回 None """ for key in range(max_key): if puzzle.unlock_solution(key): print(f"成功解锁!密钥为: {key}") return puzzle print("未能找到正确的密钥。") return None def main(): # 文件路径 file_path = "the_mini.puz" try: # 读取 .puz 文件 puzzle = puz.read(file_path) # 检查是否被锁定 if puzzle.is_solution_locked(): print("谜题被锁定,尝试暴力破解...") unlocked_puzzle = brute_force_unlock(puzzle) if unlocked_puzzle: # 输出解锁后的解决方案 print("解锁后的解决方案:") print(unlocked_puzzle.solution) else: print("暴力破解失败。") else: print("谜题未被锁定,直接输出解决方案:") print(puzzle.solution) except puz.PuzzleFormatError as e: print(f"读取谜题文件时发生错误: {e}") except Exception as e: print(f"发生未知错误: {e}") if __name__ == "__main__": main()运行得到输出
谜题被锁定,尝试暴力破解...成功解锁!密钥为: 5727解锁后的解决方案:UMDCTFCANYOUBEATMYTIME...UMDCTF{CANYOUBEATMYTIME}Reverse
deobfuscation
Challenge
the chall is not that complex. the key is to read ASSEMBLY!
Solution

入口函数的分析如图,只要提取固定数组 byte_402000 和 byte_402034,然后通过异或运算就可以还原出正确的输入,下面是提取出的 byte_402000 和 byte_402034 的内容
.data:0000000000402000 20 byte_402000 db 20h ; DATA XREF: LOAD:00000000004000C0↑o.data:0000000000402000 ; start:loc_40105D↑r.data:0000000000402001 22 db 22h ; ".data:0000000000402002 20 db 20h.data:0000000000402003 26 db 26h ; &.data:0000000000402004 35 db 35h ; 5.data:0000000000402005 37 db 37h ; 7.data:0000000000402006 14 db 14h.data:0000000000402007 07 db 7.data:0000000000402008 46 db 46h ; F.data:0000000000402009 00 db 0.data:000000000040200A 5A db 5Ah ; Z.data:000000000040200B 17 db 17h.data:000000000040200C 44 db 44h ; D.data:000000000040200D 35 db 35h ; 5.data:000000000040200E 52 db 52h ; R.data:000000000040200F 0C db 0Ch.data:0000000000402010 70 db 70h ; p.data:0000000000402011 28 db 28h ; (.data:0000000000402012 37 db 37h ; 7.data:0000000000402013 1C db 1Ch.data:0000000000402014 5B db 5Bh ; [.data:0000000000402015 1D db 1Dh.data:0000000000402016 70 db 70h ; p.data:0000000000402017 16 db 16h.data:0000000000402018 76 db 76h ; v.data:0000000000402019 50 db 50h ; P.data:000000000040201A 69 db 69h ; i.data:000000000040201B 5C db 5Ch ; \.data:000000000040201C 6E db 6Eh ; n.data:000000000040201D 6C db 6Ch ; l.data:000000000040201E 1B db 1Bh.data:000000000040201F 12 db 12h.data:0000000000402020 54 db 54h ; T.data:0000000000402021 69 db 69h ; i.data:0000000000402022 2D db 2Dh ; -.data:0000000000402023 38 db 38h ; 8.data:0000000000402024 06 db 6.data:0000000000402025 23 db 23h ; #.data:0000000000402026 11 db 11h.data:0000000000402027 3D db 3Dh ; =.data:0000000000402028 2F db 2Fh ; /.data:0000000000402029 00 db 0.data:000000000040202A 02 db 2.data:000000000040202B 4A db 4Ah ; J.data:000000000040202C 68 db 68h ; h.data:000000000040202D 45 db 45h ; E.data:000000000040202E 3B db 3Bh ; ;.data:000000000040202F 64 db 64h ; d.data:0000000000402030 1A db 1Ah.data:0000000000402031 20 db 20h.data:0000000000402032 55 db 55h ; U.data:0000000000402033 05 db 5.data:0000000000402034 ; char byte_402034[52].data:0000000000402034 75 byte_402034 db 75h ; DATA XREF: start+43↑r.data:0000000000402035 6F db 6Fh ; o.data:0000000000402036 64 db 64h ; d.data:0000000000402037 65 db 65h ; e.data:0000000000402038 61 db 61h ; a.data:0000000000402039 71 db 71h ; q.data:000000000040203A 6F db 6Fh ; o.data:000000000040203B 75 db 75h ; u.data:000000000040203C 75 db 75h ; u.data:000000000040203D 76 db 76h ; v.data:000000000040203E 69 db 69h ; i.data:000000000040203F 45 db 45h ; E.data:0000000000402040 60 db 60h ; `.data:0000000000402041 70 db 70h ; p.data:0000000000402042 7F db 7Fh ; .data:0000000000402043 65 db 65h ; e.data:0000000000402044 54 db 54h ; T.data:0000000000402045 77 db 77h ; w.data:0000000000402046 63 db 63h ; c.data:0000000000402047 74 db 74h ; t.data:0000000000402048 68 db 68h ; h.data:0000000000402049 42 db 42h ; B.data:000000000040204A 53 db 53h ; S.data:000000000040204B 54 db 54h ; T.data:000000000040204C 45 db 45h ; E.data:000000000040204D 03 db 3.data:000000000040204E 3D db 3Dh ; =.data:000000000040204F 7F db 7Fh ; .data:0000000000402050 31 db 31h ; 1.data:0000000000402051 58 db 58h ; X.data:0000000000402052 75 db 75h ; u.data:0000000000402053 46 db 46h ; F.data:0000000000402054 75 db 75h ; u.data:0000000000402055 44 db 44h ; D.data:0000000000402056 60 db 60h ; `.data:0000000000402057 78 db 78h ; x.data:0000000000402058 6A db 6Ah ; j.data:0000000000402059 74 db 74h ; t.data:000000000040205A 51 db 51h ; Q.data:000000000040205B 4F db 4Fh ; O.data:000000000040205C 1C db 1Ch.data:000000000040205D 5F db 5Fh ; _.data:000000000040205E 76 db 76h ; v.data:000000000040205F 79 db 79h ; y.data:0000000000402060 0B db 0Bh.data:0000000000402061 2D db 2Dh ; -.data:0000000000402062 75 db 75h ; u.data:0000000000402063 45 db 45h ; E.data:0000000000402064 4B db 4Bh ; K.data:0000000000402065 55 db 55h ; U.data:0000000000402066 66 db 66h ; f.data:0000000000402067 78 db 78h ; x编写脚本还原
byte_402000 = [ 0x20, 0x22, 0x20, 0x26, 0x35, 0x37, 0x14, 0x07, 0x46, 0x00, 0x5A, 0x17, 0x44, 0x35, 0x52, 0x0C, 0x70, 0x28, 0x37, 0x1C, 0x5B, 0x1D, 0x70, 0x16, 0x76, 0x50, 0x69, 0x5C, 0x6E, 0x6C, 0x1B, 0x12, 0x54, 0x69, 0x2D, 0x38, 0x06, 0x23, 0x11, 0x3D, 0x2F, 0x00, 0x02, 0x4A, 0x68, 0x45, 0x3B, 0x64, 0x1A, 0x20, 0x55, 0x05] byte_402034 = [ 0x75, 0x6F, 0x64, 0x65, 0x61, 0x71, 0x6F, 0x75, 0x75, 0x76, 0x69, 0x45, 0x60, 0x70, 0x7F, 0x65, 0x54, 0x77, 0x63, 0x74, 0x68, 0x42, 0x53, 0x54, 0x45, 0x03, 0x3D, 0x7F, 0x31, 0x58, 0x75, 0x46, 0x75, 0x44, 0x60, 0x78, 0x6A, 0x74, 0x51, 0x4F, 0x1C, 0x5F, 0x76, 0x79, 0x0B, 0x2D, 0x75, 0x45, 0x4B, 0x55, 0x66, 0x78] correct_input = []for i in range(len(byte_402000)): correct_input.append(byte_402000[i] ^ byte_402034[i]) print("".join(chr(c) for c in correct_input))UMDCTF{r3v3R$E-i$_Th3_#B3ST#_4nT!-M@lW@r3_t3chN!Qu3}