比赛地址: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.

python
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。因此,如果我们知道 ciphertextkeystream,可以通过 ciphertext ^ keystream 还原出 plaintext

因此可以爆破加密时的时间戳,使用该时间戳重新生成 keystream,通过 ciphertext ^ keystream 还原出 plaintext

python
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'))
text
Seed found: 1745447710UMDCTF{pseudo_entropy_hidden_seed}
flag
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 进行检查,域名是不区分大小写的,然而这里的判断是区分大小写的,因此我们可以通过利用大小写绕过构造一个等价的域名。

python
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}

UMDCTF2025-1

Solution

UMDCTF2025-2

先识图找ohio找到这篇文章Vacancies — Marietta Main Street

再用这篇文章里的图接着搜

UMDCTF2025-3

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

UMDCTF2025-4

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

UMDCTF2025-5

到谷歌地图直接搜

UMDCTF2025-6

Putnam Bridge - Google 地圖

flag
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}

UMDCTF2025-7

Solution

UMDCTF2025-8

搜索题目给定的这栋房子发现它是篮球篮球运动员 LeBron James 童年的住所

在第一个搜索结果Where LeBron James Lives: A Peek Into LeBron’s Homes Interbasket中发现 LeBron’s Childhood Home in Akron, Ohio

进一步搜索

UMDCTF2025-9

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

356 Hillwood Dr - Google 地圖

UMDCTF2025-10

位于 356 Hillwood Dr, Akron, OH 44320美國

flag
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}

UMDCTF2025-11

Solution

UMDCTF2025-12

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

UMDCTF2025-13

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

UMDCTF2025-14

Battelle Riverfront Park - Google 地圖

flag
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}

UMDCTF2025-15

Solution

UMDCTF2025-16

识图找到维基百科上的这张图片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

flag
UMDCTF{Main St, Lore City, OH 43755}

Nyt

the-mini

Challenge

Joel Fagliano has nothing on me. (flag is all caps)

Solution

UMDCTF2025-17

这是个填字游戏

UMDCTF2025-18

并且它的 solution 被锁住了

然而这里的 key 只有 4 位数,因此直接爆破

在 GitHub 上找了个处理 .puz 的 Python 库alexdej/puzpy,然后就是写脚本调用接口爆破了

python
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()

运行得到输出

text
谜题被锁定,尝试暴力破解...成功解锁!密钥为: 5727解锁后的解决方案:UMDCTFCANYOUBEATMYTIME...
text
UMDCTF{CANYOUBEATMYTIME}

Reverse

deobfuscation

Challenge

the chall is not that complex. the key is to read ASSEMBLY!

Solution

UMDCTF2025-19

入口函数的分析如图,只要提取固定数组 byte_402000byte_402034,然后通过异或运算就可以还原出正确的输入,下面是提取出的 byte_402000byte_402034 的内容

text
.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

编写脚本还原

python
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))
flag
UMDCTF{r3v3R$E-i$_Th3_#B3ST#_4nT!-M@lW@r3_t3chN!Qu3}