密码学赛题复现:2021-CryptoCTF(一)

admin 2023年3月13日09:06:45评论41 views字数 21624阅读72分4秒阅读模式
Crypto CTF is an online competition for hackers to test, evaluate, and expand their cryptography exploiting skills. In this CTF, we will provide various crypto challenges regarding modern cryptography techniques.
All crypto lovers are most welcome!
Crypto CTF is a revenge for everlasting complaints by CTF participants about crypto challenges in CTF contests. In this brand-new tournament, we are trying to provide the crypto lovers with fun and challenging pure crypto tasks to squeeze their heart and test their passion for cryptography.
Each task will be based on a particular cryptographic primitive, or it will include a direct application of cryptography in other fields.
The organizers of these tournaments generously offer their skills' knowledge to design original Crypto tasks and challenges for similar contests.

Long Live Crypto :)

这两天在复现2022-CryptoCTF,但是想着2021的CryptoCTF也没复现完,于是就决定先从2021开始了。

  • Farm

POINTS:41

考点:有限域,逐字节爆破

#!/usr/bin/env sage

from sage.all import *
import string, base64, math
from flag import flag

ALPHABET = string.printable[:62] + '\='

F = list(GF(64))

def keygen(l):
 key = [F[randint(1, 63)] for _ in range(l)] 
 key = math.prod(key) # Optimization the key length :D
 return key

def maptofarm(c):
 assert c in ALPHABET
 return F[ALPHABET.index(c)]

def encrypt(msg, key):
 m64 = base64.b64encode(msg)
 enc, pkey = '', key**5 + key**3 + key**2 + 1
 for m in m64:
  enc += ALPHABET[F.index(pkey * maptofarm(chr(m)))]
 return enc

# KEEP IT SECRET 
key = keygen(14) # I think 64**14 > 2**64 is not brute-forcible :P

enc = encrypt(flag, key)
print(f'enc = {enc}')

#enc = "805c9GMYuD5RefTmabUNfS9N9YrkwbAbdZE0df91uCEytcoy9FDSbZ8Ay8jj"

这道题目,生成了一个F = list(GF(64)),这里用sagemath测试一下,得到的是

[0,
 z6,
 z6^2,
 z6^3,
 z6^4,
 z6^5,
 z6^4 + z6^3 + z6 + 1,
 z6^5 + z6^4 + z6^2 + z6,
 z6^5 + z6^4 + z6^2 + z6 + 1,
 z6^5 + z6^4 + z6^2 + 1,
 z6^5 + z6^4 + 1,
 z6^5 + z6^4 + z6^3 + 1,
 z6^5 + z6^3 + 1,
 z6^3 + 1,
 z6^4 + z6,
 z6^5 + z6^2,
 z6^4 + z6 + 1,
 z6^5 + z6^2 + z6,
 z6^4 + z6^2 + z6 + 1,
 z6^5 + z6^3 + z6^2 + z6,
 z6^2 + z6 + 1,
 z6^3 + z6^2 + z6,
 z6^4 + z6^3 + z6^2,
 z6^5 + z6^4 + z6^3,
 z6^5 + z6^3 + z6 + 1,
 z6^3 + z6^2 + 1,
 z6^4 + z6^3 + z6,
 z6^5 + z6^4 + z6^2,
 z6^5 + z6^4 + z6 + 1,
 z6^5 + z6^4 + z6^3 + z6^2 + 1,
 z6^5 + 1,
 z6^4 + z6^3 + 1,
 z6^5 + z6^4 + z6,
 z6^5 + z6^4 + z6^3 + z6^2 + z6 + 1,
 z6^5 + z6^2 + 1,
 z6^4 + 1,
 z6^5 + z6,
 z6^4 + z6^3 + z6^2 + z6 + 1,
 z6^5 + z6^4 + z6^3 + z6^2 + z6,
 z6^5 + z6^2 + z6 + 1,
 z6^4 + z6^2 + 1,
 z6^5 + z6^3 + z6,
 z6^3 + z6^2 + z6 + 1,
 z6^4 + z6^3 + z6^2 + z6,
 z6^5 + z6^4 + z6^3 + z6^2,
 z6^5 + z6 + 1,
 z6^4 + z6^3 + z6^2 + 1,
 z6^5 + z6^4 + z6^3 + z6,
 z6^5 + z6^3 + z6^2 + z6 + 1,
 z6^2 + 1,
 z6^3 + z6,
 z6^4 + z6^2,
 z6^5 + z6^3,
 z6^3 + z6 + 1,
 z6^4 + z6^2 + z6,
 z6^5 + z6^3 + z6^2,
 z6 + 1,
 z6^2 + z6,
 z6^3 + z6^2,
 z6^4 + z6^3,
 z6^5 + z6^4,
 z6^5 + z6^4 + z6^3 + z6 + 1,
 z6^5 + z6^3 + z6^2 + 1,
 1]

长度正好是64。ok,虽然不是很明白怎么来的,但是可以先放一边。然后看到程序调用了keygen(l)函数,

def keygen(l):
 key = [F[randint(1, 63)] for _ in range(l)] 
 key = math.prod(key) # Optimization the key length :D
 return key

该函数首先生成长度为l的数组,元素取自F,最后prod所有的元素,但由于其结果仍会在F这个galois-field中,所以到这里已经可以预见这道题怎么做了,只用穷举一下这个F中的所有元素当作key即可,只有64种可能。

然后我们看具体的加密流程以编写解密算法。

def encrypt(msg, key):
 m64 = base64.b64encode(msg)
 enc, pkey = '', key**5 + key**3 + key**2 + 1
 for m in m64:
  enc += ALPHABET[F.index(pkey * maptofarm(chr(m)))]
 return enc

重点在enc += ALPHABET[F.index(pkey * maptofarm(chr(m)))]

看到算法是对明文逐位字节加密的,并且根据这个比赛的flag格式,我们知道明文前三个字节应该是‘CCT’,那么也就知道了其base64编码所以,我们可以先爆破出密钥,然后逐字节爆破明文

exp:

import string, base64, math


ALPHABET = string.printable[:62] + '\='

F = list(GF(64))

def keygen(l):
 key = [F[randint(1, 63)] for _ in range(l)] 
 key = math.prod(key) # Optimization the key length :D
 return key

def maptofarm(c):
 assert c in ALPHABET
 return F[ALPHABET.index(c)]

def encrypt(msg, key):
 m64 = base64.b64encode(msg)
 enc, pkey = '', key**5 + key**3 + key**2 + 1
 for m in m64:
  enc += ALPHABET[F.index(pkey * maptofarm(chr(m)))]
 return enc

def myenc(m,key):
 enc, pkey = '', key**5 + key**3 + key**2 + 1
 enc += ALPHABET[F.index(pkey * maptofarm(m))]
 return enc

flag = b'CCT'
flag64 = b''
ciphertext = '805c9GMYuD5RefTmabUNfS9N9YrkwbAbdZE0df91uCEytcoy9FDSbZ8Ay8jj'
for key in F:
 if encrypt(flag,key) == ciphertext[:4]:
  for i in ciphertext:
   for j in ALPHABET:
    if myenc(j,key) == i:
     flag64+=j.encode()
     break
print(base64.b64decode(flag64))

#'CCTF{EnCrYp7I0n_4nD_5u8STitUtIn9_iN_Fi3Ld!}'
  • Keybase

POINTS:48

考点:AES-CBC模式

#!/usr/bin/env python3

from Crypto.Util import number
from Crypto.Cipher import AES
import os, sys, random
from flag import flag

def keygen():
    iv, key = [os.urandom(16) for _ in '01']
    return iv, key

def encrypt(msg, iv, key):
    aes = AES.new(key, AES.MODE_CBC, iv)
    return aes.encrypt(msg)

def decrypt(enc, iv, key):
    aes = AES.new(key, AES.MODE_CBC, iv)
    return aes.decrypt(enc)

def die(*args):
    pr(*args)
    quit()

def pr(*args):
    s = " ".join(map(str, args))
    sys.stdout.write(s + "n")
    sys.stdout.flush()

def sc():
    return sys.stdin.readline().strip()

def main():
    border = "+"
    pr(border*72)
    pr(border, " hi all, welcome to the simple KEYBASE cryptography task, try to    ", border)
    pr(border, " decrypt the encrypted message and get the flag as a nice prize!    ", border)
    pr(border*72)

    iv, key = keygen()
    flag_enc = encrypt(flag, iv, key).hex()

    while True:
        pr("| Options: n|t[G]et the encrypted flag n|t[T]est the encryption n|t[Q]uit")
        ans = sc().lower()
        if ans == 'g':
            pr("| encrypt(flag) =", flag_enc)
        elif ans == 't':
            pr("| Please send your 32 bytes message to encrypt: ")
            msg_inp = sc()
            if len(msg_inp) == 32:
                enc = encrypt(msg_inp, iv, key).hex()
                r = random.randint(0, 4)
                s = 4 - r
                mask_key = key[:-2].hex() + '*' * 4
                mask_enc = enc[:r] + '*' * 28 + enc[32-s:]
                pr("| enc =", mask_enc)
                pr("| key =", mask_key)
            else:
                die("| SEND 32 BYTES MESSAGE :X")
        elif ans == 'q':
            die("Quitting ...")
        else:
            die("Bye ...")

if __name__ == '__main__':
    main()

是一道需要交互的题目,那么我们看到题目提供的功能。

一个是 g,会将flag的密文输出

if ans == 'g':
 pr("| encrypt(flag) =", flag_enc)

一个是 t,接受32字节的消息加密,然后输出密钥前14字节,以及相应密文,不过会对密文第一组做一个隐藏处理,只给出第一组开头和结尾共4个字节

elif ans == 't':
    pr("| Please send your 32 bytes message to encrypt: ")
    msg_inp = sc()
    if len(msg_inp) == 32:
        enc = encrypt(msg_inp, iv, key).hex()
        r = random.randint(0, 4)
        s = 4 - r
        mask_key = key[:-2].hex() + '*' * 4
        mask_enc = enc[:r] + '*' * 28 + enc[32-s:]
        pr("| enc =", mask_enc)
        pr("| key =", mask_key)

而加密和解密使用的是AES-CBC模式

密码学赛题复现:2021-CryptoCTF(一)

所以这里的切入点很明确,就是利用功能 t,

  1. 首先我们发送两组共32字节的明文 “A”*32 过去,会得到14字节的密钥和一组模糊密文和一组清晰密文
  2. 我们爆破密钥的剩余两个字节,然后用爆破出来的密钥尝试对第二组密文进行解密,解密后的结果和第一组模糊密文中的清晰部分进行异或,如果得到的均为”A“,则我们的密钥爆破正确。
  3. 由于CBC模式,我们还需要iv。此时我们拥有密钥,明文;如果拥有第一组密文,我们对第一组密文进行解密并与明文异或即可知道iv
  4. 需要意识到的是,第一组密文用于第二组明文加密,而第二组明文我们又是知道的,所以我们使用密钥解密第二组密文,并与第二组明文异或得到第一组密文,然后解密第一组密文,与第一组明文异或,得到iv
  5. 使用密钥和iv解密flag的密文,获取flag

脚本改编自https://blog.cryptohack.org/cryptoctf2021-easy#keybase

from pwn import *
from Crypto.Cipher import AES


context.log_level = 'debug'
r = process(["python3","keybase.py"])


r.sendlineafter("[Q]uitn""G")
enc_flag = bytes.fromhex(r.recvline().strip().decode().split(" = ")[1])


while True:
    r.sendlineafter("[Q]uitn""T")
    r.sendlineafter("Please send your 32 bytes message to encrypt: n", b"A"*32)
    enc = r.recvline().strip().decode().split(" = ")[1]
    key = r.recvline().strip().decode().split(" = ")[1]
    prefix = enc.split("*")[0]
    if len(prefix) == 4:
        prefix = bytes.fromhex(prefix)
        break

key = bytearray.fromhex(key[:-4]) + b"x00x00"
for k1 in range(256):
    key[-2] = k1
    for k2 in range(256):
        key[-1] = k2
        if xor(AES.new(key, AES.MODE_ECB).decrypt(bytes.fromhex(enc[-32:])), b"AA")[:2] == prefix:
            block = xor(AES.new(key, AES.MODE_ECB).decrypt(bytes.fromhex(enc[-32:])), b"A"*16)
            IV = xor(AES.new(key, AES.MODE_ECB).decrypt(block)[:16], b"A"*16)
            print("key",key.hex())
            print("IV",IV.hex())
            print(AES.new(key, AES.MODE_CBC, IV).decrypt(enc_flag))
            exit()
  • Rima

POINTS:56

考点:爆破,信息恢复

#!/usr/bin/env python

from Crypto.Util.number import *
#from flag import FLAG
from sympy import isprime
FLAG=b'CCTF{_how_finD_7h1s_1z_s3cr3T?!}'
def nextPrime(n):
    while True:
        n += (n % 2) + 1
        if isprime(n):
            return n

f = [int(x) for x in bin(int(FLAG.hex(), 16))[2:]]

print(len(f))
f.insert(0, 0)
for i in range(len(f)-1):
    f[i] += f[i+1]
a = nextPrime(len(f))
b = nextPrime(a)
print(a,b)
g, h = [[_ for i in range(x) for _ in f] for x in [a, b]]
print(len(g),len(h))
print('-'*100)

print(len(g),len(h))
c = nextPrime(len(f) >> 2)
print(c)
for _ in [g, h]:
    for __ in range(c):
        _.insert(0, 0)

    for i in range(len(_) -  c):
        _[i] += _[i+c]

g, h = [int(''.join([str(_) for _ in __]), 5) for __ in [g, h]]

# for _ in [g, h]:
#     if _ == g:
#         fname = 'g'
#     else:
#         fname = 'h'
#     of = open(f'{fname}.enc', 'wb')
#     of.write(long_to_bytes(_))
#     of.close()

这道题的函数很简单,一个nextPrime,顾名思义。

然后程序对flag取二进制,然后在最开始插入一个0。如果flag是这个比赛的常规flag,那么会以‘C’开头,而‘C’的ASCII码的二进制是“01‘开头,所以补上这个0后,不出意外这个f数组的长度应该是8 * len(flag)。

f = [int(x) for x in bin(int(FLAG.hex(), 16))[2:]]

print(len(f))
f.insert(0, 0)

然后进行一个操作,f数组的每一个(除了最后一个)元素赋值为当前元素和后一个元素值之和。

for i in range(len(f)-1):
    f[i] += f[i+1]

然后程序获取三个值 a = nextPrime(len(f)), b = nextPrime(a), c = nextPrime(len(f) >> 2)。三个值的取值只与flag的长度相关。(那么显然最后可以通过爆破flag的长度来解题)

a = nextPrime(len(f))
b = nextPrime(a)

然后是有点绕的一个数组生成 ,其实就是将数组f 分别重复 a次 和 b次。

g, h = [[_ for i in range(x) for _ in f] for x in [a, b]]

接着给g,h前面插c个0,

然后与之前类似的一个操作,g和h数组的每一个(除了后面c个)元素赋值为当前元素和后C个元素值之后。

for _ in [g, h]:
    for __ in range(c):
        _.insert(0, 0)

    for i in range(len(_) -  c):
        _[i] += _[i+c]

密码学赛题复现:2021-CryptoCTF(一)

类似这样两个数组相加。最后还将当前得到的值拼接,视为五进制


g, h = [int(''.join([str(_) for _ in __]), 5) for __ in [g, h]]

所以解题思路就很清晰。

  1. 首先我们爆破flag的长度,我们知道密文的长度为 a * f + c 和 b * f + c,而a ,b,c 又和f直接相关,所以这很好判断。

  2. 然后我们先恢复这个”移位C“的向量加,以上图为例,由于最后结果仍然保留了原来g数组的开头和结尾,故中间部分是很好恢复的。

  3. 最后我们在恢复一个”移位1“的向量加即可解出flag。

(其实用不到两个enc,有一个就够了。)

exp.py

from Crypto.Util.number import *

def to_base_5(n):
    s = ''
    while n:
        s = str(n % 5) + s
        n //= 5
    return s

def nextPrime(n):
    while True:
        n += (n % 2) + 1
        if isPrime(n):
            return n

with open('g.enc''rb') as f:
    data = f.read()
    g_long = bytes_to_long(data)

g_base5 = to_base_5(g_long)
g = [x for x in g_base5]

for len in range(55):
    f = len * 8
    a = nextPrime(f)
    b = nextPrime(a)
    c = nextPrime(f >> 2)
    
    if len(g) != a * f + c:
        continue

    g = [int(x) for x in g]

    for i in range(len(g) - c - 1, -1, -1):
        g[i] -= g[i+c]

    g = g[c:c+f]
    
    for i in range(len(g) - 1 - 1, -1, -1):
        g[i] -= g[i+1]
        
    g = [str(x) for x in g]
    g = int(''.join(g), 2)
    g = long_to_bytes(g) 
    print(g)
    
    #CCTF{_how_finD_7h1s_1z_s3cr3T?!}
  • Titu

POINTS:69

考点:丢番图方程

#!/usr/bin/env python3

from Crypto.Util.number import *
from flag import flag

l = len(flag)
m_1, m_2 = flag[: l // 2], flag[l // 2:]

x, y = bytes_to_long(m_1), bytes_to_long(m_2)

k = '''
000bfdc32162934ad6a054b4b3db8578674e27a165113f8ed018cbe9112
4fbd63144ab6923d107eee2bc0712fcbdb50d96fdf04dd1ba1b69cb1efe
71af7ca08ddc7cc2d3dfb9080ae56861d952e8d5ec0ba0d3dfdf2d12764
'
''.replace('n''')

assert((x**2 + 1)*(y**2 + 1) - 2*(x - y)*(x*y - 1) == 4*(int(k, 16) + x*y))

代码倒是不多,眼瞅着就是一道数学题

解方程 

用sage整理一下

sage: var('x,y')
(x, y)
sage: f = (x**2 + 1)*(y**2 + 1) - 2*(x - y)*(x*y - 1) - 4*x*y
sage: f.factor()
(x + 1)^2*(y - 1)^2

那看看这个 k 是不是平方数

sage: k
246389259423689900229483388850933720271363907782961941639413620688310657308991195363798484778005049234253341752674411282663124201840620584781830032437353134292733496201534415265400175632719346764031781179041636
sage: factor(k)
2^2 * 3^2 * 11^4 * 19^2 * 47^2 * 71^2 * 3449^2 * 11953^2 * 5485619^2 * 2035395403834744453^2 * 17258104558019725087^2 * 1357459302115148222329561139218955500171643099^2

那开个方的话,就是解 

然后这里 x 和 y 的长度大小应该是比较接近的,emmm,排列组合一下?方程右边的分一分,给到 (x+1) 和 (y+1)

或者换个意思,显然 ,遍历一下  的因子那就

脚本来自https://blog.cryptohack.org/cryptoctf2021-easy#titu

from Crypto.Util.number import *
from gmpy2 import iroot
k = 246389259423689900229483388850933720271363907782961941639413620688310657308991195363798484778005049234253341752674411282663124201840620584781830032437353134292733496201534415265400175632719346764031781179041636 
m = iroot(k,int(2))[0] * 2
 
for d in divisors(m):   
    x = long_to_bytes(d - 1)   
    if b'CCTF{' in x:  
        print(x)  
        y = (m // d) + 1 
        print(long_to_bytes(y))
        break
  • Symbols

POINTS:70

考点:latex

h, my eyes, my eyes! People still can solve this kind of cryptography? Mathematicians should love this one! 密码学赛题复现:2021-CryptoCTF(一)

Cap Cap Theta Finv { Pi ltimes aleph y _ wp infty therefore heartsuit _ Lsh aleph Theta eth Xi }

CCTF{Play_with_LaTeX}

  • Hyper Normal

POINTS:71

考点:Matrix,爆破

import random
from flag import FLAG

p = 8443

def transpose(x):
    result = [[x[j][i] for j in range(len(x))] for i in range(len(x[0]))]
    return result

def vsum(u, v):
    assert len(u) == len(v)
    l, w = len(u), []
    for i in range(l):
        w += [(u[i] + v[i]) % p]
    return w

def sprod(a, u):
    w = []
    for i in range(len(u)):
        w += [a*u[i] % p]
    return w

def encrypt(msg):
    l = len(msg)
    hyper = [ord(m)*(i+1) for (m, i) in zip(list(msg), range(l))]
    V, W = [], []
    for i in range(l):
        v = [0]*i + [hyper[i]] + [0]*(l - i - 1)
        V.append(v)
    random.shuffle(V)
    for _ in range(l):
        R, v = [random.randint(0, 126) for _ in range(l)], [0]*l
        for j in range(l):
            v = vsum(v, sprod(R[j], V[j]))  
        W.append(v)
    
    random.shuffle(transpose(W))
    return W

enc = encrypt(FLAG)
print(enc)

output = 

分析上述源码,可以知道,

transpose函数为矩阵转置

def transpose(x):
    result = [[x[j][i] for j in range(len(x))] for i in range(len(x[0]))]
    return result

vsum函数为向量加法

def vsum(u, v):
    assert len(u) == len(v)
    l, w = len(u), []
    for i in range(l):
        w += [(u[i] + v[i]) % p]
    return w

sprod函数为向量的倍增

def sprod(a, u):
    w = []
    for i in range(len(u)):
        w += [a*u[i] % p]
    return w

看到加密函数encrypt

首先生成hyper向量,向量的每个值与flag每个字节相关,为 

然后用hyper向量生成对角矩阵V,效果为


然后利用shuffle函数扰乱一下行向量。

def encrypt(msg):
    l = len(msg)
    hyper = [ord(m)*(i+1for (m, i) in zip(list(msg), range(l))]
    V, W = [], []
    for i in range(l):
        v = [0]*i + [hyper[i]] + [0]*(l - i - 1)
        V.append(v)
    random.shuffle(V)

之后生成 l 个 R向量,用于生成w矩阵,规则为v = vsum(v, sprod(R[j], V[j]))

for _ in range(l):
    R, v = [random.randint(0126for _ in range(l)], [0]*l
    for j in range(l):
        v = vsum(v, sprod(R[j], V[j]))  
    W.append(v)

可以知道,这样乘了之后,之前对于V的shuffle对于结果来说其实是没有意义。由于我们并不关心 R 的实际顺序,于是等价生成了R矩阵

,然后和矩阵

相乘。


然后这里还有一个random.shuffle(transpose(W))函数,但是这里 shuffle 的其实是矩阵transpose(W),而函数返回的是W矩阵,所以这句代码对返回结果其实没有任何影响。

那么我们得到矩阵 


而加密函数中两次shuffle都没了任何影响。

如果此时不是由于模运算的存在且p = 8443 太小了。那么我们对矩阵列向量求一个gcd就能过获取flag的每一个字节的值了。

所以这里的另辟蹊径。由于模运算的存在显然是逆不回去了,我们选择正向爆破。

由于这里的,那么我们就尝试所有的  和每一个可见字符

for c in printable:
    possibilities = [ord(c)*r*(idx+1) % p for r in range(127)]

如果flag密文向量中的每一个值都存在于 possibilities 向量中,那么则说明我们对于改密文向量我们的明文字符选取正确。

脚本来自 https://blog.cryptohack.org/cryptoctf2021-easy#hyper-normal

from string import printable

p = 8443

with open('output.txt''r') as f:
    enc = eval(f.read())

results = []

for i in range(len(enc[0])):
    tmp = []
    for j in enc:
        tmp.append(j[i])
    results.append(tmp)

flag = ''
for idx, result in enumerate(results):
    for c in printable:
        possibilities = [ord(c)*r*(idx+1) % p for r in range(127)]
        if all([i in possibilities for i in result]):
            flag += c
            break
print(flag)
  • Salt and Pepper

POINTS:71

考点:哈希拓展攻击

#!/usr/bin/env python3

from hashlib import md5, sha1
import sys
from secret import salt, pepper
from flag import flag


assert len(salt) == len(pepper) == 19
assert md5(salt).hexdigest()    == '5f72c4360a2287bc269e0ccba6fc24ba'
assert sha1(pepper).hexdigest() == '3e0d000a4b0bd712999d730bc331f400221008e0'


def auth_check(salt, pepper, username, password, h):
    return sha1(pepper + password + md5(salt + username).hexdigest().encode('utf-8')).hexdigest() == h

def die(*args):
    pr(*args)
    quit()

def pr(*args):
    s = " ".join(map(str, args))
    sys.stdout.write(s + "n")
    sys.stdout.flush()

def sc():
    return sys.stdin.readline().strip()

def main():
    border = "+"
    pr(border*72)
    pr(border, "  welcome to hash killers battle, your mission is to login into the ", border)
    pr(border, "  ultra secure authentication server with provided information!!    ", border)
    pr(border*72)

    USERNAME = b'n3T4Dm1n'
    PASSWORD = b'P4s5W0rd'

    while True:
        pr("| Options: n|t[L]ogin to server n|t[Q]uit")
        ans = sc().lower()
        if ans == 'l':
            pr('| send your username, password as hex string separated with comma: ')
            inp = sc()
            try:
                inp_username, inp_password = [bytes.fromhex(s) for s in inp.split(',')]
            except:
                die('| your input is not valid, bye!!')
            pr('| send your authentication hash: ')
            inp_hash = sc()
            if USERNAME in inp_username and PASSWORD in inp_password:
                if auth_check(salt, pepper, inp_username, inp_password, inp_hash):
                    die(f'| Congrats, you are master in hash killing, and it is the flag: {flag}')
                else:
                    die('| your credential is not valid, Bye!!!')
            else:
                die('| Kidding me?! Bye!!!')
        elif ans == 'q':
            die("Quitting ...")
        else:
            die("Bye ...")

if __name__ == '__main__':
    main()

看到获取flag的条件

if USERNAME in inp_username and PASSWORD in inp_password:
    if auth_check(salt, pepper, inp_username, inp_password, inp_hash):
        die(f'| Congrats, you are master in hash killing, and it is the flag: {flag}')

USERNAME 要在 inp_username 中,PASSWORD 要在 inp_password 中

然后满足

sha1(pepper + password + md5(salt + username).hexdigest().encode('utf-8')).hexdigest() == h

其中 salt,pepper 未知,但是知道 md5(salt) 和 sha1(pepper)以及salt 和 pepper 的长度;inp_username,inp_password,inp_hash可控。那么显然这里是一个哈希长度拓展攻击了。

  1. 我们知道salt的长度以及哈希,那么我们可以使用哈希拓展攻击,附加上username 并获取到 md5(salt + username) 的哈希。

    ./hash_extender -f md5 -d -s 5f72c4360a2287bc269e0ccba6fc24ba -a n3T4Dm1n -l 19

    得到

    95623660d3d04c7680a52679e35f041c
    x80x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x98x00x00x00x00x00x00x00n3T4Dm1n
  2. 然后我们知道 pepper 的长度以及哈希,还知道 password,我们再次使用哈希拓展攻击,附加上 password+md5(salt + username) 的哈希并获取到最终的哈希

    ./hash_extender -f sha1 -d  -s  3e0d000a4b0bd712999d730bc331f400221008e0 -a P4s5W0rd95623660d3d04c7680a52679e35f041c -l 19

    得到

    83875efbe020ced3e2c5ecc908edc98481eba47f
    x80x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x98P4s5W0rd95623660d3d04c7680a52679e35f041c
  3. 那么我们以

    用户名:x80x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x98x00x00x00x00x00x00x00n3T4Dm1n

    密码:x80x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x98P4s5W0rd

    哈希:

    83875efbe020ced3e2c5ecc908edc98481eba47f

    作为输入即可通过验证,获取flag

工具hash_extender来自github:https://github.com/iagox86/hash_extender

下述脚本来自:https://pwnthenope.pythonanywhere.com/writeups/salt_and_pepper.html

from pwn import remote, process
import subprocess
import re

def main():
    local = False
    username = 'n3T4Dm1n'
    password = 'P4s5W0rd'
    if local:
        md5_salt = '7ae4d6728e33ff002bf67a2e5194ccb1'
        sha1_pepper = '8923ecf3550e9ca6cbb26066590fb619a2d65e71'
    else:
        md5_salt = '5f72c4360a2287bc269e0ccba6fc24ba'
        sha1_pepper = '3e0d000a4b0bd712999d730bc331f400221008e0'

    result = subprocess.check_output(["./hash_extender""-f""md5""-d""""-s", md5_salt, "-a", username, "-l""19"]).decode()
    new_signature, new_username = re.findall(r"[a-fA-F0-9]{4,}", result) # hex-encoded
    
    result = subprocess.check_output(["./hash_extender""-f""sha1""-d""""-s", sha1_pepper, "-a", password + new_signature, "-l""19"]).decode()
    final_signature, new_password = re.findall(r"[a-fA-F0-9]{4,}", result) # hex-encoded
    
    new_password = new_password[:-64]

    print("intermediate signature =", new_signature)
    print("final signature =", final_signature)
    print("username =", new_username)
    print("password =", new_password)

    if local:
        r = process(["python""salt_pepper.py"])
    else:
        r = remote("02.cr.yp.toc.tf", 28010)
    r.recvuntil(b'uit')
    r.recvline()
    r.sendline(b'L')
    r.recvuntil(b'comma:')
    r.recvline()
    r.sendline(",".join([new_username, new_password]).encode())
    r.recvuntil(b'hash:')
    r.recvline()
    r.sendline(final_signature.encode())
    r.interactive()

if __name__ == "__main__":
    main()
  • Hamul

POINTS:83

考点:RSA

from Crypto.Util.number import *
from flag import flag

nbit = 64

while True:
    p, q = getPrime(nbit), getPrime(nbit)
    P = int(str(p) + str(q))
    Q = int(str(q) + str(p))
    PP = int(str(P) + str(Q))
    QQ = int(str(Q) + str(P))
    if isPrime(PP) and isPrime(QQ):
        break

n = PP * QQ
m = bytes_to_long(flag.encode('utf-8'))
if m < n:
    c = pow(m, 65537, n)
    print('n =', n)
    print('c =', c)

# n = 98027132963374134222724984677805364225505454302688777506193468362969111927940238887522916586024601699661401871147674624868439577416387122924526713690754043
# c = 42066148309824022259115963832631631482979698275547113127526245628391950322648581438233116362337008919903556068981108710136599590349195987128718867420453399

emmm,RSA经典整活,就是分解特殊构造的n。

我们设 

那么有

只要我们能够解出 ,因为他只有 128bit,能够大力分解,然后我们就可以解题了。

看着这里 N 的形式,本地测一下,会发现 N 的最高十八位就是  的高位,N 的最低十八、十九位就是 的低位。然后中间会缺一到两位,要爆破一下。

脚本来自https://blog.cryptohack.org/cryptoctf2021-easy#hamul

       

原文始发于微信公众号(山石网科安全技术研究院):密码学赛题复现:2021-CryptoCTF(一)

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年3月13日09:06:45
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   密码学赛题复现:2021-CryptoCTF(一)https://cn-sec.com/archives/1228781.html

发表评论

匿名网友 填写信息