2023 CryptoCTF wp by lingfeng (medium分类——2)

medium

Bertrand (21 solves)

附件代码

task.py

#!/usr/bin/env python3

import sys
import math
import functools
from PIL import Image
from random import randint
import string
from secret import flag, key, n

def pad(s, l):
	while len(s) < l:
		s += string.printable[randint(0, 61)]
	return s

def sox(n, d):
	x, y, t = 0, 0, d
	for s in range(n - 1):
		u = 1 & t // 2
		v = 1 & t ^ u
		x, y = spin(2**s, x, y, u, v)
		x += 2**s * u
		y += 2**s * v
		t = t // 4
	return x, y

def spin(n, x, y, u, v):
	if v == 0:
		if u == 1:
			x = n - 1 - x
			y = n - 1 - y
		x, y = y, x
	return x, y

def encrypt(msg, key, n):#n=16
	_msg = pad(msg, n ** 2)
	_msg, _key = [ord(_) for _ in _msg], [ord(_) for _ in key]
	img = Image.new('RGBA', (n, n), 'darkblue')
	pix = img.load()

	for _ in range(len(key)):
		w = len(_key)
		h = n**2 // w + 1
		arr = [[_msg[w*x + y] if w*x + y < n**2 else None for x in range(h)] for y in range(w)]
		_conf = sorted([(_key[i], i) for i in range(w)])
		_marshal = [arr[_conf[i][1]] for i in range(w)]
		_msg = functools.reduce(lambda a, r: a + _marshal[r], range(w), [])
		_msg = list(filter(lambda x: x is not None, _msg))
		_msg = [(_msg[_] + _key[_ % w]) % 256 for _ in range(n**2)]

	for d in range(n**2):
		x, y = sox(n, d)
		pix[x,y] = (_msg[d], _msg[d], _msg[d])
	keysum = sum(_key) if w > 0 else 0
	orient = keysum % 4
	img = img.rotate(90*orient)
	return img

assert len(key) == 3
img = encrypt(flag, key, n)
img.save('enc.png')

enc.png
在这里插入图片描述

我的理解

代码使用了一个叫做"像素置换加密"的算法。函数接受三个参数:msg(要加密的消息),key(加密密钥),n(图片的大小,即宽和高)。
函数首先对要加密的消息进行填充,填充的长度是n的平方。然后将消息和密钥转换为ASCII码的列表。接下来,创建一个大小为n x n的RGBA图片,并将其加载到像素数组中。
然后,对密钥的每个字符进行循环,计算宽度w和高度h。然后创建一个二维数组arr,将消息按照一定规则填充到arr中。然后按照密钥的顺序重新排列arr,并将其转换为一维数组_msg。然后过滤掉_msg中的None值,并对_msg中的每个值进行一定的计算。最后,将_msg中的值分别赋给图片中的像素。
接下来,根据密钥的ASCII码求和计算一个值keysum,并根据keysum%4对图片进行旋转。最后返回加密后的图片。

我的回答

仔细观察代码,先看第一步

	for _ in range(len(key)):
		w = len(_key)
		h = n**2 // w + 1
		arr = [[_msg[w*x + y] if w*x + y < n**2 else None for x in range(h)] for y in range(w)]
		_conf = sorted([(_key[i], i) for i in range(w)])
		_marshal = [arr[_conf[i][1]] for i in range(w)]
		_msg = functools.reduce(lambda a, r: a + _marshal[r], range(w), [])
		_msg = list(filter(lambda x: x is not None, _msg))
		_msg = [(_msg[_] + _key[_ % w]) % 256 for _ in range(n**2)]

这一串,其实对加密有用的就只有第三行和第四行和最后一行,中间的是那两行算是列表整合,把二维列表转为一维列表,然后去掉None。(建议加上两个print,设置一个flag和key,跑一下测试测试,测试后容易理解)
而这个加密的过程,属于线性过程,置换操作以及模加,属于很好逆的
这个就是主要加密过程
然后是

	for d in range(n**2):
		x, y = sox(n, d)
		pix[x,y] = (_msg[d], _msg[d], _msg[d])
	keysum = sum(_key) if w > 0 else 0
	orient = keysum % 4
	img = img.rotate(90*orient)

这一段,就是将msg的值全部作为像素点经过置换放在图片中的各个位置,对于这个sox函数,并不用理解
他的作用就是产生一个列表,然后msg根据这个列表置换再图片的各个位置,既然n,d都是已知的,我们只需要跑一遍这个代码,获得这个列表,那么将它逆一遍就可以的。
然后图片化成后会根据key的ASCII值和来进行旋转,我们可以根据猜测的key来将其旋转回来。
key的值我们并不知道,只知道是3位,我们可以根据题上中给的范围string.printable[randint(0, 61)](这个是我猜的)进行爆破key。
但是这个题不只是这么简单,如果靠爆破进行得到key的话,中间的那些获取像素点来提取msg,以及三次循环解密的运算时间是不短的特别是 6 1 3 61^3 613,时间几乎无解,我采用的方法是,key三位,ascii大小排序一共6种情况,而图片旋转一共四种情况,我们先将这些情况列举出来,以字典或者列表的形式保存,当爆破的key满足这些条件时直接调用,而不再重复进行这些运算,节约时间。
即便如此,也要需要二十多分钟的时间来爆破出来😭,(应该有更好的方法降低时间复杂度,但是我没找到,ψ(._. )>)
师傅们有更好的方法,欢迎来讨论

我的代码

# !/usr/bin/env python3.10
# -*- coding: utf-8 -*-
# @Time    : 2023/7/16 15:56
# @Author  : lingfeng
# @File    : test-w.py
import functools
import itertools
import string

from PIL import Image
from tqdm import *


def sox(n, d):
    x, y, t = 0, 0, d
    for s in range(n - 1):
        u = 1 & t // 2
        v = 1 & t ^ u
        x, y = spin(2 ** s, x, y, u, v)
        x += 2 ** s * u
        y += 2 ** s * v
        t = t // 4
    return x, y


def spin(n, x, y, u, v):
    if v == 0:
        if u == 1:
            x = n - 1 - x
            y = n - 1 - y
        x, y = y, x
    return x, y


def inv(S_BOX):
    return [(S_BOX.index(i)) for i in range(len(S_BOX))]


def get_inv_arr_dec(_key):
    _conf = sorted([(_key[i], i) for i in range(w)])
    t = tuple([_conf[i][1] for i in range(w)])
    return ARR2[x[t]]


def decrypt(_msg, _key, n):
    inv_arr_dec = get_inv_arr_dec(_key)
    for _ in range(3):
        _msg = [(_msg[_] - _key[_ % w]) % 256 for _ in range(n ** 2)]
        _msg = [_msg[i] for i in inv_arr_dec]
    return _msg


def get_ori_key(key):
    _key = [ord(_) for _ in key]
    keysum = sum(_key)
    orient = keysum % 4
    return orient, _key


def get_msg(orient):
    return Msg[orient]


n, w, ARR2, Msg = 256, 3, [], []
h = n ** 2 // w + 1
dic = {(sox(n, d)): d for d in trange(n ** 2)}
arr = [[(w * x + y) if w * x + y < n ** 2 else None for x in range(h)] for y in range(w)]
x = {j: i for i, j in zip(range(6), list(itertools.permutations(range(3), 3)))}
ARR = [[arr[i[0]], arr[i[1]], arr[i[2]]] for i in x.keys()]
for arr_dec in tqdm(ARR):
    arr_dec = functools.reduce(lambda a, r: a + arr_dec[r], range(w), [])
    arr_dec = list(filter(lambda x: x is not None, arr_dec))
    inv_arr_dec = inv(arr_dec)
    ARR2.append(inv_arr_dec)
for orient in range(4):
    img = Image.open("enc.png")
    width, height = img.size
    img = img.rotate(90 * (4 - orient))
    pixels = img.load()
    _msg = [0] * n ** 2
    for y in range(height):
        for z in range(width):
            r, g, b, a = pixels[z, y]
            _msg[dic[(z, y)]] = r
    Msg.append(_msg)
KEY = list(itertools.product(string.printable[:61], repeat=3))
for i in tqdm(KEY):
    key = ''.join(i)
    orient, _key = get_ori_key(key)
    _msg = get_msg(orient)
    msg = decrypt(_msg, _key, n)
    flag = bytes(msg)
    if b'CCTF{' in flag[:60]:
        print(flag)  # CCTF{3nCrypTioN_8Y_c0lUmn4R_7rAnSp05it!On!}
        break

Insights ( 88 solves)

附件代码

Insights.sage

#!/usr/bin/env sage

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

def getRandomNBits(n):
	nb = '1' + ''.join([str(randint(0, 1)) for _ in range(n - 1)])
	return nb

def getLeader(L, n):
	nb = L + getRandomNBits(n)
	return int(nb, 2)

def genPrime(L, nbit):
	l = len(L)
	assert nbit >= l
	while True:
		p = getLeader(L, nbit - l)
		if is_prime(p):
			return p

def genKey(L, nbit):
	p, q = [genPrime(L, nbit) for _ in '__']
	n = p * q
	d = next_prime(pow(n, 0.2919))
	phi = (p - 1) * (q - 1)
	e = inverse(d, phi)
	pubkey, privkey = (n, e), (p, q)
	return pubkey, privkey

def encrypt(msg, pubkey):
	n, e = pubkey
	m = bytes_to_long(msg)
	c = pow(m, e, n)
	return c

nbit = 1024
L = bin(bytes_to_long(b'Practical'))[2:]
pubkey, privkey = genKey(L, nbit)
p, q = privkey
c = encrypt(flag, pubkey)

print('Information:')
print('-' * 85)
print(f'n = {p * q}')
print(f'e = {pubkey[1]}')
print(f'c = {c}')
print(f'p = {bin(p)[2:len(L)]}...[REDACTED]')
print(f'q = {bin(q)[2:len(L)]}...[REDACTED]')
print('-' * 85)

我的回答

没啥好说的,非预期应该是,题目应该是想考Boneh and Durfee attack的扩大范围,因为刚好卡在边界,理论是有限的,跑不出的,需要手动更改。
但是非预期就很离谱
d = n e x t p r i m e ( p o w ( n , 0.2919 ) ) d = next_prime(pow(n, 0.2919)) d=nextprime(pow(n,0.2919))
直接出d,然后解就完了

我的代码

sage

# !/usr/bin/env python3.10
# -*- coding: utf-8 -*-
# @Time    : 2023/7/8 10:26
# @Author  : lingfeng
# @File    : wp.sage

n = 12765231982257032754070342601068819788671760506321816381988340379929052646067454855779362773785313297204165444163623633335057895252608396010414744222572161530653104640020689896882490979790275711854268113058363186249545193245142912930804650114934761299016468156185416083682476142929968501395899099376750415294540156026131156551291971922076435528869024742993840057342092865203064721826362149723366381892539617642364692012936270150691803063945919154346756726869466855557344213050973081755499746750276623648407677639812809665472258655462846021403503851719008687214848550916999977775070011121527941755954255781343103086789
e = 459650454686946706615371845737527916539205656667844780634386049268800615782964920944229084502752167395446158290854047696006034750210758341744841762479191173017773034647739346927390580848998121830029134542880713409306092967282675122699586503684943407535067216738556403169403622104762516293879994387324370835718056251706150557820106296417750402984941838652433642298378976899556042987560946508887315484380807248331504458640857234708123277403252632993828101306072382329879857946191508782246793011691530554606521701055094223574951862129713872918021549814674387049788995785872980320871421550616327471735316980754238323013
c = 10992248752412909788626396175372747713079469256270100576886987393986576680666320383209810005318254336440105142571546847427454822405793626080251363454531982746373841267986148332456716023293306870382809568309620264499225135226626560298741596462262513921032733814032790312163314776421380481083058518893602887082464123177575742160690315666730642727773288362853901330620841098230284739614618790097180848133698381487679399364400048499041582830157094876815030301231505774900176910650887780842536610942820066913075027528705150102760422836458745949063992228680293226303245265232017738712226154128654682937687199768621565945171
d = next_prime(pow(n, 0.2919))
from Crypto.Util.number import *
print(long_to_bytes(int(pow(c,d,n))))#CCTF{RSA_N3w_rEc0rd5_4Nd_nEw_!nSi9h75!}

Resuction (78 solves)

附件代码

#!/usr/bin/env python3

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

from decimal import *

def keygen(nbit, r):
	while True:
		p, q = [getPrime(nbit) for _ in '__']
		d, n = getPrime(64), p * q
		phi = (p - 1) * (q - 1)
		if GCD(d, phi) == 1:
			e = inverse(d, phi)
			N = bin(n)[2:-r]
			E = bin(e)[2:-r]
			PKEY = N + E
			pkey = (n, e)
			return PKEY, pkey

def encrypt(msg, pkey, r):
	m = bytes_to_long(msg)
	n, e = pkey
	c = pow(m, e, n)
	C = bin(c)[2:-r]
	return C

r, nbit = 8, 1024
PKEY, pkey = keygen(nbit, r)
print(f'PKEY = {int(PKEY, 2)}')
FLAG = flag.lstrip(b'CCTF{').rstrip(b'}')
enc = encrypt(FLAG, pkey, r)
print(f'enc = {int(enc, 2)}')

我的理解

题目上已经说是前面的suction的加强版,仔细观察代码,先看不同之处
发现d是直接生成了64bit的素数,p,q的bit数远远的增大了,其他一切都和suction的一样,少给了n,c,e的低8位。

我的方法

既然n的值非常大,再使用fact分解是不现实的,根据题目给出了d的比特,分析d,n的关系,发现d十分小,貌似满足维纳攻击的条件,既然如此,不妨一试,爆破e,n通过维纳攻击来进行分解出p,q。
首先和suction一样,将n,e的爆破范围降低,但是e并不是素数,没有办法减少,而n仍是由两个大素数相乘组成,还是和suction的一样通过模小素数以及判断是不是素数来缩小范围。
得到p,q之后,就是正常的rsa解密即可。

我的代码

# !/usr/bin/env python3.10
# -*- coding: utf-8 -*-
# @Time    : 2023/7/8 14:40
# @Author  : lingfeng
# @File    : WP.py
#脚本1
#Sage
def factor_rsa_wiener(N, e):
    N = Integer(N)
    e = Integer(e)
    cf = (e / N).continued_fraction().convergents()
    for f in cf:
        k = f.numer()
        d = f.denom()
        if k == 0:
            continue
        phi_N = ((e * d) - 1) / k
        b = -(N - phi_N + 1)
        dis = b ^ 2 - 4 * N
        if dis.sign() == 1:
            dis_sqrt = sqrt(dis)
            p = (-b + dis_sqrt) / 2
            q = (-b - dis_sqrt) / 2
            if p.is_integer() and q.is_integer() and (p * q) % N == 0:
                p = p % N
                q = q % N
                if p > q:
                    return (p, q)
                else:
                    return (q, p)


from tqdm import *
from Crypto.Util.number import *
PKEY = 14192646310719975031517528381795548241077678859071194396837281472399230674325587198691913743313024193030641258581477544395982020385534616950314446352119543012689979705497443206671093873748633162188213109347667494028713308821945628880987033909436936504594085029207071182583896573425433818693447573712242776054326253393149643818428222532313129014785625379928796322492111783102063504659053965652092334907431265629283336869752591405010801363428649651892548988084920295512198406822149854508798413366425646089176325412867633899847841343293434518769693835679828109184625256833518392375615524221729773443578746961887429674099018040291053535429314874943299587513900900515776980848746070077651676814430155460898107362207677739452859298842563030631706907437662807884529549746843462830493900480079096937402325307522965877069080418178692616875205678928420840955518031258855869218152431304423803589723140983606576549207164114711076498723237274262054605174412193097533550076687418481230734706280737017543741247718967059747548710091320650704988384742281590019869955579949961574733610565593105027342454880292474482792325237942870408329807427182014062811782475262070063065860763705715879581562335668163076088547827008755841277828137570366416095778
ENC = 93313196155732054514477836642637636744872135106456047659057794344503071105783322399713135615723000054324693644981340568454628360027598469597864407205974007198804288563284521413279406211756674451582156555173212403946908121125010635246043311803494356106191509999360722019559999844621905376368824621365540442906142224342650371557766313381899279520110833822291649001754956653102495882094754863493058001964760438040783400782352466943990226083197340594364084294954324101604417550048379969516185353765224920719355485680872367930581872987972421836853197205534334204586713387469939986387582911728909524428102693874671302382
C=ENC<<8
N_=int(bin(PKEY)[2:2042],2)<<8
E_=int(bin(PKEY)[2042:],2)<<8
x=sieve_base
n=[]
for i in trange(0,255):
    N=N_+i
    if N%2==0 or isPrime(N):
        continue
    t=[N%a for a in x]
    if 0 in t:
        continue
    n.append(N)
# [print(i) for i in n]
#
# print(len(n))
t=[1,2,4,5,6,7,9,11,12,14,16]#这里是我根据factor网站手动排除的
#print(len(t))
n=[n[i] for i in range(len(n)) if i not in t ]
#print(len(n),n)
e=[E_+i for i in range(255)]
for N in n:
    for e0 in tqdm(e):
        try:
            p,q=factor_rsa_wiener(N,e0)
            if (p,q):
                print(p,q,e0)
        except:continue
c=[C+i for i in range(255)]
#174371810769321904879771281457264126835536885769795883249689917534917724369418768788724295833034211032483643000304760729751539874477879712047563093622220226046317887445404441674034749799537927102249473198079484568936482286828850749395726000380023524546597995716458255074061858150920326214596269436009922505467 165057747190263284822060070867326404037226426769214461653453077347642660157038079939800175423376926190278742477747265143003662594094338527240051087411682630746699434893569121815945138770655880775127567084882226276395011876712312835431878615781804989285062397026979540636501309686456639319755442794081237335613
p,q,e=174371810769321904879771281457264126835536885769795883249689917534917724369418768788724295833034211032483643000304760729751539874477879712047563093622220226046317887445404441674034749799537927102249473198079484568936482286828850749395726000380023524546597995716458255074061858150920326214596269436009922505467, 165057747190263284822060070867326404037226426769214461653453077347642660157038079939800175423376926190278742477747265143003662594094338527240051087411682630746699434893569121815945138770655880775127567084882226276395011876712312835431878615781804989285062397026979540636501309686456639319755442794081237335613, 19152712448778858582528734875468196678366984818842265525346340114296810907435357813959451387293270496095878944786775775749129832803842508074794234774568097809721690831345120778762600713106116293626590641716601095020202233532544196547654794913903350183891867544539554967347396716482565232986995497267273877597593761608770699282404807896050347585632153075234094034163801474316224123620090879021107631960008144066862084573910442635526649884938724881478713853362879412453150893601267748827792136092224063120914443976032390554506925020506643629449426005820918745312311984391868895996772772355715765028825561022860823765675
phi=(p-1)*(q-1)
for c0 in c:
    d = inverse(e, phi)
    m= pow(c0,d,p*q)
    flag=long_to_bytes(int(m))
    if len(str(flag))<=80:
        print(flag)

Blobfish (51 solves)

附件代码

#!/usr/bin/env python3

import os
from hashlib import md5
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from PIL import Image
from PIL import ImageDraw
from flag import flag

key = get_random_bytes(8) * 2
iv = md5(key).digest()

cipher = AES.new(key, AES.MODE_CFB, iv=iv)
enc = cipher.encrypt(flag)

img = Image.new('RGB', (800, 50))
drw = ImageDraw.Draw(img)
drw.text((20, 20), enc.hex(), fill=(255, 0, 0))
img.save("flag.png")

hkey = ''.join('\\x{:02x}'.format(x) for x in key[:10])

os.system(f'/bin/zip -0 flag.zip flag.png -e -P \"$(/bin/echo -en \"{hkey}\")\"')

我的理解

感觉本次比赛最离谱的题,没有之一。
一眼能看出来这个题的密钥根本爆破不出来,8bytes的未知,这个计算量很难搞。
然后发现也没有其他方法,一直在g,后来还是看春哥的解法才知道,真烧脑啊。
是使用bkcrack工具进行压缩包的已知明文攻击。

我的方法

没有什么密码学思想,学会使用bkcarck工具,一步一步破出来即可
可以跟着项目里面的例子学习理解一下(misc题里面经常有这种题,百度一下还挺多的,比葫芦画瓢就可以的)
bkcrack工具
学习链接

我的代码

# !/usr/bin/env python3.10
# -*- coding: utf-8 -*-
# @Time    : 2023/7/17 20:23
# @Author  : lingfeng
# @File    : wp.py
# echo 89504E470D0A1A0A0000000D49484452 | xxd -r -ps > png_header
# ../bkcrack -C flag.zip -c flag.png -p png_header
# ../bkcrack -C flag.zip -c flag.png -k 03492be6 b81a5123 24d7b146 -d advice_deciphered.deflate
# ../bkcrack -k 03492be6 b81a5123 24d7b146 -r 10 ?b
from hashlib import md5
from Crypto.Cipher import AES

key_hex = 'ad6efb792aea5aaa'
enc = '69f421d9e241933cbc62a9ffe937779c864ffa193de014aeb57046b16c40c7353910c61a2676f14f4c7737f038a6db53262c50'
enc = bytes.fromhex(enc)
key = bytes.fromhex(key_hex)

key = key * 2
iv = md5(key).digest()

cipher = AES.new(key, AES.MODE_CFB, iv=iv)
msg = cipher.decrypt(enc)
print(msg)#CCTF{d3ep-Zip_fi5h_0f_tH3_knOwN_pL4!n_7exT_ATtAcK!}

至此,medium已经完成2/3了,这四个题感觉难度不是很大,有点奇葩,剩下的四个题,其中有两道丢番图的学习,下篇的任务更大啊!加油!

愿护一枚银杏叶

努力不是为了一点的甜头,而是我们的责任担当的,我们每个人都有自己想要守护的人,为了心里的她,为了我们自己,加油吧!青春年少,热血努力更适合我们!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值