今天东北的师傅给了他们那边比赛的三密码道题,期间看了第一道,求出n之后就没思路了;还有两道赛后复现的,都是AES.CBC,第二道从尚师傅那里获得提示,跳开了CBC模式本身,攻击点在pad;第三道pad可以利用的点被抹除了,并给了一个看似需要攻击的地方,但攻击点却在CBC模式本身
好了,我宣布尚师傅最强,实验室密码顶梁柱,我就是尚师傅的WP手,专门记录尚师傅出过的题目
以及被IO支配的恐惧
一开始只给了个nc,在Your vote后面输入一个十进制的数字,尝试复制上面的send过去,得到如下
可以通过尝试看出e=3(依次send1
,2
,3
这样一些比较小的数,得到的是各自立方转字节后的结果),猜测底层和RSA有关
后来才上了源码,关键的逻辑如下
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来
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
比赛结束环境就关了,所以在本地复现下,9月以后所有的附件可以在github上找到
想要交互还是挺麻烦的,这里就当于已经得到n了
c = 15160582676846215658796665511167073968034171296661551317484064414530852799771618235942954787122243854880075538411956180191332121712749102940834469106520160911820689656980924928940857041289182520881995375555391429116695091017132599877027688472814801911156488488572658968858514549082874942198209100374290699361245526127362315158765216477920262317888955067180876826737792124089711465313919322691350383089478781115531028327464978498168550501234908278822070734957259085100730831106569924227163225782388696667537281068625312828784413432273812618076026392105724267064367773254829708738371876092746215522784893719413972288863
n = 15746945526432122479214630433270149949898055696087559742180830690847507891453515116075588796192340169435734319815395381666689989899222866782097160453565424738446970488798269177137604678808633207375125945542218010260891606865601540037996424336907526029576270546861238099120672228457910383070027371579273232850527367464802499941689387144111902397279853997069146494099457026354542952331566285341348508821513652475468126522170376081188733013126170230888215340038166274999418672245444957134175669183047604867371997408007732297783287752312442659031828940988055885376811763957465614461850705292428963651655262273479037747317
e = 3
已知enc,要用d签名一个以b’:Biden!'结尾的字节流并send过去,用e验证
也就是要使
m = c e m o d n m=c^e\ mod\ n m=ce mod n
m在转字节流之后结尾是:Biden!
,显然这是RSA验证的过程,我们只要用d生成某个以:Biden!
结尾的字节流的密文
c = m d m o d n c=m^{d}\ mod\ n c=md mod n
但是n无法分解,也就是说求不出d,那唯一的攻击点只有已知末尾是:Biden!
但从稍微结合实际一点的角度出发,这个加密系统是为了投票而设立的,想知道最后一部分是什么,就是想知道票投给了谁了,加上用d来签名,是为了验证这是谁投的票,莫名其妙就遇到了老师上《安全协议与标准》的投票情景。所以,这么看的话,这个加密系统是没有漏洞的,除非n是特殊的,但是目前ctf的常用手段并没有更加明朗的指向,只能按下不表了,希望到时候有WP
好家伙,有地方省赛出到这道题了,我。。(原来没有出的题目是可以重复利用的)
期间很多人私聊我这道题,唔,可是我太菜了,始终不会,对不起各位师傅
但是在众师傅的协力下,有师傅在评论区留下他的解法,更新一下
有种发现新大陆的感觉
其实不知道n也是完全可以的,我这样搞出n反而把自己带偏
关键是构造一个在模2的倍数(且和3互素)的几次幂下的RSA签名,因为n是足够大的,完全可以构造使得三次方验证的时候,计算结果是小于模数的,也就是不用考虑n;但问题是模这样一个数为什么可以保留最低位
想了几天没想明白,有机会再问问别的师傅,先收了
完整的exp,贴
#!/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)
当然flag是假的
这道题感觉似曾相识,但是一直执着于三个CBC分块,一下午脑子都绕晕,尚师傅二十分钟就出了;做题太少,感觉不够,不敢跳出去
如果你觉得勤奋在天赋面前不值一提,那说明你不够努力
来看主要代码逻辑
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函数
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,那么情况就不一样了(所以很好奇Crypto库里的unpad函数是怎么实现的呢)
关于CBC基本流程可以看Macaw_Revenge,而这道题尚师傅说可以从Xoro获得提示
借一下上次的图
直接说做法了,可以看出,无论我们在这三块中如何闹腾,都不可能整出一个解密是flag却又不是flag对应的密文
所以我们不妨手动加上一块,密文块2作为IV,而明文块3我们让最后其最后一位是16+flag的填充位数
转字节;这样就可以在加密的过程中,完全可以把这一块和前面三块并在一起,相当于加密flag+我们自己的填充
,而解密出来在unpad的时候,由于unpad的机制,就把所有的pad,包括flag本身的,以及我们添加的pad全部去掉了,得到的就是flag,但是密文不同
因为环境关了,在本地复现的,报一些奇怪的错,最后想要的已经求出来send过去了,但是在unpad的时候有问题;虽然原脚本是py2的,但是运行这个老是因为各种换行的IO错误,最后还是改了下unpad这个函数,加了第一句转字符串的
这样下面这个脚本在本地就可以打通了
#!/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)
flag是我自己uuid的
因为看原附件中DASCTF{*********}
总长度是40,所以上面才这么果断设置了pad为chr(8+16).encode()*16
;如果不是的话,可以从一开始的密文知道,48长,也就是分三段,flag至少33长,稍微爆破下也可以出来的
这道题就把pad给改掉了
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("=","")
来看主逻辑
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,如果通过了for循环,那么可以得到你自己输入的密文的明文,但是如果没有通过,说明你指定的密文得到的明文有flag的一部分,emmmmm那还是没通过的好。不过不通过的话,又得不到解密的结果,还是白给。但是如果通过了,说明我们是已经知道flag的内容,并且加以一定的方式将其绕过,也就没有再输出明文的必要。。。事情开始向不好的方向发展
结合我们已经有flag的密文,可以搞点事情
所以总结一下,到此为止,联系上一道题,这里就是给你flag的密文,但是又提供你解密的机会,但是要求解密出来的东西里面十六块一部分的十六块一部分的不和flag中的不完全相同(绕过判定机制),但是你知道怎么从这个解密结果里面看出flag;至于如何构造?大概率就是利用中间的那个已知明文,相同key的加密
显然
如果是第二题,我们利用这种方法也可以得到,可以得到与flag对应的密文不同的密文;但这道题不行,因为它是判断明文之间的比较。有什么办法将一些pad插入到明文0~2中吗
最烦的就是,KEY和IV一样真的没有利用点吗
想了一个晚上还有第二天的网络安全课,课上也糟糟的,不知道说什么好,以为出不来又要作罢了。可是尚师傅花了一节python课的时间就弄明白这是怎么一回事了
此刻我已经找不到足以表达我崇拜之情的表情包了.jpg
下面开始整活,确实,攻击点不在我上面一直在纠结的博弈上。由于KEY和IV一样,这就可以提供我们很大的便利,这也算是这个系统的漏洞吧。最后一层解密不应该看做得到flag的直接途径,而是看成一次解密的机会更好
图上说的很清楚,左边是flag的加密过程,右边是我们借助已有的条件进行攻击的过程。主要的思路就是,因为KEY=IV,又分别有一次加密和解密,那为什么不直接把IV和KEY搞出来呢
在我们自由控制明文的那里,我们输入两块全是\x00
的字节流,为的是消除明文对加密过程产生不可干预的影响,这样整个加密流程,除了KEY一切就变得透明起来
做完一次加密,我们得到了两密文,再加上我们还剩下一次解密的机会,如果你和尚师傅一样聪明,就可以想到把刚才的加密的流程给拆开,其中一半用解密函数来完全道破,另一半也就不攻自破
正如图上所画将c4
送过去解密,得到回显
;显然回显^key
就是c4
通过加密之前的c3
即回显 ^ key = c3
,那么c3
已知,key = c3 ^ 回显
,就此我们得到key,后面再现aes解密就好了
完整的本地exp如下
#!/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())
可以打通
当然和第二题一样,pad和unpad函数也做过一丢丢的修改,不知道这个修改伤不伤大雅
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"")
我觉得非常不好,好吃懒做,脑子不灵光,注意力不集中,转牛角尖,死咬一道题不放(WM也),导致今天效率很慢
如果天赋不够,那就用勤奋来弥补吧
总结一下可以发现,今天的题目并不难(bushi第一题还没想出来),想不到就是想不到,想到了就是走一遍流程的问题,有很多小细节和平时的积累有关;然后在今天的做这些题目的过程中,我也尝试经历了出题,测试的环节,感觉确实很多讲究,现在还是本地了,如果放到服务器上,打在端口上,对出题人的要求更大;在此向所有合格的出题人表示敬意和感谢
【我也会继续前进的,不择手段地前进】 ——Thomas Wade