Insomni-hack Teaser-Unstringify
On nous fournit un fichier : unstringify
unstringify: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=cd0484e35a7fab1db8a1834a3a3a52574c089c6a, for GNU/Linux 4.4.0, not stripped
Analyse du main
un premier coup d’oeil révele que le binaire utilise beaucoup de l’inline ASM pour utiliser les registres AVX (principalement YMM0, YMM1) qui sont des registres 256bits (32octets) ainsi que des registres XMM (128bit).
La fonction generate_key() est une fonction qui genère une clé de 32 octets à l’aide de permutation et se base sur des flags (rabbit hole) hardcodé dans la mémoire.
Cette fonction retourne toujours une clé fixe de 32 octets
Analyse du round AES
Juste après generatekey, un code asm inline est executé.
vmovdqa ymm1, ymm0
vpermq ymm0, ymmword ptr [rsp+60h+r1], 1Bh
vaesenc ymm0, ymm0, ymm1
vpcmpeqq ymm0, ymm0, ymmword ptr cs:stored_flag
vpmovmskb eax, ymm0
vmovdqa ymm1, ymm0 copie les 32octets de YMM0 dans YMM1 (la clé retourné par generatekey).
vpermq ymm0, ymmword ptr [rsp+60h+r1], 1Bh (permute 32 octets de r1 avec 0x1B dans YMM0, r1 est l’input de l’utilisateur)
vaesenc ymm0, ymm0, ymm1 fait un round AES avec l’instruction vaesenc et met le résultat dans YMM0
YMM0 étant l’input user (input AES, YMM1 la clé AES)
le détail de l’instruction :
(KL,VL) = (1,128), (2,256)
FOR I := 0 to KL-1:
STATE := SRC1.xmm[i]
RoundKey := SRC2.xmm[i]
STATE := ShiftRows( STATE )
STATE := SubBytes( STATE )
STATE := MixColumns( STATE )
DEST.xmm[i] := STATE XOR RoundKey
DEST[MAXVL-1:VL] := 0
S’en suit un check entre YMM0 (résultat du round aes et cs:stored_flag qui est le ciphertext chiffré en mémoire).
Resolution
je me suis trompé de direction au début, je pensais que vaesdec était l’instruction inverse de vaesenc. Mais non.
Un shellcode aurait pu être utiliser :
section .data
aes_key dq 1.0767242e16, 1.5942744e-37, 2.736913e-38, 2.324516e18, 1.1416161e-25, 5.978845e-31, 0.00074127543, 1.04546e-29
stored_flag dq -0.14042784, 8.5904241e-27, 1.0001436e-9, 6.5840891e-22, 1.1724008e35, 7.5752593e-35, -0.12882388, -1942.2239
section .text
global _start
_start:
vmovdqa ymm1, [stored_flag] ; y1 = ciphertext
vmovdqa ymm0,[aes_key] ; Y0 = aeskey
vaesdec ymm1, ymm1, ymm0
int3
;exit
mov rax, 60
xor rdi, rdi
syscall
(J’ai mit les valeurs en float, mais un db aurait suffit avec v2_int128 dans gdb via info register ymm0)
Ce script gdb permet de break et de récuperer automatiquement la clé également (pour le debug)
import gdb
import binascii
bp = "*0x0000555555555104"
bp_aesencrypted = "*0x555555555114"
class BP128(gdb.Breakpoint):
def __init__(self, bp):
super().__init__(bp)
def stop(self):
ymm0_value = gdb.parse_and_eval('$ymm0')
raw_bytes = ymm0_value["v32_int8"]
print(raw_bytes) #dump de la clé AES
return True
class BPAES(gdb.Breakpoint):
def __init__(self, bp):
super().__init__(bp)
def stop(self):
ymm0_value = gdb.parse_and_eval('$ymm0')
ymm0_128bitcast_1 = ymm0_value["v2_int128"][0]
ymm0_128bitcast_2 = ymm0_value["v2_int128"][1]
hex_ymm0 =f"{ymm0_128bitcast_1}"[2:]
hex_ymm0_2 =f"{ymm0_128bitcast_2}"[2:]
if len(hex_ymm0) % 2 != 0:
hex_ymm0 = "0" + hex_ymm0
if len(hex_ymm0_2) % 2 != 0:
hex_ymm0_2 = "0" + hex_ymm0_2
print("encrypted user_input with aes key:", hex_ymm0 + hex_ymm0_2) #dump de l'user data chiffré après AES
return False
testflag = "A"*32
gdb.execute("d") #remove old bp
bp128 = BP128(bp)
bp_aes = BPAES(bp_aesencrypted)
gdb.execute(f"r <<< {testflag}")
Pour résoudre le chall, on peut prendre une implementation AES (Merci Mister7f pour le lien) sur internet et faire l’opération inverse de aesenc, c’est à dire
ciphertext ^ RoundKey
inv_mix_columns(plain_state)
inv_sub_bytes(plain_state)
inv_shift_rows(plain_state)
ce qui donne:
#!/usr/bin/env python3
import binascii
s_box = (
0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16,
)
inv_s_box = (
0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,
0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,
0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,
0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,
0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,
0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,
0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,
0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,
0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,
0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,
0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D,
)
def sub_bytes(s):
for i in range(4):
for j in range(4):
s[i][j] = s_box[s[i][j]]
def inv_sub_bytes(s):
for i in range(4):
for j in range(4):
s[i][j] = inv_s_box[s[i][j]]
def shift_rows(s):
s[0][1], s[1][1], s[2][1], s[3][1] = s[1][1], s[2][1], s[3][1], s[0][1]
s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2]
s[0][3], s[1][3], s[2][3], s[3][3] = s[3][3], s[0][3], s[1][3], s[2][3]
def inv_shift_rows(s):
s[0][1], s[1][1], s[2][1], s[3][1] = s[3][1], s[0][1], s[1][1], s[2][1]
s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2]
s[0][3], s[1][3], s[2][3], s[3][3] = s[1][3], s[2][3], s[3][3], s[0][3]
def add_round_key(s, k):
for i in range(4):
for j in range(4):
s[i][j] ^= k[i][j]
# learned from https://web.archive.org/web/20100626212235/http://cs.ucsb.edu/~koc/cs178/projects/JT/aes.c
xtime = lambda a: (((a << 1) ^ 0x1B) & 0xFF) if (a & 0x80) else (a << 1)
def mix_single_column(a):
# see Sec 4.1.2 in The Design of Rijndael
t = a[0] ^ a[1] ^ a[2] ^ a[3]
u = a[0]
a[0] ^= t ^ xtime(a[0] ^ a[1])
a[1] ^= t ^ xtime(a[1] ^ a[2])
a[2] ^= t ^ xtime(a[2] ^ a[3])
a[3] ^= t ^ xtime(a[3] ^ u)
def mix_columns(s):
for i in range(4):
mix_single_column(s[i])
def inv_mix_columns(s):
# see Sec 4.1.3 in The Design of Rijndael
for i in range(4):
u = xtime(xtime(s[i][0] ^ s[i][2]))
v = xtime(xtime(s[i][1] ^ s[i][3]))
s[i][0] ^= u
s[i][1] ^= v
s[i][2] ^= u
s[i][3] ^= v
mix_columns(s)
r_con = (
0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40,
0x80, 0x1B, 0x36, 0x6C, 0xD8, 0xAB, 0x4D, 0x9A,
0x2F, 0x5E, 0xBC, 0x63, 0xC6, 0x97, 0x35, 0x6A,
0xD4, 0xB3, 0x7D, 0xFA, 0xEF, 0xC5, 0x91, 0x39,
)
def bytes2matrix(text):
""" Converts a 16-byte array into a 4x4 matrix. """
return [list(text[i:i+4]) for i in range(0, len(text), 4)]
def matrix2bytes(matrix):
""" Converts a 4x4 matrix into a 16-byte array. """
return bytes(sum(matrix, []))
def xor_bytes(a, b):
""" Returns a new byte array with the elements xor'ed. """
return bytes(i^j for i, j in zip(a, b))
def inc_bytes(a):
""" Returns a new byte array with the value increment by 1 """
out = list(a)
for i in reversed(range(len(out))):
if out[i] == 0xFF:
out[i] = 0
else:
out[i] += 1
break
return bytes(out)
def pad(plaintext):
"""
Pads the given plaintext with PKCS#7 padding to a multiple of 16 bytes.
Note that if the plaintext size is a multiple of 16,
a whole block will be added.
"""
padding_len = 16 - (len(plaintext) % 16)
padding = bytes([padding_len] * padding_len)
return plaintext + padding
def unpad(plaintext):
"""
Removes a PKCS#7 padding, returning the unpadded text and ensuring the
padding was correct.
"""
padding_len = plaintext[-1]
assert padding_len > 0
message, padding = plaintext[:-padding_len], plaintext[-padding_len:]
assert all(p == padding_len for p in padding)
return message
def split_blocks(message, block_size=16, require_padding=True):
assert len(message) % block_size == 0 or not require_padding
return [message[i:i+16] for i in range(0, len(message), block_size)]
class AES:
"""
Class for AES-128 encryption with CBC mode and PKCS#7.
This is a raw implementation of AES, without key stretching or IV
management. Unless you need that, please use `encrypt` and `decrypt`.
"""
rounds_by_key_size = {16: 10, 24: 12, 32: 14}
def __init__(self, master_key):
"""
Initializes the object with a given key.
"""
assert len(master_key) in AES.rounds_by_key_size
#self.n_rounds = AES.rounds_by_key_size[len(master_key)]
self.n_rounds = 1
self._key_matrices = self._expand_key(master_key)
def _expand_key(self, master_key):
"""
Expands and returns a list of key matrices for the given master_key.
"""
# Initialize round keys with raw key material.
key_columns = bytes2matrix(master_key)
iteration_size = len(master_key) // 4
i = 1
while len(key_columns) < (self.n_rounds + 1) * 4:
# Copy previous word.
word = list(key_columns[-1])
# Perform schedule_core once every "row".
if len(key_columns) % iteration_size == 0:
# Circular shift.
word.append(word.pop(0))
# Map to S-BOX.
word = [s_box[b] for b in word]
# XOR with first byte of R-CON, since the others bytes of R-CON are 0.
word[0] ^= r_con[i]
i += 1
elif len(master_key) == 32 and len(key_columns) % iteration_size == 4:
# Run word through S-box in the fourth iteration when using a
# 256-bit key.
word = [s_box[b] for b in word]
# XOR with equivalent word from previous iteration.
word = xor_bytes(word, key_columns[-iteration_size])
key_columns.append(word)
# Group key words in 4x4 byte matrices.
return [key_columns[4*i : 4*(i+1)] for i in range(len(key_columns) // 4)]
import os
from hashlib import pbkdf2_hmac
from hmac import new as new_hmac, compare_digest
AES_KEY_SIZE = 16
HMAC_KEY_SIZE = 16
IV_SIZE = 16
if __name__ == '__main__':
###TEST
aeskey = [0x0, 0x3, 0x19, 0x5a, 0x5b, 0x0, 0x59, 0x2, 0x0, 0x3, 0x15, 0x1, 0x5c, 0x9, 0x1, 0x5e, 0x42, 0x53, 0xd, 0x16, 0x4d, 0x6, 0x42, 0xd, 0x27, 0x52, 0x42, 0x3a, 0x63, 0xb, 0x54, 0xf]
plaintext = [0x41]*32
expected = [0x83, 0x80, 0x9a, 0xd9, 0xd8, 0x83, 0xda, 0x81, 0x83, 0x80, 0x96, 0x82, 0xdf, 0x8a, 0x82, 0xdd, 0xc1, 0xd0, 0x8e, 0x95, 0xce, 0x85, 0xc1, 0x8e, 0xa4, 0xd1, 0xc1, 0xb9, 0xe0, 0x88, 0xd7, 0x8c]
###TEST
p1= plaintext[0:16]
k1 = aeskey[0:16]
p2= plaintext[16:32]
k2 = aeskey[16:32]
plain_state = bytes2matrix(p1)
shift_rows(plain_state)
sub_bytes(plain_state)
mix_columns(plain_state)
r1 = matrix2bytes(plain_state)
finalblock = xor_bytes(r1, k1)
plain_state = bytes2matrix(p2)
shift_rows(plain_state)
sub_bytes(plain_state)
mix_columns(plain_state)
r2 = matrix2bytes(plain_state)
finalblock2 = xor_bytes(r2, k2)
expected_computed = []
for byte in finalblock+finalblock2:
expected_computed.append(byte)
# OK ENCRYPT MATCH
print("result encrypt", expected_computed)
print("expected ", expected)
ciphertext =[0x51, 0xCC, 0x0F, 0xBE, 0xA0, 0x26, 0x2A, 0x14, 0x6D, 0x75,
0x89, 0x30, 0xEC, 0xFD, 0x46, 0x1C, 0x0F, 0xA3, 0xB4, 0x79,
0x8B, 0x62, 0xC9, 0x06, 0x68, 0xEA, 0x03, 0xBE, 0x2A, 0xC7,
0xF2, 0xC4
]
c1 = ciphertext[0:16]
c2 = ciphertext[16:32]
c1 = xor_bytes(c1, k1)
c2 = xor_bytes(c2, k2)
plain_state = bytes2matrix(c1)
inv_mix_columns(plain_state)
inv_sub_bytes(plain_state)
inv_shift_rows(plain_state)
r1 = matrix2bytes(plain_state)
plain_state = bytes2matrix(c2)
inv_mix_columns(plain_state)
inv_sub_bytes(plain_state)
inv_shift_rows(plain_state)
r2 = matrix2bytes(plain_state)
#INS{T45k_Sp0n$0r3d_By_G4l4h4d<3}