kcsc-club / ctfs

repository for kscs-ctfs
8 stars 1 forks source link

Kinda AESthetic - Crypto - PragyanCTF 2022 #20

Closed LilThawg29 closed 2 years ago

LilThawg29 commented 2 years ago

Kinda_AESthetic

Source chall : Kinda_AESthetic.py

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import os, sys
import string
import random

KEY = os.urandom(16)
IV = os.urandom(16)
flag = REDACTED

def encrypt(msg):
    msg = pad(msg, 16)
    cipher = AES.new(KEY, AES.MODE_CBC, IV)
    encrypted = cipher.encrypt(msg)
    encrypted = encrypted.hex()
    msg = IV.hex() + encrypted
    return msg

def decrypt(msg, iv):
    cipher = AES.new(KEY, AES.MODE_CBC, iv)
    decrypted = unpad(cipher.decrypt(msg), 16).decode()
    return decrypted

def parse(inp):
    iv = bytes.fromhex(inp[:32])
    msg = bytes.fromhex(inp[32:])
    msg = decrypt(msg,iv)
    return msg

chars = string.printable[:-5]
x = random.randint(5, 15)
passwd = ''.join(random.choice(chars) for _ in range(x))

secrets = {
    'abrac': 'iloveyou',
    'sudo': REDACTED, # ;)
    'gg': passwd,
    'yeager': 'ironman'
}

x = random.randint(5, 13)
token = ''.join(random.choice(chars) for _ in range(x))

def lookup(inp):
    try:
        cipher = AES.new(KEY, AES.MODE_CBC, inp[:16])
        inp = unpad(cipher.decrypt(inp[16:]), 16)
    except:
        return 'idek'
    try:
        name = inp.decode()
        assert name[:len(token)] == token
        name = name[len(token):]
        return secrets[name]
    except:
        return 'idk'

print('Here is an encrypted token for you:')
print(encrypt(token.encode()))

while True:
    try:
        inp = input()
        try:
            user = parse(inp)
            assert user == 'gg'
            print('Welcome gg! Enter your secret passphrase:')
            inp = input()
            password = parse(inp)
            if password == secrets['gg']:
                print(flag)
                sys.exit(0)
            else:
                print(r'p_ctf{potato}')
        except:
            inp = bytes.fromhex(inp)
            print(lookup(inp))
    except:
        print('')
        sys.exit(0)

Hmm mới vào thấy bài sử dụng CBC và có pad, unpad là mình đã đoán ngay là có Padding Oracle Attack rồi

image

Vậy là padding đúng sẽ return ra idk và padding sai sẽ return idek

Nhưng theo bản năng mình vẫn đi tìm chỗ có flag trước :

try:
        inp = input()
        try:
            user = parse(inp)
            assert user == 'gg'
def parse(inp):
    iv = bytes.fromhex(inp[:32])
    msg = bytes.fromhex(inp[32:])
    msg = decrypt(msg,iv)
    return msg

Nghĩ rằng làm gì thì làm, đầu tiên phải làm user = gg đã, thấy rằng có IV thì ta dễ dàng flip bit để return gg nhưng sau một hồi đặt bút mới nhận ra rằng P_new = gg rồi P_old làm gì có ? =)))

image

Lần này mình tự lừa mình ạ :( Thế là quay lại xem cái Token cho ở đầu xem như thế nào.

Ta thấy rằng nếu chỉ có token như này, chắc chắn sẽ vào đây

except:
            inp = bytes.fromhex(inp)
            print(lookup(inp))

mà khi vào lookup thì như đã đề cập ở trên sử dụng Padding Oracle Attack ta sẽ recover lại được Token tức là P_old mà ta cần để tý flip bit đó xD.

Ok, xong rồi tiếp theo như nào ? Đi flip bit để user = gg luôn à ? Không ta để ý rằng sau khi user = gg ta phải nhập password nữa, vậy thì chi bằng ta tìm password trước rồi tý nhập 1 thể là xong.

 try:
        inp = input()
        try:
            user = parse(inp)
            assert user == 'gg'
            print('Welcome gg! Enter your secret passphrase:')
            inp = input()
            password = parse(inp)
            if password == secrets['gg']:
                print(flag)

Vậy việc tìm password như thế nào ?

def lookup(inp):
    try:
        cipher = AES.new(KEY, AES.MODE_CBC, inp[:16])
        inp = unpad(cipher.decrypt(inp[16:]), 16)
    except:
        return 'idek'
    try:
        name = inp.decode()
        assert name[:len(token)] == token
        name = name[len(token):]
        return secrets[name]
    except:
        return 'idk'

Ta thấy nếu inp = E(token||gg, k) thì ta sẽ nhận lại được secrets['gg'], vậy còn chờ gì nữa mà không flip bit ? =))

Có được password rồi thì ta tiếp tục flip bit với inp = E(pad('gg',16), k) là có thể vào trong được rồi, sau khi vào thì nhập mật khẩu là lĩnh được flag thôi xD.

À mà quên đó cái password nó còn qua parse 1 lần nữa nên ta phải gửi inp = E(pad(password,16), k) nhé ! Má thảo nào làm mãi không ra (Mệt quá 5h hơn rồi T.T)

from pwn import *
from Crypto.Util.Padding import pad, unpad

def bit_flip_IV(IV_Old, P_Old, P_New):
    IV_New = xor(xor(P_Old,P_New),IV_Old)
    return IV_New

def padding_oracle_attack(block1, block2):
    pt = [ord('.')]*16
    for i in range(15,-1,-1):
        fake_block1 = list(block1)

        for j in range(15,i,-1):
            fake_block1[j] = pt[j] ^ block1[j] ^ (16-i)

        for j in range(256):
            if j == block1[i] and i == 15:
                continue

            fake_block1[i] = j
            ct = (bytes(fake_block1) + block2).hex()
            r.sendline(ct.encode())
            res = r.recvline().strip().decode()
            if res == 'idk':
                pt[i] = j ^ (16-i) ^ block1[i]
                print(bytes(pt))
                break
    return bytes(pt)

r = remote('crypto.challs.pragyanctf.tech', 5001)
r.recvuntil(b':\n')
ct = r.recvline().strip().decode()
ct = bytes.fromhex(ct)
iv, block2 = ct[:16], ct[16:]
token = padding_oracle_attack(iv, block2)
print(f'{token = }')

# lookup : token||gg
s = unpad(token,16)+b'gg'
req = (bit_flip_IV(iv,token,pad(s,16)) + block2).hex()
r.sendline(req.encode())
password = r.recvline().strip()
print(f'{password = }')

#bypass user == 'gg'
s = b'gg'
req = (bit_flip_IV(iv,token,pad(s,16)) + block2).hex()
r.sendline(req.encode())
print(r.recvline())
#bypass password
req = (bit_flip_IV(iv,token,pad(password,16)) + block2).hex()
r.sendline(req.encode())
r.interactive()

Output :

b'...............\x03'
b'..............\x03\x03'
b'.............\x03\x03\x03'
b'............E\x03\x03\x03'
b'...........PE\x03\x03\x03'
b'..........RPE\x03\x03\x03'
b'.........2RPE\x03\x03\x03'
b"........'2RPE\x03\x03\x03"
b".......b'2RPE\x03\x03\x03"
b"......Kb'2RPE\x03\x03\x03"
b".....KKb'2RPE\x03\x03\x03"
b"....,KKb'2RPE\x03\x03\x03"
b"...O,KKb'2RPE\x03\x03\x03"
b"..WO,KKb'2RPE\x03\x03\x03"
b".'WO,KKb'2RPE\x03\x03\x03"
b"%'WO,KKb'2RPE\x03\x03\x03"
token = b"%'WO,KKb'2RPE\x03\x03\x03"
password = b'4^<iVqa?ERyb'
b'Welcome GG! Enter your secret passphrase:\n'
[*] Switching to interactive mode
p_ctf{4_l1ttl3_p4d4tt4ck_h3r3_&4_l1ttl3_x0r_THERE}
idk
[*] Got EOF while reading in interactive