【Hgame2024】#Week1 Crypto详细全解+部分Web

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
A***ept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
A***ept-Language: zh-***,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
A***ept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Pragma: no-cache
Cache-Control: no-cache

然后得到最后的界面是flag已经给我了

我懵了一下 找了下没有

然后对比一下和上面的界面 发现多了一个Bearer

这里使用jwt解密

Bearer eyJhbGciOiJIUzI1NiIsInR5***I6IkpXVCJ9.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 取模
解题:
python">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} C1K=O1C2K=O2C3K=O3...C20K=O20C21K=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)

reference

上面脚本就是注意一下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}

转载请说明出处内容投诉
CSS教程_站长资源网 » 【Hgame2024】#Week1 Crypto详细全解+部分Web

发表评论

欢迎 访客 发表评论

一个令你着迷的主题!

查看演示 官网购买