CTF Crypto中涉及的AES题目

  • 单独涉及AES_ECB模式
  • 单独涉及AES_CBC模式
    • ProblemProblemProblem
    • AnalysisAnalysisAnalysis
    • SolvingcodeSolving~codeSolving code
  • AES_CBC与AES_ECB的联合求解
    • ProblemProblemProblem
    • AnalysisAnalysisAnalysis
    • SolvingCodeSolving~CodeSolving Code
  • 单独涉及AES_CTR模式
    • ProblemProblemProblem
    • AnalysisAnalysisAnalysis
    • SolvingcodeSolving~codeSolving code
    • ConclusionConclusionConclusion

keywords:keywords:keywords: AES_CBC模式AES_ECB模式AES_CTR模式

单独涉及AES_ECB模式

最常见的是RSA中套了一个简单的AES_ECB加密(AES加密flag),RSA加密的是AES_ECB模式的密钥key,解RSA得到密钥key进而求得flag

单独涉及AES_CBC模式

来源于陇原战疫2021网络安全大赛 Civet cat for Prince

keywords:keywords:keywords: AES_CBCxor狸猫换太子

ProblemProblemProblem

from Crypto.Cipher import AES
import os
from hashlib import sha256
import socketserver
import signal
import string
import randomtable = string.ascii_letters + string.digits
BANNER = br'''.d8888b.  d8b                   888                            888
d88P  Y88b Y8P                   888                            888
888    888                       888                            888
888        888 888  888  .d88b.  888888        .d8888b  8888b.  888888
888        888 888  888 d8P  Y8b 888          d88P"        "88b 888
888    888 888 Y88  88P 88888888 888          888      .d888888 888
Y88b  d88P 888  Y8bd8P  Y8b.     Y88b.        Y88b.    888  888 Y88b.           "Y8888P"  888   Y88P    "Y8888   "Y888        "Y8888P "Y888888  "Y888          .d888                        8888888b.          d8b
d88P"                         888   Y88b         Y8P
888                           888    888
888888  .d88b.  888d888       888   d88P 888d888 888 88888b.   .d8888b  .d88b.
888    d88""88b 888P"         8888888P"  888P"   888 888 "88b d88P"    d8P  Y8b
888    888  888 888           888        888     888 888  888 888      88888888
888    Y88..88P 888           888        888     888 888  888 Y88b.    Y8b.
888     "Y88P"  888           888        888     888 888  888  "Y8888P  "Y8888
'''guard_menu = br'''
1.Tell the guard my name
2.Go away
'''cat_menu = br'''1.getpermission
2.getmessage
3.say Goodbye
'''def Pad(msg):return msg + os.urandom((16 - len(msg) % 16) % 16)class Task(socketserver.BaseRequestHandler):def _recvall(self):BUFF_SIZE = 2048data = b''while True:part = self.request.recv(BUFF_SIZE)data += partif len(part) < BUFF_SIZE:breakreturn data.strip()def send(self, msg, newline=True):try:if newline:msg += b'\n'self.request.sendall(msg)except:passdef recv(self, prompt=b'[-] '):self.send(prompt, newline=False)return self._recvall()def proof_of_work(self):proof = (''.join([random.choice(table) for _ in range(12)])).encode()sha = sha256(proof).hexdigest().encode()self.send(b"[+] sha256(XXXX+" + proof[4:] + b") == " + sha)XXXX = self.recv(prompt=b'[+] Give Me XXXX :')if len(XXXX) != 4 or sha256(XXXX + proof[4:]).hexdigest().encode() != sha:return Falsereturn Truedef register(self):self.send(b'')username = self.recv()return usernamedef getpermission(self, name, iv, key):aes = AES.new(key, AES.MODE_CBC, iv)plain = Pad(name)+b"a_cat_permission"return aes.encrypt(plain)def getmessage(self, iv, key, permission):aes = AES.new(key, AES.MODE_CBC, iv)return aes.decrypt(permission)def handle(self):signal.alarm(50)if not self.proof_of_work():returnself.send(BANNER, newline=False)self.key = os.urandom(16)self.iv = os.urandom(16)self.send(b"I'm the guard, responsible for protecting the prince's safety.")self.send(b"You shall not pass, unless you have the permission of the prince.")self.send(b"You have two choices now. Tell me who you are or leave now!")self.send(guard_menu, newline=False)option = self.recv()if option == b'1':try:self.name = self.register()self.send(b"Hello " + self.name)self.send(b"Nice to meet you. But I can't let you pass. I can give you a cat. She will play with you")self.send(b'Miao~ ' + self.iv)for i in range(3):self.send(b"I'm a magic cat. What can I help you")self.send(cat_menu, newline=False)op = self.recv()if op == b'1':self.send(b"Looks like you want permission. Here you are~")permission = self.getpermission(self.name, self.iv, self.key)self.send(b"Permission:" + permission)elif op == b'2':self.send(b"Looks like you want to know something. Give me your permission:")permission = self.recv()self.send(b"Miao~ ")iv = self.recv()plain = self.getmessage(iv, self.key, permission)self.send(b"The message is " + plain)elif op == b'3':self.send(b"I'm leaving. Bye~")breakself.send(b"Oh, you're here again. Let me check your permission.")self.send(b"Give me your permission:")cipher = self.recv()self.send(b"What's the cat tell you?")iv = self.recv()plain = self.getmessage(iv, self.key, cipher)prs, uid = plain[16:],plain[:16]if prs != b'Princepermission' or uid != self.name:self.send(b"You don't have the Prince Permission. Go away!")returnelse:self.send(b"Unbelievable! How did you get it!")self.send(b"The prince asked me to tell you this:")f = open('flag.txt', 'rb')flag = f.read()f.close()self.send(flag)except:self.request.close()if option == b'2':self.send(b"Stay away from here!")self.request.close()class ThreadedServer(socketserver.ThreadingMixIn, socketserver.TCPServer):passclass ForkedServer(socketserver.ForkingMixIn, socketserver.TCPServer):passif __name__ == "__main__":HOST, PORT = '0.0.0.0', 10005print("HOST:POST " + HOST + ":" + str(PORT))server = ForkedServer((HOST, PORT), Task)server.allow_reuse_address = Trueserver.serve_forever()

AnalysisAnalysisAnalysis

首先是执行proof of work函数,简单的爆破四个字符使得其连接上剩余已知字符的sha256等于函数给出的sha256

然后进入正题,第一段守卫处没有有用信息,需要输入你的name(之后称为m1),然后在magic cat处可以获得

  • getpermission()

得到对m1+b'a_cat_permission'AES_CBC加密之后得到的密文,其中iv是系统生成的已知量,key未知

    def getpermission(self, name, iv, key):aes = AES.new(key, AES.MODE_CBC, iv)plain = Pad(name)+b"a_cat_permission"return aes.encrypt(plain)
  • getmessage()

输入一段密文进行AES_CBC解密,其中iv由我们给出,key是之前使用的,得到解密之后给出的明文

    def getmessage(self, iv, key, permission):aes = AES.new(key, AES.MODE_CBC, iv)return aes.decrypt(permission)

总共有三次询问的机会(循环次数为3),循环完成之后由守卫进行验证

提交给守卫某个密文和自己给出的偏移量iv,使得进行相同的key的AES_CBC解密之后得到的明文是m1+b'Princeperimission'

self.send(b"Give me your permission:")
cipher = self.recv()
self.send(b"What's the cat tell you?")
iv = self.recv()
plain = self.getmessage(iv, self.key, cipher)
prs, uid = plain[16:],plain[:16]
if prs != b'Princepermission' or uid != self.name:...

以上就是对题目代码的分析

简单来说,我们需要想办法把从magic cat处得到的perimission = m1 + b'a_cat_perimission'的密文换成m1 + b'Princepermission'的密文

其中可以用到的是magic cat会提供一次或两次解密的机会(用自己给出的iv

显然没有办法知道key进而直接求解,但是我们不需要老老实实地直接找need_m1+need_m2need\_m_1+need\_m_2need_m1​+need_m2​的密文,因为我们可以构造合适的iv代入最后的AES_CBC解密

先令

m1=input_namem_1 = input\_namem1​=input_name

m2=a_cat_permissionm_2 = a\_cat\_permissionm2​=a_cat_permission

need_m1=m1need\_m_1=m_1need_m1​=m1​

need_m2=Princepermissionneed\_m_2=Princepermissionneed_m2​=Princepermission

由于AES_CBC加密是分块加密,而每一块的大小是128bits也就是16字节,而由于题目要求,m1m_1m1​和m2m_2m2​以及need_m2need\_m_2need_m2​都是16字节大小的

这就意味着加密得到的密文块会有特殊的性质

令c1=permission[:16]c_1=permission[:16]c1​=permission[:16],c2=permission[16:]c_2=permission[16:]c2​=permission[16:];其中permissionpermissionpermission是 magic cat通过getpermission()给出的密文

通过图片明确一下AES_CBC加解密过程

加密过程是,先将明文按16字节一分组拆开,然后进行如图加密(先进行异或,非明文分组1的异或对象是前一个密文分组或者是明文分组1的异或对象初始化向量iv),再分别投入加密器,得到各个密文分组后拼接起来成为最后的密文

解密过程是,将密文按16字节一分组拆开,然后分别投入解密器,得到的结果与前一个密文分组或者初始化向量进行异或,最后得到各个明文分组再直接拼接在一起组成最后的明文

其中加密器和解密器在本题算是黑盒,只需要知道进去的数据和原来是不一样的即可

我们构造的密文要先经过一次解密器,这就意味着非经过AES_CBC加密产生的密文(比如c1,c2c_1,c_2c1​,c2​)都会产生一个未知的数据;那么这时就需要用到magic cat提供的解密的机会

经过解密器之后会进行异或,那么这就是本题的关键之一了,异或的性质就是两个一样的数异或均为0,也就是说对其他一起的数不会有影响,利用这个性质我们构造

fake_c1=c1⊕m2⊕need_m2fake\_c_1=c_1\oplus m_2\oplus need\_m_2fake_c1​=c1​⊕m2​⊕need_m2​

使用系统给出的iv代入magic cat提供的一次解密的机会

先经过解密器得到
m1⊕iv⊕decrypted_m2⊕decrypted_need_m2m_1\oplus iv\oplus decrypted\_m_2\oplus decrypted\_need\_m_2 m1​⊕iv⊕decrypted_m2​⊕decrypted_need_m2​

在与初始化向量iv异或得到
m=m1⊕decrypted_m2⊕decrypted_need_m2m=m_1\oplus decrypted\_m_2\oplus decrypted\_need\_m_2 m=m1​⊕decrypted_m2​⊕decrypted_need_m2​

接着构造

fake_iv=m⊕m1⊕iv=decrypted_m2⊕decrypted_need_m2⊕ivfake\_iv=m\oplus m_1\oplus iv=decrypted\_m_2\oplus decrypted\_need\_m_2\oplus iv fake_iv=m⊕m1​⊕iv=decrypted_m2​⊕decrypted_need_m2​⊕iv

那么最后传入的密文是fake_c1+c2fake\_c_1+c_2fake_c1​+c2​,传入的iv也就是fake_ivfake\_ivfake_iv

推导一下最后的解密过程,(AES_CBC是分块解密的)

fake_c1fake\_c_1fake_c1​单独进行解密得到m1⊕iv⊕decrypted_m2⊕decrypted_need_m2m_1\oplus iv\oplus decrypted\_m_2\oplus decrypted\_need\_m_2m1​⊕iv⊕decrypted_m2​⊕decrypted_need_m2​

然后再与fake_ivfake\_ivfake_iv异或得到

m1⊕iv⊕decrypted_m2⊕decrypted_need_m2⊕decrypted_m2⊕decrypted_need_m2⊕iv=m1m_1\oplus iv\oplus decrypted\_m_2\oplus decrypted\_need\_m_2\oplus decrypted\_m_2\oplus decrypted\_need\_m_2\oplus iv =m_1 m1​⊕iv⊕decrypted_m2​⊕decrypted_need_m2​⊕decrypted_m2​⊕decrypted_need_m2​⊕iv=m1​

得到明文分组1,符合最后的判断条件

然后c2c_2c2​单独进行解密,得到c1⊕m2c_1\oplus m_2c1​⊕m2​

再与fake_c1fake\_c_1fake_c1​进行异或得到
c1⊕m2⊕need_m2⊕c1⊕m2=need_m2c_1\oplus m_2\oplus need\_m_2 \oplus c_1\oplus m_2=need\_m_2 c1​⊕m2​⊕need_m2​⊕c1​⊕m2​=need_m2​

这就是明文分组2,符合最后的判断条件

SolvingcodeSolving~codeSolving code

from re import L
from pwn import *
import hashlib,string,random
from Crypto.Cipher import AESio = remote("node4.buuoj.cn","27370")
temp = io.recvline()
# print(temp)
temp1 = temp.split(b"==")
# print(temp1)
part_proof = bytes.decode(temp1[0].split(b"XXXX")[1])[1:-2]
sha = bytes.decode(temp1[1]).strip()
table = string.ascii_letters + string.digits
while True:XXXX = "".join([random.choice(table)for _ in range(4)])temp_proof = XXXX + part_prooftemp_sha = hashlib.sha256(temp_proof.encode()).hexdigest()if sha == temp_sha:io.recvuntil(b"[+] Give Me XXXX :")io.sendline(XXXX.encode())break
io.recvuntil(b"[-] ")
io.sendline(b"1")
io.sendline(b"1" * 16)
io.recvuntil(b'Miao~ ')
iv = io.recvuntil(b"\n")[:-1]
# print(iv)
io.recvuntil(b'[-]')
io.sendline(b'1')
io.recvuntil(b'Permission:')
cat_permission = io.recvline()[:-1]
# print(cat_permission)
io.recvuntil(b"[-]")
io.sendline(b'2')
io.recvuntil(b"Looks like you want to know something. Give me your permission:")m2 = b"a_cat_permission"
m1 = b'1' * 16 # your name,直接16字节,不会进行pad()函数,如果那样的话,m1也会成为一个未知量
need_m2 = b"Princepermission"
need_m1 = m1
c1 = cat_permission[:16]
c2 = cat_permission[16:]
fake_c1 = xor(xor(c1,m2),need_m2)io.sendline(fake_c1)
io.recvuntil(b"Miao~ ")
io.sendline(iv)
io.recvuntil(b"The message is ")
m = io.recvuntil(b"\n")[:-1]
# print(plain)
io.recvuntil(b"[-]")
io.sendline(b'3')
io.recvuntil(b"Give me your permission:")fake_permission = fake_c1 + c2
fake_iv = xor(xor(m,m1),iv)io.sendline(fake_permission)
io.recvuntil(b"What's the cat tell you?")
io.sendline(fake_iv)
io.interactive()

AES_CBC与AES_ECB的联合求解

来源于安洵杯2020 easyAES

keywords:keywords:keywords: AES_CBCAES_CBC所使用的加解密器和AES_ECB模式所使用的相同

ProblemProblemProblem

#!/usr/bin/python
from Crypto.Cipher import AES
import binascii
from Crypto.Util.number import bytes_to_long
from flag import flag
from key import keyiv = flag.strip(b'd0g3{').strip(b'}')LENGTH = len(key)
assert LENGTH == 16hint = os.urandom(4) * 8
print(bytes_to_long(hint)^bytes_to_long(key))msg = b'Welcome to this competition, I hope you can have fun today!!!!!!'def encrypto(message):aes = AES.new(key,AES.MODE_CBC,iv)return aes.encrypt(message)print(binascii.hexlify(encrypto(msg))[-32:])'''
56631233292325412205528754798133970783633216936302049893130220461139160682777
b'3c976c92aff4095a23e885b195077b66'
'''

AnalysisAnalysisAnalysis

flag赋值给了iv,用作AES_CTR加密模式;其中也用到了key,而关于key的提示是key与一串重复的字节进行异或

len(key) == 16hint = os.urandom(4) * 8,很显然两者是不等长的,所以很容易恢复出key,因为并没有都异或;也可以这样来验证

>>> from Crypto.Util.number import *
>>> hint_xor_key = 56631233292325412205528754798133970783633216936302049893130220461139160682777
>>> long_to_bytes(hint_xor_key)
b'}4$d}4$d}4$d}4$d\x19\x04CW\x06CA\x08\x1e[I\x01\x04[Q\x19'

很显然发现前4个字节是重复出现了很多次,这就是hint的重复字节

那么

hint_xor_key = 56631233292325412205528754798133970783633216936302049893130220461139160682777
hint = long_to_bytes(hint_xor_key)[:4]
key = hint_xor_key ^ bytes_to_long(hint * 8)
key = long_to_bytes(key)

看到脚本最后给出的encbinascii.hexlify(encrypto(msg))[-32:],转换回来得到16字节长的密文(实际上就是最后一组密文分组)

>>> import binascii
>>> part_enc = b'3c976c92aff4095a23e885b195077b66'
>>> part_enc = binascii.unhexlify(part_enc)
>>> part_enc
b'<\x97l\x92\xaf\xf4\tZ#\xe8\x85\xb1\x95\x07{f'

现在key,msg已知,回到CBC模式实现的细节

以上是加密过程,那么反过来,当我们已知整个msg和最后一组密文分组,由于CBC模式使用的加解密器与ECB模式的是一样的,那么我们可以应用ECB模式的解密器将最后一组密文分组进行解密器解密,得到的结果是上一个密文分组与最后一个明文分组的异或结果;由于明文分组已知,与之异或得到上一个密文分组的值,以此类推,我们可以知道所有密文分组和偏移量iv(也即是flag

SolvingCodeSolving~CodeSolving Code

from Crypto.Cipher import AES
from Crypto.Util.number import *
from pwn import *
import binascii
hint_xor_key = 56631233292325412205528754798133970783633216936302049893130220461139160682777
part_enc = b'3c976c92aff4095a23e885b195077b66'
msg = b'Welcome to this competition, I hope you can have fun today!!!!!!'
# print(long_to_bytes(hint_xor_key))
hint = long_to_bytes(hint_xor_key)[:4]
key = hint_xor_key ^ bytes_to_long(hint * 8)
key = long_to_bytes(key)
# print(len(msg))
aes = AES.new(key,AES.MODE_ECB)
part_enc = binascii.unhexlify(part_enc)
enc1 = part_enc
m1 = aes.decrypt(enc1)
enc2 = xor(msg[-16:],m1)
m2 = aes.decrypt(enc2)
enc3 = xor(msg[-32:-16],m2)
m3 = aes.decrypt(enc3)
enc4 = xor(msg[-48:-32],m3)
m4 = aes.decrypt(enc4)
enc5 = xor(msg[-64:-48],m4)
iv = enc5
print(iv)

单独涉及AES_CTR模式

来源于安洵杯2021 air encryption

keywords:keywords:keywords: AES_CTRxor交互欧拉定理数据处理

ProblemProblemProblem

#!/usr/bin/python
import socketserver
import random
import os
import string
import binascii
import hashlib
from Crypto.Cipher import AES
from Crypto.Util import Counter
from Crypto.Util.number import getPrime
from hashlib import sha256
import gmpy2
from flag import flagdef init():q = getPrime(512)p = getPrime(512)e = getPrime(64)n = q*pphi = (q-1) * (p-1)d = gmpy2.invert(e, phi)hint = 2 * d + random.randint(0, 2**16) * e * phimac = random.randint(0, 2**64)c = pow(mac, e, n)counter = random.randint(0, 2**128)key = os.urandom(16)score = 0return n, hint, c, counter, key, mac, scoreclass task(socketserver.BaseRequestHandler):def POW(self):random.seed(os.urandom(8))proof = ''.join([random.choice(string.ascii_letters+string.digits) for _ in range(20)])result = hashlib.sha256(proof.encode('utf-8')).hexdigest()self.request.sendall(("sha256(XXXX+%s) == %s\n" % (proof[4:],result)).encode())self.request.sendall(b'Give me XXXX:\n')x = self.recv()if len(x) != 4 or hashlib.sha256((x+proof[4:].encode())).hexdigest() != result: return Falsereturn Truedef recv(self):BUFF_SIZE = 2048data = b''while True:part = self.request.recv(BUFF_SIZE)data += partif len(part) < BUFF_SIZE:breakreturn data.strip()def padding(self, msg):return  msg + chr((16 - len(msg)%16)).encode() * (16 - len(msg)%16)def encrypt(self, msg):msg = self.padding(msg)if self.r != -1:self.r += 1aes = AES.new(self.key, AES.MODE_CTR, counter = Counter.new(128, initial_value=self.r))return aes.encrypt(msg)else:return msgdef send(self, msg, enc=True):print(msg, end= '   ')if enc:msg = self.encrypt(msg)print(msg, self.r)self.request.sendall(binascii.hexlify(msg) + b'\n')def set_key(self, rec):if self.mac == int(rec[8:]):self.r = self.counterdef guess_num(self, rec):num = random.randint(0, 2**128)if num == int(rec[10:]):self.send(b'right')self.score += 1else:self.send(b'wrong')def get_flag(self, rec):assert self.r != -1if self.score ==  5:self.send(flag, enc=False)else:self.send(os.urandom(32) +  flag)def handle(self):self.r = -1if not self.POW():self.send(b'Error Hash!', enc= False)returnself.n, self.hint, self.c ,self.counter, self.key, self.mac, self.score = init()self.send(str(self.n).encode(), enc = False)self.send(str(self.hint).encode(), enc = False)self.send(str(self.c).encode(), enc = False)for _ in range(6):rec = self.recv()if rec[:8] == b'set key:':self.set_key(rec)elif rec[:10] == b'guess num:':self.guess_num(rec)elif rec[:8] == b'get flag':self.get_flag(rec)else:self.send(b'something wrong, check your input')class ForkedServer(socketserver.ForkingMixIn, socketserver.TCPServer):passdef main():HOST, PORT = '127.0.0.1', 10086server = ForkedServer((HOST, PORT), task)server.allow_reuse_address = Trueserver.serve_forever()if __name__ == '__main__':main()

AnalysisAnalysisAnalysis

经典的密码nc连接题,用了套接字模块,那么重点函数就是handle()

    def handle(self):self.r = -1if not self.POW():self.send(b'Error Hash!', enc= False)returnself.n, self.hint, self.c ,self.counter, self.key, self.mac, self.score = init()self.send(str(self.n).encode(), enc = False)self.send(str(self.hint).encode(), enc = False)self.send(str(self.c).encode(), enc = False)for _ in range(6):rec = self.recv()if rec[:8] == b'set key:':self.set_key(rec)elif rec[:10] == b'guess num:':self.guess_num(rec)elif rec[:8] == b'get flag':self.get_flag(rec)else:self.send(b'something wrong, check your input')

先进行POW函数验证(相当于拖慢脚本运行速度),就是判断hash值,爆破即可

按照RSA加密的方式给出了n,chint(注意服务端给出的这几个值是通过字节转十六进制了的,需要转回正常的字节,而原来的字节是十进制数,所以使用binascii.unhexlify()),且加密的明文是mac;先把ta求出来
hint=2⋅d+random⋅e⋅ϕ(n)∵aϕ(p)≡1(modp),欧拉定理∴chint≡c2⋅d⋅crandom⋅e⋅ϕ(n)≡c2⋅d(modn)hint = 2\cdot d+random\cdot e \cdot \phi(n)\\ \because a^{\phi(p)}\equiv 1\pmod p,欧拉定理\\ \therefore c^{hint}\equiv c^{2\cdot d}\cdot c^{random\cdot e\cdot \phi(n)}\equiv c^{2\cdot d}\pmod n \\ hint=2⋅d+random⋅e⋅ϕ(n)∵aϕ(p)≡1(modp),欧拉定理∴chint≡c2⋅d⋅crandom⋅e⋅ϕ(n)≡c2⋅d(modn)
所以mac = gmpy2.iroot(pow(c,hint,n),2)[0];需要知道mac的作用

再来看到之后提供三种选择,set keyguess numget flag

分别查看函数内容

    def set_key(self, rec):if self.mac == int(rec[8:]):self.r = self.counter

似乎是判定输入的内容是否和mac一致,若一致,则进行一个赋值操作,看到是将counter赋值给r

寻找counter的生成

def init():...counter = random.randint(0, 2**128)

就是一个随机数,而self.r初始值是-1

为什么要进行赋值操作呢,可以在encrypt()函数中看到

    def encrypt(self, msg):msg = self.padding(msg)if self.r != -1:self.r += 1aes = AES.new(self.key, AES.MODE_CTR, counter = Counter.new(128, initial_value=self.r))return aes.encrypt(msg)else:return msg

如果self.r的值没有改变的话,也就是说没有进行set key操作的话,AES_CRT加密过程就不会生效;加密不生效就可以直接拿flag吗,再来看到get flag操作

    def get_flag(self, rec):assert self.r != -1if self.score ==  5:self.send(flag, enc=False)else:self.send(os.urandom(32) +  flag)

有个assert self.r != -1,意味着必须self.r被随机生成的counter赋值才能进行get flag,否则会直接assert报错

三种选择中还剩下一个guess num操作

    def guess_num(self, rec):num = random.randint(0, 2**128)if num == int(rec[10:]):self.send(b'right')self.score += 1else:self.send(b'wrong')

如果猜对随即生成的数字,则使得self.score += 1,而self.score的作用是在get flag

        if self.score ==  5:self.send(flag, enc=False)

意味着连续猜中5次则能使得get flag操作直接返回flag

但是细看一下,首先随机数生成的范围很大,不可能爆破;只能猜测一次,否则会在6次选择中浪费一次机会;最重要的是,6次选择中有一次机会必须用来set key(否则无法进行get flag操作),有一次机会用来get flag,只剩下4次机会,而我们需要连续猜中5次随机数;所以guess num操作在现在看来就是一个幌子


那么关于能取得flag的机会只有落在encrypt()函数上,是AES_CTR加密,关于这个模式

  • 加密过程

如图所示,引入了一个计数器CTR,使得在第二步中与各个明文分组进行异或的加密流不同,因为每加密一个明文分组(16 bytes),计数器CTR就会自动+1

题目当中是如何生成CTR

from Crypto.Util import Counter
aes = AES.new(self.key, AES.MODE_CTR, counter = Counter.new(128, initial_value=self.r))

看得出来是Counter.new(128, initial_value=self.r))initial_value变量在设置CTR的初始值

CTR所表示的值也不是简单的0001,而是一组由随机数nonce分组序号组成的初始值

每加密一个明文分组,CTR的分组序号则会+1,使得进行加密操作之后的加密流与之前的加密流完全不同,达到与各个明文分组进行异或的字节串互不相同

PS:这里以及之后提到的加密流都是
这一部分的结果;并且加密操作特指CTR生成后马上进行的加密器的操作

  • 解密过程

可以发现就是重置计数器CTR使其在对应的密文分组上产生与加密时一样的CTR,再将其进行加密操作,生成与加密过程中一样的加密流,与密文分组异或即得到明文分组


分析完AES_CTR的加解密过程,发现实际上加解密过程都可以分为两大块,CTR加密异或

再回到题目脚本,这里容易发现,在每次服务器脚本send的时候,函数中很多地方都是send(x,enc = False),可以找到脚本中自己定义的send函数

    def send(self, msg, enc=True):print(msg, end= '   ')if enc:msg = self.encrypt(msg)print(msg, self.r)self.request.sendall(binascii.hexlify(msg) + b'\n')

作用就是写出enc = Falsesend函数里面,会直接输出内容,也就是正常交互过程中的send函数;而当enc = True,或者说send函数里面没有对enc进行再赋值,那么输出的内容会进行encrypt()加密,也就是AES_CTR加密

仔细观察可以发现

        self.send(b'something wrong, check your input')
# 以及if num == int(rec[10:]):self.send(b'right')self.score += 1else:self.send(b'wrong')

这里的send函数返回的实际上不是明文rightsomething wrong...;而是AES_CTR加密后的密文(也可以在本地测试debug的时候发现这个特征)

不可能无缘无故地把这些返回内容设置为加密之后再返回,所以这里一定是突破点

现在我们可以得到一对明密文AES_CTR加密后的flag;加密密钥key未知也无法知道,显然不能通过正常求密钥key来解密

总共给予了6次选择的机会,那如果我们多得到几组明密文,有什么用呢

AES_CTR加密中,如果我们已知明文以及对应的密文,将两者异或即可得到加密流,因为加密过程中
m⊕enc_stream=cm\oplus enc\_stream=c m⊕enc_stream=c
而加密流是由不同的CTR生成的,如果我们多收集一些加密流拼接在一起,再用来和加密后的flag进行异或,不就模拟了AES_CTR模式的解密过程吗

那么最终的问题就是我们需要哪些加密流,也就是说哪些加密流是加密flag的时候真正使用的

加密流当然是越长越好,所以我们使用something wrong...作为明密文组求得加密流,默认经过padding函数后明文长度为48

正常情况下(本题不是这样的正常情况,之后会提到,也是关键点之一)是得到的加密流应该是由CTRCTR+1CTR+2生成的,那么很可能不够长足以使得与加密之后的flag异或可以得到完整的flag(因为flag加密过程中是作为padding(os.urandom(32)+flag)进行加密的,所以密文会比较长)

那么现在有一个疑惑就是如果连续加密两次及以上的话,counter会不会自动继续+1,使得我们确实可以将对不同的加密过程(虽然明文确实是相同的)中生成的多个加密流按照CTR+i的顺序拼接在一起,作为一整个加密流

实践一下

>>> from Crypto.Cipher import AES
>>> from Crypto.Util import Counter
>>> from pwn import *
>>> key = b'\x01' * 16
>>> msg = b"flag1111111111111111111111111111111111111111111111111111111"
>>> t = Counter.new(128, initial_value=123)
>>> aes = AES.new(key, AES.MODE_CTR, counter = t)
>>> enc1 = aes.encrypt(msg)
>>> enc1
b"0=J\xe6\x87\xec\x07r\xf1{L\x94\xf7\xf2\xfcp|-\xf0\xca\xba\xe4:\x89\x7f\rI\xb5\x84\xb8\x00\xe2%(\x02o\xa5\x8c\xa3\x93\x18'\xb0\xa2\xe2\xd4]\xb1*\xb5\x8fH7\xd8\xfa\x8f\x08\xe7X"
>>> enc2 = aes.encrypt(msg)
>>> enc2
b'\x97\xf5\xe3_\xdc\xb3\x1c\x11\x1fM\n\x16\x06}:hvU\x1d\x93"\xf6\x89\xc3\x05\x94\x8b>6ha\xce\'\x9f\xb5$\x07Hm\xa5\xd1]y)P\xff\xd3e\xea\x7f\xa9\xb3\xe9\xcd\x88\x97>\x8d\xad'

实践证明确实可以将不同加密过程中得到的加密流拼接在一起,但是如何确保对flag加密的加密流和我们求得的加密流一致呢,那就是重复使用set key操作

大致重演一下重复使用set key的作用

>>> from Crypto.Cipher import AES
>>> from Crypto.Util import Counter
>>> from pwn import *
>>> key = b'\x01' * 16
>>> msg = b"flag1111111111111111111111111111111111111111111111111111111"
>>> t = Counter.new(128, initial_value=123)
>>> aes = AES.new(key, AES.MODE_CTR, counter = t)
>>> enc1 = aes.encrypt(msg)
>>> enc2 = aes.encrypt(msg)
>>> t = Counter.new(128, initial_value=123)
>>> aes = AES.new(key, AES.MODE_CTR, counter = t)
>>> enc3 = aes.encrypt(msg)
>>> enc4 = aes.encrypt(msg)
>>> assert xor(enc1,msg) == xor(enc3,msg)
>>> assert xor(enc2,msg) == xor(enc4,msg)
>>>

意思就是当我们重复对counter进行赋相同的值时,整个CTR初始值会被刷新

那么我们是不是只需要两到三个something wrong...明密文对得到的加密流拼接在一起与flag进行异或就好了呢

实践发现只能得到一半的flag,刚好就是前48位的padding(os.urandom(32)+flag);之后的加密流为什么不是正确对应的呢

发现作者在self.raes上面动了手脚,在每次加密整个明文之前self.r会自己首先+1,也就是CTR + 1;并且在每次加密整个明文之前都会重新定义一遍 aes = AES.new(self.key, AES.MODE_CTR, counter = Counter.new(128, initial_value=self.r)),这就意味着对两次明文(不是明文分组)加密中所使用两个的CTR生成的加密流中,上一个明文的最后一组明文分组使用的CTR + i,下一个明文的第一组明文分组的不是CTR + i + 1

    def encrypt(self, msg):msg = self.padding(msg)if self.r != -1:self.r += 1aes = AES.new(self.key, AES.MODE_CTR, counter = Counter.new(128, initial_value=self.r))return aes.encrypt(msg)else:return msg

所以加密过程中的加密流实际上是CTR + 1CTR + 2CTR + 3等等通过加密操作生成的

并且上一个明文(不是明文分组)加密所涉及的CTR初始值是CTR + 1,下一个明文加密所涉及的CTR初始值是CTR + 2(因为每次加密整个明文之前self.r += 1

所以
m1:CTR+1CTR+2CTR+3m2:CTR+2CTR+3CTR+4m3:CTR+3CTR+4CTR+5m_1 : CTR+1~~CTR+2~~CTR+3\\ m_2:CTR+2~~CTR+3~~CTR+4\\ m_3:CTR+3~~CTR+4~~CTR+5\\ m1​:CTR+1  CTR+2  CTR+3m2​:CTR+2  CTR+3  CTR+4m3​:CTR+3  CTR+4  CTR+5
其中mim_imi​是somgthing srong...的明文,意思是该明文在前后的加密过程中所使用的加密流是由以上CTR + i生成的

为了使得flag使用的是已知的加密流,我们使用set key操作把CTR刷新,使得
flag:CTR+1CTR+2CTR+3CTR+4CTR+5flag:CTR+1~~CTR+2~~CTR+3~~CTR+4~~CTR+5 flag:CTR+1  CTR+2  CTR+3  CTR+4  CTR+5
那么对mim_imi​进行对应的截取即可

SolvingcodeSolving~codeSolving code

from pwn import *
from Crypto.Util.number import *
import gmpy2
import hashlib
import string
import random
import binasciicontext.log_level='debug'
io = remote("192.168.109.129","9998")
io.recvuntil(b"+")
tmp = io.recvuntil(b"\n")
part_proof = tmp.split(b") == ")[0]
result = tmp.split(b") == ")[1][:-1]
table = string.ascii_letters + string.digits
while True:XXXX = ("".join(random.sample(table,4))).encode()if hashlib.sha256(XXXX + part_proof).hexdigest() == result.decode():io.recvuntil(b"\n")io.sendline(XXXX)breakn = int(binascii.unhexlify(io.recvline()[:-1]))
hint = int(binascii.unhexlify(io.recvline()[:-1]))
c =  int(binascii.unhexlify(io.recvline()[:-1]))
mac = gmpy2.iroot(pow(c,hint,n) % n,2)[0]io.sendline(b"set key:" + str(mac).encode())
enc = []
for _ in range(3):io.sendline(b"I_want_flag")time.sleep(0.5)tmp = io.recvline()[:-1].decode()if _ != 2:enc.append(long_to_bytes(int(str(tmp),16))[:16])else:enc.append(long_to_bytes(int(str(tmp),16)))
# print(enc)
io.sendline(b"set key:" + str(mac).encode())
time.sleep(0.5)
io.sendline(b"get flag")
time.sleep(0.5)
tmp = io.recvline()
enc_flag = binascii.unhexlify(tmp[:-1])msg = b'something wrong, check your input'
msg = msg + chr((16 - len(msg)%16)).encode() * (16 - len(msg)%16)
key_stream = b""
for i in enc:key_stream += xor(i,msg[:len(i)])
print(xor(enc_flag,key_stream[:len(enc_flag)]))

ConclusionConclusionConclusion

关于AES的深入一点的题目都是交互式的,正常情况不会要求求密钥key,而是利用xor的特性以及加密器(相当于黑盒加密)来从服务器脚本处骗取可以求得flag的数值

关于交互式的题目没有头绪的时候更倾向于直接与服务端进行交互查看debug返回,而不单是查看服务器所使用的脚本

Crypto的交互式题目需要注意服务端返回的内容是什么类型的,很可能脚本会对十进制数值转字节再转十六进制,可能要进行一定的数据处理

CTF Crypto中涉及的AES题目相关推荐

  1. CTF CRYPTO从零开始的RSA2

    题源:BUUCTF RSA2 题目 e = 65537 n = 24825400785152624117772152669890180298583276617622160961225887737162 ...

  2. 基于Python实现的CTF Crypto加密解密工具

    纯小白,记录一下自己小学期内做的项目.基于Python实现一个能够对凯撒密码.维吉尼亚密码.栅栏密码.摩斯密码.Base64编码.Ascii编码.AES.DES.RSA.RC4的加密解密以及维吉尼亚密 ...

  3. 中国航信官笔试计算机基础,中国航信笔试题目

    中国航信笔试题目 收到中国航信的offer,立刻就据了,虽然很后悔,但是只能这样了,看来跟中航信无缘了.提供经验攒人品. 笔试挺简单的,就是50分的公务员考题和50分的技术类题目,全是选择题,公务员的 ...

  4. 中科大计算机复试题目,08中科大11系(计算机)复试

    08 08中科大11系(计算机)复试 今年成绩出来的比较早,所以复试的也比较早29复试,一般都周末复试,31号成绩就出来了,然后给三天时间联系导师签收,一般都能联系上,只是好坏的差别,交表,体检(过场 ...

  5. 【2021.12.25】ctf逆向中常见加密算法和编码识别

    [2021.12.25]ctf逆向中常见加密算法和编码识别(含exe及wp) 文章目录 [2021.12.25]ctf逆向中常见加密算法和编码识别(含exe及wp) 0.前言 1.基础加密手法 2.b ...

  6. BugKu CTF(Crypto篇)---where is flag 5

    BugKu CTF(Crypto篇)-where is flag 5 文章目录 BugKu CTF(Crypto篇)---where is flag 5 题目描述 首先一看就是base64 解码内容好 ...

  7. MPLS转发过程中涉及的相关概念—Vecloud微云

    MPLS术语 • 标签(Label):是一个短而定长的.只具有本地意义的标识符,用于唯一标识一个分组所属的FEC.在某些情况下,例如要进行负载分担,对应一个FEC可能会有多个入标签,但是一台设备上,一 ...

  8. TEE_ObjectHandle在Crypto中的使用

    Storage API和Crypto API中都会依赖TEE_ObjectHandle,该结构体可用于存储: Secret key.Public keys.key-paris Persistent d ...

  9. C/C++中涉及存储方式的关键字:auto,static,register,extern2009-01-22 11:23auto关键字:

    C/C++中涉及存储方式的关键字:auto,static,register,extern 2009-01-22 11:23 auto关键字: auto对象和变量被存储在栈中,它的生命周期仅存在于它的声 ...

最新文章

  1. 100%抄袭!ICLR 2022投稿竟公然剽窃两篇顶会,程序主席放「实锤」严词拒稿
  2. js进阶 11-16 jquery如何查找元素的父亲、祖先和子代、后代
  3. Vue 中的compile操作方式
  4. 关于IOCP乱序的探讨
  5. Go 如何利用 Linux 内核的负载均衡能力?
  6. html 使用百度搜索,百度搜索uzer,进入主页
  7. Windows Live Messenger 2011,离线安装、多开、去广告……
  8. 3月第1周网络安全报告:发现放马站点域名仍为162个
  9. win10java配置环境变量msi_Windows10+eclipse+hadoop2.7.1环境配置+wordcount-折腾笔记
  10. mysql追溯历史性能问题_【踩坑記錄】記一次MySQL主從復制延遲的坑
  11. 京东物流研发岗位会背景调查吗_【秋招资讯】京东健康于港交所主板上市 | 京东健康2021校园招聘火热进行中!...
  12. 常用的DIV+CSS网站布局的基本框架结构-完整版
  13. 北大青鸟 ASP.NET视频教程批量下载
  14. MPU9250调试笔记(融合磁力计计算Yaw)
  15. 【机器学习-周志华】学习笔记-第十章
  16. 韩立刚计算机网络——第七章:Internet 上面的音频和视频
  17. “WFCF”数据安全及隐私保护声明
  18. Au:剪辑的基础操作
  19. 用计算机弹奏体面6,抖音计算器乐谱汇总 抖音计算器按出的音乐乐谱有哪些
  20. 【华为OD机试 2023最新 】 查找充电设备组合(C++ 100%)

热门文章

  1. 关于Python打包文件的步骤
  2. 事实、循环、条件判断
  3. 优矿量化实验室———转自知乎
  4. XML Publisher 模板开发小技巧
  5. [JavaWeb-04]HTML和CSS
  6. 【心田花开】三年级语文阅读《独立宣言》赏析
  7. vs2017 pdo mysql_在VS2017上使用Objectarx 2019向导
  8. Linux文件和目录管理(1)
  9. 洛谷P1710 地铁涨价
  10. 判断xarray中小于0的位置坐标