知识导入(AES-CBC模式)
加密过程
1、首先将明文分组(常见的以16字节为一组),位数不足的使用特殊字符填充。
2、生成一个随机的初始化向量(IV)和一个密钥。
3、将IV和第一组明文异或。
4、用key对3中xor后产生的密文加密。
5、用4中产生的密文对第二组明文进行xor操作。
6、用key对5中产生的密文加密。
7、重复4-7,到最后一组明文。
8、将IV和加密后的密文拼接在一起,得到最终的密文。
解密过程
(加密的逆过程)
设明文为X,密文为Y,解密函数为k。
X[i] = k(Y[i]) Xor Y[i-1]
CBC字节翻转攻击原理
对于CBC模式的解密算法,每一组明文进行分组算法解密之后,需要和前一组的密文异或才能得到明文。第一组则是和初始向量IV进行异或。
CBC字节翻转攻击的核心原理 是通过破坏一个比特的密文来篡改一个比特的明文。攻击流程可参考下图:
可知: A ⊕ B = C 若想要改变输出的明文 C ,那么只需改变密钥 A 即可 那么要如何改变 A 呢? 我们从明文 C 入手,假设改变后的 C 为 C ′ , A 为 A ′ ,那么: 可知:A\oplus B = C\\若想要改变输出的明文C,那么只需改变密钥A即可\\那么要如何改变A呢?\\我们从明文C入手,假设改变后的C为C^{\prime},A为A^{\prime},那么: 可知:A⊕B=C若想要改变输出的明文C,那么只需改变密钥A即可那么要如何改变A呢?我们从明文C入手,假设改变后的C为C′,A为A′,那么:
C ′ = C ⊕ C ⊕ C ′ = A ⊕ B ⊕ C ⊕ C ′ = B ⊕ A ⊕ C ⊕ C ′ 令 A ′ = A ⊕ C ⊕ C ′ ⟹ A ′ ⊕ B = C ′ ( 翻转成功 ) ( 注意 : B 不管怎么样都是不变的 ) \begin{aligned}C^{\prime}&=C\oplus C\oplus C^{\prime}\\&=A\oplus B\oplus C\oplus C^{\prime}\\&=B\oplus A\oplus C\oplus C^{\prime}\\&令A^{\prime}=A\oplus C\oplus C^{\prime}\\&\Longrightarrow A^{\prime}\oplus B=C^{\prime}(\text{翻转成功})\end{aligned}\\(注意:B不管怎么样都是不变的) C′=C⊕C⊕C′=A⊕B⊕C⊕C′=B⊕A⊕C⊕C′令A′=A⊕C⊕C′⟹A′⊕B=C′(翻转成功)(注意:B不管怎么样都是不变的)
总结: 只要把 A 改变成 A ′ = A ⊕ C ⊕ C ′ 便能将输出的明文从 C 变为 C ′ 总结:\\只要把A改变成A^{\prime} = A\oplus C\oplus C^{\prime}\\便能将输出的明文从C变为C^{\prime} 总结:只要把A改变成A′=A⊕C⊕C′便能将输出的明文从C变为C′
题一(NewStarCTF flip-flop)
题目描述:
python
import os
from Crypto.Cipher import AES
auth_major_key = os.urandom(16)
from flag import secret
BANNER = """
Login as admin to get the flag !
"""
MENU = """
Enter your choice
[1] Create NewStarCTF Account
[2] Create Admin Account
[3] Login
[4] Exit
"""
print(BANNER)
def bxor(b1, b2): # use xor for bytes
result = b""
for b1, b2 in zip(b1, b2):
result += bytes([b1 ^ b2])
return result
while True:
print(MENU)
option = int(input('> '))
if option == 1:
auth_pt = b'NewStarCTFer____'
user_key = os.urandom(16)
cipher = AES.new(auth_major_key, AES.MODE_CBC, user_key)
code = cipher.encrypt(auth_pt)
print(f'here is your authcode: {user_key.hex() + code.hex()}')
elif option == 2:
print('GET OUT !!!!!!')
elif option == 3:
authcode = input('Enter your authcode > ')
user_key = bytes.fromhex(authcode)[:16]
code = bytes.fromhex(authcode)[16:]
cipher = AES.new(auth_major_key, AES.MODE_CBC, user_key)
auth_pt = cipher.decrypt(code)
if auth_pt == b'AdminAdmin______':
a = user_key.hex() + code.hex()
print(a)
elif auth_pt == b'NewStarCTFer____':
print('Have fun!!')
else:
print('Who are you?')
elif option == 4:
print('ByeBye')
exit(0)
else:
print("WTF")
题目分析:
可以看出只要明文输出为 可以看出只要明文输出为 可以看出只要明文输出为b'AdminAdmin______', 即可得到 a , a 便是我们需要的 f l a g 即可得到a,a便是我们需要的flag 即可得到a,a便是我们需要的flag
现在我们已知①变换后的明文 现在我们已知①变换后的明文 现在我们已知①变换后的明文b'NewStarCTFer____' ②变换前的明文 ②变换前的明文 ②变换前的明文b'AdminAdmin______' ③密文④ i v ,毫无疑问 C B C 字节翻转攻击 ③密文④iv,毫无疑问CBC字节翻转攻击 ③密文④iv,毫无疑问CBC字节翻转攻击
A ′ = A ⊕ C ⊕ C ′ ,其中: A^{\prime} = A\oplus C\oplus C^{\prime},其中: A′=A⊕C⊕C′,其中:
C = b'NewStarCTFer____'
C' = b'AdminAdmin______'
A = iv
A,C,C'都知道了,那么A'也就知道了,这题很容易就出来了
python
def strxor(a1, a2):
return bytes([b1 ^ b2 for b1,b2 in zip(a1,a2)])
authcode =
user_key = bytes.fromhex(authcode)[:16]
code = bytes.fromhex(authcode)[16:]
user_key=strxor(user_key,b'AdminAdmin______')
user_key=strxor(user_key,b'NewStarCTFer____')
authcode=user_key.hex()+code.hex()
print(authcode)
变换后的authcode出来了提交即可得到flag
题二
题目描述:
python
import socketserver
import os, sys, signal
import string, random
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad,unpad
from secret import flag
key = os.urandom(32)
def decrypt(ciphertext,iv):
cipher = AES.new(key, AES.MODE_CBC, iv)
print(cipher.decrypt(ciphertext))
decrypted = unpad(cipher.decrypt(ciphertext),16)
if decrypted[23:31]==b'lingfeng' and decrypted[40:46]==b'123456':
a=b'welcome!,you are right!\n this is your flag\n'
return a+flag
else:
return decrypted
def encrypt(c):
iv = os.urandom(16)
payload = pad(c,16)
cipher = AES.new(key, AES.MODE_CBC, iv)
encrypted = cipher.encrypt(payload)
return iv.hex() + encrypted.hex()
class Task(socketserver.BaseRequestHandler):
def _recvall(self):
BUFF_SIZE = 2048
data = b''
while True:
part = self.request.recv(BUFF_SIZE)
data += part
if len(part) < BUFF_SIZE:
break
return data.strip()
def send(self, msg, newline=True):
if type(msg) is str:
msg = msg.encode()
try:
if newline:
msg += b'\n'
self.request.sendall(msg)
except:
pass
def recv(self, prompt=b'> '):
self.send(prompt, newline=False)
return self._recvall()
def close(self):
self.send(b"Bye~")
self.request.close()
def handle(self):
menu = '''1.register\n2.login\n'''
for _ in range(3):
self.send('\n' + menu)
try:
r = int(self.recv())
except:
continue
if r == 1:
self.send(b'please input your username')
username=self.recv().strip()
if username==b'lingfeng':
self.send(b'no,you is not lingfeng')
continue
self.send(b'please enter your password')
password=self.recv().strip()
m1=b'{"permission":username:'+username+b'password:'+password+b'}'
self.send(encrypt(m1))
elif r == 2:
self.send(b'please enter your encrypted: ')
m2=self.recv().strip().decode()
iv=bytes.fromhex(m2[:32])
data=bytes.fromhex(m2[32:])
self.send(decrypt(data, iv))
else:
self.send(b'please input again')
self.close()
class ThreadedServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
pass
class ForkedServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
pass
if __name__ == "__main__":
HOST, PORT = '0.0.0.0', 80
server = ForkedServer((HOST, PORT), Task)
server.allow_reuse_address = True
server.serve_forever()
题目分析:
(PS:非预期解,username = lingfeng111111111123456 即可得到,不是本文重点,下面以预期解来讲述)
本题流程和上题一样, u s e r n a m e 自己随便输,但不能是 l i n g f e n g 假设 u s e r n a m e = 0 i n g f e n g 我们要通过 C B C 字节翻转将最终明文 0 i n g f e n g 变为 l i n g f e n g 本题流程和上题一样,username自己随便输,但不能是lingfeng\\假设username = 0ingfeng\\我们要通过CBC字节翻转将最终明文0ingfeng变为lingfeng 本题流程和上题一样,username自己随便输,但不能是lingfeng假设username=0ingfeng我们要通过CBC字节翻转将最终明文0ingfeng变为lingfeng
现在我们已知①变换后的明文 现在我们已知①变换后的明文 现在我们已知①变换后的明文b'lingfeng' ②变换前的明文 ②变换前的明文 ②变换前的明文b'0ingfeng' ③密文④ i v ,毫无疑问 C B C 字节翻转攻击 ③密文④iv,毫无疑问CBC字节翻转攻击 ③密文④iv,毫无疑问CBC字节翻转攻击
A ′ = A ⊕ C ⊕ C ′ ,其中: A^{\prime} = A\oplus C\oplus C^{\prime},其中: A′=A⊕C⊕C′,其中:
C = b'0'
C' = b'l'
A = iv
(因为0ingfeng和lingfeng后面一部分一样,不需要翻转,翻转首字母即可)
注意!!!此处的iv是前一块密文,并不是初始向量
分块为1-16,17-32,此时0的位置为24,那么前一个密文相对应的位置是8,bytes转16进制长度变为2倍,在输出的encrypt中前32位是初始向量,encrypt[46,48]是0的前一个密文相对应的位置(好吧,确实有些拗口),总之iv不一定是初始向量,看流程图也能知道iv总是变化的
我的(哈哈哈,太low了):
python
encrypt = '598a4ebb9cd737b35e64b64522a2a0eb9d324530fda1dd2a1f712dc1b2b80291912eb35dc912ae1359b5affc6aebc989b66a55437c0ecf76246f37ccbf99ccf0'
password = 123456
iv_bytes = bytes.fromhex(encrypt[:32])
encrypt = 'b14bcfb810c6df7c2241ab1f9d37daace87ab04a67fe9dfd89835d0212078c0ade230b479e7eaf409c39d167062941f0ef17a798c440a1df616d15761a2833cb'
new_encrypt = encrypt[:46] + hex(int(encrypt[46:48],16) ^ ord('0') ^ ord('l'))[2:] + encrypt[48:]
print(new_encrypt)
官方的:
python
from pwn import *
sh = remote('39.105.144.62', 718)
sh.sendlineafter(b'>', b'1')
sh.sendlineafter(b'>', b'1ingfeng')
sh.sendlineafter(b'>', b'123456')
a = sh.recvline().decode().strip()
a = a[:32]+a[32:46]+hex(int(a[46:48],16)^ord('1')^ord('l'))[2:]+a[48:]
sh.sendlineafter(b'>', b'2')
sh.sendlineafter(b'>', a.encode())
sh.interactive() # flag{31be9656-81bb-4e8b-9d5e-db84f21184b2}
浅记一下:
做题过程中发现一个好玩的
python
for i in b'kdlsjf':
print(i) # 107 100 108 115 106 102
for b1, b2 in zip(b'dsk', b'kdj'):
print(b1, b2)
输出结果是每个字节对应的整数值,我说怎么有时字节异或报错,有时又不报错,原来是这个原因
还发现一个好玩的
python
long_to_bytes(8) ①
bytes(8) ②
bytes([8]) ③
# 输出结果:
b'\x08'
b'\x00\x00\x00\x00\x00\x00\x00\x00'
b'\x08'
①③输出结果相同,②比较特立独行,我说怎么用bytes()还给我报错,原来是这个原因,哈哈
long_to_bytes():用于将长整数转换为对应的字节对象。在这个例子中,将整数 8 转换为字节对象。
bytes():可以创建一个指定长度的字节对象,其中每个字节初始化为零。在这个例子中,创建了一个长度为 8 的字节对象。
bytes([ ]):不同的是,它接受一个整数列表作为参数,列表中的整数会被转换为相应的字节值。在这个例子中,创建了一个只包含一个字节值为8 的字节对象
又给我学到了,继续加油!