Intigrity_1337UP 2024
solution for crypto challenges in Intigrity_1337UP
Conversationalist
Alice and Bob are having a conversation, do you want to join in on the fun and grab the flag?
main.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
use cocoon::MiniCocoon;
...
fn write_message(plaintext: &str, cacoon: &MiniCocoon) {
let mut data = plaintext.to_owned().into_bytes();
let detached_prefix = cacoon.encrypt(&mut data).unwrap();
println!("< {}:{}", hex(detached_prefix), hex(data));
}
fn read_message(cocoon: &MiniCocoon) -> Result<String, Box<dyn error::Error>> {
...
if let Some((detached_prefix, ciphertext)) = input.split_once(':') {
let detached_prefix = hexhex::decode(detached_prefix.trim()).map_err(|_| "Prefix is invalid hex")?;
let mut ciphertext = hexhex::decode(ciphertext.trim()).map_err(|_| "Ciphertext is invalid hex")?;
cocoon.decrypt(&mut ciphertext, &detached_prefix).map_err(|_| "Decryption failed")?;
let data = String::from_utf8(ciphertext).map_err(|_| "Invalid UTF-8")?;
Ok(data)
} else {
Err("Prefix and message must be separated by a colon.")?
}
}
fn main() -> Result<(), Box<dyn error::Error>> {
let key = rand::thread_rng().gen::<[u8; 32]>();
let seed = rand::thread_rng().gen::<[u8; 32]>();
let cocoon = MiniCocoon::from_key(&key, &seed).with_cipher(cocoon::CocoonCipher::Aes256Gcm);
println!("Conversation:");
let messages = include_str!("../messages.txt").lines().collect::<Vec<_>>();
for message in &messages {
write_message(message, &cocoon);
}
println!("\nJoin in on the conversation! Make sure your message is encrypted and uses the same format.");
let input = read_message(&cocoon)?;
println!();
if messages.contains(&input.as_str()) {
println!("Why did you repeat that message?");
} else if input.contains("Give me the flag") {
println!("Since you asked so nicely, here is your flag:");
write_message(include_str!("../flag.txt"), &cocoon);
} else {
println!("You said: {input:?}");
}
Ok(())
}
Sau khi đọc và phân tích mình tổng hợp lại chương trình sẽ hoạt động như sau:
- Đọc và mã hoá các message trong
message.txt
bằng cocoon vàaes256gcm
- Ta được phép gửi 1 message và server sẽ decrypt nó nếu messsage chứa “Give me the flag” thì server sẽ gửi cho chúng ta flag bị mã hoá.
Có thể thấy ciphertext có dạng : mini-header (bao gồm nonce và data length) + tag + encrypted data
1
2
3
4
5
6
7
8
9
10
Conversation:
< 2c62b6a788d6fb75a4ecbdbc00000000000000ac8834c014a55bc71b7d922568d68ba7ad:b8795d6df55a03a610d5ed003d5050dc895924f013465e2626706dd71d5f76eaa340df109174ac5c744515f8cae29552f23202ff5aa478ff9ef96780e2570a20cc2ea47c7ed40539f15041924cc77fe6bfda07601b209d9664131f03c8b76b12670c82b25639ac553211d7c1f6e7d5e667730b5f672ec7f82873a2a80ce817866fefc0fadf2bfee629c93bdd00926a2acd41d0c72ae336ea36e0ddafc299d6fcc9391b15aa314c7a3bf2bb25
< 2c62b6a788d6fb75a4ecbdbc00000000000000a2701014b35f260bb41b53397e5f7b005b:a079562bd25615a610fef41a2a15099db44c2cf1135a5474203323df1b0d72b9a45ddb179162bd5b674c59b786dadd44b7234ce958fd69e593b67ac5ed4301209e32ea2f6fd90d39f54d0e84468467e5e3912b781972828e6d0d1d019ba86804205fc3bb5d7681522b1ad7cbe0b390e976274e1a7a3fcbf22760ebb30aad58802eecdda9d224f1a37fcf26d916976a3a8641ffc02da677e734a489b4cad6cbe1c67f
< 2c62b6a788d6fb75a4ecbdbc000000000000009b1be0cd4e70116bf34fe819a0cb0205ad:b7734b2997410eaa58daf90168506ec9c05e61e65654436336776adf130d6fa5f740df12d423ba146b4541f1cfe0d201e32e43fe0df739f395ad7cc5f05711268761ab326e9c137ce2575c830d8442effc9f2f741b20c48e6d0905449cb269182c08c7f54e33ba107d1b92cde3aedeef23244f0b7c6bd6f92967e7fc08ec508977a1d2ecc523f3ea6acb20d91f907c61882fd8ce26b73ae728a588
< 2c62b6a788d6fb75a4ecbdbc00000000000000b2e440af6a9409494248e234ae2acd3ec0:bf74086dd35a0fad449fea16241949d9c74024b51366596f363f70d400586beabe5b9e08d868ac5b670051ebc3efd801f53f02e945e969f088b0678aef0c58158034b9702ad20f39f34340824cc930eee39532790b2697c26308510981a8691c781ccab04a76bb1a7d1996dea3e7c7e06a304e5f7d3882f93373e7f244cc59972ee8cae0c323f4ef29de3cdf0599672adb41dec76eab38f17aa9dde0d499ccf4c035035cb73f0b6134faad7644b806b1c90c
< 2c62b6a788d6fb75a4ecbdbc00000000000000b02ece736e2612253819d980e64ed169da:b17f5038d6590df31c9fd1532d19439d860d30e15a515a26317a70c5580d7aa4b308d7109670e913694c51f0c8e99554e7664be75af67ce289b06280ed5b5832892da6722af9167cef02598f57cc30f9fe9727361b2a90906d5a020d85ae681c781ac6f54d24a9133b169480afa2c6ed712a52177d25c5b13560e3a501e9179d7ae0c6e5d264b5ca29de3cd91e952f29cd46c3cc6ea438e93ee0dda887cad0f4dc254f46ba39477d34f9e8770bbb07f1
< 2c62b6a788d6fb75a4ecbdbc00000000000000ae19e22020dfeb4899d1e7d77a7ea540f5:a4744539904641eb47daeb1c241507d3825a32b5137e5472626c23dc1b4372beb85a9e0dc523af14740054b9c4e7c101fb294ced4ff635b198ac60c5c80210249a24ea3d2ada057ced4b408103d078e3e2da2f7f193a90c26a131f0584b77d5d6e1a82a15133e806321382d8e6a8dea874360109716bc0f4237aa2ab05e4438760e684efd838bba35dc235de1b8d2f38c71391c822af77ff35b5dbe7d4dcd0e0de71185aab3307341bf2a16701f5
Join in on the conversation! Make sure your message is encrypted and uses the same format.
>
2c62b6a788d6fb75a4ecbdbc00000000000000ae19e22020dfeb4899d1e7d77a7ea540f5
Trong 36 bytes này :
2c62b6a788d6fb75a4ecbdbc
12 bytes nonce00000000000000ae
8 bytes length19e22020dfeb4899d1e7d77a7ea540f5
16 bytes tag
Nhìn vào source code của cocoon và trang này
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const MINI_HEADER_SIZE: usize = 20;
const TAG_SIZE: usize = 16;
impl MiniFormatPrefix {
...
pub fn new(header: MiniCocoonHeader) -> Self {
let mut raw = [0u8; MINI_SIZE];
header.serialize_into(&mut raw);
MiniFormatPrefix { header, raw }
}
pub fn serialize(mut self, tag: &[u8; TAG_SIZE]) -> [u8; Self::SERIALIZE_SIZE] {
self.raw[MINI_HEADER_SIZE..MINI_HEADER_SIZE + TAG_SIZE].copy_from_slice(tag);
self.raw
}
}
impl MiniCocoonHeader {
...
pub fn serialize_into(&self, buf: &mut [u8]) {
debug_assert!(buf.len() >= Self::SIZE);
let length = u64::try_from(self.length).expect("Data too large");
buf[..12].copy_from_slice(&self.nonce);
buf[12..Self::SIZE].copy_from_slice(&length.to_be_bytes());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[[package]]
name = "cocoon"
version = "0.3.3" <-
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3da52429f9a6d0652b469b35f877e4d92e7f12238d4870d13622c87ab2a2ea9"
dependencies = [
"aes-gcm",
"chacha20poly1305",
"hmac",
"pbkdf2",
"rand",
"sha2",
"zeroize",
]
Ở đây thay vì tạo một nonce mới cho mỗi lần mã hóa, phiên bản này (<4.0) tái sử dụng cùng một nonce khi thực hiện nhiều lần gọi hàm mã hóa trên cùng một đối tượng Cocoon. Dẫn đến việc các thông điệp mã hóa chia sẻ chung một nonce - nonse reuse
tạo điều kiện để khai thác.
Trang này cũng đã giải thích rõ cách hoạt động và tấn công khi sử dụng nonse-reuse trên aes-gcm
Tuy nhiên ta cần phải biết một message ban đầu như thế nào thì mới có thể forge message được
Đầu tiên mình cần phải đi tìm các msg đó bằng cách tạo ra forge tag cho ciphertext bị modified 1 bytes vì server sẽ không cho ta dùng lại ciphertext đã in ra.
Vì mình đã xor byte cuối của ciphertext với b”1” nên ở đây msg đầu tiên sẽ là
Hey Bob, just wanted to confirm that we're all set up and secure on the protocol. Everything looks good on my end - seeing the handshake complete and all the right signals!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# From https://github.com/jvdsn/crypto-attacks/blob/master/attacks/gcm/forbidden_attack.py
def forge_tag_from_ciphertext(h, a, c, t, target_a, target_c):
"""
Forges an authentication tag for a target message given a message with a known tag.
This method is best used with the authentication keys generated by the recover_possible_auth_keys method.
More information: Joux A., "Authentication Failures in NIST version of GCM"
:param h: the authentication key to use (gf2e element)
:param a: the associated data of the message with the known tag (bytes)
:param c: the ciphertext of the message with the known tag (bytes)
:param t: the known authentication tag (bytes)
:param target_a: the target associated data (bytes)
:param target_c: the target ciphertext (bytes)
:return: the forged authentication tag (bytes)
"""
ghash = _from_gf2e(_ghash(h, a, c))
target_ghash = _from_gf2e(_ghash(h, target_a, target_c))
return int(ghash ^ int.from_bytes(t, byteorder="big") ^ target_ghash).to_bytes(16, byteorder="big")
def forge_tag_from_plaintext(h, a, c, t, m, target_a, target_msg):
"""
Forges an authentication tag for a target message given a message with a known tag.
This method is best used with the authentication keys generated by the recover_possible_auth_keys method.
More information: Joux A., "Authentication Failures in NIST version of GCM"
:param h: the authentication key to use (gf2e element)
:param a: the associated data of the message with the known tag (bytes)
:param c: the ciphertext of the message with the known tag (bytes)
:param t: the known authentication tag (bytes)
:param m: the known plaintex of c (bytes)
:param target_a: the target associated data (bytes)
:param target_msg: the target message (bytes)
:return: (the forged ciphertext (bytes), the forged authentication tag (bytes))
"""
assert len(m) >= len(target_msg), "The length of the known plaintext should be at least the length of the target message"
target_c = xor(xor(c, m),target_msg)
return target_c, forge_tag_from_ciphertext(h, a, c, t, target_a, target_c)
pt1 = b"Hey Bob, just wanted to confirm that we're all set up and secure on the protocol. Everything looks good on my end - seeing the handshake complete and all the right signals!"
pt2 = b"Perfect, Alice. Same here, no issues at all. The encryption layers seem to be working flawlessly, and I've got authentication on my end verified. Nice and smooth."
from pwn import*
context.log_level = "debug"
p = remote("conversationalist.ctf.intigriti.io", 1349)
p.recvline()
messages = []
while line := p.recvline().strip():
messages.append(line.decode().split(" ")[1])
detached_prefix1, enc1 = messages[0].split(":")
detached_prefix2, enc2 = messages[1].split(":")
detached_prefix1 = bytes.fromhex(detached_prefix1)
enc1 = bytes.fromhex(enc1)
detached_prefix2 = bytes.fromhex(detached_prefix2)
enc2 = bytes.fromhex(enc2)
tag1 = detached_prefix1[-16:]
tag2 = detached_prefix2[-16:]
ad1 = detached_prefix1[:20]
ad2 = detached_prefix2[:20]
target_m = b"Give me the flag"
ad3 = ad1[:19] + bytes.fromhex(str(hex(len(target_m)))[2:])
from aes_gcm_forgery import *
keys = list(recover_possible_auth_keys(ad1, enc1, tag1, ad2, enc2, tag2))
enc11 = enc1[:-1] + bytes([enc1[-1]^1])
for h in keys[:1]:
target_ct, target_tag = forge_tag_from_plaintext(h, ad1, enc1, tag1, pt1, ad3, target_m)
ct_send = (ad3.hex() + target_tag.hex() + ':' + target_ct.hex())
p.sendlineafter(b"> ", ct_send.encode())
p.recvuntil(b"< ")
flag_ciphertext = p.recvline().decode().split(":")[1]
flag_ciphertext = bytes.fromhex(flag_ciphertext)
keystream = xor(enc1, pt1)
flag = xor(keystream, flag_ciphertext)
print(flag)
# b"INTIGRITI{Coc0OnV3rS4tI0n5_Ar3-wH4t_Y0u'R3_h4V1nG,_1t'5-c4Ll3D_NuMB3r_US3d_0nC3_F0r_4_R34s0n!}"