文章目录
基础知识
CBC翻转攻击
简介:
当我们的一个值C是由A和B异或得到
C = A XOR B
那么
A XOR B XOR C很明显是=0的
当我们知道B和C之后,想要得到A的值也很容易
A = B XOR C
因此,A XOR B XOR C等于0。有了这个公式,我们可以在XOR运算的末尾处设置我们自己的值,即可改变。
CBC-AES:Encrypt(明文^IV)
例如,已知CBC第一块(16位)
- 明文
- 密文
想直接修改第11位字符 0
->1
已知第11位字符为
1
,我们可以直接将加密后的
密文[10]=密文[10] xor ‘0’ xor ‘1’
即可修改
BASE64基本加密原理
A=>QQ==
a=>YQ==
97=110 0001=>右移2位 成6位=>1 1000==>查表(24)Y
上一个剩余的01二进制右移四位 010000=>查表(16)Q
填充(= =)
结果YQ==
- 在BASE64中,通常是以3个字符(ASCII)开始编码为一组
- 在转换为2进制中,又会以6位二进制分组(即一组:4*6=24)
- 在空数据,以
=
作为填充字符
a | |||
---|---|---|---|
1 1000 | 010000 | ||
Y | Q | = | = |
SpecialLCG
分析
MSSCTF 2021 初赛题学习
笔记from Crypto.Util.number.inverse = gmpy2.invert 求逆元
bin(数值),可求出数值的二进制
- 不要想着现学现用,长期来看,慢慢去理解 不执著
EXP
from Crypto.Util.number import *
n=18253588106473969889
data=[8331802587873314500,16970700310063771377,16378474859328460142,13073117282614811463,747433301416436433]
t=[]
for i in range(4):
t.append(data[i+1]-data[i])
a1=(t[2]*inverse(t[0],n)-t[3]*inverse(t[1],n))*inverse((t[1]*inverse(t[0],n)-t[2]*inverse(t[1],n)),n)%n
b1=(t[3]-a1*t[2])*inverse(t[1],n)%n
c1=(data[2]-data[1]*a1-data[0]*b1)%n
print(long_to_bytes(a1)+long_to_bytes(b1)+long_to_bytes(c1))
babyLCG
分析
a、b、m已知,求seed就可解出LCG
线性递归表达式:self._state = (self._key['a'] * self._state + self._key['b']) % self._key['m']
143893630627599013207723094044959571968=
(107763262682494809191803026213015101802*x1+153582801876235638173762045261195852087)%226649634126248141841388712969771891297
跟题目去理解(LCG算法==>欧几里得拓展算法)
https://blog.csdn.net/superprintf/article/details/108964563
根据公式,还原seed(求出a,n的逆元,再套公式算seed)
还原seed的公式:seed = (ani*(seed-b))%n
a=107763262682494809191803026213015101802
n=226649634126248141841388712969771891297
b = 153582801876235638173762045261195852087
c =11267068470666042741<<64#old str
ani=invert(a,n)#
seed=c
print(ani)
print("seed:\n")
seed = (ani*(seed-b))%n#公式
print(seed)# 种子:222435278211805578675570877319055662119
能解出第一个seed,相关seed有什么联系?(下一个seed与上一个Seed无关)
[ACTF新生赛2020]crypto-aes
题目描述
from Cryptodome.Cipher import AES
import os
import gmpy2
from flag import FLAG
from Cryptodome.Util.number import *
def main():
key=os.urandom(2)*16
iv=os.urandom(16)
print(bytes_to_long(key)^bytes_to_long(iv))
aes=AES.new(key,AES.MODE_CBC,iv)
enc_flag = aes.encrypt(FLAG)
print(enc_flag)
if __name__=="__main__":
main()
1144196586662942563895769614300232343026691029427747065707381728622849079757
b'\x8c-\xcd\xde\xa7\xe9\x7f.b\x8aKs\xf1\xba\xc75\xc4d\x13\x07\xac\xa4&\xd6\x91\xfe\xf3\x14\x10|\xf8p'
分析
要大胆去猜,大胆去尝试
xor是一种可还原的算法
a^b=c
c^a=b
CBC特性: IV=前一块密文,明文需要先异或IV再加密
EXP
from Crypto.Cipher import AES
import os
import gmpy2
#from flag import FLAG
from Crypto.Util.number import *
XOR = lambda s1 , s2 : bytes([x1^x2 for x1,x2 in zip(s1,s2)])
def main():
flag="flag{xxx}"
oldxorcipher=b'\xc9\x81\xc9\x81\xc9\x81\xc9\x81\xc9\x81\xc9\x81\xc9\x81\xc9\x81N\xed\x98\xe3\x80\xb15gc\x84\x990\xc8P\xb9\xcd'
#泄漏16位明文key,后16被xor 是可还原的算法 xor
cipher=b'\x8c-\xcd\xde\xa7\xe9\x7f.b\x8aKs\xf1\xba\xc75\xc4d\x13\x07\xac\xa4&\xd6\x91\xfe\xf3\x14\x10|\xf8p'
iv=b'\x87lQbI0\xfc\xe6\xaa\x05P\xb1\x01\xd1pL'#long_to_bytes(bytes_to_long(key[:16])^bytes_to_long(oldxorcipher[16:]))
key=b"\xc9\x81"*16
aes=AES.new(key,AES.MODE_CBC,iv)
ans=aes.decrypt(cipher[:16])
print(ans)#pre 16:actf{W0W_y0u_can
aes=AES.new(key,AES.MODE_CBC,cipher[:16])#CBC特性 IV=前一块密文
ans=aes.decrypt(cipher[16:])
print(ans)#last 16:_so1v3_AES_now!}
#actf{W0W_y0u_can_so1v3_AES_now!}
if __name__=="__main__":
main()
# 91144196586662942563895769614300232343026691029427747065707381728622849079757
# b'\x8c-\xcd\xde\xa7\xe9\x7f.b\x8aKs\xf1\xba\xc75\xc4d\x13\x07\xac\xa4&\xd6\x91\xfe\xf3\x14\x10|\xf8p'
StandardCBC
题目描述
from gmssl import sm4 #https://github.com/duanhongyi/gmssl
import socketserver
import signal
from flag import flag
import os
from base64 import *
import random
menu = '''1.enc;
2.dec;
3.getflag;
'''
XOR = lambda s1 , s2 : bytes([x1^x2 for x1,x2 in zip(s1,s2)])
def pad(m):
padlen = 16 - len(m) % 16
return m + padlen * bytes([padlen])
def unpad(m):
return m[:-m[-1]]
def enc(iv , m , key):
enc = sm4.CryptSM4(mode=sm4.SM4_ENCRYPT)
enc.set_key(key = key , mode = sm4.SM4_ENCRYPT)
c = enc.crypt_cbc(iv, m)
return iv + c
def dec(iv , c , key):
dec = sm4.CryptSM4(mode=sm4.SM4_DECRYPT)
dec.set_key(key = key , mode = sm4.SM4_DECRYPT)
m = dec.crypt_cbc(iv, c)
return m
class server(socketserver.BaseRequestHandler):
def _recv(self):
data = self.request.recv(1024)
return data.strip()
def _send(self, msg, newline=True):
if isinstance(msg , bytes):
msg += b'\n'
else:
msg += '\n'
msg = msg.encode()
self.request.sendall(msg)
def handle(self):
signal.alarm(600)
key = os.urandom(16)
secret = os.urandom(random.randint(16 , 31))
while 1:
try:
iv = os.urandom(16)
self._send(menu)
choice = self._recv()
if choice == b'1':
self._send(b'your message:')
msg = b64decode(self._recv())
self._send(b64encode(enc(iv , msg + secret , key)))
elif choice == b'2':
self._send('your ciphertext:')
c = b64decode(self._recv())
self._send('your iv:')
iv = b64decode(self._recv())
self._send(b64encode(dec(iv , c , key))[-1:])
elif choice == b'3':
self._send('do you know my secret?')
guess = b64decode(self._recv())
if guess == secret:
self._send('congratulations')
self._send(flag)
else:
self._send('I know you can\'t know it')
break
else:
self._send('wrong!')
break
except:
pass
class ForkedServer(socketserver.ForkingMixIn, socketserver.TCPServer):
pass
if __name__ == "__main__":
HOST, PORT = '0.0.0.0', 10001
server = ForkedServer((HOST, PORT), server)
server.allow_reuse_address = True
server.serve_forever()
分析
MINIL的题目
aes(明文^iv)=cipher
第二块chunk enc(明文^上一块密文)
不用怀疑,是程序逻辑需要爆破,dec只返回倒数第一位
请先看清程序整体逻辑,再写代码~
不要[[过度简化]],也不要想的太复杂
干就完了!
事实:
- IV每一次都是不一样的
- 解密只返回倒数1位的base64编码字符
难度有点高 暂时放弃
EXP
EXP选择了暴力破解思路
From XDSEC GITHUB:https://github.com/XDSEC/miniLCTF_2021/blob/main/WriteUps/H4n53r/H4n53r-TEAM.md
from pwn import *
from base64 import b64decode, b64encode
from Crypto.Util.number import long_to_bytes
def get_least_length():
for i in range(1, 16):
guess = b'\x00' * i
c = b64decode(get_recv(guess))
if i == 1:
base = len(c)
if len(c) != base:
return base - 16 - i
def get_recv(x):
io.send(b'1')
io.recvuntil(b':')
io.send(b64encode(x))
Res = io.recvuntil(b'flag;').decode().split('\n')
return Res[1]
def get_message_last(c):
guess = long_to_bytes(66) * 239
for i in range(256):
G = guess + long_to_bytes(i)
io.send(b'2')
io.recvuntil(b':')
io.send(b64encode(G + c))
io.recvuntil(b':')
io.send(b64encode(IV))
resp = io.recvuntil('flag;').decode().split('\n')[1]
if resp == '':
return i
if __name__ == "__main__":
IV = b'\x00'*16
LengTh = 0
ciphertext = []
M = [0]*17
while LengTh == None or LengTh != 17:
try:
io = remote('0.0.0.0', 10001)
io.recv()
LengTh = get_least_length()
print(LengTh)
except:
io.close()
print('Get Length!!!')
for i in range(16):
pad = b'\x76' * 16 + (15 - i) * b'\x00'
res = get_recv(pad)
ciphertext.append(b64decode(res))
print('Get Ciphertext!!!')
i = 0
for c in ciphertext:
print(i)
if i == 0:
c16 = c[48:64]
M[-1] = long_to_bytes(get_message_last(c16) ^ c[47])
c16 = c[32:48]
M[i] = long_to_bytes(get_message_last(c16) ^ c[31])
i += 1
m = b''.join(M)
print('Get Message!!!')
io.recv()
io.send(b'3')
print(io.recv())
io.send(b64encode(m))
print(io.recv())
print(io.recv())
额外知识
[[Padding-Oracle]]
故名思义,Padding Oracle Attack背后的关键性概念便是加/解密时的填充(Padding)。明文信息可以是任意长度,但是块状加密算法需要所有的信息都由一定数量的数据块组成。为了满足这样的需求,便需要对明文进行填充,这样便可以将它分割为完整的数据块。
加密时可以使用多种填充规则,但最常见的填充方式之一是在PKCS#5标准中定义的规则。PCKS#5的填充方式为:明文的最后一个数据块包含N个字节的填充数据(N取决于明文最后一块的数据长度)。下图是一些示例,展示了不同长度的单词(FIG、BANANA、AVOCADO、PLANTAIN、PASSIONFRUIT)以及它们使用PKCS#5填充后的结果(每个数据块为8字节长)。
————————————————
什么是PKCS#5?
PKCS#5,就是一种由RSA信息安全公司设计的填充标准。
对于PKCS#5标准来说,一般缺少几位,就填充几位那个数字。
比方说,在上面的例子里,我们有三位空缺,那么就要在空缺处都填上。这样,第二组的内容就变成了‘bc333’ 。
一个0x01(0x01)
两个0x02(0x02,0x02)
三个0x03(0x03,0x03,0x03)
四个0x04(0x04,0x04,0x04,0x04)
……
如果解密后的最后一个数据块末尾并非这些合法的字节序列,大部分加/解密程序都会抛出一个填充异常。这个异常对于攻击者尤为关键,它是Padding Oracle Attack的基础。