CBC字节翻转攻击介绍 & 例题

知识导入(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},那么: 可知:AB=C若想要改变输出的明文C,那么只需改变密钥A即可那么要如何改变A呢?我们从明文C入手,假设改变后的CCAA,那么:
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=CCC=ABCC=BACCA=ACCAB=C(翻转成功)(注意:B不管怎么样都是不变的)
总结: 只要把 A 改变成 A ′ = A ⊕ C ⊕ C ′ 便能将输出的明文从 C 变为 C ′ 总结:\\只要把A改变成A^{\prime} = A\oplus C\oplus C^{\prime}\\便能将输出的明文从C变为C^{\prime} 总结:只要把A改变成A=ACC便能将输出的明文从C变为C

题一(NewStarCTF flip-flop)

题目描述:
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=ACC,其中:
C = b’NewStarCTFer____’
C’ = b’AdminAdmin______’
A = iv
A,C,C’都知道了,那么A’也就知道了,这题很容易就出来了

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

题二

题目描述:
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=ACC,其中:
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了):

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)

官方的:

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}

浅记一下:

做题过程中发现一个好玩的

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)

输出结果是每个字节对应的整数值,我说怎么有时字节异或报错,有时又不报错,原来是这个原因

还发现一个好玩的

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 的字节对象

又给我学到了,继续加油!

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值