2023 HGAME网络攻防大赛wp

目录

WEEK1

MISC

e99p1ant_want_girlfriend

神秘的海报

Where am I

 Crypto

兔兔的车票

神秘的电话

Be Stream

 WEEK2

CRYPTO

Rabin

 包里有什么

RSA 大冒险1


WEEK1

MISC

e99p1ant_want_girlfriend

题目

兔兔在抢票网站上看到了一则相亲广告,人还有点小帅,但这个图片似乎有点问题,好像是CRC校验不太正确?

解题思路

题目已经提示 图片CRC校验不正确,而对于crc校验出错有两种情况:一是宽高正确,crc被篡改,那么只需要计算出正确的crc即可;二是crc正确,宽或高被篡改,可以用脚本计算出正确宽或高。

先试了第一种情况,图片用tweakpng打开,报错,2023 HGAME网络攻防大赛wp_第1张图片                                                                       按提示修改成正确值后再打开图片,没什么作用,说明是第二种情况

运行脚本得到正确高,修改后打开图片得到flag

2023 HGAME网络攻防大赛wp_第2张图片                                    很有新意的题,目的在于征婚

神秘的海报

题目

坐车回到家的兔兔听说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}

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}

解题思路

导出HTTP对象, 获得upload文件,010打开观察发现是个rar压缩包,删除前后多余部分,注意结尾也要删,rar结尾块也是一个固定字节串的块,依次是 C4 3D 7B 00 40 07 00,再打开还是报错,考虑有伪加密,将密码位(第24字节的第二位)改0,(4是加密),再把文件重命名填上文件后缀

2023 HGAME网络攻防大赛wp_第3张图片

得到一张全黑的图片,由于要获得图片的位置信息,自然想到去属性里找

2023 HGAME网络攻防大赛wp_第4张图片

hgame{116_24_1488_E_39_54_5418_N}
 

 Crypto

兔兔的车票

题目

兔兔刚买到车票就把车票丢到一旁,自己忙去了。结果再去找车票时发现原来的车票混在了其他东西里,而且票面还被污染了。你能帮兔兔找到它的车票吗。 注: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 xor 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。 示意图:

2023 HGAME网络攻防大赛wp_第5张图片

题目本身图片不多,就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张图片中我们可以找到

2023 HGAME网络攻防大赛wp_第6张图片 hgame{Oh_my_Ticket}

 (以上源于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 Stream

题目

很喜欢李小龙先生的一句话"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++写的,学习算法就好)
回到本题,根据递归函数可以得到stream的递推式 S[ i ] = 4 ∗ S [ i − 1] + 7 ∗ S [ i − 2]
类似于斐波那契数列,只不过增加了系数。 考虑⽤斐波那契矩阵和矩阵快速幂优化,下⾯提供
sagemath和python两种。
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)

 WEEK2

CRYPTO

Rabin

题目

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

Rabin密码的解密思路就是先将密⽂c分别在模p和模q下开根,由于p mod 4=3,q mod 4=3,所以⾮常好开根。然后在把开根结果两两组合进⾏CRT来恢复在模N下的值。
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

题⽬本⾝是⼀个背包加密,只要有 m, w, a 就可以解。
w 也可以不⽤求出,求出 w 的模逆元即可。 ⽤ exgcd 即可求出
原理: http://t.csdn.cn/JdwY4
gcdext函数参考: 密码学之模乘法逆元算法 欧几里得拓展算法 逆元 python java实现_SK Primin的博客-CSDN博客_gmpy2.gcdext
这⾥ a 使⽤了特殊的构造⽅式,从⼆进制数来看, a 的每个元素只有 1 位是 1 ,
(a=[10,100,1000,...]),显然不同元素之间不 存在冲突,所以可以通过转化为⼆进制来迅速解出。
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)

感觉这题出的嘎嘎好

RSA 大冒险1

题目

马上要过年喽,兔兔开心地去超市买年货,但是超市门口却写着"只有完成挑战才能进入超市",你能帮帮兔兔吗

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:共模攻击

你可能感兴趣的:(CTF,crypto,CTF,MISC,算法,python,网络安全)