Hgame 2024
文章目录
第一周
解题情况:
Web
ezHTTP
考点:HTTP请求头 JWT
解题:
首先是基础的HTTP请求头伪造
GET / HTTP/1.1
Host: 47.100.137.175:30761
#1
User-Agent: Mozilla/5.0 (Vidar; VidarOS x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0
#2
Referer: vidar.club
#3
X-Forwarded-For:127.0.0.1
Forwarded-For:127.0.0.1
Forwarded:127.0.0.1
X-Forwarded-Host:127.0.0.1
X-remote-IP:127.0.0.1
X-remote-addr:127.0.0.1
True-Client-IP:127.0.0.1
X-Client-IP:127.0.0.1
Client-IP:127.0.0.1
X-Real-IP:127.0.0.1
Ali-CDN-Real-IP:127.0.0.1
Cdn-Src-Ip:127.0.0.1
Cdn-Real-Ip:127.0.0.1
CF-Connecting-IP:127.0.0.1
X-Cluster-Client-IP:127.0.0.1
WL-Proxy-Client-IP:127.0.0.1
Proxy-Client-IP:127.0.0.1
Fastly-Client-Ip:127.0.0.1
True-Client-Ip:127.0.0.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,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
Connection: close
Upgrade-Insecure-Requests: 1
Pragma: no-cache
Cache-Control: no-cache
然后得到最后的界面是flag已经给我了
我懵了一下 找了下没有
然后对比一下和上面的界面 发现多了一个Bearer
这里使用jwt解密
Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJGMTRnIjoiaGdhbWV7SFRUUF8hc18xbVAwclQ0bnR9In0.VKMdRQllG61JTReFhmbcfIdq7MvJDncYpjaT7zttEDc
Bypass it
考点:javascrit禁用
解题:
无法登录
注册有弹窗拦截
根据题目提示到js enabled
所以先禁用注册 然后解禁
进行正常登录 直接拿到flag
hgame{2ea3880a2c9973b41606b1cfe1dce682aebdf972}
2048
考点:前段小游戏 F12
解题:
前端小游戏的思路就是去查看网页源码 看F12的代码
界面本身F12被禁用了 查看不了
解决方法就是在页面没有完全加载之前狂按F12卡进去
game-won":n(443),t=x?s0(n(439),"V+g5LpoEej/fy0nPNivz9SswHIhGaDOmU8CuXb72dB1xYMrZFRAl=QcTq6JkWK4t3"):n(453);this[n(438)][n(437)].add(e),this[n(438)][n(435)]("p")[-1257*-5+9*1094+-5377*3].textContent=t}
找到敏感信息game-won
游戏获胜的结果 对后面的内容丢进cyberchef看看
非常像base64的换表
找到密文
flag{b99b820f-934d-44d4-93df-41361df7df2d}
选课
考点:查接口 格式 爆破
解题:
先对提交的时候抓包
在前端看到格式
多次暴力攻击 修改成功
hgame{w0W_!_1E4Rn_To_u5e_5cripT_^_^}
Reverse
ezIDA
考点:IDA使用
解题:
拖进IDAx64 在IDA View-A窗口 按空格快捷键转化 查到flag
Crypto
ezRSA
考点:RSA 取模
解题:
from Crypto.Util.number import *
# from secret import flag
# m=bytes_to_long(flag)
# p=getPrime(1024)
# q=getPrime(1024)
# n=p*q
# phi=(p-1)*(q-1)
e=0x10001
# c=pow(m,e,n)
# leak1=pow(p,q,n)
# leak2=pow(q,p,n)
# print(f'leak1={leak1}')
# print(f'leak2={leak2}')
# print(f'c={c}')
# """
leak1=149127170073611271968182576751290331559018441805725310426095412837589227670757540743929865853650399839102838431507200744724939659463200158012469676979987696419050900842798225665861812331113632892438742724202916416060266581590169063867688299288985734104127632232175657352697898383441323477450658179727728908669
leak2=116122992714670915381309916967490436489020001172880644167179915467021794892927977272080596641785569119134259037522388335198043152206150259103485574558816424740204736215551933482583941959994625356581201054534529395781744338631021423703171146456663432955843598548122593308782245220792018716508538497402576709461
c=10529481867532520034258056773864074017027019578041866245400647840230251661652999709715919620810933437191661180003295923273655675729588558899592524235622728816065501918076120812236580344991140980991532347991252705288633014913479970610056845543523591324177567061948922552275235486615514913932125436543991642607028689762693617305246716492783116813070355512606971626645594961850567586340389705821314842096465631886812281289843132258131809773797777049358789182212570606252509790830994263132020094153646296793522975632191912463919898988349282284972919932761952603379733234575351624039162440021940592552768579639977713099971
# """
import gmpy2
n = leak2 * leak1
phi = (leak1 - 1) * (leak2 - 1)
d = gmpy2.invert(e, phi)
print(long_to_bytes(pow(c,d,n)))
根据leak的生成方式 直接作为p和q解即可
StrangePicture
考点:异或 图片加密
解题:
首先本地想测试一下 但是没有PIL
模块
直接下载发现找不到 是因为python3的缘故
目前PIL在pip下载的时候改名了
pip install Pillow
这样下载 使用PIL即可
题目:
import time
from PIL import Image, ImageDraw, ImageFont
import threading
import random
import secrets
flag = "hgame{fake_flag}"
#生成指定宽度和高度的随机颜色图像 大概率是个标准化的东西 不重要 完全没有
def generate_random_image(width, height):
image = Image.new("RGB", (width, height), "white")
pixels = image.load()
for x in range(width): #遍历像素的每一列
for y in range(height): #遍历像素的每一行
red = random.randint(0, 255)
green = random.randint(0, 255)
blue = random.randint(0, 255)
#给当前像素点赋颜色
pixels[x, y] = (red, green, blue)
return image
#在给定图像上随机位置绘制一个文本标志 单纯的绘制 对于解密没帮助
def draw_text(image, width, height, token):
font_size = random.randint(16, 40) #成一个介于 16 和 40 之间的随机字体大小。
font = ImageFont.truetype("arial.ttf", font_size) #加载字体文件 "arial.ttf" 并创建一个指定大小的字体对象。
text_color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) #生成一个随机的 RGB 值,作为文本的颜色。
#在图像上的 (x, y) 坐标处绘制给定的文本标志 token,使用指定的字体、颜色进行填充。 同时x和y的操作 保证随机坐标一定在图像内部
x = random.randint(0, width - font_size * len(token))
y = random.randint(0, height - font_size)
draw = ImageDraw.Draw(image)
draw.text((x, y), token, font=font, fill=text_color)
return image
#对图像进行异或 感觉可逆
def xor_images(image1, image2):
if image1.size != image2.size:
raise ValueError("Images must have the same dimensions.")
xor_image = Image.new("RGB", image1.size) #创建一个新图像 存储异或后的结果
pixels1 = image1.load() #载入像素数据
pixels2 = image2.load()
xor_pixels = xor_image.load()
for x in range(image1.size[0]): #遍历图像的每一列
for y in range(image1.size[1]): #遍历每一行
r1, g1, b1 = pixels1[x, y] #颜色值RGB
r2, g2, b2 = pixels2[x, y]
xor_pixels[x, y] = (r1 ^ r2, g1 ^ g2, b1 ^ b2) #三值异或 完成像素点的异或
return xor_image
#生成一定数量的指定长度的唯一随机字符串 恢复时间的 看看有没有什么顺序 => 无
def generate_unique_strings(n, length):
unique_strings = set()
while len(unique_strings) < n:
random_string = secrets.token_hex(length // 2)
unique_strings.add(random_string)
return list(unique_strings)
#给图像命名 图片个数就是flag的字符数
random_strings = generate_unique_strings(len(flag), 8)
current_image = generate_random_image(120, 80)
key_image = generate_random_image(120, 80)
#以一定的时间顺序保存图片
def random_time(image, name):
time.sleep(random.random())
image.save(".\\png_out\\{}.png".format(name))
for i in range(len(flag)):
current_image = draw_text(current_image, 120, 80, flag[i]) #将flag的每个字符逐个绘制到current_image中
#为了保证字符的顺序 设置了时间线程 与keyimage异或 函数是random_time 参数args分别是图片和名称
threading.Thread(target=random_time, args=(xor_images(current_image, key_image), random_strings[i])).start()
注意读取当前目录的文件时 一定要看控制台的目录!
- 时间函数测试
import time
import threading
import random
def random_time(h):
time.sleep(random.random())
print(h)
for i in range(10):
threading.Thread(target=random_time, args=(i,)).start()
4
6
9
3
1
2
5
8
0
7
证明生成的顺序是不一定的
分析一下题目的加密过程:
C
1
⨁
K
=
O
1
C
2
⨁
K
=
O
2
C
3
⨁
K
=
O
3
.
.
.
C
20
⨁
K
=
O
20
C
21
⨁
K
=
O
21
C_1 \bigoplus K = O_1\\ C_2 \bigoplus K = O_2\\ C_3 \bigoplus K = O_3\\ ...\\ C_{20} \bigoplus K = O_{20}\\ C_{21} \bigoplus K = O_{21}
C1⨁K=O1C2⨁K=O2C3⨁K=O3...C20⨁K=O20C21⨁K=O21
目前我们得到的是Output 一共是21张图片 证明flag的长度是21
按照正常顺序 从C1到C21是每张图片多一个字符 假设flag为hgame{123456789abcde}
则有:
C
1
=
h
C
2
=
h
g
C
3
=
h
g
a
C
4
=
h
g
a
m
C
5
=
h
g
a
m
e
.
.
.
C
20
=
h
g
a
m
e
{
123456789
a
b
c
d
e
C
21
=
h
g
a
m
e
{
123456789
a
b
c
d
e
}
C_1=h\\ C_2=hg\\ C_3=hga\\ C_4=hgam\\ C_5=hgame\\ ...\\ C_{20}=hgame\{123456789abcde\\ C_{21}=hgame\{123456789abcde\}
C1=hC2=hgC3=hgaC4=hgamC5=hgame...C20=hgame{123456789abcdeC21=hgame{123456789abcde}
所以我们创建一个21*21的循环空间 让每一个Output都与其他Output做异或 这样K与K异或消失 只剩下C与C异或
因为图像的异或是逐个像素点 所以像素点相同的地方会直接消失
我们关注最后两组 当O20和O21异或 等价于 C20和C21异或
相同部分消失 则只剩下}
但是因为进程时间的操作 我们不知道O20和O21是哪个 但是只有}
的图片只能有两个
一个是O20与其他所有循环异或 直到O20^O21
一个是O21与其他所有循环异或 直到O21^O20
范围大大所以 这只要看一下这两个循环哪个能有结果即可
因为O21与其他异或的时候
与C20异或得到}
与C19异或得到e}
与C18异或得到de}
字符个数是确定的
所以我们就能从后往前恢复出flag啦!
回到题目
exp:
import os
import time
from PIL import Image, ImageDraw, ImageFont
import threading
import random
# 图片文件夹路径
folder_path = "png_out"
# 获取文件夹中的所有图片文件
image_files = [file for file in os.listdir(folder_path) if file.endswith(".png")]
#对图像进行异或
def xor_images(image1, image2):
if image1.size != image2.size:
raise ValueError("Images must have the same dimensions.")
xor_image = Image.new("RGB", image1.size)
pixels1 = image1.load()
pixels2 = image2.load()
xor_pixels = xor_image.load()
for x in range(image1.size[0]):
for y in range(image1.size[1]):
r1, g1, b1 = pixels1[x, y]
r2, g2, b2 = pixels2[x, y]
xor_pixels[x, y] = (r1 ^ r2, g1 ^ g2, b1 ^ b2)
return xor_image
count = 0
for i in range(21):
for j in range(21):
# 读取第二个图片并执行异或操作
out1 = Image.open(os.path.join(folder_path, image_files[i]))
out2 = Image.open(os.path.join(folder_path, image_files[j]))
key_image = xor_images(out1, out2)
key_image.save(".\\keykey\\{}.png".format(count))
count += 1
result:
这是异或的结果 锁定第73个 生成下标是(3,10) 确认的方法是66为空 是3与3异或
所以下标为3的图片可能是C20也可能是C21 生成范围是63-83这21张图
提取出来 根据字符个数得到后16个字符的生成过程
前6个为hgame{
就不浪费时间了
如果上面这个推不出来
其实我们也能看到另一个 相当于下标(10,3)产生的结果 这个无法恢复
综上证明下标为3(也就是png_out的第4张图)的是C21 下标为10(也就是png_out的第11张图)的是C20
到此完结flag:hgame{1adf_17eb_803c}
ezMath
考点:佩尔方程
解题:
题目:
from Crypto.Util.number import *
from Crypto.Cipher import AES
import random,string
from secret import flag,y,x
def pad(x):
return x+b'\x00'*(16-len(x)%16)
def encrypt(KEY):
cipher= AES.new(KEY,AES.MODE_ECB)
encrypted =cipher.encrypt(flag)
return encrypted
D = 114514
assert x**2 - D * y**2 == 1
flag=pad(flag)
key=pad(long_to_bytes(y))[:16]
enc=encrypt(key)
print(f'enc={enc}')
#enc=b"\xce\xf1\x94\x84\xe9m\x88\x04\xcb\x9ad\x9e\x08b\xbf\x8b\xd3\r\xe2\x81\x17g\x9c\xd7\x10\x19\x1a\xa6\xc3\x9d\xde\xe7\xe0h\xed/\x00\x95tz)1\\\t8:\xb1,U\xfe\xdec\xf2h\xab`\xe5'\x93\xf8\xde\xb2\x9a\x9a"
对佩尔方程求解 拿到AES的密钥key
#sage
def solve_pell(N, numTry = 10000000):
cf = continued_fraction(sqrt(N))
for i in range(numTry):
denom = cf.denominator(i)
numer = cf.numerator(i)
if numer^2 - N * denom^2 == 1:
return numer, denom
return None, None
N = 114514
solve_pell(N)
#(3058389164815894335086675882217709431950420307140756009821362546111334285928768064662409120517323199,9037815138660369922198555785216162916412331641365948545459353586895717702576049626533527779108680)
上面脚本就是注意一下numTry的值调一下即可
exp:
from Crypto.Util.number import *
from Crypto.Cipher import AES
import random,string
# from secret import flag,y,x
def pad(x):
return x+b'\x00'*(16-len(x)%16)
enc=b"\xce\xf1\x94\x84\xe9m\x88\x04\xcb\x9ad\x9e\x08b\xbf\x8b\xd3\r\xe2\x81\x17g\x9c\xd7\x10\x19\x1a\xa6\xc3\x9d\xde\xe7\xe0h\xed/\x00\x95tz)1\\\t8:\xb1,U\xfe\xdec\xf2h\xab`\xe5'\x93\xf8\xde\xb2\x9a\x9a"
# def encrypt(KEY):
# cipher= AES.new(KEY,AES.MODE_ECB)
# encrypted =cipher.encrypt(flag)
# return encrypted
def decrypt(Key):
cipher = AES.new(Key,AES.MODE_ECB)
decrypted = cipher.decrypt(enc)
return decrypted
#pell方程求解
D = 114514
# assert x**2 - D * y**2 == 1
# flag=pad(flag)
y = 9037815138660369922198555785216162916412331641365948545459353586895717702576049626533527779108680
key=pad(long_to_bytes(y))[:16]
m=decrypt(key)
print(f'enc={m}')
#enc=b'hgame{G0od!_Yo3_k1ow_C0ntinued_Fra3ti0ns!!!!!!!}\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
ezPRNG
考点:PRNG 与运算 移位运算
解题:
题目
from Crypto.Util.number import *
import uuid
def PRNG(R,mask):
nextR = (R << 1) & 0xffffffff #与1相与 有1则1 否则全为0 作用是限制位数
# print(bin(R), bin(nextR), R.bit_length(), R.bit_length())
i=(R&mask)&0xffffffff #nextbit就是i的所有值相异或 对当前R操作
nextbit=0
while i!=0:
nextbit^=(i%2) #取最后一位
i=i//2 #舍弃最后一位
nextR^=nextbit
return (nextR,nextbit)
R=str(uuid.uuid4())
flag='hgame{'+R+'}'
R=R.replace('-','')
Rlist=[int(R[i*8:i*8+8],16) for i in range(4)] #8位一组 进行切割 一共四组
mask=0b10001001000010000100010010001001
output=[]
#对切割的四组进行加密
for i in range(4):
R=Rlist[i]
out=''
for _ in range(1000):
(R,nextbit)=PRNG(R,mask)
out+=str(nextbit)
output.append(out)
print(f'output={output}')
output=['1111110110111011110000101011010001000111111001111110100101000011110111111100010000111110110111100001001000101101011110111100010010100000011111101101110101011010111000000011110000100011101111011011000100101100110100101110001010001101101110000010001000111100101010010110110111101110011011001011111011010101011000011011000111011011111001101010111100101100110001011010010101110011101001100111000011110111000001101110000001111100000100000101111100010110111001110011010000011011110110011000001101011111111010110011010111010101001000010011110110011110110101011110111010011010010110111111010011101000110101111101111000110011111110010110000100100100101101010101110010101001101010101011110111010011101110000100101111010110101111110001111111110010000000001110011100100001011111110100111011000101001101001110010010001100011000001101000111010010000101101111101011000000101000001110001011001010010001000011000000100010010010010111010011111111011100100100100101111111001110000111110110001111001111100101001001100010', '0010000000001010111100001100011101111101111000100100111010101110010110011001011110101100011101010000001100000110000000011000000110101111111011100100110111011010000100011111000111001000101001110010110010001000110010101011110011101000011111101101011000011110001101011111000110111000011000110011100100101100111100000100100101111001011101110001011011111111011010100010111011000010010101110110100000110100000100010101000010111101001000011000000000111010010101010111101101011111011001000101000100011001100101010110110001010010001010110111011011111101011100111001101111111111010011101111010010011110011111110100110011111110110001000111100010111000101111000011011011111101110101110100111000011100001010110111100011001011010011010111000110101100110100011101101011101000111011000100110110001100110101010110010011011110000111110100111101110000100010000111100010111000010000010001111110110100001000110110100100110110010110111010011111101011110000011101010100110101011110000110101110111011010110110000010000110001', '1110110110010001011100111110111110111001111101010011001111100100001000111001101011010100010111110101110101111010111100101100010011001001011101000101011000110111000010000101001000100111010110001010000111110110111000011001100010001101000010001111111100000101111000100101000000001001001001101110000100111001110001001011010111111010111101101101001110111010111110110011001000010001010100010010110110101011100000101111100100110011110001001001111100101111001111011011010111001001111010001100110001100001100000110000011111010100101111000000101011111010000111110000101111100010000010010111010110100101010101001111100101011100011001001011000101010101001101100010110000010001110011110011100111000110101010111010011010000001100001011000011101101000000011111000101111101011110011000011011000100100110111010011001111101100101100011000101001110101111001000010110010111101110110010101101000000101001011000000001110001110000100000001001111100011010011000000011011101111101001111110001011101100000010001001010011000001', '0001101010101010100001001001100010000101010100001010001000100011101100110001001100001001110000110100010101111010110111001101011011101110000011001000100100101000011011101000111001001010011100010001010110111011100100111110111001010010111010100000100111110101110010010110100001000010010001101111001110100010001011101100111011101011101100100101011010101000101001000101110011011111110110011111111100000000011100000010011000110001000110101010001011000010101000110000101001110101010111011010010111011001010011100010101001100110000110101100010000100110101110100001101001011011110011100110011001010110100101010111110110111100000111010001111101110000000000111011011101000011001010010111001110111000100111011110100101000100011011101100011111000101110110110111111001111000000011100011000010000101001011001101110101000010101001000100110010000101001111100101000001011011010011110001101000001101111010100101001100010100000111000011110101010100011011001110001011110111010111011010101101100000110000001010010101111011']
分析:
由于mask的限制 只有mask为1的位置和mask相与才有可能为1 其余全部为0
- 理解一下nextbit到底泄露的是什么信息:
是每一次生成的R与mask做相与运算 然后对每一位做异或运算的结果 但是我们知道 mask只有特定的几个位置为1 其他全部为0 与0异或没有任何意义 1与0异或为1 0与0异或为0 所以不改变任何东西
故nextbit是每次的R与mask相与之后 mask为1的位置的值进行异或的结果 可能为1 也可能为0
- 分析一下R的每一次迭代:
因为每次R都是左移一位 高位溢出 低位补0 然后与1相与结果还是0 之后与上次生成的nextbit进行异或
其实就是在低位补充上次生成的nextbit
- 分析一下nextbit的前31位的含义:
R一共32位 其中高31位已经全部被挤出 最高位保留的是R的最低位,下图展示R全部移出的前一个状态
针对PRNG这个函数进行转化 每次R都会向左移动一位 然后在低位填充的值是nextbit 题目虽然给了1000位 虽然一开始想全部用上,但是这也是一种陷阱吧 稍微对PRNG函数分析一下就会发现其实根本用不上 只需要前31个nextbit就可以恢复
那么我们恢复的思路就是对1bit进行猜 只有0和1两种情况 校验位分别是mask中为1的位置和生成的下一个bit进行比对,如下图所示
先假设猜测位为0 如果验证成功则该位置为0 否则验证失败 该位置为相反值1
现在得到了本次的状态 然后回溯上次的状态
相同的方法进行猜测
依次类推直到R全部恢复
到此分析流程结束!
exp:
#首先针对mask提取出相与有效位1
mask = '10001001000010000100010010001001'
for i in range(len(mask)):
if int(mask[i]) == 1:
print(i, end=' ')
#0 4 7 12 17 21 24 28 31
#然后对R进行恢复
output = ['...','...','...','...']
flag = ''
for i in range(4):
nextbits = output[i]
R = [] #列表的形式便于插入 从头部 存放已知R的bit位
for _ in range(32): #每次恢复1bit 一共32bit 因为与0xffffffff 为限制位数的作用 如果实在不理解 可以用题目的脚本跑一下 看看真实的数据是什么就可以了
temp = '0' + ''.join(R) + nextbits[:(32-1-len(R))] #凑齐32位 第一个是猜测位为0 第二部分是已知R位 第三部分是nextbit填充位
print(temp)
#进行猜测校验判断
if(int(temp[0]) ^ int(temp[4]) ^ int(temp[7]) ^ int(temp[12]) ^ int(temp[17]) ^ int(temp[21]) ^ int(temp[24]) ^ int(temp[28]) ^ int(temp[31]) == int(nextbits[32-1-len(R)])):
#猜测成功填充0
R.insert(0, '0') #在第0位插入0
else:
R.insert(0, '1')
R = ''.join(R)
R = hex(int(R,2))[2:] #二进制转十进制 转16进制
flag += R
print(flag)
#fbbbee823f434f919337907880e4191a
最后需要对结果划分
其格式是固定的 所以flag:
hgame{fbbbee82-3f43-4f91-9337-907880e4191a}