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},那么: 可知: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 的字节对象

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

相关推荐
北京搜维尔科技有限公司2 小时前
搜维尔科技:【应用】Xsens在荷兰车辆管理局人体工程学评估中的应用
人工智能·安全
云起无垠2 小时前
技术分享 | 大语言模型赋能软件测试:开启智能软件安全新时代
人工智能·安全·语言模型
ac-er88883 小时前
PHP弱类型安全问题
开发语言·安全·php
One_Blanks3 小时前
渗透测试-Linux基础(1)
linux·运维·安全
易云码3 小时前
信息安全建设方案,网络安全等保测评方案,等保技术解决方案,等保总体实施方案(Word原件)
数据库·物联网·安全·web安全·低代码
Qspace丨轻空间3 小时前
气膜场馆:推动体育文化旅游创新发展的关键力量—轻空间
大数据·人工智能·安全·生活·娱乐
Suckerbin5 小时前
Hms?: 1渗透测试
学习·安全·网络安全
Diamond技术流6 小时前
从0开始学习Linux——网络配置
linux·运维·网络·学习·安全·centos
Spring_java_gg6 小时前
如何抵御 Linux 服务器黑客威胁和攻击
linux·服务器·网络·安全·web安全
newxtc7 小时前
【国内中间件厂商排名及四大中间件对比分析】
安全·web安全·网络安全·中间件·行为验证·国产中间件