HGAME 2023 将于 1 月 5 日 20:00 正式开始,祝大家玩得开心 :-)
线上赛平台:https://hgame.vidar.club
请尽快注册,注册时请选择校外选手,注册将于 1 月 12 日 20:00 关闭
本次比赛的奖励事宜以及赛后沟通反馈以邮件为主,请各位使用真实的邮件地址
比赛奖金(针对校外榜):
第1名:1000Pwnhub金币
第2名:800Pwnhub金币
第3名:600Pwnhub金币
4-10名:300Pwnhub金币
补充说明:排行榜分数相同者,以先达到该分数的时间次序划定排名,每位获奖选手额外赠送 Pwnhub 邀请码一个
注意:
* 所有选手均以个人为单位参赛;
* 在解题过程中遇到瓶颈或困难可以私聊出题人
* 禁止所有破坏比赛公平公正的行为,如:散播或与其他人交换 Flag、解题思路,对平台、参赛者或其他人员进行攻击。违者分数作废并取消比赛资格。
* HGAME 线上赛分为四周,每周至官方wp发布前前禁止一切讨论本周题目以及公开自己 wp 的行为。在收集完成后会开放讨论,但仅能讨论已结束的题目。
* 每周比赛结束后本周前20名需提交wp到指定邮箱
本比赛最终解释权归 Vidar-Team 所有
Rank: 1
Misc
Sign In
欢迎参加HGAME2023,Base64解码这段Flag,然后和兔兔一起开始你的HGAME之旅吧,祝你玩的愉快!
aGdhbWV7V2VsY29tZV9Ub19IR0FNRTIwMjMhfQ==
签到,base64解码,flag:hgame{Welcome_To_HGAME2023!}
。
Where am I
兔兔回家之前去了一个神秘的地方,并拍了张照上传到网盘,你知道他去了哪里吗?
flag格式为:
hgame{经度时_经度分_经度秒_东经(E)/西经(W)_纬度时_纬度分_纬度秒_南纬(S)/北纬(N)}
,秒精确到小数点后两位例如: 11°22’33.99’’E, 44°55’11.00’’S 表示为 hgame{11_22_3399_E_44_55_1100_S}
wireshark打开流量文件,在TCP流15提取rar文件;
16进制下查看rar文件,查看第24个16进制数为24
,修改为 20
去除伪加密,解压得到 Exchangeable.jpg
;
查看jpg文件属性,发现GPS经纬度信息,提取经纬度数据按格式得到flag:hgame{116_24_1488_E_39_54_5418_N}
。
神秘的海报
坐车回到家的兔兔听说ek1ng在HGAME的海报中隐藏了一个秘密……(还记得我们的Misc培训吗?
zsteg查看png图片,发现在 b1,rgb,lsb,xy
存在lsb隐写内容:
1 | Sure enough, you still remember what we talked about at that time! This is part of the secret: `hgame{U_Kn0w_LSB&W` |
得到flag前半部分 hgame{U_Kn0w_LSB&W
。
到google云盘下载 Bossanova.wav
文件,根据上面文字提示 I use Steghide to encrypt, the password is also the 6-digit password
,使用了 Steghide
工具用6位数字密码隐写信息,爆破密码提取信息:
stegseek Bossanova.wav rockyou.txt
解出密码为 123456
,隐写内容:恭喜你解到这里,剩下的Flag是 av^Mp3_Stego},我们Week2见!
flag:hgame{U_Kn0w_LSB&Wav^Mp3_Stego}
e99p1ant_want_girlfriend
兔兔在抢票网站上看到了一则相亲广告,人还有点小帅,但这个图片似乎有点问题,好像是CRC校验不太正确?
16进制下修改png图片的高为更大的值,发现flag:hgame{e99p1ant_want_a_girlfriend_qq_524306184}
。
Crypto
兔兔的车票
兔兔刚买到车票就把车票丢到一旁,自己忙去了。结果再去找车票时发现原来的车票混在了其他东西里,而且票面还被污染了。你能帮兔兔找到它的车票吗。
注:flag.png已经提前保存在source文件夹下,并且命名为picture{x}.png
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63 from PIL import Image
from Crypto.Util.number import *
from random import shuffle, randint, getrandbits
flagImg = Image.open('flag.png')
width = flagImg.width
height = flagImg.height
def makeSourceImg():
colors = long_to_bytes(getrandbits(width * height * 24))[::-1]
img = Image.new('RGB', (width, height))
x = 0
for i in range(height):
for j in range(width):
img.putpixel((j, i), (colors[x], colors[x + 1], colors[x + 2]))
x += 3
return img
def xorImg(keyImg, sourceImg):
img = Image.new('RGB', (width, height))
for i in range(height):
for j in range(width):
p1, p2 = keyImg.getpixel((j, i)), sourceImg.getpixel((j, i))
img.putpixel((j, i), tuple([(p1[k] ^ p2[k]) for k in range(3)]))
return img
"""
source文件夹下面的图片生成过程:
def makeImg():
colors = list(long_to_bytes(getrandbits(width * height * 23)).zfill(width * height * 24))
shuffle(colors)
colors = bytes(colors)
img = Image.new('RGB', (width, height))
x = 0
for i in range(height):
for j in range(width):
img.putpixel((j, i), (colors[x], colors[x + 1], colors[x + 2]))
x += 3
return img
for i in range(15):
im = makeImg()
im.save(f"./source/picture{i}.png")
"""
n1 = makeSourceImg()
n2 = makeSourceImg()
n3 = makeSourceImg()
nonce = [n1, n2, n3]
index = list(range(16))
shuffle(index)
e=0
"""
这里flag.png已经提前被保存在source文件夹下了,文件名也是picture{xx}.png
"""
for i in index:
im = Image.open(f"source/picture{i}.png")
key = nonce[randint(0, 2)]
encImg = xorImg(key, im)
encImg.save(f'pics/enc{e}.png')
e+=1
15张随机明文图片 $P_{m_k},k \in [1,15]$ 与1张flag图片 $P_f$,经过3张密钥图片 $P_k,k \in [1,3]$ 随机异或得到密文图片 $P_{c_k}, k \in [1,16]$,在随机生成明文图片的 makeImg()
函数中有概率生成像素为 (0,0,0) 的点,这些点在随机异或操作后,密文图片与密钥图片对应的该点像素值相同,则有:
flag图片对应的密文图片:$P_{c_f}(x,y) = P_{f}(x,y) \oplus P_{k_a}(x,y)$
非flag随机明文图片对应的密文图片: $P_{c_i}(x,y) = P_{m_i}(x,y) \oplus P_{k_b}(x,y)$,
当满足 $a=b$ 时,即找到使用相同密钥图片加密的两组原始图片,有 $P_{k_a}(x,y)=P_{k_b}(x,y)$,
则 $P_{c_f}(x,y) \oplus P_{c_i}(x,y) = P_{f}(x,y) \oplus P_{m_i}(x,y)$;
又有大部分随机明文图片的点的像素值为 (0,0,0),即 $P_{m_i}(x_0,y_0)=0$,则
$P_{c_f}(x_0,y_0) \oplus P_{c_i}(x_0,y_0) = P_{f}(x_0,y_0) \oplus P_{m_i}(x_0,y_0)=P_{f}(x_0,y_0)$
有很大概率可以恢复flag图片。
只需找到满足 $a=b$ 的使用相同密钥图片加密的两组原始图片即可,通过爆破遍历。
1 | from PIL import Image |
得到还原的flag图片,flag:hgame{Oh_my_Ticket}
。
RSA
众所周知,RSA的安全性基于整数分解难题。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 from Crypto.Util.number import *
flag = open('flag.txt', 'rb').read()
p = getPrime(512)
q = getPrime(512)
n=p*q
e = 65537
m = bytes_to_long(flag)
c = pow(m, e, n)
print(f"c={c}")
print(f"n={n}")
"""
c=110674792674017748243232351185896019660434718342001686906527789876264976328686134101972125493938434992787002915562500475480693297360867681000092725583284616353543422388489208114545007138606543678040798651836027433383282177081034151589935024292017207209056829250152219183518400364871109559825679273502274955582
n=135127138348299757374196447062640858416920350098320099993115949719051354213545596643216739555453946196078110834726375475981791223069451364024181952818056802089567064926510294124594174478123216516600368334763849206942942824711531334239106807454086389211139153023662266125937481669520771879355089997671125020789
"""
$n$ 分解出 $p,q$,常规RSA。
1 | p = 11239134987804993586763559028187245057652550219515201768644770733869088185320740938450178816138394844329723311433549899499795775655921261664087997097294813 |
Be Stream
很喜欢李小龙先生的一句话”Be water my friend”,但是这条小溪的水好像太多了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 from flag import flag
assert type(flag) == bytes
key = [int.from_bytes(b"Be water", 'big'), int.from_bytes(b"my friend", 'big')]
def stream(i):
if i==0:
return key[0]
elif i==1:
return key[1]
else:
return (stream(i-2)*7 + stream(i-1)*4)
enc = b""
for i in range(len(flag)):
water = stream((i//2)**6) % 256
enc += bytes([water ^ flag[i]])
print(enc)
# b'\x1a\x15\x05\t\x17\tu"-\x06lm\x01-\xc7\xcc2\x1eXA\x1c\x15\xb7\xdb\x06\x13\xaf\xa1-\x0b\xd4\x91-\x06\x8b\xd4-\x1e\xab\xaa\x15-\xf0\xed\x1f\x17\x1bY'
递推关系等同于矩阵运算:
$s_i=4s_{i-1}+7s_{i-2}\Longrightarrow \begin{bmatrix} s_i \\ s_{i-1} \end{bmatrix}=\begin{bmatrix} 4 & 7 \\ 1 & 0 \end{bmatrix}\begin{bmatrix} s_{i-1} \\ s_{i-2} \end{bmatrix}=\begin{bmatrix} 4 & 7 \\ 1 & 0 \end{bmatrix}^{i-1}\begin{bmatrix} s_1 \\ s_0 \end{bmatrix}$
利用矩阵快速幂快速求值即可。
1 | # Sage |
神秘的电话
学校突然放假了,tr0uble正在开开心心的收拾东西准备回家,但是手机铃声突然响起,tr0uble接起电话,但是只听到滴答滴答的声音。努力学习密码学的tr0uble一听就知道这是什么,于是马上记录下来并花了亿点时间成功破译了,但是怎么看这都不像是人能看懂的,还没等tr0uble反应过来,又一通电话打来,依然是滴答滴答的声音。tr0uble想到兔兔也在学习密码学,于是不负责任地把密文都交给了兔兔,兔兔收到密文后随便看了一眼就不屑地说”这么简单都不会?自己解去,别耽误我抢车票”。
flag为最后得到的结果套上hgame{}, flag中字母均为小写
txt文件中有提示:
几个星期前,我们收到一个神秘的消息。但是这个消息被重重加密,我们不知道它的真正含义是什么。唯一知道的信息是关于密钥的:“只有倒着翻过十八层的篱笆才能抵达北欧神话的终点”。
从wav文件中手搓摩斯密码:
----- ..--- ..--- ...-- . ..--.- .--. .-. .. .. -... .-.. -.-- ..--.- ..--.- .... --- -. .-- .- ..--.- .--- -- --. .... ..--.- ..-. --. -.- -.-. --.- .- --- --.- - -- ..-. .-.
解码:0223e_priibly__honwa_jmgh_fgkcqaoqtmfr
根据txt提示,字符串逆序 + 栅栏密码(18栏) 得到:rmocfhm_wo_ybipe2023_ril_hnajg_katfqqg
由 2023
猜测 ybipe
对应 hgame
,尝试Vigenere密码,密钥 vidar
,得到:welcome_to_hgame2023_and_enjoy_hacking
flag:hgame{welcome_to_hgame2023_and_enjoy_hacking}
Web
Classic Childhood Game
兔兔最近迷上了一个纯前端实现的网页小游戏,但是好像有点难玩,快帮兔兔通关游戏!
js游戏,查看源码,在 /Res/Events.js
中找到关键函数:
1 | function mota() { |
在控制台中运行,弹窗内容即为flag:hgame{fUnnyJavascript&FunnyM0taG4me}
。
Become A Member
学校通知放寒假啦,兔兔兴高采烈的打算购买回家的车票,这时兔兔发现成为购票网站的会员账户可以省下一笔money……
想成为会员也很简单,只需要一点点HTTP的知识……等下,HTTP是什么,可以吃吗
考察HTTP请求头中的User-Agent、Cookie、来源(Referer)和本地访问(X-Forwarded-For)。
需依次满足:
1 | User-Agent: Cute-Bunny |
以JSON格式请求登录即可:
1 | GET / |
返回flag值:hgame{H0w_ArE_Y0u_T0day?}
。
Guess Who I Am
刚加入Vidar的兔兔还认不清协会成员诶,学长要求的答对100次问题可太难了,你能帮兔兔写个脚本答题吗?
查看源码:
<!-- Hint: https://github.com/Potat0000/Vidar-Website/blob/master/src/scripts/config/member.js -->
访问发现为页面问题的答案,以list方式提取数据。
另在js文件中搜索发现页面的3个路由:
获取问题:/api/getQuestion
,验证答案:/api/verifyAnswer
,获取分数:/api/getScore
。
python脚本模拟页面100次回答:
1 | data = [ |
运行得到100次循环后的结果:{"message":"hgame{Guess_who_i_am^Happy_Crawler}"}
。
Show Me Your Beauty
登陆了之前获取的会员账号之后,兔兔想找一张自己的可爱照片,上传到个人信息的头像中 :D
不过好像可以上传些奇怪后缀名的文件诶 XD
图片文件上传,抓包尝试,文件名存在关键字黑名单,包括 php/phtml/ini/htaccess
等;
测试发现可以大小写绕过,将文件名后缀修改为 Php
,内容修改为 <?=`cat /flag`;
,上传 1.Php
,访问即可得到flag:hgame{Unsave_F1L5_SYS7em_UPL0ad!}
。
Reverse
test your IDA
签到
IDA打开查看字符串有flag:hgame{te5t_y0ur_IDA}
。
easyasm
非常简单的汇编
关键操作在 xor eax, 33h
,将结果异或0x33即可还原:
1 | c = [0x5b,0x54,0x52,0x5e,0x56,0x48,0x44,0x56,0x5f,0x50,0x3,0x5e,0x56,0x6c,0x47,0x3,0x6c,0x41,0x56,0x6c,0x44,0x5c,0x41,0x2,0x57,0x12,0x4e] |
easyenc
easyenc
代码逻辑为逐字符先异或0x32后减86,逆向还原:
1 | c = [4, 255, 253, 9, 1, 243, 176, 0, 0, 5, |
a_cup_of_tea
兔兔的家人都爱喝茶,所以兔兔带了些茶叶回去
魔改了delta为 0xABCDEF23
的Tea算法,用解密算法还原:
1 | from Crypto.Util.number import * |
encode
兔兔把自己行李箱的密码用一种编码写在了纸条上,但他忘了怎么解密,你能帮帮他吗?
代码逻辑为将低4位和高4位分别取出存入 v4[2*i]
和 v4[2*i+1]
,提取比对字符串 dword_403000
,逆向还原:
1 | c = [8, 6, 7, 6, 1, 6, 13, 6, 5, 6, 11, 7, 5, 6, 14, 6, 3, 6, 15, 6, 4, 6, 5, 6, 15, 5, 9, 6, 3, 7, 15, 5, 5, 6, 1, 6, 3, 7, 9, 7, 15, 5, 6, 6, 15, 6, 2, 7, 15, 5, 1, 6, 15, 5, 2, 7, 5, 6, 6, 7, 5, 6, 2, 7, 3, 7, 5, 6, 15, 5, 5, 6, 14, 6, 7, 6, 9, 6, 14, 6, 5, 6, 5, 6, 2, 7, 13, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] |
Pwn
test_nc
pwn签到,直接 cat flag
得flag。
easy_overflow
简单栈溢出到 b4ckd0or()
函数上,特别的一点是用 close(1)
关闭了标准输出,可以用 execv 1>&0
将标准输出重定向到标准输入,因为默认打开一个终端后,0/1/2都指向同一个位置也就是当前终端,所以这条语句相当于重启了标准输出,此时就可以执行命令并且看得到输出了。
1 | from pwn import * |
choose_the_seat
兔兔在买高铁票时想要选一个好座位。
HINTS:
数组下标的检查好像少了点东西
由代码逻辑知可以写入bss段,只限制了上限未限制下限,使用负数可打GOT表内容。覆盖 exit()
的GOT表为 main()
使程序循环,再覆盖 puts()
的GOT表泄露 puts()
地址计算得到libc基地址,最后覆盖 puts()
的GOT表为 system("/bin/sh\x00")
。
1 | from pwn import * |
orw
HINTS:
标题就是考点捏,没思路的可以按照标题查一查
觉得溢出的不够多?那就先找个地方把ROP链写进去,再把栈迁移过去执行吧
标题表明需orw,但开启了沙盒禁用了 execve()/execveat()
,read()
读的字节数足以用ROP链泄露libc,但溢出字节0x28不足以执行orw,需先做栈迁移后,再执行orw。
1 | from pwn import * |
simple_shellcode
HINTS:
一次read不够多,为什么不再读一次呢?
初看代码像ret2shellcode,但开启了沙盒禁用了 execve()/execveat()
,而且 read()
只能读入0x10字节。
需要写入shellcode,但需先调用一次 read()
以读取更多的字节,再执行orw即可。
1 | from pwn import * |
Blockchain
Checkin
题目中给出了三个端口,分别是 RPC、水龙头、题目交互端。 由于靶机端口随机,需要选手自行尝试。
其中,浏览器可直接访问的是水龙头,浏览器直接访问报 403 的是 RPC,浏览器无法访问的是题目交互端,需使用 nc 连接。
nc连接,选1生成账号,在水龙头里转账后,选2生成合约地址,选4查看源码:
1 | // contracts/checkin.sol |
代码逻辑为通过 setGreeting()
传入字符串使得 isSolved()
返回 true
,传入的字符串为 HelloHGAME!
即满足条件。
尝试在Remix中攻击已生成的合约地址没打通,问出题人知对Remix环境做了限制(防作弊),需采用web3py脚本方式进行攻击:
1 | from web3 import Web3, HTTPProvider |
最后nc连接,选4获取flag。
Iot
Help marvin
兔兔发现售票的marvin只会吐出三个白头 决定去修一修marvin(-30)
HINTS:
Hint: SPI
给定的是sr文件,解压,根据文件内容搜索知为逻辑分析套件sigrok生成的文件。
使用PulseView工具打开sr文件,在D0/D1/D2有数据,参考 2022DASCTF X SU 三月春季挑战赛 What’s In The Bits 以及后放的提示,知符合SPI协议特征。
选择SPI解码器,clock选D0,输入选D2,导出所有解码的01字符:
00110100001100111011000010110110101100101011110110011010001011111001101010111010000110100011011100110011101100101010111110101001101110000001100010111110
去掉头位0,8位一组可还原字符串 hgame{4_5t4nge_Sp1>
,修正最后一位补足一位1,得 hgame{4_5t4nge_Sp1}
。
Help the uncle who can’t jump twice
兔兔在车站门口看到一张塑料凳子,上边坐着一个自称V的男人.他希望你能帮他登上他的大号 Vergil 去那边的公告栏上康康Nero手上的YAMATO怎么样了
broker:117.50.177.240:1883
HINTS:
Hint: mqtt
根据broker地址知使用的MQTT协议,参考 物联网安全实战从零开始-MQTT协议分析 安装mqtt-pwn。
使用命令爆破 Vergil 的密码,指定给定的密码本:
bruteforce --host 117.50.177.240 --port 1883 -u Vergil -pf password.txt
得到结果:
[+] Found valid credentials: Vergil:power
下载broker连接工具MQTT.fx,使用账密 Vergil/power
登录broker,在订阅处输入主题 Nero/YAMATO
,收到包含flag的信息:hgame{mqtt_1s_p0w3r}
。