【20210908 人在寝室坐题从天上来】Crypto方向WP

人在寝室坐题从天上来

今天东北的师傅给了他们那边比赛的三密码道题,期间看了第一道,求出n之后就没思路了;还有两道赛后复现的,都是AES.CBC,第二道从尚师傅那里获得提示,跳开了CBC模式本身,攻击点在pad;第三道pad可以利用的点被抹除了,并给了一个看似需要攻击的地方,但攻击点却在CBC模式本身

好了,我宣布尚师傅最强,实验室密码顶梁柱,我就是尚师傅的WP手,专门记录尚师傅出过的题目

以及被IO支配的恐惧

文章目录

  • 人在寝室坐题从天上来
    • Yusa的日常生活—美国大选(recuring)
    • Yusa的密码学课堂—CBC第二课(recuring)
    • Yusa的密码学课堂—CBC第三课(recuring)
  • 总结

Yusa的日常生活—美国大选(recuring)

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

【20210908 人在寝室坐题从天上来】Crypto方向WP_第1张图片

可以通过尝试看出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)

【20210908 人在寝室坐题从天上来】Crypto方向WP_第2张图片

当然flag是假的

Yusa的密码学课堂—CBC第二课(recuring)

这道题感觉似曾相识,但是一直执着于三个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获得提示

借一下上次的图

【20210908 人在寝室坐题从天上来】Crypto方向WP_第3张图片

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

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

因为环境关了,在本地复现的,报一些奇怪的错,最后想要的已经求出来send过去了,但是在unpad的时候有问题;虽然原脚本是py2的,但是运行这个老是因为各种换行的IO错误,最后还是改了下unpad这个函数,加了第一句转字符串的

【20210908 人在寝室坐题从天上来】Crypto方向WP_第4张图片

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

#!/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的

【20210908 人在寝室坐题从天上来】Crypto方向WP_第5张图片

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

Yusa的密码学课堂—CBC第三课(recuring)

这道题就把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的加密

显然

【20210908 人在寝室坐题从天上来】Crypto方向WP_第6张图片

如果是第二题,我们利用这种方法也可以得到,可以得到与flag对应的密文不同的密文;但这道题不行,因为它是判断明文之间的比较。有什么办法将一些pad插入到明文0~2中吗

最烦的就是,KEY和IV一样真的没有利用点吗

想了一个晚上还有第二天的网络安全课,课上也糟糟的,不知道说什么好,以为出不来又要作罢了。可是尚师傅花了一节python课的时间就弄明白这是怎么一回事了

此刻我已经找不到足以表达我崇拜之情的表情包了.jpg


下面开始整活,确实,攻击点不在我上面一直在纠结的博弈上。由于KEY和IV一样,这就可以提供我们很大的便利,这也算是这个系统的漏洞吧。最后一层解密不应该看做得到flag的直接途径,而是看成一次解密的机会更好

【20210908 人在寝室坐题从天上来】Crypto方向WP_第7张图片

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

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

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

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

【20210908 人在寝室坐题从天上来】Crypto方向WP_第8张图片

回显 ^ 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())

【20210908 人在寝室坐题从天上来】Crypto方向WP_第9张图片

可以打通

当然和第二题一样,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

你可能感兴趣的:(树哥让我天天写之Crypto,python,密码学)