目录
WEEK1
MISC
e99p1ant_want_girlfriend
神秘的海报
Where am I
Crypto
兔兔的车票
神秘的电话
Be Stream
WEEK2
CRYPTO
Rabin
包里有什么
RSA 大冒险1
题目
兔兔在抢票网站上看到了一则相亲广告,人还有点小帅,但这个图片似乎有点问题,好像是CRC校验不太正确?
解题思路
题目已经提示 图片CRC校验不正确,而对于crc校验出错有两种情况:一是宽高正确,crc被篡改,那么只需要计算出正确的crc即可;二是crc正确,宽或高被篡改,可以用脚本计算出正确宽或高。
先试了第一种情况,图片用tweakpng打开,报错, 按提示修改成正确值后再打开图片,没什么作用,说明是第二种情况
运行脚本得到正确高,修改后打开图片得到flag
题目
坐车回到家的兔兔听说ek1ng在HGAME的海报中隐藏了一个秘密......(还记得我们的Misc培训吗
解题思路
考的LSB隐写,打开后一堆英文,大概意思就是在这给了一半的flag,另一半去https://drive.google.com/file/d/13kBos3Ixlfwkf3e0z0kJTEqBxm7RUk-G/view?usp=sharing这个鬼地方找,还告诉你use Scientific Internet解决进入外网慢的问题,另一半flag藏在音乐里,用steghide解密,密码是6-digit(实际就是弱密码123456)
steghide extract -sf Bossanova.wav -p 123456
hgame{U_Kn0w_LSB&Wav^Mp3_Stego}
题目
兔兔回家之前去了一个神秘的地方,并拍了张照上传到网盘,你知道他去了哪里吗? 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}
解题思路
导出HTTP对象, 获得upload文件,010打开观察发现是个rar压缩包,删除前后多余部分,注意结尾也要删,rar结尾块也是一个固定字节串的块,依次是 C4 3D 7B 00 40 07 00,再打开还是报错,考虑有伪加密,将密码位(第24字节的第二位)改0,(4是加密),再把文件重命名填上文件后缀
得到一张全黑的图片,由于要获得图片的位置信息,自然想到去属性里找
hgame{116_24_1488_E_39_54_5418_N}
题目
兔兔刚买到车票就把车票丢到一旁,自己忙去了。结果再去找车票时发现原来的车票混在了其他东西里,而且票面还被污染了。你能帮兔兔找到它的车票吗。 注:flag.png已经提前保存在source文件夹下,并且命名为picture{x}.png
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)])) #p1 p2各像素值的各位按位异或
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
解题思路
先了解一下其中用到的函数
PIL库:http://t.csdn.cn/aHtyp
zfill函数:http://t.csdn.cn/9yvIn
shuffle函数:http://t.csdn.cn/2GxJt
bytes:http://t.csdn.cn/AQRgr
randint:http://t.csdn.cn/oZCPy
题目的流程是先随机生成3张噪声图片作为nonce,然后把flag.png和picture{xx}.png打乱顺序再与nonce其中随机一张进行按位异或。即picture{i}.png nonce.png = enc{e}.png 。因为nonce只有三张,而picture有16张,所以大概率有picture和flag.png重用了同一张nonce。而再根据picture{x}.png的生成函数(makeImg),可以看到picture{x}.png中有很多位等于0的点,那么就可以暴露出flag.png的特征。通俗讲,由于异或运算满足结合律和交换律,所以有
(p1 xor nonce) xor (p2 xor nonce) = p1 xor p2 = enc1 xor enc2
也就是说我们可以通过将enc1,enc2异或,从而得到p1 xor p2。 示意图:
题目本身图片不多,就16张,可以进行爆破。而出题人也没为难我们,不需要再单独求flag.png,到这步得到两图异或的合成图后就能看到flag了。
from PIL import Image
from Crypto.Util.number import *
im = Image.open('pics/enc0.png')
width = im.width
height = im.height
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
for i in range(16):
key=Image.open(f'pics/enc{i}.png')
for j in range(16):
encImg = Image.open(f'pics/enc{j}.png')
pt = xorImg(key, encImg)
pt.save(f'pts/pt{i*16+j}.png')
一些error:
1.要先安装PIL库 pip install pillow
2.记得把脚本和图片放到同一目录下
3.要提前创建pts文件夹,否则会not found
在生成的256张图片中我们可以找到
(以上源于HGAME2023官方wp,有些许删改)
题目
学校突然放假了,tr0uble正在开开心心的收拾东西准备回家,但是手机铃声突然响起,tr0uble接起电话,但是只听到滴答滴答的声音。努力学习密码学的tr0uble一听就知道这是什么,于是马上记录下来并花了亿点时间成功破译了,但是怎么看这都不像是人能看懂的,还没等tr0uble反应过来,又一通电话打来,依然是滴答滴答的声音。tr0uble想到兔兔也在学习密码学,于是不负责任地把密文都交给了兔兔,兔兔收到密文后随便看了一眼就不屑地说"这么简单都不会?自己解去,别耽误我抢车票"。
5Yeg5Liq5pif5pyf5YmN77yM5oiR5Lus5pS25Yiw5LiA5Liq56We56eY55qE5raI5oGv44CC5L2G5piv6L+Z5Liq5raI5oGv6KKr6YeN6YeN5Yqg5a+G77yM5oiR5Lus5LiN55+l6YGT5a6D55qE55yf5q2j5ZCr5LmJ5piv5LuA5LmI44CC5ZSv5LiA55+l6YGT55qE5L+h5oGv5piv5YWz5LqO5a+G6ZKl55qE77ya4oCc5Y+q5pyJ5YCS552A57+76L+H5Y2B5YWr5bGC55qE56+x56yG5omN6IO95oq16L6+5YyX5qyn56We6K+d55qE57uI54K54oCd44CC
解题思路
给的encrypted_message.txt是第一次破译好的密文,给的morse.wav是第二次打来的。
先把txt base64解码,得到明文
几个星期前,我们收到一个神秘的消息。但是这个消息被重重加密,我们不知道它的真正含义是什么。唯一知道的信息是关于密钥的:“只有倒着翻过十八层的篱笆才能抵达北欧神话的终点”。
提示有密钥,且为逆序,栅栏密码,北欧神话代表Vidar (来源于北欧神话"诸神⻩昏"中幸存于难、带领⼈类重建了家园的神 Víðarr)(这个真没找到...),是维吉尼亚密码的密钥
倒序 rfmtqoaqckgf_hgmj_awnoh__ylbiirp_e3220
18层栅栏一直以为只是普通栅栏,但结果不对,原来是W形栅栏,这个“层”就很灵性了。rmocfhm_wo_ybipe2023_ril_hnajg_katfqqg
顺便补补课吧 http://t.csdn.cn/vhTZy
维吉尼亚 key:vidar welcome_to_hgame2023_and_enjoy_hacking
题目
很喜欢李小龙先生的一句话"Be water my friend",但是这条小溪的水好像太多了
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\t\xf5\xa2-\x06\xec\xed\x01-\xc7\xcc2\x1eXA\x1c\x157[\x06\x13/!-\x0b\xd4\x91-\x06\x8b\xd4-\x1e+*\x15-pm\x1f\x17\x1bY'
解题思路
了解一下用到的函数
int.from_bytes: http://t.csdn.cn/VKhqk
法一:迭代转循环:看了这位大佬http://t.csdn.cn/5A8Uq的wp,个人感觉比官方简单点
法二:参考官方wp: 这道题给了⼤家⼀个跑不出来的程序,需要优化程序得到flag。优化⽅法还挺多的,预期是⽤矩阵快速幂的⽅法。
补习一下 快速幂算法:http://t.csdn.cn/0oph2
矩阵快速幂算法: http://t.csdn.cn/Xamal http://t.csdn.cn/Ldpum
(以上博主的代码都是c++写的,学习算法就好)
from sage.all import *
enc = b'\x1a\x15\x05\t\x17\t\xf5\xa2-\x06\xec\xed\x01-\xc7\xcc2\x1eXA\x1c\x157[\x06\x13/!-\x0b\xd4\x91-\x06\x8b\xd4-\x1e+*\x15-pm\x1f\x17\x1bY'
A = matrix(Zmod(256), [[4, 7],[1, 0]]).transpose()
key = [int.from_bytes(b"Be water", 'big'), int.from_bytes(b"my friend", 'big')]
stream = vector(Zmod(256), [key[1], key[0]])
pt = ""
for i in range(len(enc)):
n = (i//2)**6
water = ZZ((stream * A**(n-1))[0])
pt += chr(water ^ enc[i])
print(pt)
enc = b'\x1a\x15\x05\t\x17\t\xf5\xa2-\x06\xec\xed\x01-\xc7\xcc2\x1eXA\x1c\x157[\x06\x13/!-\x0b\xd4\x91-\x06\x8b\xd4-\x1e+*\x15-pm\x1f\x17\x1bY'
def mul(a, b):
c = [[0, 0], [0, 0]]
for i in range(2):
for j in range(2):
for k in range(2):
c[i][j] += (a[i][k] * b[k][j]) % 256
c[i][j] %= 256
return c
def power(n):
if n==1: return key[1] % 256
if n==0: return key[0] % 256
res = [[1, 0], [0, 1]]
A = [[4, 7], [1, 0]]
while n:
if n & 1: res = mul(A, res)
A = mul(A, A)
n >>= 1
return (res[1][0] * key[1] + res[1][1] * key[0]) % 256
flag = b''
for i in range(0, len(enc)):
water = power((i//2)**6)
flag += bytes([water ^ enc[i]])
print(flag)
题目
from Crypto.Util.number import *
def gen_key(kbits):
while True:
p = getPrime(kbits)
q = getPrime(kbits)
if p % 4 == 3 and q % 4== 3:
break
return p, q
p ,q = gen_key(256)
flag = open("flag", 'rb').read()
pt = bytes_to_long(flag)
c = pow(pt, 2, p*q)
print(f"p={p}\nq={q}")
print(f"c={hex(c)[2:]}")
"""
p=65428327184555679690730137432886407240184329534772421373193521144693375074983
q=98570810268705084987524975482323456006480531917292601799256241458681800554123
c=4e072f435cbffbd3520a283b3944ac988b98fb19e723d1bd02ad7e58d9f01b26d622edea5ee538b2f603d5bf785b0427de27ad5c76c656dbd9435d3a4a7cf556
"""
解题思路
开始没想那么多直接按RSA算了,结果不对,发现p,q的选取多了mod 4 =3这个条件
补习Rabin加密算法:http://t.csdn.cn/wFYSE
from Crypto.Util.number import *
from sage.all import *
import itertools
p=65428327184555679690730137432886407240184329534772421373193521144693375074983
q=98570810268705084987524975482323456006480531917292601799256241458681800554123
c=int('4e072f435cbffbd3520a283b3944ac988b98fb19e723d1bd02ad7e58d9f01b26d622edea5ee538b2f603d5bf785b0427de27ad5c76c656dbd9435d3a4a7cf556')
pt1 = pow(c, (p+1)//4, p)
pt2 = p-pt1
pt3 = pow(c, (q+1)//4, q)
pt4 = q-pt3
for m1, m2 in itertools.combinations([pt1, pt2, pt3, pt4], 2):
m = CRT_list([m1, m2], [p, q])
m = long_to_bytes(m)
if m.startswith(b'hgame'): #直接挑出四个解中是flag的那一个
print(m)
print(m1, m2)
exit()
题目
from random import randint
from libnum import gcd, s2n
from secret import flag
plain = flag[6:-1]
assert flag == 'hgame{' + plain + '}'
v = bin(s2n(plain))[2:]
l = len(v)
a = [2 << i for i in range(l)] #生成私钥
m = randint(sum(a), 2 << l + 1) #生成模数
w = randint(0, m) #乘数
assert gcd(w, m) == 1 #设置互素条件
b = [w * i % m for i in a] #根据w,m运算生成公钥
c = 0
for i in range(l):
c += b[i] * int(v[i]) #生成密文
print(f'm = {m}')
print(f'b0 = {b[0]}')
print(f'c = {c}')
# m = 1528637222531038332958694965114330415773896571891017629493424
# b0 = 69356606533325456520968776034730214585110536932989313137926
# c = 93602062133487361151420753057739397161734651609786598765462162
解题思路
学习下背包加密算法:
理解看这篇:http://t.csdn.cn/rdLAz 推导看这篇:http://t.csdn.cn/TwtRs
from Crypto.Util.number import long_to_bytes
from gmpy2 import gcdext
m = 1528637222531038332958694965114330415773896571891017629493424
b0 = 69356606533325456520968776034730214585110536932989313137926
c = 93602062133487361151420753057739397161734651609786598765462162
winv = gcdext(b0, m)[1] #exgcd求模逆元
v = c * winv % m >> 1 #私钥序列和,注意>>1的作用
flag = 'hgame{' + long_to_bytes(int(bin(v)[2:][::-1], 2)).decode() + '}' #倒序切片,私钥为增序列
print(flag)
感觉这题出的嘎嘎好
题目
马上要过年喽,兔兔开心地去超市买年货,但是超市门口却写着"只有完成挑战才能进入超市",你能帮帮兔兔吗
task.py
import socketserver
import signal
from challenges.challenge1 import RSAServe as C0S
from challenges.challenge2 import RSAServe as C1S
from challenges.challenge3 import RSAServe as C2S
from challenges.challenge4 import RSAServe as C3S
FLAG = flag = b'hgame{This is a fake flag}'
SCORE = [0, 0, 0, 0]
BANNER = """
____ ____ _
| _ \/ ___| / \
| |_) \___ \ / _ \
| _ < ___) / ___ \
|_| \_\____/_/ \_\
Here are four challenges(1, 2, 3, 4), solve them all then you can get flag.
"""
MEMU = """
/----------------------------\\
| options |
| 1. get public key |
| 2. get cipher text |
| 3. check |
\\---------------------------/
"""
class Task(socketserver.BaseRequestHandler):
def _recvall(self):
BUFF_SIZE = 2048
data = b''
while True:
part = self.request.recv(BUFF_SIZE)
data += part
if len(part) < BUFF_SIZE:
break
return data.strip()
def send(self, msg, newline=True):
try:
if newline:
msg += b'\n'
self.request.sendall(msg)
except:
pass
def recv(self, prompt=b'> '):
self.send(prompt, newline=False)
return self._recvall()
def timeout_handler(self, signum, frame):
raise TimeoutError
def Serve(self, S):
self.send(MEMU.encode())
while True:
option = self.recv()
if option == b'1':
pubkey = S.pubkey()
for s in pubkey:
self.send(str(s).encode())
elif option == b'2':
c = S.encrypt()
self.send(c.encode())
elif option == b'3':
usr_answer = self.recv(b"input your answer: ")
return S.check(usr_answer)
else:
self.send(b"invaild option")
def handle(self):
signal.signal(signal.SIGALRM, self.timeout_handler)
signal.alarm(600)
self.send(BANNER.encode())
while True:
self.send(f'your score {sum(SCORE)}'.encode())
if sum(SCORE) == 4:
self.send(FLAG)
break
self.send(b'select challange')
code = self.recv()
if code == b'1':
S = C0S()
res = self.Serve(S)
if res == True:
SCORE[0] = 1
elif code == b'2':
S = C1S()
res = self.Serve(S)
if res == True:
SCORE[1] = 1
elif code == b'3':
S = C2S()
res = self.Serve(S)
if res == True:
SCORE[2] = 1
elif code == b'4':
S = C3S()
res = self.Serve(S)
if res == True:
SCORE[3] = 1
else:
self.send(b'invaild input')
class ThreadedServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
pass
class ForkedServer(socketserver.ForkingMixIn, socketserver.TCPServer):
pass
if __name__ == "__main__":
HOST, PORT = '0.0.0.0', 10002
server = ForkedServer((HOST, PORT), Task)
server.allow_reuse_address = True
print(HOST, PORT)
server.serve_forever()
challenge1.py
from Crypto.Util.number import *
from challenges import chall1_secret
class RSAServe:
def __init__(self) -> None:
self.e = 65537
self.p = getPrime(128)
self.q = getPrime(100)
self.r = getPrime(100)
self.m = chall1_secret
def encrypt(self):
m_ = bytes_to_long(self.m)
c = pow(m_, self.e, self.p*self.q*self.r)
return hex(c)
def check(self, msg):
return msg == self.m
def pubkey(self):
return self.p*self.q*self.r, self.e, self.p
challenge2.py
from Crypto.Util.number import *
from challenges import chall2_secret
class RSAServe:
def __init__(self) -> None:
self.p = getPrime(512)
self.q = getPrime(512)
self.e = 65537
self.m = chall2_secret
def encrypt(self):
m_ = bytes_to_long(self.m)
c = pow(m_ ,self.e, self.p*self.q)
self.q = getPrime(512)
return hex(c)
def check(self, msg):
return msg == self.m
def pubkey(self):
return self.p*self.q, self.e
challenge3.py
from Crypto.Util.number import *
from challenges import chall3_secret
class RSAServe:
def __init__(self) -> None:
self.p = getPrime(512)
self.q = getPrime(512)
self.e = 3
self.m = chall3_secret
def encrypt(self):
m_ = bytes_to_long(self.m)
c = pow(m_, self.e, self.p*self.q)
return hex(c)
def check(self, msg):
return msg == self.m
def pubkey(self):
return self.p*self.q, self.e
challenge4.py
from Crypto.Util.number import *
from challenges import chall4_secret
class RSAServe:
def __init__(self) -> None:
self.p = getPrime(512)
self.q = getPrime(512)
self.e = getPrime(17)
self.m = chall4_secret
def encrypt(self):
m_ = bytes_to_long(self.m)
c = pow(m_, self.e, self.p*self.q)
self.e = getPrime(17)
return hex(c)
def check(self, msg):
return msg == self.m
def pubkey(self):
return self.p*self.q, self.e
解题思路
参考文章:http://t.csdn.cn/BziLM
challenge1:
关于def __init__(self) -> None 这个用法,我查了一下:
->表示返回值为None 就是没有定义需要返回的值,比如:
def add(x, y) -> int:
return x+y
就是返回一个int类型(整数)但不是硬性要求,:“-> 类型”无实质性作用,比如某函数定义时“-> int”,但我依旧可以返回其他非int类型,"->"应该是为了增加可读性
已知p*q*r,e,p,即可求得q*r,可以直接在线分解质因数得到q和r
challenge2:加密后q发生改变,所以要两次获取pubkey
challenge3:小指数加密
challenge4:共模攻击