2023 hgame crypto wp 全
做了今年hgame的题目,ak crypto的同时也从题目中学到了不少,贴一份所有密码方向题目的wp。
Week 1
RSA | 100pts
根据题目描述,用factorb分解n,在这里用的factorb在线api:
http://factordb.com/api?query=
exp:
from Crypto.Util.number import *
import gmpy2
import requests
def queryFactors(n):
s=[]
url="http://factordb.com/api?query="+str(n)
r = requests.get(url)
factors=r.json()['factors']
for f in factors:
for i in range(f[1]):
s.append(int(f[0]))
return s
c=110674792674017748243232351185896019660434718342001686906527789876264976328686134101972125493938434992787002915562500475480693297360867681000092725583284616353543422388489208114545007138606543678040798651836027433383282177081034151589935024292017207209056829250152219183518400364871109559825679273502274955582
n=135127138348299757374196447062640858416920350098320099993115949719051354213545596643216739555453946196078110834726375475981791223069451364024181952818056802089567064926510294124594174478123216516600368334763849206942942824711531334239106807454086389211139153023662266125937481669520771879355089997671125020789
e=65537
p=queryFactors(n)[0]
q=n//p
phi=(p-1)*(q-1)
d=gmpy2.invert(e,phi)
m=pow(c,d,n)
print(long_to_bytes(m))
Be Stream | 100pts
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)
这里流密码的生成器是一个二阶线性递推数列,利用数列中的项进行对明文的异或加密操作,暴力递归求解每一项时间复杂度显然是不够的
注意到对明文的异或是在mod 256意义下进行的,于是可以将该数列放在mod 256意义下考虑,而二阶线性递推数列在模意义下具有周期性,本题经验证得到周期为64,于是得到flag
exp:
key = [int.from_bytes(b"Be water", 'big'), int.from_bytes(b"my friend", 'big')]
enc= b''#data
stream=[]
stream.append(key[0]%256)
stream.append(key[1]%256)
for i in range(2,64):
stream.append((stream[i-2]*7 + stream[i-1]*4)%256)
flag=b""
for i in range(len(enc)):
water=stream[((i//2)**6)%64]
flag += bytes([(water ^ enc[i])])
print(flag)
另外在处理二阶线性递推的问题时,往往还会采用矩阵快速幂进行加速,出题人给出的官方wp也用到了这样的方法
enc = b''#data
key = [int.from_bytes(b"Be water", 'big'), int.from_bytes(b"my friend", 'big')]
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)
兔兔的车票 | 100pts
题目中生成了部分noise data,利用三张noise图片对原始图片进行异或加密,实际上是key reuse的想法,把经过noise data混淆后的十六张图片两两异或可以试出来
上述方法基于的原理:
1.生成的noise图片中保留了部分(0,0,0)的数据点,导致异或时没有起到混淆作用
2.两张有实际意义的图片进行异或,可以大致看出图片信息,并不会完全混淆,如下图
exp:
from PIL import Image, ImageDraw
width=379
height=234
image_path="path\\enc1.png"
img1=Image.open(image_path)
image_path="path\\enc6.png"
img2=Image.open(image_path)
img3 = Image.new("RGB", (width, height))
for i in range(height):
for j in range(width):
p1, p2 = img1.getpixel((j, i)), img2.getpixel((j, i))
img3.putpixel((j, i), tuple([(p1[k] ^ p2[k]) for k in range(3)]))
img3.save('path\\0.png')
神秘的电话 | 100pts
这题套娃了没啥意思,手解morse,得到
0223E_PRIIBLY__HONWA_JMGH_FGKCQAOQTMFR
根据提示,倒序后再解18层的栅栏,得到
rmocfhm_wo_ybipe2023_ril_hnajg_katfqqg
凭感觉像是维吉尼亚,稍微手试结合爆破得到密钥vidar,从而得到flag
week2
包里有什么 | 200pts
经典的背包密码问题,a数组是2为公比的等比数列,符合超递增形式,利用w混淆a数组得到数组b,m为模数
题目给出b[0],已知a[0],可求w
在mod m意义下对c乘w的逆元可以恢复通过用a数组进行背包加密的密文cc,从而利用a数组的超递增性质得到flag
exp:
from Crypto.Util.number import *
import gmpy2
m = 1528637222531038332958694965114330415773896571891017629493424
b0 = 69356606533325456520968776034730214585110536932989313137926
c = 93602062133487361151420753057739397161734651609786598765462162
l=m.bit_length()-2
#w=(b0+m)//2
w=b0//2
a = [2 << i for i in range(l)]
b = [w * i % m for i in a]
assert b[0]==b0
cc=(c*gmpy2.invert(w,m))%m
flag=""
for i in range(len(a)-1,-1, -1):
if cc >= a[i]:
cc -= a[i]
flag+="1"
else:
flag+="0"
flag=flag[::-1]
print(long_to_bytes(int(flag,2)))
也可以求w之后得到b,利用LLL算法进行规约得到flag,但利用LLL算法解背包密码是有约束条件的,并不能对所有情况适用,不能算是本问题的最优解法
Rabin | 150pts
Rabin的板题
exp:
from Crypto.Util.number import *
import gmpy2
e=2
p=65428327184555679690730137432886407240184329534772421373193521144693375074983
q=98570810268705084987524975482323456006480531917292601799256241458681800554123
c=0x4e072f435cbffbd3520a283b3944ac988b98fb19e723d1bd02ad7e58d9f01b26d622edea5ee538b2f603d5bf785b0427de27ad5c76c656dbd9435d3a4a7cf556
n=p*q
a,inv_q ,inv_p= gmpy2.gcdext(q,p)
mp = pow(c, (p + 1) // 4, p)
mq = pow(c, (q + 1) // 4, q)
a = (inv_p * p * mq + inv_q * q * mp) % n
b = n - int(a)
c = (inv_p * p * mq - inv_q * q * mp) % n
d = n - int(c)
for i in(a,b,c,d):
print(long_to_bytes(i))
RSA大冒险1 | 200pts
level1:
把p当作模数,RSA中针对明文m较小的常规解法
level2:
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)
每次调用encrypt进行加密的时候,会更新q的值,但p的值始终不变,产生了后门漏洞,分析清楚了就是常规的RSA模不互素的板子
level3:
e=3低指数攻击的板子
level4:
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)
每次调用encrypt更新公钥e的值不更新p q,共模攻击的板子
exp:
from Crypto.Util.number import *
import gmpy2
#level1
n=361067668573234937895991468059126957309979597456638107518559509053801374729041826137407865616458091
e=65537
p=283512251808796606274360612102827230673
c=0x59f3320c32623159430da5f90aeb570c113b8773b190269405a57682da80541fd0a137545f9924fac5
phi=p-1
d=gmpy2.invert(e,phi)
m=pow(c,d,p)
print(long_to_bytes(m))
#m<n_But_also_m<p
#level2
num1=51714374014784626103389830324623745659758993696818466586014210858366422589570962326044934205165918356339002759574360310601646796554589261089973724419748591357516363775394363541872361180983687867285327133570976393450434372893591590304184278633127463257220213248050866258191414660072916311978916360257899176269
num2=78196272419906767037902832849004105232756023123277232046319587647595140446895325078900581644916520119933600043244420093307315612825163702610998235549248103079987514827335240153615251940195733209944058117702322811676907822899379157063913023209806857080156813524890681666184681309091756341694731879686951138779
c=0x2efe7c797fe9a3485abd4e6c5020f1a0037e0765b363bb3d857bdf41955ff50225fb87568b885fbfe3a79215afd79f4e5eb3adb1ea2d981a0bba1c2401c8db6aa8a0afb72115240dd02e8a79db70bb96b1c9d3fa1acbd66d20aa0308fcb7e814a8714b0ec0f50d7c8ee3e88433f1d7e3204cd64eb1672829f8d4af7ed9a89647
e=65537
p=GCD(num1,num2)
n=num1
phi=p-1
d=gmpy2.invert(e,phi)
m=pow(c,d,p)
print(long_to_bytes(m))
#make_all_modulus_independent
#level3
n=95956427905848709110790455827730781859758109105059149423798771136301462430886494550451263147653782416592902380881006366962525095344162802236463218784261545406669496659045343242247652612436969313296341862158360642994537918335689393284271914642479655859203315571729535683775913838294618830103890837529279728977
e=3
c=0xfec61958cefda3eb5f709faa0282bffaded0a323fe1ef370e05ed3744a2e53b55bdd43e9594427c35514505f26e4691ba86c6dcff6d29d69110b15b9f84b0d8eb9ea7c03aaf24fa957314b89febf46a615f81ec031b12fe725f91af9d269873a69748
temp=gmpy2.iroot(c,3)[0]
print(long_to_bytes(temp))
#encrypt_exponent_should_be_bigger
#level4
e1=69317
c1=0x9ced9e743f5a8e6d7b492001a607464f166cbf01869aa2348462681a54ef620e7698c8c8bfcd3e69357959fc561fb567b7bfd110103fc4a9749b44b2d8d92d16a8d3febf064d62c22b0c86e0915b54697e657e509e370a7233a6fe16cc08d1f5423a9f109b2e3d24cdece14b37a768bd09e3d4b73b724aaeb75c85633c443c72
n=132737573094397166200928323625376462448848709444893383097753580817921870674064012914464680073446196954339074738165979967268640670655884248726027366288498282161714749783486609569598319765091144131157441483840208937402381051309052892634969923866469069795545874253572601030174066186475561988582476654384721746937
e2=113723
c2=0x714d0aff617f24ded351530f942ca8dc9e9c2f5466c24c494245ddf625720fe4e1d34c7fc3c9e99d6ac1d21eb0d2daba111de9e13c778eed4fe6cf03d2951b27a46b0b9b931bb416b1d3bbca76cdd0225f7187075e81e49e9ccc713cb2bb39ced501276ea3c03363eecaade4445fad88216f5e11b812121f963582f79d339bad
gcd, s, t = gmpy2.gcdext(e1, e2)
if s < 0:
s = -s
c1 = gmpy2.invert(c1, n)
if t < 0:
t = -t
c2 = gmpy2.invert(c2, n)
plain = (gmpy2.powmod(c1, s, n) * gmpy2.powmod(c2, t, n)) % n
print(long_to_bytes(plain))
#never_uese_same_modulus
零元购年货商店 | 250pts
审一下源码
func Encrypt(u string) (string, error) {
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
plainText := []byte(u)
blockMode := cipher.NewCTR(block, iv)
cipherText := make([]byte, len(plainText))
blockMode.XORKeyStream(cipherText, plainText)
return base64.StdEncoding.EncodeToString(cipherText), nil
}
func Decrypt(cipherText string) (string, error) {
decodeData, err := base64.StdEncoding.DecodeString(cipherText)
if err != nil {
return "", errors.New("invalid base64")
}
block, err := aes.NewCipher(key)
blockMode := cipher.NewCTR(block, iv)
plainText := make([]byte, len(decodeData))
blockMode.XORKeyStream(plainText, decodeData)
return string(plainText), nil
}
利用CTR模式进行了AES加密,CTR模式密文改动某一字节,明文只有对应字节改动,尝试得到将cookie第十三位ascii+=1,对应用户名第一位ascii+=3,因此先将用户名设置为Ridar-Tu
cookie:
ESJsI22Q3vTqbTR5am7kjg7Qwm3RlmQbZA2VFfx4OfQu1veLIrevOSpWQ1MQiszz8eKa%2FaF%2FazDChg%3D%3D
修改为:
ESJsI22Q3vTqaTR5am7kjg7Qwm3RlmQbZA2VFfx4OfQu1veLIrevOSpWQ1MQiszz8eKa%2FaF%2FazDChg%3D%3D
因为没有go环境所以只能这样枚举试一试,实际上应该直接调用题目中的encrypt和decrypt函数求解,只需要满足传入用户名为八位字符即可
官方exp:
package main
import (
"encoding/base64"
"fmt"
"net/url"
)
func main() {
var encodedToken = ""
token, _ := url.QueryUnescape(encodedToken)
decodeData, _ := base64.StdEncoding.DecodeString(token)
decodeData[9] ^= 'a' ^ 'V'
decodeData[10] ^= 'a' ^ 'i'
decodeData[11] ^= 'a' ^ 'd'
decodeData[12] ^= 'a' ^ 'a'
decodeData[13] ^= 'a' ^ 'r'
decodeData[14] ^= 'a' ^ '-'
decodeData[15] ^= 'a' ^ 'T'
decodeData[16] ^= 'a' ^ 'u'
attackData := base64.StdEncoding.EncodeToString(decodeData)
fmt.Println(token)
fmt.Println(attackData)
attackToken := url.QueryEscape(attackData)
fmt.Println(attackToken)
}
week3
ezDH | 300pts
离散对数可以求B,从而得到shared secret,然后简单推一下式子:
exp:
from Crypto.Util.number import *
N=0x2be227c3c0e997310bc6dad4ccfeec793dca4359aef966217a88a27da31ffbcd6bb271780d8ba89e3cf202904efde03c59fef3e362b12e5af5afe8431cde31888211d72cc1a00f7c92cb6adb17ca909c3b84fcad66ac3be724fbcbe13d83bbd3ad50c41a79fcdf04c251be61c0749ea497e65e408dac4bbcb3148db4ad9ca0aa4ee032f2a4d6e6482093aa7133e5b1800001
g=2
B=0x1889c9c65147470fdb3ad3cf305dc3461d1553ee2ce645586cf018624fc7d8e566e04d416e684c0c379d5819734fd4a09d80add1b3310d76f42fcb1e2f5aac6bcdd285589b3c2620342deffb73464209130adbd3a444b253fc648b40f0acec7493adcb3be3ee3d71a00a2b121c65b06769aada82cd1432a6270e84f7350cd61dddc17fe14de54ab436f41b9c9a0430510dde
#Bob_secret=sympy.discrete_log(N,B,g)
#print(B_s)
Bob_secret=523395080617584542626887374797915530377197776414307699064494325754883865689240606662282052952769155869279302851357127396057956141700522707614969003724858649469855458912748021620773222294584347455737042066389082761054791847391943575954645806724433924897674867376454396257875249718213145700160788809038411884417511593659507
A=0x22888b5ac1e2f490c55d0891f39aab63f74ea689aa3da3e8fd32c1cd774f7ca79538833e9348aebfc8eba16e850bbb94c35641c2e7e7e8cb76032ad068a83742dbc0a1ad3f3bef19f8ae6553f39d8771d43e5f2fcb986bd72459456d073e70d5be4d79ce5f10f76edea01492f11b807ebff0faf6819d62a8e972084e1ed5dd6e0152df2b0477a42246bbaa04389abf639833
p=6864797660130609714981900799081393217269435300143305409394463459185543183397656052122559640661454554977296311391480858037121987999716643812574028291115057151
a=-3
b=1093849038073734274511112390766805569936207598951683748994586394495953116150735016013708737573759623248592132296706313309438452531591012912142327488478985984
E = EllipticCurve(GF(p), [a, b])
shared_secret=210079986310539059026469300720655426951919393335485123618687987029746658763798439576376646226161293006261472023436870446704160814487044427272498126726277523426054334489402918696632471053566567964288295442264373353184045686758631952518445700562724692050307270259376021103356466045824174058551054535796277627154836626119222585862131679625673279898703654
P1=E((2032638959575737798553734238953177065671021112450002471824225734491735604600003028491729131445734432442510201955977472408728415227018746467250107080483073647, 3510147080793750133751646930018687527128938175786714269902604502700248948154299853980250781583789623838631244520649113071664767897964611902120411142027848868))
c=E(6670373437344180404127983821482178149374116817544688094986412631575854021385459676854475335068369698875988135009698187255523501841013430892133371577987480522, 6648964426034677304189862902917458328845484047818707598329079806732346274848955747700716101983207165347315916182076928764076602008846695049181874187707051395)
P2=shared_secret*P1
m=(c-P2)[0]
print(long_to_bytes(m))
至于为什么起手想到了离散对数解B,N的结尾是00001,N-1应该比较光滑,自然可以联想到Pohlig-Hellman algorithm解DLP
ezBlock | 350pts
近期应该会更一篇差分的文章,更完了贴到这里,这里就先简单说下思路
经过了四轮S-box,每一次已知经过S-box混淆前后的值,就有一定概率得到对应的key,感觉这题设计成可以选择明文的交互,主动探索良好的差分性质会更理想
exp:
m_list = [hex(i * 0x1111)[2:].rjust(4, '0') for i in range(16)]
c_list = [28590, 33943, 30267, 5412, 11529, 3089, 46924, 59533, 12915, 37743, 64090, 53680, 18933, 49378, 23512, 44742]
c_list = [hex(i)[2:].rjust(4, '0') for i in c_list]
s_box = {0: 0x6, 1: 0x4, 2: 0xc, 3: 0x5, 4: 0x0, 5: 0x7, 6: 0x2, 7: 0xe, 8: 0x1, 9: 0xf, 10: 0x3, 11: 0xd, 12: 0x8, 13: 0xa, 14: 0x9, 15: 0xb}
ans = []
for k in range(4):
t = {}
for i in range(16):
t[int(m_list[i][k], 16)] = int(c_list[i][k], 16)
for a in range(16):
for b in range(16):
for c in range(16):
for d in range(16):
for e in range(16):
key = [a,b,c,d,e]
for i in range(16):
m = i
for j in range(4):
m = m ^ key[j]
m = s_box[m]
m = m ^ key[4]
if m != t[i]:
break
elif i==15:
ans.append(key)
print(ans)
key = [0, 0, 0, 0, 0]
for k in ans:
key = [(key[i]*16+k[i]) for i in range(5)]
print([hex(i) for i in key])
flag = 'hgame{' + hex(key[0])[2:] + '_' + hex(key[1])[2:] + '_' + hex(key[2])[2:] + '_' + hex(key[3])[2:] + '_' + hex(key[4])[2:] + '}'
print(flag)
#[[4, 15, 4, 4, 13], [15, 4, 15, 5, 8], [4, 9, 9, 7, 13], [2, 3, 2, 0, 5]]
#['0x4f42', '0xf493', '0x4f92', '0x4570', '0xd8d5']
RSA大冒险2 | 350pts
level1:
Boneh_Durfee's attack板子
level2:
yafu可以分解
看了官方wp,出题人想出一个费马分解,那yafu可以分解n就不奇怪了
level3:
指向我的另一篇文章:https://www.cnblogs.com/App1eTree/p/coppersmith.html
coppersmith的板子,但是被卡了上界,需要手动调下参数
这里只给出level3利用small roots方法解根的exp:
def coppersmith(pol, modulus, beta, h, t, X):
n = d * h + t
polZ = pol.change_ring(ZZ)
x = polZ.parent().gen()
g = []
for i in range(h):
for j in range(d):
g.append((x * X)**j * modulus**(h - i) * polZ(x * X)**i)
for i in range(t):
g.append((x * X)**i * polZ(x * X)**h)
B = Matrix(ZZ, n)
for i in range(n):
for j in range(i+1):
B[i, j] = g[i][j]
B = B.LLL()
new_pol = 0
for i in range(n):
new_pol += x**i * B[0, i] / X**i
potential_roots = new_pol.roots()
roots = []
for root in potential_roots:
if root[0].is_integer():
result = polZ(ZZ(root[0]))
if gcd(modulus, result) >= modulus^beta:
print("p: ",(gcd(modulus, result)))
roots.append(ZZ(root[0]))
return roots
N = 131435096190932476282107019932753538842822700854000789960859541290365971093411528417224106949167098928833908993920820537191845190683132521684195137876258992862198519678667815269992895368819967417416360582282152133401991348142208574505338629119903776681479509250804953601559034849627028222835444168985167733627
ZmodN = Zmod(N)
P.<x> = PolynomialRing(ZmodN)
leak = 884164308941646697376000124313396231277143112153939022790874440209793278332165
#f = pbar + x
for i in range(132,2**9):
print(i)
pbar=((leak<<9)+i)<<244
f=pbar + x
beta = 0.50
d = f.degree()
epsilon = beta / 45
h = ceil(beta**2 / (d * epsilon))
t = floor(d * h * ((1/beta) - 1))
X = ceil(N**((beta**2/d) - epsilon))
roots = coppersmith(f, N, beta, h, t, X)
print(roots)
#b'now_you_know_how_to_use_coppersmith'
week4
LLLCG | 50pts
最开始的附件少打了一个括号,用后一个数据除掉前一个数据就可以了,好像RCTF也有一样的非预期哈哈
ECRSA | 400pts
RSA毕竟是基于大整数分解困难的,但是这里给出了p q,只是定义在椭圆曲线上,故对于私钥d的定义变为
d=inverse(e, order)
其中order是圆锥曲线的阶,再就是需要在F(p) F(q)上分别求出明文,利用中国剩余定理CRT进行合并得到最终的flag
exp:
from sage.all import *
from sage.all_cmdline import *
from Crypto.Util.number import *
p=115192265954802311941399019598810724669437369433680905425676691661793518967453
q=109900879774346908739236130854229171067533592200824652124389936543716603840487
n = 1265973137163332340636107173548074387094288440751164714475805591193132153433
a = 3457301624586139606837804088262299224575469302815229087413111295501888448568
b = 1032821371338209482066820365696715669963814382548975103442891640397173555138
e = 11415307674045871669
ciphertext = b''
Ep = EllipticCurve(Zmod(p), [a, b])
Eq = EllipticCurve(Zmod(q), [a, b])
cx = bytes_to_long(ciphertext)
cp = Ep.lift_x(Integer(cx))
cq = Eq.lift_x(Integer(cx))
dp = inverse(e, Ep.order())
dq = inverse(e, Eq.order())
mp = (dp * cp).xy()[0]
mq = (dq * cq).xy()[0]
flag = CRT_list([ZZ(mp), ZZ(mq)], [p, q])
print(long_to_bytes(int(flag)))
LLLCG Revenge | 350pts
HNP问题,LCG、DSA等算法都可以作为oracle产生相邻项参数之间线性递推的效果
这里n是360bit,r最大是340bit,至少泄露20bit的MSB,起手构造矩阵进行LLL,过后会把之前blog关于HNP的文章迁移过来,到时候在这里放链接
exp:
from Crypto.Util.number import *
t = 39
n = 2348542582773833227889480596789337027375682548908319870707290971532209025114608443463698998384768703031935081
# Load data
s=[data]
# Calculate A & B
A = []
B = []
for i in range(len(s)-1):
A.append(ZZ((-1*s[i])%n))
for i in range(1,len(s)):
B.append(ZZ(s[i]))
# Construct Lattice
K = 2^340 # ki < 2^340
X = n * identity_matrix(QQ, t) # t * t
Z = matrix(QQ, [0] * t + [K/n] + [0]).transpose() # t+1 column
Z2 = matrix(QQ, [0] * (t+1) + [K]).transpose() # t+2 column
Y = block_matrix([[X],[matrix(QQ, A)], [matrix(QQ, B)]]) # (t+2) * t
Y = block_matrix([[Y, Z, Z2]])
# Find short vector
Y = Y.LLL()
a=n*Y[1][39]/2**340
print(long_to_bytes(a))
Good luck for hgame 2024