20210908-Untitled-CryptoSecWriteUp

 

Yusa的日常生活—美国大选

一开始只给了个nc,在Your vote后面输入一个十进制的数字,尝试复制上面的send过去,得到如下

image-20210908123225549

可以通过尝试看出e=3(依次send1,2,3这样一些比较小的数,得到的是各自立方转字节后的结果),猜测底层和RSA有关

后来才上了源码,关键的逻辑如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
print(r"Form of vote:{voter}:{votee}! eg: ")
print("Yusa:Trump!")
vote = pow(bytes_to_long(b"Yusa:Trump!"), d, p * q)
print("vote:", vote)
try:
yusa = int(input("Your vote: "))
vote = long_to_bytes(pow(yusa, e, p * q)).split(b":")
print(vote)
if vote[-1] == "Trump!":
print(flag[:10])
elif vote[-1] == "Biden!":
print(flag[10:])
except Exception as e:
print(str(e))
exit()

RSA签名以及选择明文攻击,像下面这样可以搞出n来

1
2
3
4
5
6
7
8
9
10
11
12
from gmpy2 import *

m = 11441834613624826172418663634046279113882505385891956376343615067285261133850481718598387372799863015467637880949144273419549065969128475439028853349338539694499051803136413392677937180372746004434886408226683060356000756320682607488203211095719491230534679239863637181733978031967798145219807433667347849650017657700970435971961705992349692203870546936934197834532861409558927204969349851725967348387731892180785297131580108320422093208933302332371110292195085685318752738251192012489360801049345566481979567998761296129083101798420484839100061738554182863915465349499485814533228278687226339268848069597015443528832
c2 = int(iroot(m, 3)[0]) + 0x10001
c4 = c2 ** 2
c8 = c2 ** 3
m2 = b':'.join([b'Z\xa3\x04\x9a\xf5Co\x84xu?\xf4\xda\xd9%;\x81\xa6\x85=\x80Fp<\xf5\x01M2\x16\x15\xe0\xc5\xc3\xa4\x04\xc1\xe1\xc8)\xe6\xa0\xdeDX&\x90\xad\x00\x89a%C\xdek\xed\xde\xc8{\xf0\x7fW', b"\xc8t\x81\xaaQ\xd5/D\xa0r]\x16\x8b\xbf=b\x19\x11\xf2V\xf0\xd7>\x8b\xa1=T0\x92\x15?\xbdR{\x0b\x91\xfbF\xf5H\xd7 [\xf9\xd1E\x12B3\xcf\x04VE\x9a\xa6K<\xc2-\x1b>V\xa8\x19\xfa\xf9\x8a\xc5_o\xf0)u\xb4\x92l\x1al=\xe9\n\x00C\x7f\x06\xd4\xf9\xfc\xeaX\x901\xceZ\xde\xcc 7\xb2j\x86-i\xbc|\x81\xf0\x08\r\x03\x16\x0c\x8aN\x18\xc90\xfc\xd8\x87\x07}A'D[\xa9\xb9\xb1\x11$N{\x10\x05\xab\x0e\x86~\xff\rT\xe3\xd7%\xb4\x8d6\x95\x9a\x96;\xc4h~\xe3\xbab\xecC\x9b\xf11'c\xfc&\xe3\x11\x850\xe5\xaf\t\xf1\x83\x83\x15M\x84=\x92\x8c\xda\xb9\xd7\xb2\x1e\x05\x9c\x1b\xb7\x06\xe8"])
m4 = b':'.join([b"eW\xd5\xbd\x08w\x0c\xc9I\xac\x17\xecE\xcb\xda\xfd^;\xa4\xf7\xa6\xf3\x04PE/A\xe5qK\xe9JOm\xee\xd1h\xf7o\xa8\x1c\xa0\x068\xb3\xf1\xe7\x7f-\x9f\x19\xb4j\xa3\x90\x07\x1c\xb7\x12z\xad9A\xb9\xa7\xee\x8eBI\xd0]0\t\x9a\x08]\xbb\x1a\xca\x137}G\xc1\xfa\x14\xd1\t\xad\x1d\x1e\xa1\xb4\x88\x1a\x8d\xd3\x8a\xbcU\x86\x00Im\x05\xaaVJ(\xa9\x04rS\x00\x19\x02\x18\x8a\xa0g4\xcdg!9\xf5\x98z\xcc\x9b\x84\xd5>x\x13\xe2\xcd'1\x8b3a\xe6\x93\xd3\xa4\x06\xe0\xe9\x1aO\xa9\xb5\xe7@\xe8\x9atv\x8d\xf8_rK\xf2u\xf1\x04\x7f\xb45\xdf\x0f\x87\x97V\x15\xcc\xda\xd3\x8c\x8b\xd2V\x1e\x1d\xbd\xd1'\xca\xaa\xec\x1c\xee\xed\xe0\r6\xd7\xd3\x93;\x1e@Z\xaf6\xc4\xae\xe0\xe3\xbf\x85\xfd\x8f\x0b\xd9\xce^`\xbc\xc8\x7f\xfbQ\xd7\xae\xc8\x9bL\x02^\xcf\xbd\xcc\xa4D\xb7\x9f\xf9\x0c\xd5\xe9\xda\xa1\x03\x9d\xed0\x9aY\x85d$(}"])
m8 = b':'.join([b'\x0fB\xc18\x04\x9c)\xbe\xf7J\xcc\xe2\x88\xd4\x12\xa7FwL\xa6\xc9\xf3\xaeRVV\xb6\xaeo\xd8\xc2h\xedo\xc7\xae\xd3\xf9\xab\xd6\xc43\x932\xf9\xff\xc9\xf6S\xd5\x9c\xba]f\xb6M\xde\x07\x9dx\xaaX6\xb5\x0fz\xf7\xc2\x0fY8\x81l\x14\x87\x91\x1c&\x1a\xce\xc30\xc4\xdd\xe2"V&(J\x9eD3S*O\t\x07A\xabZ\xdd>\xc0`\xc0d\xa3\xa7\xb4\xaa\xb5Tf\xae\x88\x8ex(\xf0\xaco0j\xdd\xc5\x13\xda\x86[\xee#\x18s\xe5\xfd\xb8 \xffP4\xd1\xc3\xd9\xe6\x18%\x1a\x13\x0ey+VQVb}{\xa5\xe1A\xa2y!Tu\xe2O\xf649\xa5u?\xbb\x02\xe4\x95z\xfef\x9c\x92E\x15\xadM\t\x98\x8dj\xd9\xd9wU\x93#;\xb6\xa2\x08\xe73\xcf\xf7;\xc7/\xc9\x0e\xa3\xf6\x18\xd2\xe8\xd7\xac\'\x07\xe6c\xc5\xedEhvf\xcdO\x11\xaf\xac^Y({\x19!\x81_h\x9c\x15\xfb\xbd\x0f\x89\xe8\xc6U0MM\xe0\x89}'])
m1, m2, m3 = bytes_to_long(m2), bytes_to_long(m4), bytes_to_long(m8)
n = gcd(m1 ** 2 - m2, m1 ** 3 - m3)
# n = 12998510197135204376024977476677066247754836878539929011686148268745119316209020562579171398886840449113325708748228673135311752569187260449619807807903218621065777199171595498055285073001556241300819299111213719824569275786074961700296240844459968460491714678805615009589712788617029938309306389913328704049259197596077475679388746327337019377724684383107233046619031888347065776485290701116620689771500575467431146354734895248116917853679826521686058032549240850391628465710220654255931003952332899029099575800979554751748212159051355321567757716972030886957773638513566062484273620681928826749865617412173047445893

复现的时候重新搞了组数据

1
2
3
c = 15160582676846215658796665511167073968034171296661551317484064414530852799771618235942954787122243854880075538411956180191332121712749102940834469106520160911820689656980924928940857041289182520881995375555391429116695091017132599877027688472814801911156488488572658968858514549082874942198209100374290699361245526127362315158765216477920262317888955067180876826737792124089711465313919322691350383089478781115531028327464978498168550501234908278822070734957259085100730831106569924227163225782388696667537281068625312828784413432273812618076026392105724267064367773254829708738371876092746215522784893719413972288863
n = 15746945526432122479214630433270149949898055696087559742180830690847507891453515116075588796192340169435734319815395381666689989899222866782097160453565424738446970488798269177137604678808633207375125945542218010260891606865601540037996424336907526029576270546861238099120672228457910383070027371579273232850527367464802499941689387144111902397279853997069146494099457026354542952331566285341348508821513652475468126522170376081188733013126170230888215340038166274999418672245444957134175669183047604867371997408007732297783287752312442659031828940988055885376811763957465614461850705292428963651655262273479037747317
e = 3

已知enc,要用d签名一个以b’:Biden!’结尾的字节流并send过去,用e验证

也就是要使
$$
m=c^e\ mod\ n\notag
$$
m在转字节流之后结尾是:Biden!,显然这是RSA验证的过程,我们只要用d生成某个以:Biden!结尾的字节流的密文
$$
c=m^{d}\ mod\ n\notag
$$
但是n无法分解,也就是说求不出d,那唯一的攻击点只有已知末尾是:Biden!


而实际上,不知道n也是完全可以做的,我这样搞出n反而把自己带偏

关键是构造一个在模2的倍数(且和3互素)的几次幂下的RSA签名,因为n是足够大的,完全可以构造使得三次方验证的时候,计算结果是小于模数的,也就是不用考虑n;但问题是模这样一个数为什么可以保留最低位?

我的理解是

初步理解是$c^3$低位就是f,因为在模$2^x$下,会保留低x位,这个比较容易懂;所以在$c^3$又是在RSA中的验证环节,得到的m,由于f是先d次方得到的c,在这个RSA签名验证的环节,低x位是成立的,所以m的低x位就是f。这应该是模$2^x$次方的性质(但好像模$14^x$也可以,不懂了)

完整的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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from Crypto.Util.number import *
from pwn import *
from gmpy2 import *
from re import *

# context.log_level = 'debug'

e = 3

# :20
sh = remote('47.96.253.167', 10001)
# sh = process(["python", "server.py"])
payload1 = sh.recvuntil(b'Your vote:').decode()
payload1 = findall(r"\d+", payload1)[0]
sh.sendline(payload1)
sh.recvuntil(b']\n')
flag1 = sh.recvline()[:-1]
sh.close()

# 20:
sh = remote('47.96.253.167', 10001)
f = bytes_to_long(b':Biden!')
N = 2 ** 47
phiN = 2 ** 46
d = invert(e, phiN)
c = pow(f, d, N)
payload2 = str(c)
sh.sendline(payload2)
sh.recvuntil(b']\n')
flag2 = sh.recvline()[:-1]
sh.close()

print(flag1 + flag2)

image-20211005133747776

Yusa的密码学课堂—CBC第二课

来看主要代码逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def task():
try:
key = os.urandom(16)
iv = os.urandom(16)
cipher = _enc(flag, key, iv).hex()
print(cipher)
paintext = bytes.fromhex(input("Amazing function: "))
print(enc(paintext, key).hex())

backdoor = input("Another amazing function: ")
assert backdoor != cipher

if dec(bytes.fromhex(backdoor), key, iv) == flag:
print(flag)
else:
print("Wow, amazing results.")
except Exception as e:
print(str(e))
exit()

先加密flag,然后给我们一个自己定IV和Plaintext的机会,进行加密,最后我们输入密文,提供一个解密的机会,如果这个解密的结果等于flag,但是我们输入的密文不能是flag加密后的密文,那么就输出flag

乍一听不可能啊,一个不是flag加密得到的密文,进行解密也可以得到flag?

这道题特殊就特殊在pad和unpad函数

1
2
3
4
5
6
7
8
9
10
def pad(data):
pad_len = BLOCKSIZE - (len(data) % BLOCKSIZE) if len(data) % BLOCKSIZE != 0 else 0
if pad_len == 0:
return data
return data + chr(pad_len) * pad_len


def unpad(data):
num = ord(data[-1])
return data[:-num]

这里unpad是关键,会根据最后一位将加上的pad给去掉;一般来说是没有问题的,但是如果有不怀好意的人刻意构造假的pad,那么情况就不一样了

下面是CBC加密的流程示意图

AES_CBC

可以看出,无论我们在这三块中如何闹腾,都不可能整出一个解密是flag却又不是flag对应的密文

所以我们不妨手动加上一块,密文块2作为IV,而明文块3我们让最后其最后一位是16+flag的填充位数转字节;这样就可以在加密的过程中,完全可以把这一块和前面三块并在一起,相当于加密flag+我们自己的填充,而解密出来在unpad的时候,由于unpad的机制,就把所有的pad,包括flag本身的,以及我们添加的pad全部去掉了,得到的就是flag,但是密文不同

本地复现的,报一些奇怪的错,稍微改了下源码,应该无伤大雅

image-20210908210437443

这样下面这个脚本在本地就可以打通了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *

# context.log_level = 'debug'
sh = process(["python3", "./server_py3.py"])
cipher = bytes.fromhex(sh.recvline()[:-1].decode())
c0, c1, c2 = cipher[:16], cipher[16:32], cipher[32:]
# len(cipher) == 48
# our plaintext
sh.recvuntil(b'Amazing function:')
pad = chr(8+16).encode() * 16
sh.sendline(pad.hex())
# our iv
sh.recvuntil(b'Your iv: ')
sh.sendline(c2.hex())
cx = bytes.fromhex(sh.recvline()[:-1].decode())
# our backdoor
sh.recvuntil(b'Another amazing function: ')
backdoor = cipher + cx
sh.sendline(backdoor.hex())
flag = sh.recvline().decode()
print(flag)

image-20210908210552799

因为看原附件中DASCTF{*********}总长度是40,所以上面才这么果断设置了pad为chr(8+16).encode()*16;如果不是的话,可以从一开始的密文知道,长48,也就是分三段,flag至少长33,稍微爆破下也可以出来的

Yusa的密码学课堂—CBC第三课

这道题就把pad给改掉了

1
2
3
4
5
6
def pad(data):
pad_len = BLOCKSIZE - (len(data) % BLOCKSIZE) if len(data) % BLOCKSIZE != 0 else 0
return data + "=" * pad_len

def unpad(data):
return data.replace("=","")

来看主逻辑

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
def task():
try:
key = os.urandom(16)
asuy = enc(flag,key)
print asuy.encode('hex')

paintext = raw_input("Amazing function(in hex): ")
paintext = paintext.decode('hex')
print enc(paintext,key).encode('hex')
asuy = raw_input("Another amazing function(in hex): ").decode('hex')
yusa = dec(asuy,key)

flag_l = s_2_l(flag)
yusa_l = s_2_l(yusa)
for each in yusa_l:
if each in flag_l:
print(r"You're not yusa!")
exit()
print yusa.encode('hex')
except Exception as e:
print str(e)
exit()
if __name__ == "__main__":
task()

先对flag进行加密,IV和KEY是相同的

然后是一个加密,可以由我们自己指定的明文的,而且IV和KEY也是之前的IV和KEY

最后是一个解密,同样也是IV和KEY

由于KEY和IV一样,这就可以提供我们很大的便利,最后一层解密不应该看做得到flag的直接途径,而是看成一次解密的机会更好

攻击流程如下图所示

cbc

左边是flag的加密过程,右边是我们借助已有的条件进行攻击的过程。主要的思路就是,因为KEY=IV,又分别有一次加密和解密,那为什么不直接把IV和KEY搞出来呢。

在我们自由控制明文的那里,我们输入两块全是\x00的字节流,为的是消除明文对加密过程产生不可干预的影响,这样整个加密流程,除了KEY一切就变得透明起来。

做完一次加密,我们得到了两密文,再加上我们还剩下一次解密的机会,就可以想到把刚才的加密的流程给拆开,其中一半用解密函数来完全道破,另一半也就不攻自破

正如图上所画将c4送过去解密,得到回显;显然回显^key就是c4通过加密之前的c3

image-20210909131814443

回显 ^ key = c3,那么c3已知,key = c3 ^ 回显,就此我们得到key,后面再现aes解密就好了

完整的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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *
from Crypto.Cipher import AES


def unpad(s):
return s.replace(b"=", b"")


context.log_level = 'debug'
sh = process(["python3", "./server_py3.py"])

key = bytes.fromhex(sh.recvline()[:-1].decode())

cipher = bytes.fromhex(sh.recvline()[:-1].decode())
c1, c2, c3 = cipher[:16], cipher[16:32], cipher[32:48]

sh.recvuntil(b'Amazing function(in hex): ')
plaintext = b'\x00' * 32
sh.sendline(plaintext.hex())

cx = bytes.fromhex(sh.recvline()[:-1].decode())

sh.recvuntil(b'Another amazing function(in hex): ')
yusa = cx[16:32]
sh.sendline(yusa.hex())

asuy = bytes.fromhex(sh.recvline()[:-1].decode())
keyx = xor(asuy[:32], cx[:16])
assert key == keyx
aes = AES.new(keyx, AES.MODE_CBC, keyx)
print(unpad(aes.decrypt(cipher)).decode())

image-20210909132014232

可以打通

当然和第二题一样,pad和unpad函数也做过一丢丢的修改,不知道这个修改伤不伤大雅

1
2
3
4
5
6
7
8
9
def pad(data):
pad_len = BLOCKSIZE - (len(data) % BLOCKSIZE) if len(data) % BLOCKSIZE != 0 else 0
if pad_len == 0:
return data
return data + "=" * pad_len


def unpad(data):
return data.replace(b"=", b"")

总而言之今天的题偏脑洞