CTF crypto 2021-10-4 record

Basic knowledge

CBC flip attack

Introduction:

When one of our values C is obtained by the exclusive or of A and B
C = A XOR B
that
A XOR B XOR C is obviously = 0
When we know B and C, it's easy to get the value of A
A = B XOR C
Therefore, A XOR B XOR C is equal to 0. With this formula, we can set our own value at the end of XOR operation to change it.

Cbc-aes: encrypt (plaintext ^ IV)

For example, the CBC first block (16 bits) is known

  1. Plaintext
  2. ciphertext

Want to directly modify the 11th character 0 - > 1

Given that the 11th character is 1, we can directly encrypt the encrypted character
Ciphertext [10] = ciphertext [10] xor '0' xor '1'
Can be modified

BASE64 basic encryption principle

A=>QQ==
a=>YQ==

97 = 110 0001 = > shift 2 bits right to 6 bits = > 1 1000 = = > look up table (24)Y
The last remaining 01 binary shifts four bits to the right 010000 = > look up table (16)Q
Fill (= =)
Result YQ==

  1. In BASE64, it is usually encoded as a group starting with 3 characters (ASCII)
  2. In the conversion to binary, it will be grouped in 6-bit binary (i.e. a group: 4 * 6 = 24)
  3. In empty data, use = as the padding character
a
1 1000010000
YQ==



SpecialLCG

analysis

MSSCTF 2021 preliminary question learning

note
from Crypto.Util.number.inverse = gmpy2.invert inverse
Bin (numeric value), the binary value of the numeric value can be obtained

  • Don't think about learning and using now. In the long run, understand slowly and don't be persistent

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

analysis

a. If b and m are known, LCG can be solved by finding seed

Linear recursive expression:
self._state = (self._key['a'] * self._state + self._key['b']) % self._key['m']

143893630627599013207723094044959571968=
(107763262682494809191803026213015101802*x1+153582801876235638173762045261195852087)%226649634126248141841388712969771891297

Follow the topic to understand (LCG ALGORITHM = = > Euclidean expansion algorithm)
https://blog.csdn.net/superprintf/article/details/108964563

According to the formula, restore seed (calculate the inverse element of a and N, and then set the formula to calculate seed)

Formula for restoring 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#formula 
print(seed)# Seed: 222435278211805578675570877319055662119 

The first seed can be solved. What is the relationship between related seeds? (the next seed has nothing to do with the previous seed)

[ACTF freshman competition 2020] crypto AES

Title Description

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'

analysis

Be bold to guess and try

xor is a reducible algorithm

a^b=c
c^a=b

CBC feature: IV = the previous ciphertext, and the plaintext needs XOR IV before encryption

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'
    #The 16 bit plaintext key is leaked, and the last 16 xor is the reducible algorithm 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 feature IV = previous ciphertext
    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

Title Description

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()
 

analysis

Title of MINIL

AES (plaintext ^ iv)=cipher

The second chunk enc (plaintext ^ previous ciphertext)

There is no doubt that the program logic needs to be exploded, and dec only returns the last digit

Please see the overall logic of the program before writing the code~
Don't [[oversimplify]], and don't think too complicated

It's over!

fact:

  1. IV it's different every time
  2. Decryption only returns the last 1-bit base64 encoded character

It's a little difficult. Give up temporarily

EXP

EXP chose the idea of brute force cracking
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())

Additional knowledge

[[Padding-Oracle]]
Therefore, the key concept behind Padding Oracle Attack is Padding during encryption / decryption. Plaintext information can be of any length, but block encryption algorithm requires that all information be composed of a certain number of data blocks. In order to meet this demand, the plaintext needs to be filled, so that it can be divided into complete data blocks.

A variety of filling rules can be used for encryption, but one of the most common filling methods is the rules defined in the PKCS#5 standard. The filling method of PCKS#5 is: the last data block of plaintext contains N bytes of filling data (N depends on the data length of the last block of plaintext). The following figure shows some examples of words with different lengths (FIG, BANANA, AVOCADO, PLANTAIN, passionfront) and their results filled with PKCs #5 (each data block is 8 bytes long).
--------
What is PKCS#5?
PKCS#5 is a filling standard designed by RSA information security company.
For the PKCS#5 standard, if several digits are missing, fill in the number.
For example, in the above example, we have three vacancies, so we should fill in all the vacancies. In this way, the content of the second group becomes' bc333 '.

One 0x01 (0x01)
Two 0x02 (0x02, 0x02)
Three 0x03 (0x03, 0x03, 0x03)
Four 0x04 (0x04, 0x04, 0x04, 0x04)
......
If the end of the last decrypted data block is not these legal byte sequences, most encryption / decryption programs will throw a fill exception. This exception is particularly critical for attackers. It is the basis of Padding Oracle Attack.

Tags: Algorithm cryptology linear algebra BUUCTF Crypto

Posted on Mon, 04 Oct 2021 16:36:34 -0400 by gitosh