Hgame 2024
第一周
解题情况:
Web
ezHTTP
考点:HTTP请求头 JWT
解题:
首先是基础的HTTP请求头伪造
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 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解密
1 Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJGMTRnIjoiaGdhbWV7SFRUUF8hc18xbVAwclQ0bnR9In0.VKMdRQllG61JTReFhmbcfIdq7MvJDncYpjaT7zttEDc
Bypass it
考点:javascrit禁用
解题:
无法登录
注册有弹窗拦截
根据题目提示到js enabled
所以先禁用注册 然后解禁
进行正常登录 直接拿到flag
hgame{2ea3880a2c9973b41606b1cfe1dce682aebdf972}
2048
考点:前段小游戏 F12
解题:
前端小游戏的思路就是去查看网页源码 看F12的代码
界面本身F12被禁用了 查看不了
解决方法就是在页面没有完全加载之前狂按F12卡进去
1 game-won":n(443),t=x?s0(n(439)," V+g5LpoEej/fy0nPNivz9SswHIhGaDOmU8CuXb72dB1xYMrZFRAl=QcTq6JkWK4 t3"):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 取模
解题:
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 from Crypto.Util.number import *e=0x10001 leak1=149127170073611271968182576751290331559018441805725310426095412837589227670757540743929865853650399839102838431507200744724939659463200158012469676979987696419050900842798225665861812331113632892438742724202916416060266581590169063867688299288985734104127632232175657352697898383441323477450658179727728908669 leak2=116122992714670915381309916967490436489020001172880644167179915467021794892927977272080596641785569119134259037522388335198043152206150259103485574558816424740204736215551933482583941959994625356581201054534529395781744338631021423703171146456663432955843598548122593308782245220792018716508538497402576709461 c=10529481867532520034258056773864074017027019578041866245400647840230251661652999709715919620810933437191661180003295923273655675729588558899592524235622728816065501918076120812236580344991140980991532347991252705288633014913479970610056845543523591324177567061948922552275235486615514913932125436543991642607028689762693617305246716492783116813070355512606971626645594961850567586340389705821314842096465631886812281289843132258131809773797777049358789182212570606252509790830994263132020094153646296793522975632191912463919898988349282284972919932761952603379733234575351624039162440021940592552768579639977713099971 import gmpy2n = 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下载的时候改名了
这样下载 使用PIL即可
题目:
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 64 65 66 67 68 69 70 71 72 73 74 75 76 import timefrom PIL import Image, ImageDraw, ImageFontimport threadingimport randomimport secretsflag = "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 ) font = ImageFont.truetype("arial.ttf" , font_size) text_color = (random.randint(0 , 255 ), random.randint(0 , 255 ), random.randint(0 , 255 )) 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] 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) 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]) threading.Thread(target=random_time, args=(xor_images(current_image, key_image), random_strings[i])).start()
注意读取当前目录的文件时 一定要看控制台的目录!
1 2 3 4 5 6 7 8 9 10 11 import timeimport threadingimport randomdef random_time (h ): time.sleep(random.random()) print (h) for i in range (10 ): threading.Thread(target=random_time, args=(i,)).start()
1 2 3 4 5 6 7 8 9 10 11 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}
C 1 ⨁ K = O 1 C 2 ⨁ K = O 2 C 3 ⨁ K = O 3 . . . C 2 0 ⨁ K = O 2 0 C 2 1 ⨁ K = O 2 1
目前我们得到的是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\}
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 2 0 = h g a m e { 1 2 3 4 5 6 7 8 9 a b c d e C 2 1 = h g a m e { 1 2 3 4 5 6 7 8 9 a b c d e }
所以我们创建一个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:
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 import osimport timefrom PIL import Image, ImageDraw, ImageFontimport threadingimport randomfolder_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
考点:佩尔方程
解题:
题目:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from Crypto.Util.number import *from Crypto.Cipher import AESimport random,stringfrom secret import flag,y,xdef 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} ' )
对佩尔方程求解 拿到AES的密钥key
1 2 3 4 5 6 7 8 9 10 11 12 13 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)
reference
上面脚本就是注意一下numTry的值调一下即可
exp:
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 from Crypto.Util.number import *from Crypto.Cipher import AESimport random,stringdef 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 decrypt (Key ): cipher = AES.new(Key,AES.MODE_ECB) decrypted = cipher.decrypt(enc) return decrypted D = 114514 y = 9037815138660369922198555785216162916412331641365948545459353586895717702576049626533527779108680 key=pad(long_to_bytes(y))[:16 ] m=decrypt(key) print (f'enc={m} ' )
ezPRNG
考点:PRNG 与运算 移位运算
解题:
题目
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 from Crypto.Util.number import *import uuiddef PRNG (R,mask ): nextR = (R << 1 ) & 0xffffffff i=(R&mask)&0xffffffff 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 )] 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
是每一次生成的R与mask做相与运算 然后对每一位做异或运算的结果 但是我们知道 mask只有特定的几个位置为1 其他全部为0 与0异或没有任何意义 1与0异或为1 0与0异或为0 所以不改变任何东西
故nextbit是每次的R与mask相与之后 mask为1的位置的值进行异或的结果 可能为1 也可能为0
因为每次R都是左移一位 高位溢出 低位补0 然后与1相与结果还是0 之后与上次生成的nextbit进行异或
其实就是在低位补充上次生成的nextbit
R一共32位 其中高31位已经全部被挤出 最高位保留的是R的最低位,下图展示R全部移出的前一个状态
针对PRNG这个函数进行转化 每次R都会向左移动一位 然后在低位填充的值是nextbit 题目虽然给了1000位 虽然一开始想全部用上,但是这也是一种陷阱吧 稍微对PRNG函数分析一下就会发现其实根本用不上 只需要前31个nextbit就可以恢复
那么我们恢复的思路就是对1bit进行猜 只有0和1两种情况 校验位分别是mask中为1的位置和生成的下一个bit进行比对,如下图所示
先假设猜测位为0 如果验证成功则该位置为0 否则验证失败 该位置为相反值1
现在得到了本次的状态 然后回溯上次的状态
相同的方法进行猜测
依次类推直到R全部恢复
到此分析流程结束!
exp:
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 mask = '10001001000010000100010010001001' for i in range (len (mask)): if int (mask[i]) == 1 : print (i, end=' ' ) output = ['...' ,'...' ,'...' ,'...' ] flag = '' for i in range (4 ): nextbits = output[i] R = [] for _ in range (32 ): temp = '0' + '' .join(R) + nextbits[:(32 -1 -len (R))] 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)])): R.insert(0 , '0' ) else : R.insert(0 , '1' ) R = '' .join(R) R = hex (int (R,2 ))[2 :] flag += R print (flag)
最后需要对结果划分
其格式是固定的 所以flag:
hgame{fbbbee82-3f43-4f91-9337-907880e4191a}