OTPasvraiment
Ce challenge était un challenge de cryptography. Un serveur C2 a été récupéré par la défense, mais les logs sont encryptés avec du one-time-pad. Il faut réussir à décrypter ces logs.
Parce que le python c’est naze, j’ai fais ma solution en javascript. J’ai d’abord commencé par lire le fichier, et le “parse” (le format de donné était créé par cryptkblib.py)
import assert from "assert";
import fs from "fs";
const header_size = 16
const chunk_size = 256
const magic_bytes = Buffer.from("CrypTKBLog")
const eof = Buffer.concat([
Buffer.from("CrypTKBEOF"),
Buffer.alloc(6)
])
assert(eof.length === header_size)
let data = fs.readFileSync("logs")
data = data.subarray(magic_bytes.length)
assert(data.readUint16BE(0) == chunk_size, "Invalid chunk size")
const metadata_chunk_count = data.readUint16BE(2)
const data_chunk_count = data.readUint16BE(4)
data = data.subarray(6)
const chunks = []
for(let i = 0; i < metadata_chunk_count + data_chunk_count; i++) {
let chunk = data.subarray(i * chunk_size, (i + 1) * chunk_size)
let usable_length = chunk_size
for(let i = chunk_size - 1; i > 0; i--) {
if(chunk[i] == 0)continue
usable_length = i + 1
break
}
chunks.push(chunk.subarray(0, usable_length))
}
data = data.subarray((metadata_chunk_count + data_chunk_count) * chunk_size)
assert(data.equals(eof), "Invalid EOF")
À ce point là, j’avais ma liste de chunks. Il me restait à cracker la clé. Cependant, un one-time-pad n’est sécurisé uniquement si la clé n’est pas réutilisée. Or, chaque chunk réutilisait la même clé. En connaissant une partie ou alors un chunk entier, on peut extraire un morceau de clé.
let metadata3 = Buffer.from(`
---- INFORMATIONS ----
Now, you can check the list of machines that we comprosimed :
`)
assert(chunks[2].length == metadata3.length)
let key = Buffer.alloc(chunk_size)
for(let i = 0; i < chunks[2].length; i++) {
key[i] = metadata3[i] ^ chunks[2][i]
}
On obtient un morceau de clé:
cbb9463a474255eb63990b1de748f8fe9e666d17d6ee7ab43899fa5d6900297d37ca0b022ffec6db2ed155d239be3f5e668541c27c637c272ba47468f21607aae577825d37818ce0329cc2329d862112a5ef42b3cf6c
Cependant, le message que nous essayons de décrypter est plus long que la clé. Nous ne pouvons en décrypter qu’une partie. Mais le générateur de nombre aléatoire de la clé est vulnerable et exploitable:
class MyRandomGenerator():
def __init__(self):
self.state = [random.randint(0,255) for s in range(8)]
def getrandombyte(self):
x = 42
for i in range(8):
x ^= (self.state[i] << 19)
y = ((self.state[0]) << 7)
self.state[0] = self.state[1]
self.state[1] = self.state[2]
self.state[2] = self.state[3]
self.state[3] = self.state[4]
self.state[4] = self.state[5]
self.state[5] = self.state[6]
self.state[6] = self.state[7]
self.state[7] = ((self.state[7] + self.state[0]) + (x ^ y)) % 0xff
return self.state[7]
En effet, si on connait 8 nombre de la clé, on connait donc tout self.state, et on peut donc générer les prochains nombres aléatoires.
J’ai implémenté ma fonction de cette façon:
export function crack_key(key){
const states = [
key[0],
key[1],
key[2],
key[3],
key[4],
key[5],
key[6],
key[7]
]
for(let i = 8; i < key.length; i++){
let x = 42
for(let i = 0; i < 8; i++){
x ^= states[i] << 19
}
let y = states[0] << 7
for(let i = 0; i < 7; i++){
states[i] = states[i + 1]
}
states[7] = key[i] = (states[0] + states[7] + (x ^ y)) % 0xff
}
return key
}
Ceci nous donne la clé:
cbb9463a474255eb63990b1de748f8fe9e666d17d6ee7ab43899fa5d6900297d37ca0b022ffec6db2ed155d239be3f5e668541c27c637c272ba47468f21607aae577825d37818ce0329cc2329d862112a5ef42b3cf6c9230f2c6eb2b59674e29016d6835b61f999a8f81e40c13b56146f4a510b9ee79dcce05765de34144823d1c526172c0c272d73ad5fd605ca459c9b7873f2e754e438edadddcab13ae09a7383ab11d6d1648503f8ea2344ded890e664589e8d2556479e21c1c124e3ff9cb21f8e7e442b266d384e419bed03b390842d69b5143fc52a05bdc262a29894c604d94c6fcc030dacf6512e1c72828b1a2a3dc1216bfaf19befd3d22310ed7bab2
Nous pouvons ensuite décrypter le chunk du flag:
let flag_chunk = Buffer.alloc(chunks[1].length)
for(let i = 0; i < flag_chunk.length; i++){
flag_chunk[i] = chunks[1][i] ^ key[i]
}
console.log(flag_chunk.toString("utf8"))
Solution
Code complet:
// decrypt.mjs
import assert from "assert";
import fs from "fs";
const header_size = 16
const chunk_size = 256
const magic_bytes = Buffer.from("CrypTKBLog")
const eof = Buffer.concat([
Buffer.from("CrypTKBEOF"),
Buffer.alloc(6)
])
assert(eof.length === header_size)
let data = fs.readFileSync("logs")
data = data.subarray(magic_bytes.length)
assert(data.readUint16BE(0) == chunk_size, "Invalid chunk size")
const metadata_chunk_count = data.readUint16BE(2)
const data_chunk_count = data.readUint16BE(4)
data = data.subarray(6)
const chunks = []
for(let i = 0; i < metadata_chunk_count + data_chunk_count; i++) {
let chunk = data.subarray(i * chunk_size, (i + 1) * chunk_size)
let usable_length = chunk_size
for(let i = chunk_size - 1; i > 0; i--) {
if(chunk[i] == 0)continue
usable_length = i + 1
break
}
chunks.push(chunk.subarray(0, usable_length))
}
data = data.subarray((metadata_chunk_count + data_chunk_count) * chunk_size)
assert(data.equals(eof), "Invalid EOF")
let metadata3 = Buffer.from(`
---- INFORMATIONS ----
Now, you can check the list of machines that we comprosimed :
`)
assert(chunks[2].length == metadata3.length)
let key = Buffer.alloc(chunk_size)
for(let i = 0; i < chunks[2].length; i++) {
key[i] = metadata3[i] ^ chunks[2][i]
}
import {crack_key} from "./random.mjs"
key = crack_key(key)
let flag_chunk = Buffer.alloc(chunks[1].length)
for(let i = 0; i < flag_chunk.length; i++){
flag_chunk[i] = chunks[1][i] ^ key[i]
}
console.log(flag_chunk.toString("utf8"))
// random.mjs
export function crack_key(key){
const states = [
key[0],
key[1],
key[2],
key[3],
key[4],
key[5],
key[6],
key[7]
]
for(let i = 8; i < key.length; i++){
let x = 42
for(let i = 0; i < 8; i++){
x ^= states[i] << 19
}
let y = states[0] << 7
for(let i = 0; i < 7; i++){
states[i] = states[i + 1]
}
states[7] = key[i] = (states[0] + states[7] + (x ^ y)) % 0xff
}
return key
}
Le flag obtenu est:
SHLK{L4vez-v0us_L3s_m4ins_apr3s_ut1l154710n}
Conclusion
J’ai bien aimé ce challenge, il mélange one-time-pad et cracking de RNG. Je l’ai trouvé un peu plus original que les autres challenges que j’ai pu faire en rapport avec le one-time-pad.