# DG'hAck 2021 - Electro
## Ranking
- 22nd on the general with 1400 pts,
- 13th on the professional category.
![](https://i.imgur.com/riyY5Yy.png)
## Achievement
- First Blood on "Crypto Be Crushed"
![](https://i.imgur.com/8dADoNI.png)
# Cryptography
## Crypto Be Crushed - 1st Blood
I have just noticed the hint in the name of the challenge "CBC" during the redaction of this write-up ...
### Description
```
Vous participez à un CTF, ainsi que votre colocataire, Alice, avec laquelle vous partagez la même connexion internet et le Wifi.
Reconnaissons-le, vous êtes un peu paresseux : pourquoi résoudre un challenge si elle l’a déjà terminé ?
Cependant, comme expliqué dans les règles du CTF, les soumissions de flag sont chiffrées avec une clé propre à chaque utilisateur (voir le fichier CTF_Submission.md) et même si vous avez réussi à capturer la solution du dixième challenge, celui-ci est chiffré (en hexadécimal) :
61499b3f31cee611a72eaf3cbfcf7d1cebb228a44db94e7b0504c145fcf00e57d2e0b9e24c7259bbeebccd03c100a645f418f2f58cc073cc71f214eb64a3b20ddfb406f6ebbd6781119efe13116af3abfe52609961727213ea69b8f8f1e4298ed3a42bc9ae4b8f1785184153ee3e113a8c9d55ddec48c85c53d5aa4a4089e47c3026a0bdb4d5d2659e57c31a76cca407ea0a92430d8540b8ef677405e8c4b193
Heureusement, Alice a décidé d’adapter le script de chiffrement fourni par l’organisateur du CTF et l’a transformé en un serveur de chiffrement.
Par chance, Alice a oublié de configurer correctement son pare-feu et vous êtes capable d’y accéder
```
### CTF Submission
```
# CTF Submission instructions
Welcome to the CTF, player!
This year, for security purposes, we decided to use specific procedure for you to use when submitting a flag.
Indeed, it must be submitted in a CBC-encrypted-JSON with a per-user key. We sent you the key at registration.
If you are not comfy with cryptography, no worries, you will find the code in the FAQ (`encrypt_challenge.py`, which uses the `PyCryptodome` library -- *don't roll your own crypto*).
You will just have to put your provided key in a `key.txt` file.
To be properly processed, the JSON file must have the following format :
- a `"sig"` value, with a random 32 bytes values encoded as a hex string which is provided to you for each challenge;
- a `"flag"` value containing the submitted flag;
- a `"user"` value, the user's login;
- and a `"cid"` value, which is the challenge id.
An example JSON is provided by the `ex_flag.json` file.
Happy hacking!
```
### Encrypt Challenge
```
#!/usr/bin/env python3
from Crypto.Cipher import AES
from Crypto import Random
import secrets
def pkcs7_padding(m):
# There is padding and there is PKCS#7 padding 🤮
l = len(m)
pad_len = 16 - (l % 16)
pad_len_hex = pad_len.to_bytes(1, byteorder="little")
padding = bytes([pad_len_hex[0] for i in range(0, pad_len)])
return m+padding
# Prevent IV replays
iv_list = set()
def encrypt(iv, m):
if iv in iv_list:
print("ERROR: REPLAYED IV")
return bytes([])
iv_list.add(iv)
m = pkcs7_padding(m)
cipher = AES.new(KEY, AES.MODE_CBC, iv)
return iv + cipher.encrypt(m)
def get_iv():
return Random.new().read(AES.block_size)
KEY = open('key.txt', 'rb').read()
# stdin/stdout version
if __name__ == "__main__":
while True:
iv = get_iv()
print("IV for encryption (hex):")
print(iv.hex())
print("Enter message (as a hex bytes string):")
x = bytes.fromhex(input())
# print("Message: " + str(x) + "\n")
print("Ciphertext (hex): " + str(encrypt(iv, x).hex()) + "\n")
```
### ex_flag
```
{"sig":"80a7ccd5aa2f3b0f917267640c6ff37c50e7f3673a30d20c0e133fe8c20d5cd1","flag":"DG'hAck-{{b51613f7}}","user":"JohnDoe","cid":4}
```
### Solution
In the md file, there is an interesting comment ***don't roll your own crypto***, which may indicates a crypto methodology related weakness. Within the encrypt_challenge.py file, we have an AES-CBC encryption server. AES-CBC is dependent of an IV which is provided as an output.
From the ex_flag file, we are provided with a flag sample, for John Doe and a cid of 4, whereas we are looking for Alice and a cid of 10 (see md file).
The objective is clearly to transform the AES-CBC encryption server into an AES-CBC encryption oracle. This is possible ***because Alice own crypto*** has been implemented ***and the IV is provided before the encryption***.
As the IV is provided before encryption, it is possible to nullify its effects and to used the iv of the intercepted flag (nullification is performed with a xor operation).
By filling the flag example with Alice data, we have a well aligned (16 bytes) flag including the future padding.
```
{"sig":"80a7ccd5aa2f3b0f917267640c6ff37c50e7f3673a30d20c0e133fe8c20d5cd1","flag":"DG'hAck-{{b51613f7}}","user":"Alice","cid":10}
```
Lets break it into 16 bytes chunk, and bruteforce the flag !
```
from pwn import *
import binascii
def getiv(io):
io.recvuntil(b"IV for encryption (hex):\n")
return io.recvline().strip()
def send(io,txt):
io.recvuntil(b"Enter message (as a hex bytes string):\n")
io.sendline(txt.hex().encode())
def getct(io):
io.recvuntil(b"Ciphertext (hex): ")
return io.recvline().strip()
def xor(a,b,order="little"):
tbs = b""
for j,elt in enumerate(a):
tbs += (elt ^b[j]).to_bytes(1, byteorder=order)
return tbs
def template(k,n):
if n==4:
_ = b':"DG\'hAck-{{'+binascii.hexlify(k.to_bytes(2, byteorder="big"))
if n==3:
_ = binascii.hexlify(k.to_bytes(2, byteorder="big"))+b'}}","user":"'
if n==2:
_ = b"""Alice","cid":10}"""
if n==1:
_ = b"\x10"*16
return _
flag="61499b3f31cee611a72eaf3cbfcf7d1cebb228a44db94e7b0504c145fcf00e57d2e0b9e24c7259bbeebccd03c100a645f418f2f58cc073cc71f214eb64a3b20ddfb406f6ebbd6781119efe13116af3abfe52609961727213ea69b8f8f1e4298ed3a42bc9ae4b8f1785184153ee3e113a8c9d55ddec48c85c53d5aa4a4089e47c3026a0bdb4d5d2659e57c31a76cca407ea0a92430d8540b8ef677405e8c4b193"
iv = flag[:32]
ct = flag[32:]
print("Alive IV : ")
print("======================")
print(iv)
print("Alice FLAG : ")
print("======================")
print(ct)
print("Defining Target")
print("======================")
target = []
for i in range(0,len(flag),32):
if ct[i:i+32] != b"":
target.append(binascii.unhexlify(ct[i:i+32]))
del target[-1]
print(target)
print("=============")
# Connection
io = remote("cryptobecrushed.chall.malicecyber.com",4242)
# For all interesting segment
for n in range(1,4+1,1):
for k in range(0,256*256):
# Get the flag segment
found = template(k,n)
# getiv
iv=binascii.unhexlify(getiv(io))
# Xor
__ =found
s = xor(xor(__,target[-(n+1)],order="little"),iv,"little")
# Interact with the encryption oracle
send(io,s)
res =binascii.unhexlify(getct(io))
# Check if we found the string
if res.find(target[-n])>-1:
print(target[-n])
print([res[i:i+16] for i in range(0,len(res),16)])
print(found)
print("=========")
break
```
Execution of the script:
```
Alive IV :
======================
61499b3f31cee611a72eaf3cbfcf7d1c
Alice FLAG :
======================
ebb228a44db94e7b0504c145fcf00e57d2e0b9e24c7259bbeebccd03c100a645f418f2f58cc073cc71f214eb64a3b20ddfb406f6ebbd6781119efe13116af3abfe52609961727213ea69b8f8f1e4298ed3a42bc9ae4b8f1785184153ee3e113a8c9d55ddec48c85c53d5aa4a4089e47c3026a0bdb4d5d2659e57c31a76cca407ea0a92430d8540b8ef677405e8c4b193
Defining Target
======================
[b'\xeb\xb2(\xa4M\xb9N{\x05\x04\xc1E\xfc\xf0\x0eW', b'\xd2\xe0\xb9\xe2LrY\xbb\xee\xbc\xcd\x03\xc1\x00\xa6E', b'\xf4\x18\xf2\xf5\x8c\xc0s\xccq\xf2\x14\xebd\xa3\xb2\r', b'\xdf\xb4\x06\xf6\xeb\xbdg\x81\x11\x9e\xfe\x13\x11j\xf3\xab', b'\xfeR`\x99arr\x13\xeai\xb8\xf8\xf1\xe4)\x8e', b'\xd3\xa4+\xc9\xaeK\x8f\x17\x85\x18AS\xee>\x11:', b'\x8c\x9dU\xdd\xecH\xc8\\S\xd5\xaaJ@\x89\xe4|', b'0&\xa0\xbd\xb4\xd5\xd2e\x9eW\xc3\x1av\xcc\xa4\x07', b'\xea\n\x92C\r\x85@\xb8\xefgt\x05\xe8\xc4\xb1\x93']
=============
[+] Opening connection to cryptobecrushed.chall.malicecyber.com on port 4242: Done
b'\xea\n\x92C\r\x85@\xb8\xefgt\x05\xe8\xc4\xb1\x93'
[b'\xc51f\xde\x0f\xa7\x91W\x05\x84\x08[kT\xb1\x95', b'\xea\n\x92C\r\x85@\xb8\xefgt\x05\xe8\xc4\xb1\x93', b'\t\x90\xb9Q\xbd`\x1a\xdd\x8c\xd4\x9bK\xd8@\xa0\x15']
b'\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
=========
b'0&\xa0\xbd\xb4\xd5\xd2e\x9eW\xc3\x1av\xcc\xa4\x07'
[b'[\xc6\xbf"\x1aT\'\xf1\x0cG\'wF\xd7\xbb\xc4', b'0&\xa0\xbd\xb4\xd5\xd2e\x9eW\xc3\x1av\xcc\xa4\x07', b'\xea\n\x92C\r\x85@\xb8\xefgt\x05\xe8\xc4\xb1\x93']
b'Alice","cid":10}'
=========
b'\x8c\x9dU\xdd\xecH\xc8\\S\xd5\xaaJ@\x89\xe4|'
[b'\xc1\x9e\x05\xf9\xd7Q\xed\xff\xceN\xac\xa1\xd3\x15\x99\x80', b'\x8c\x9dU\xdd\xecH\xc8\\S\xd5\xaaJ@\x89\xe4|', b'\x85SFJ\x9ei\x1c\x01\xc4\x908}\nY\x00X']
b'b967}}","user":"'
=========
b'\xd3\xa4+\xc9\xaeK\x8f\x17\x85\x18AS\xee>\x11:'
[b'`\\`\x1a?^\xbf{\x1a\x99\xe6f\x08&\x0e\xfc', b'\xd3\xa4+\xc9\xaeK\x8f\x17\x85\x18AS\xee>\x11:', b"?\x0e\xee\x0e\\\xae\xb9'%\x9a%9\xa8\xab'\xb3"]
b':"DG\'hAck-{{e20e'
=========
[*] Closed connection to cryptobecrushed.chall.malicecyber.com port 4242
real 11m36,058s
user 1m14,446s
sys 0m6,693s
```
Job Done in less than 12 minutes! ***DG'hAck-{{e20eb967}}***
## Mascarade
### Description
```
Bob a le flag. Et il l’envoie à Alice en utilisant un échange de clé sécurisé et du chiffrement authentifié.
Le code qu’il utilise est dans le fichier ake_server.rs.
Comme on peut le voir, c’est du Rust, donc pas de vulnérabilité à exploiter sur le serveur...
```
### Cargo.toml
```
[package]
name = "chall_ake"
version = "0.1.0"
edition = "2018"
# [[bin]]
# name = "noise_clien-server"
# path = "src/noise.rs"
[[bin]]
name = "ake_server"
path = "src/ake_server.rs"
[[bin]]
name = "ake_client"
path = "src/ake_client.rs"
[[bin]]
name = "ake_attack"
path = "exploit/ake_attack.rs"
[dependencies]
# noise-protocol = "0.1.3"
# noise-rust-crypto = "0.4.1"
# noise-sodiumoxide = "0.1.1"
chacha20poly1305 = "0.8.0"
blake2 = "0.9.1"
x25519-dalek = "1.1.1"
rand_core = "0.5"
futures = "0.3"
tokio = { version = "1", features = ["full"] }
```
### ake_server.rs
```
use blake2::{Blake2b, Digest};
use chacha20poly1305::aead::{Aead, NewAead};
use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce};
use rand_core::OsRng;
use x25519_dalek::{EphemeralSecret, PublicKey, StaticSecret};
use std::{fs, sync::Arc};
use tokio::net::TcpListener;
use tokio::net::TcpStream;
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
task,
};
use std::{
error::Error,
sync::atomic::{AtomicUsize, Ordering},
};
const FLAG_PATH: &str = "flag.txt";
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let addr = "0.0.0.0:7878";
server_loop(addr, FLAG_PATH).await
}
async fn server_loop(addr: &str, flag_loc: &str) -> Result<(), Box<dyn Error>> {
let flag =
Arc::new(fs::read_to_string(flag_loc).expect("Something went wrong reading the file"));
println!("Flag {}", flag);
let listener = TcpListener::bind(addr).await?;
println!("Listening on: {}", addr);
let counter = AtomicUsize::new(0);
loop {
// Asynchronously wait for an inbound socket.
let (stream, _) = listener.accept().await?;
let c = counter.fetch_add(1, Ordering::SeqCst) + 1;
if c % 1000 == 0 {
println!("{} connections", c);
}
let flag = flag.clone();
tokio::spawn(async move {
handle_connection_initiator(stream, flag).await.ok();
});
}
}
const HELLO_ALICE: &str = "Hello Alice!\n";
const HELLO_BOB: &str = "Hello Bob!\n";
async fn handle_connection_initiator(
mut stream: TcpStream,
flag: Arc<String>,
) -> Result<(), Box<dyn Error>> {
let bob_static_secret: StaticSecret = StaticSecret::from([
128, 0, 20, 121, 100, 3, 92, 119, 70, 203, 20, 8, 122, 109, 231, 12, 103, 203, 231, 222,
127, 221, 171, 139, 176, 8, 114, 52, 61, 98, 3, 64,
]);
let alice_static_public: PublicKey = PublicKey::from([
20, 2, 29, 90, 241, 67, 52, 1, 217, 46, 238, 54, 248, 8, 227, 39, 81, 48, 215, 36, 220,
241, 207, 33, 186, 112, 32, 254, 188, 140, 12, 10,
]);
// Say Hello!
stream.write_all(&HELLO_ALICE.as_bytes()).await?;
let buffer_size = 1024;
let mut buffer = vec![0; buffer_size];
let size_read = stream.read(&mut buffer).await?;
let s = std::str::from_utf8(&buffer[..size_read])?;
if s != HELLO_BOB {
return Ok(()); // I don't want to implement annoying error management here. So we just stop
}
assert_eq!(
std::str::from_utf8(&buffer[..size_read]).unwrap(),
HELLO_BOB
);
// run the handshake.
// generate ephemerals
// spawn a blocking task for that (it is CPU-intensive)
let (bob_secret, bob_public) = task::spawn_blocking(move || {
let bob_secret = EphemeralSecret::new(OsRng);
let bob_public = PublicKey::from(&bob_secret);
(bob_secret, bob_public)
})
.await?;
// send the initiator message with our ephemeral public key
stream.write_all(bob_public.as_bytes()).await?;
let mut buffer = [0; 32]; // size of a public key
// get the responder message
let _ = stream.read_exact(&mut buffer).await?;
let alice_public = PublicKey::from(buffer);
// the next steps are CPU-intensive
let ct = task::spawn_blocking(move || {
// compute the shared secrets
let shared_ephemeral_secret = bob_secret.diffie_hellman(&alice_public);
let shared_static_secret = bob_static_secret.diffie_hellman(&alice_static_public);
let shared_static_ephemeral_secret = bob_static_secret.diffie_hellman(&alice_public);
// derive the key
let shared_secret = Blake2b::new()
.chain(shared_ephemeral_secret.as_bytes())
.chain(shared_static_secret.as_bytes())
.chain(shared_static_ephemeral_secret.as_bytes())
.finalize();
// construct the cipher and encrypt
let cipher = ChaCha20Poly1305::new(Key::from_slice(&shared_secret[..32]));
let nonce = Nonce::from_slice(&[0u8; 12]); // we only use one nonce, so pick something simple
cipher
.encrypt(nonce, flag.as_bytes())
.expect("encryption failure!") // NOTE: handle this error to avoid panics!
})
.await?;
stream.write_all(&ct).await?;
Ok(())
}
```
### Solution
Here, we are part of a Diffie-Ellman exchange on an elliptic curve, and we can use the shared knowledge to perform additional hashing/encryption operations (Blakeb/ChaCha20Poly1305). Once the Rust code is translated into another langage, we can decipher the flag.
```
from pwn import *
from hashlib import blake2b
import pysodium
from chacha20poly1305 import ChaCha20Poly1305
import hashlib
import time
# conversion function from rust to python
def convert(s):
_=b""
for elt in s:
_+=elt.to_bytes(1,"little")
return _
# Store key as on rust file
alice_private_key =convert([128, 0, 20, 121, 100, 3, 92, 119, 70, 203, 20, 8, 122, 109, 231, 12, 103, 203, 231, 222,127, 221, 171, 139, 176, 8, 114, 52, 61, 98, 3, 64])
bob_static_secret =convert([128, 0, 20, 121, 100, 3, 92, 119, 70, 203, 20, 8, 122, 109, 231, 12, 103, 203, 231, 222, 127, 221, 171, 139, 176, 8, 114, 52, 61, 98, 3, 64])
alice_static_public =convert([20, 2, 29, 90, 241, 67, 52, 1, 217, 46, 238, 54, 248, 8, 227, 39, 81, 48, 215, 36, 220,241, 207, 33, 186, 112, 32, 254, 188, 140, 12, 10])
# Calculate alice public key
alice_public_key = pysodium.crypto_scalarmult_curve25519_base(alice_private_key)
#print(alice_public_key,alice_public_key.hex())
print("Alice",[i for i in alice_public_key])
time.sleep(1)
# Do handshake wiht the server
server = remote("mascarade.chall.malicecyber.com",4999)
#server = remote("127.0.0.1",7878)
print(server.recv())
server.sendline(b"Hello Bob!")
print("Getting Bob")
bob_public_key =server.recv(32).strip()
print("Bob_public",[i for i in bob_public_key],len(bob_public_key))
server.send(alice_public_key)
print("Getting Flag")
flag =server.recvall().strip()
print(flag,len(flag))
# Do Diffie Hellman ECC Calculation
shared_ephemeral_secret = pysodium.crypto_scalarmult_curve25519(alice_private_key, bob_public_key)
shared_static_secret = pysodium.crypto_scalarmult_curve25519(bob_static_secret, alice_static_public)
shared_static_ephemeral_secret = pysodium.crypto_scalarmult_curve25519(bob_static_secret, alice_public_key)
print([i for i in shared_ephemeral_secret])
sharedsecret = shared_ephemeral_secret +shared_static_secret +shared_static_ephemeral_secret
# Do Blake2b hashing
print(len(sharedsecret))
h = blake2b(sharedsecret).digest()
g = blake2b()
g.update(shared_ephemeral_secret)
g.update(shared_static_secret)
g.update(shared_ephemeral_secret)
g =g.digest()
key =h
print([i for i in sharedsecret])
# Do ChaCha20Poly1305
cip = ChaCha20Poly1305(key[:32])
nonce = b"\x00"*12
plaintext = cip.decrypt(nonce, flag)
print(plaintext)
```
and executing the script:
```
python3 test.py
Alice [165, 154, 157, 189, 14, 3, 59, 231, 9, 61, 115, 119, 7, 84, 221, 178, 90, 71, 156, 128, 80, 190, 39, 152, 45, 165, 220, 160, 57, 50, 255, 9]
[+] Opening connection to mascarade.chall.malicecyber.com on port 4999: Done
b'Hello Alice!\n'
Getting Bob
Bob_public [50, 173, 174, 25, 209, 54, 43, 133, 36, 98, 102, 202, 1, 217, 34, 22, 194, 245, 159, 58, 236, 253, 223, 211, 182, 99, 76, 84, 220, 204, 71, 107] 32
Getting Flag
[+] Receiving all data: Done (56B)
[*] Closed connection to mascarade.chall.malicecyber.com port 4999
b'\xc7AVZ\x16u\'\xc4|\xe3"\xdf\xfe*t%d\xc3i<\xe0C\xab\x9e{\xeb\x1a\x96\x04\'B\x8dWy\xacP\xf7\xebr\x00XtG\x1bR\xf8M\x8d\x81\xd7\xc1\xb9\'\xea\xa5\xf6' 56
[117, 61, 160, 223, 16, 203, 209, 85, 30, 40, 20, 90, 242, 53, 122, 174, 91, 94, 94, 96, 213, 249, 213, 64, 13, 118, 212, 157, 104, 160, 214, 42]
96
[117, 61, 160, 223, 16, 203, 209, 85, 30, 40, 20, 90, 242, 53, 122, 174, 91, 94, 94, 96, 213, 249, 213, 64, 13, 118, 212, 157, 104, 160, 214, 42, 59, 139, 168, 244, 116, 60, 183, 239, 4, 6, 177, 231, 69, 203, 243, 35, 102, 54, 215, 51, 237, 15, 14, 94, 72, 240, 16, 145, 127, 4, 1, 112, 3, 148, 116, 44, 225, 84, 92, 116, 68, 2, 93, 70, 160, 181, 98, 222, 167, 67, 18, 72, 49, 121, 108, 61, 32, 80, 220, 237, 40, 62, 59, 115]
bytearray(b'DGHACK{penurie_complete,penurie_basmati}')
```
***DGHACK{penurie_complete,penurie_basmati}***
## Hashsig
### Description
```
La DGA met à votre disposition un serveur sur lequel vous pouvez
créer un compte et enregistrer des informations à mémoriser.
Votre collègue a croisé l’administrateur du site au stand DGA
lors de l’European Cyber Week. Il vous apprend que celui-ci
utilise le serveur pour conserver des informations sensibles.
Vous êtes intrigué, et une curiosité excessive vous tiraille :
vous souhaitez en savoir plus. Vous demandez alors à votre
collègue de vous décrire le personnage en question, et vous
déployez des trésors d’ingénierie sociale pour boire un verre
avec lui rue sainte Anne. L’occasion rêvée de déposer un
logiciel espion dans son ordinateur personnel !
Au bout de quelques jours, vous récupérez les données ci-dessous,
contenant la librairie client utilisée pour se connecter au
serveur, le logiciel cryptographique utilisé pour s’authentifier,
et un échantillon des échanges entre le portable de
l’administrateur et le serveur.
Note : pour résoudre ce challenge sous Windows,
les messages de 1 octet n’étant pas acceptés avec
la configuration native (NAT), vous devrez vous mettre en
Bridge ou utiliser une machine Linux.
```
### Hash_based
```
from hashlib import sha256
import secrets
digest_size = 32 #bytes = 256 bits
# must be doubled in hexa.
#
# Winternitz parameters:
#
w = 8 # one byte
W = 256 #2^w
l1 = 32
l2 = 2
lW = l1+l2
#
# Encoding functions
#
def _int_from_bytes(xbytes: bytes) -> int:
return int.from_bytes(xbytes, 'big')
def _even_hex(x):
y = hex(x)[2:]
if (len(y)%2 == 1):
y = "0"+y
return y
def _hex_length(x,l0):
y = hex(x)[2:]
while len(y)<l0:
y = "0"+y
return y
def _signature_encoding(sign):
acc = ""
for s in sign:
acc = acc+s
return acc
def _signature_decoding(sign_enc):
sign = []
if len(sign_enc) == lW * digest_size * 2 :
for i in range(lW):
sign.append(sign_enc[2*i*digest_size:2*(i+1)*digest_size])
return True,sign
else:
return False,sign
#
#
# Internal functions of the scheme
#
#
def _sequence(digest):
#digest must be a string representing a hex value
#_sequence outputs a list of int
seq = []
s = 0
for i in range(l1):
x = int(digest[i:i+2],16)
seq.append(x)
s += x
check = (l1*W - s)
new_d = hex(check)[2:]
while (len(new_d) < 2*l2):
new_d = "0"+new_d
for i in range(l2):
x = int(digest[i:i+2],16)
seq.append(x)
return seq
def _main(key,seq):
# key is a list of hex strings
# seq is a list of int
res = []
for i in range(lW):
x = bytes.fromhex(key[i])
e = seq[i]
for j in range(e):
x = sha256(x).digest()
res.append(_hex_length(_int_from_bytes(x),2*digest_size))
return res
def _keysequence(sk):
#sk must be a hex string
#returns a list of hex strings
def key_derivation(sk,i):
#returns a hex-string
ski = sk + _even_hex(i)
y = sha256(bytes.fromhex(ski)).hexdigest()
while (len(y) < 2*digest_size):
y = "0"+y
return y
return [key_derivation(sk,i) for i in range(lW)]
def _get_key(pks):
acc = ""
for i in range(lW):
acc = acc + pks[i]
y= sha256(bytes.fromhex(acc)).hexdigest()
while (len(y) < 2*digest_size) :
y = "0" + y
return y
#
#
# Functions of the signature scheme
#
#
def sign(sk,message):
digest = sha256(message).hexdigest()
seq = _sequence(digest)
keys = _keysequence(sk)
sign = _main(keys,seq)
return _signature_encoding(sign)
def verify(pk,message,sign_enc):
formated,sign = _signature_decoding(sign_enc)
if formated:
digest = sha256(message).hexdigest()
seq = _sequence(digest)
true_seq = [(W - e) for e in seq ]
pks = _main(sign,true_seq)
return (pk == _get_key(pks))
else:
return False
def key_regen(sk):
keys = _keysequence(sk)
e_max = [W for i in range(lW)]
pks = _main(keys,e_max)
pk = _get_key(pks)
return pk
def key_gen():
x = secrets.randbits(8*digest_size)
sk = _hex_length(x,2*digest_size)
return (sk,key_regen(sk))
#
# Nonce generation function
#
def nonce():
x = secrets.randbits(8*digest_size)
return _hex_length(x,2*digest_size)
#
#
# Test
#
#(sk,pk) = key_gen()
#sign = sign(sk,b'toto')
#print("message signed")
#b = verify(pk,b'toto',sign)
#if b:
# print("verified")
#else:
# print("not verified")
#
#b = verify(pk,b'toro',sign)
#if b:
# print("verified")
#else:
# print("not verified")
```
### client_lib
```
import socket
import hash_based
import sys
#s = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM,protocol=0)
def add_user(login,pk):
s = socket.socket() # Create a socket object
host = socket.gethostname() # Get local machine name
s.connect((host,port))
s.send(b'a')
s.recv(1024)
s.send(bytes(login,"utf-8"))
s.recv(1024)
s.send(bytes(pk,"utf-8"))
r = s.recv(1024)
if r == b'r':
print("User already exists. Operation forbidden.")
elif r == b'a':
print("Success")
s.close()
def connect(login,sk):
s = socket.socket() # Create a socket object
host = socket.gethostname() # Get local machine name
s.connect((host,port))
s.send(b'c')
s.recv(1024) # should receive byte b'l'
s.send(bytes(login,"utf-8"))
chall_b = s.recv(1024) #receives the challenge
# chall = chall_b.hex()
sign = hash_based.sign(sk,chall_b)
sign_b = bytes.fromhex(sign)
s.send(sign_b)
r = s.recv(1024)
if r == b'r':
print("Authentication failed. Connection refused.")
elif r == b'm':
print("Connection success.")
return s
def add_secret(s,sec):
s.send(b'a')
s.recv(1024)
s.send(bytes(sec,"utf-8"))
r = s.recv(1024)
if r == b'm' :
print("Secret added.")
return ""
def get_secrets(s):
s.send(b'g')
r = s.recv(1024).decode("utf-8")
print(r)
def stops_connection(s):
s.send(b's')
def close_connection(s):
s.close()
# Reserve a port for your service
if len(sys.argv) < 3:
host='localhost'
port=12345
else:
host=sys.argv[1]
port=int(sys.argv[2])
#Usage example:
#(sk,pk) = hash_based.key_gen()
#add_user("username",pk)
#s = connect("username",sk)
#add_secret(s,"Super secret")
#get_secrets(s)
#s.close()
#
```
### Solution
In this challenge, we are dealing with the ***Winternitz One Time Signature***, and we are provided a pcap file where we can observe several transaction with the server.
Clearly, the objective is to re-use the knowledge acquired observing the transactions and to forge a signature.
The Winternitz OTS is based on the iteration of hashing functions (sha256), onto 32x private values forming a private key. A public key is generated by hashing iteratively these 32 private values up to the 256 iteration.
Based on a common protocol, to generate a one-time signature, the "prover" will generate a sha256 hash of its message, and provide for each of the 32x private values (i) their iterative hashes up according to 256-msg_hash(i). On the verification side, the server complete the iterative hashing up to 256 for each of the 32x partial public key. Then, the server compares with the known public and verify.
![](https://i.imgur.com/EwuSTKI.png)
As we observe a **re-use** of the ***one time*** signature, we can find the lowest iteration and use those values until all the byte of the challenge are above the recovered iterations.
It is important to mention in this challenge 34 values are used, and some strange things are done in the code, however see below the recovered values after analysis the transaction in the pcap file.
![](https://i.imgur.com/19Gzzbf.png)
So let's just had a forge function in hash_based.py
```
# Added
def forge(message,hashes,known_seq):
digest = sha256(message).hexdigest()
seq = _sequence(digest)
#print(seq)
#print(known_seq)
_seq=[]
for i,elt in enumerate(seq):
_seq.append(elt-known_seq[i])
#print(min(_seq),_seq)
if min(_seq) <0:
return False,""
#print(_seq)
sign = _main(hashes,_seq)
return True,_signature_encoding(sign)
```
and running the following code to solve:
```
from pwn import *
import hash_based
import binascii
from hashlib import sha256
challs =[
b"51dccd2a76757ed416c2475f630aac1b1ae0b0a754e196223fd13d68c3b8b5df",
b"b66158d1852d75d6e62c90f0b43bd1ea519c3c36cbe9b60d9590bd041a014c62",
b"8a5a253cad5b50a4f6d6820c89f6013a9ac7c9e0d02f90da337fe4c4a1373367",
b"f8d742ecea0699d56a3599eaa87c68d1c2a0d117ad4ab1ff8afdb2bba99e810c",
b"6c556a795e614569fb1b61acb500fe82965a8fab998306beee99e221e80cb4ff",
]
proofs=[
b"de0fa2c1b4b49bce4a2a53b7301e2cb70239aeab336b7d7296e5d4cda2b63907102e960c65f207f1cd5a42aca37912f8ccb3e7a36cea0dce13c1aced5f7e69d10462a2599b1072a128137b07489c37dc8ebd859068a0f1ddbc5545fb212ee81f3899f0abc70fc8d03c82c16383be5bafe7ecab055245e6de46b7b8dee103bee470e15aab8998cb8c5d37e0565dd80e8cdde15d3d99bd058c50113ad32698f4f33642c555dd1bdc13415a693bcc2a9c1e1ed97039b1f31f4455b98fe8e652da0c473881072f5bb66c726633b0bcfd431d46bb2bbac3a1a53d49d431cd885a0c4898aae6dd18cccf45925e431efb4f48e1dd5ac21a17f8fbf88471b6f03c148c7995d25c7aa28f913fbbbdb177f0dede916dfecd882f044cecd1d06a98b4cd64c578cb6a38db84d72628e97765071b01d3c1aec047c13712dbde56f1f1ac996f863e2b42caa936109e3ef7e9a4b03734034f978439af9303662a8684427c190907639651a5538ffa13690d2be0916b038b84e4812b495a0f63ee8033bee58270a0b6df06f98a4e52c23a5d43801cc66d7d5ea3ccf64426b6a7ff22022123fddb0565747a429356b37195046034568c9a5b8326d0543cb6bca4505ad4067692640e9eb281aad18a99f13d4dc1dd37a61268cbdfb80ed81a9b3dca71e2070b18f5a544d4d101badaf9826a4448438bb0b97f547615095bfc4bd7591ca53bca411d1b46112a8254668e3af4d876c1e604e8dd637cab16da0c3b892dd6cbbb52529d5bdcdd99b10f02a1b2614c6ce90f754170cf0c7cd5d39c4e9c4a0dc7393643553c6d74b68cb0fe2a8266313aa3fed523d8fb4ed587d655b0ea4c28ec2d50d6893700829c5f157e09ce72ff73d512dfb83e99762c80c2e5a5b443cbf48120454fc081a9289fa801900818ef28c72f5650a399fbc59156127335886f9617e2c64ac4d210fd20b416aa2f01a839566aa1b95fbd17aef10142770b12114c554608226833a2cd0ea7217fc5231e4fd6c36f004e259cee8478cf20ded30dc2b2db63b0f91b57bac59cf2a1398360d6e1c99fbc2f5df9c3d7d9a40d179b6c37460a758605c6af8ce6ae01b2a159e3e069ac003b8156b37888626057ad1fa98bf4c343e707f452bf68340fd97db8b5e7312f4ebc2fa2ab55b354d5adeac2f0c3a87cb89e3e13b146f18b8874d537e176928b1eb6ebb7d7aeb075332527249a88e7954da9c17551a6c1f84af0eb821020b8d87f5c73a0b1d9ddde620a31de5380c25c9fdf5ea382ea5e4228d2ab8dd80505b773b18834aa32eb1f999007435f676da4b2e0ae01b3c0675fb2e5a3ec7bf91cf22be8031871fc8bcedaef9ba0cbf84d93a5d4e40891d6ac8be326bed6f72b9040e164e56be6d3566093c69498e4e8e78e895231959d9edb341874352b09d5484cd02d1b0a72530db516c474d802a7be2f67d1eb91d83aabec5e3df34c569e6db418e140f93604b8c9631f5303fd5de845bca8214f61b3f1170aa10d4fd17b791677114d1c07723ae8d9aaf7705a47f7d1133a32",
b"0f892c0301604f83b943d3557c61057840960f1c72f73da69fd14d3baa029bd698ca338dd9adbbda1f5eeb5bba3825b7cba9fec876522213c3ee2885d146d24b3ac932a5425824d6bcf7f6579706b358146720e9a8f9a6703908e1c7c1e3c6e8ffcdad7bfd002ea9f3ba580849c6b7cdc892b7afed0c298c0c39203b8fbe438cf89fd866538e10003f4b078d56fd0ab3e0b4793066bfe1a038d682d91d936ad94801a749516d914b01ccb2b8d476ed19b2bee677e3d9f099c302608e3e1e59cda1702118237523aa50f8abdfd7a0812fc92e32c732e35398b0ec7789ca37afb1ab9bd416a4a4c72a7e621181ecb80447d194526427dd7ba5e597774adbbac106081868213ff226049937df44fc714c5b91a711a27f8e201a9772b748579408c34a182eb22b3d257115fae9a45ccc85b3d5af6e8ebeda8a6aa6dfb9228717d1a62b059014d7991ded41fdacefa4a3d6ff75b190cdf155161e8bccd4a72ebc4dfdb5d7f68d14ee0c40bc2e91ea00c9cfe5bfc4bc78f168fd1d1ee74ee58712117d73fb550f1f48bf1195f5036e11c7531c32a8107c035acd197f084dae4f66c48c8d5076962d991579901354cfe11577add54915696da1b73632f99ce6256481acb8abf2ec996c3245ed4589d3f28514bd9ab85e8c04666c4762693f51073b2df89b7fadfb22b21873e476e1be0db62068689d83a85e71b4ef48133c1d2a47b2ea499b5a3ddd4e6eadbb208066c9bba3171aacf1e107b4f699bb9f9dc9c02115350dc58223ec08722bf48e6bdb9615ef167e92ac68bf43efbe1f7914cb2310ce08def0aa66d8a70f3b6ff7c184d12f644232f6fa7fcd199a324e6ad9ad11b713030c8ad40869bfd46828f1b6839461f02c4892d608d01d3e83c75a5021429942dfd2cb41b96e835e122674fd86f9b88a1afbe153a444a9487841f26def568f99bb47e8a763e2a9f2e63be1a61833d28b7f8847f7ff219143468640e72060b01d0d45453307c98a5806f11a162740b1935000bcd8cefb7873a56a326c6116abd3e53fffb262c88536002115591f1511c73665fa9368991a9275488f221fdf844e2dab1fd27c49db8c7fbb72a34ff977ed71ea9b1f7bf38a569914a179ae067ba4d3c047ac725af02032500616018e2bb35f6b3a4f8cea8017449996cb2c044d241778a344ec1604aa39087f78671c41f020f69b8c9a1e6ad14d34c44d56285c6433d0e310ee720ba60fd6c4c0242ef01499b30a764f3dc4e50f1910a3b6209819dddc5d84b68434f421685b40f913ebecd4847f2a457279c5d19f7ac84f48a45e434a282cf778bd2a4cffe45ac97d60fac95a67acbd6bd46b8ac0eb97fb04f3471002153a8b39695ceb8c7a1bc5f7e53a2d4bc22505b1146ad453db4004860701e033df042fd8f30fda07bd0071c037d812f763945176defe70dd1634767c0e13b060c34ab3f9c988ae2cc4caaa5ef7a20761d29b0aa99b3baca26d913c550a8609fdd429b84e676ffed70f021d81821ccbe963bc626f4a530e04bef1b446e32615",
b"859235209668766eaa014b00ebba6fded69404ecf70d668ffea316e86c7e0442d2f622b30e2a5e6e46756875888686c6cb924d5ef0e87252e1432746d17ec75476b867a85e23728f606b1b5edb6bbd44d27f34d7cac4e17c3aad514b7084be9ebc80f9a694ba7a3d1e275992821d579b415d117f1ee9efd08c22eef752ca9094dcba679ffa4ad8425ff08e99607ed1b0615569042e1b66cff13678b3264695d41ef34e07a68382f2c9dd1aa4d199fa0308d08512b7f99b78b031802ec9443d22ad5dce27e5398ee59ed6545353700197a076f09b90ff9723cc169a75438ec287cbf7c5d25d124e67a0ce24b777995663b44934de9705dca9f7fdc5da3ef2ffec8f0a0456b3c3d16686a5565a114a527dc815ff1c2bc7471c9299db1b921e89cfa051d85c0cf4c52e9dc71a98dd6ed2468f2feaa39f1b0e606bdb39a7e9350efd50f164a0a9a0a3d78c6701e01346a5c8748113d7677c51b0df14b7ace8c7485eb5d7f68d14ee0c40bc2e91ea00c9cfe5bfc4bc78f168fd1d1ee74ee58712117d79da08680cac547cf87dc3ff27931e68c7c60347a5d2273e40860ccba130b3fad071f41c6520e4396fbd9919dce274e90262475db045a0168ceaf531c3a609a141fe5c49c2d0128c84d8dd8bc9b533a2d0eec207cbbf733944cd98130800036dc34ecfcdd2cb26ca982a90e497e7a369cff1f60635e6254bd73ac25db7bd568099c3e56ef6dd3282b7548f65b208982de27d3cc8f2bb7e5ff04ce1d47f7630cdb2750d063e23e4f6bca5d70c03fa63a0e30ed8f8aace0e55cb9046c223cd3c81108a1dea0e7256c0013e5944aca8621da20fdd2b35bf9a770c40c742575cf00ac2626c1786bf06f506c73ebbbb809f6ad9e928922010963fe837aff4a794b07190e724f957aa19bb691139dd7484c36b4b874820c5bf64c4805e7f0aad37c41b86ce5a6a6bb046ee728d806af6dd7ec06b63b8d8c750e07f31fff745aa1d852da38290deecdfdd07ba69fc9ebb6e5a974980f0bafb2b40a84e477a2615f888f79c4afbede31c9f98c042fed8e53dae72f252bab739844f313ab096be711e9f1826eadcf52d558ab4b34695fdfbf71d8dad8655d38b5f6344a8f5a14bb0b45b19a74b18e3801af3bcf101b23df5c24167347024c55a4bfad2a53b11aa41442e73139be9b41d6d6b63e791d58bddd68ad2cd0bed00e3d364571c377620690bf06213ea2b3c114d255b9de4dffed31b96efb477d0a5058a6849f2edf6c424589687d05d2a8fefbdf72471d5f1f94c365db23f049e4a8b956448835cefa99744b67839ec27ca99f15c883482f74d83e096b9bd61ad16c1e4269e35174a9182386ca590750e591554d997d29a356e06ddcd333fcefc715bb00767d5343e635edc497f4cc3b448c822746410da03bc0e0a98dc110fcc0d76aea2671adc5fb5236fdd76bc37f10399e86ce431cfd0e7b13faaae68ff0cd9cc6ef6f048c3a8e22719665f779035dfff12c7064ef92143f7c626426027b2b31e2cfb37453fb69596212df8",
b"7599049a79972b1f3c5f9cc30f67d09f6ebc14ef1e2283121d79bacdc4e5e54b87e2fb04bda41e434ce76ed613f2ff7f6cb29c96509bee15d2fe4d329a1f2559a6e8addd8c2b188a0b1191619d96f99d0e71d5642a7d548580948d5f9204070ae8a0b2aec7714e234c0df324f7e8c23fb7d58dad8c596dcabd2b83bd5ecf7ee3fea7a3324a033fd0edc085446af3884c13a2bc59b0fa683fa3f3e0eff40a2c9adf8aff3ef4513f0f1ec6cfd864a51f66518608818aa503e1445733887731a188e27f6643afd9515e574a9666a8b886d30b727a770e3c01bee0bb544c4cefd5f496bac3c70d6f2be538dbc0f899e55ada287c07eec8652dfacf6f7137f8c1c7240a62c9236bb9e59763d71a1d8c0038b0f8eeb250a1a0123c1c115a5ff4b2d9b6fc25d01134e5b11fb958eae7e4f7bd2dd8abe466b19768048bd34fca2e34acbd11d3ff2e4f27cd1a39dfed5e35a9daddada20e93dbbed7cda6965857951c63a38a6810fc7bbc474643db9ab7295191a0252b047ac01607b1c8379129e7becacca969d7b5a80c7a32dd6ddfa6382560b40888b1381782e45fb4b4c32307d850df4469ba2dece48e3b78892728f0197f1a898be482b55bc4c6047e7a9fad8cf94b7fcdd01a37b810426d4808fbaa7ecf562b9a796e88a9bf97be43f2ae66abb2ac71ccc13e1f98c5f020f4cf57d1e920085a1cba729ccef9acfea71dfb3f81da7d67ec027e0f45c27195ad9979e06685f4e2b06fea4cbd1132aa3d5b4528af7d0eb3ec6ed26594f87ab53ab61b131b1b6ced53f65c2788d79f66f37c616c4c0c3abdd52a4654359b047a4caa148285c775934c0dfd884f8ee7f21ca606ff0777cd2a26f799ecc1865359173075e451a76dd6461c5f2141ae83e9531d824c8ed216e80d6187f0138023b94ccef65caf8c948e344534b4069fece83d93582d7828bd5a0dc4d99009cbabd31c1e82cf79fca22ac7ebadbbe1d2b601d7178a34f99ac61bafe3f6e2b081e3a5a1726df441905638440d2872c1835fbfcaa0e5bbc0054fa25edcf1256bbb312fb0bbe419a85de29df69032859120ea444f396d2eed136ace3e34f5ca830576472048f7a2c709c9546429b5cc75ed259ee0697d5f175af5ae351b5ff3abdb4f71e78005313d680c831f7c8ff05c14cddc26d6fb4ad80a6720cf1cd1938fe71f6fd4bcc71cda2ed7cdf6b6b792f91d25c36b80acadcfb6436d46dd6ed413b60873a7cd7a23927d0ab656257b0dd134e4f812c146dcd17e68ec0ff78fceec49dbfb45db861a561c100678ca68be00b912df9df3680f216e91b25301a2f92ceff1282a85e0825a0f02d05384f5a7dbb8bc17fcbc4c96b8e67e6423abba2bc9643498deda344dd5b6865634d481e5e312e4d1a2489bebccadf6dd3bde12737925493c5493389a3e79e56495013cbf0aebc4d5f836134119ddc6580aa555892d0c93a64e571bd4f39c20d720fc7cabf04ca7653d6a83adbedb94df4ad46605bdbc172f417015fbd8cd6402f4ac3d7abf55222ac759fc3888854b",
b"1322e6ac9c21bd93cce046b5ca1cd5832d8f71d3c2308fc5f64ddecd59b05d71ef93d7cfa83f4f73dc32b3bfe6004962cc1bdb69233d5cc3f8424ed4fcf6892fad6730aab296031505fd0d54ba05973fe917298e5de6def434399d3ea2ad6a97caf4570cfcf89c02af237da482f87ecdc2565af01b296612faced38cda3ff04022d4e66ddd08b7026c0ab12bc072dbbb7dc5dd850d14f19e3ee28c5fe481f2d48209b93911c309528d904177b43930e9dca48040417bd1d713bf8fdc6b46220c3ad1ef9f8c7f52aed0920886a129981d3d930135a01e473c838bfd47544e002e5816f1665327d4ffebc76ce6b8a81756227f1f4ed924df72b13edd3719df89ad461ee175ecaea51c47625c86b531bbd8225ddc8350677877264283bd4411f5b16e92835ef9163b821e6a354ece850310fe228d0464b029ac8c80717a5ac45a96a49e76187e88a825a51095c9e0803ed7f298632cb3559165c07d15cddc08461b432dde857fbb03933527e5ddc287c8248a16c80d2ac1bc1a0c0eda7e153b971acf7bf968d049a6c13004fde80d47915f5dbca092586cfcbfcee6f4dbd9075dad64e6b37d7ccfac7cd18c01a40a34ce711476e217e9b2d65923cbdcc9c70036844e9f2624af0eff20e0cc3a464ca8e080a1b85cb6bf305f24c9e9123bb3334e63ea0cd7abb4569ea1c217692bed35e4e8e423202b4290d103406a5833bfa561b429f15b05a29c67e3a8196038cd83565e1157be8b80ab4f1d0b38a15f019b5ecc71b516b2dddfc1f9caf333775dad678894d5bce227c7b39a1da514e6657f68247a01f1064a466b13b7aa58ec7dbeeaf7ae79abe789e9747af52898027dbb8e601f18d3624726828875b1ed714b2f04ba1f5448233f3813a7741b2c0226af16c7cbb5efa49c0b1649014afff05557c159e9cdbc873a349038129b2a31fa8210d64e5f1bdbe905d0dca4528b68be9b0c67bb88463982492f3991489e473844307617d16084e6bffb5180fe045be176391b47eeb30089373836f889982c13319718b6f378bab1d14e4ae0093905c1d0511ba8da616dd20b29c395eadc0f8eda92369898339ba916edc188dd8efbf2412ad53a996f9e5febf361bad73833cfe348b95ae1d4124ce5dc6a734446a45fbf23396e2559c6a06d50f286a01ee589476ca37c4b7abed1053e3d7fb29a234779d3024970b6060ffd6d76b44b3da0de9cc90da59ae3354b3bc7c7861849dcf095a6ccb9e4e4a4ff0d828650a8ba0b44924f52d6ca82bc46b9fd007bda66226544cf2826e984fb9f5475d25a65a6f045db7638c7012deb4f7af394ec388eb88d3da74329d3626d1ea75bd40edb106c9f9821edc9c5b65f8060b57b6b44e2e33ec31bf913b607e4408484cbf18c32628588c259b22c061a34334aea7cd17ae185289835a6833a3fb433961a6a4a194b4207cf7a6570ed9c9a57f9eeaad0570b889c3c9a82591d621859cb83b38b2df000ba43b3fea9113d2fbdec41460991864df26441381a87c56eba01bc007bc11b2f581698"
]
def observe(challs,proofs):
seqs=[]
for elt in challs:
digest = sha256(binascii.unhexlify(elt)).hexdigest()
seq = hash_based._sequence(digest)
seqs.append(seq)
_ = []
hashes=[]
for j in range(len(seqs[0])):
_.append(257)
hashes.append(0)
for i in range(len(seqs)):
if seqs[i][j] < _[-1]:
_[-1]=seqs[i][j]
hashes[-1]=proofs[i][j*32*2:(j+1)*32*2].decode()
return _,hashes
known_seq,hashes=observe(challs,proofs)
print(known_seq,hashes)
for i in range(len(challs)):
forged = hash_based.forge(binascii.unhexlify(challs[i]),hashes,known_seq)
assert forged[-1].encode() == proofs[i]
print(forged[-1].encode())
print(proofs[i])
print("========")
#print(len(proofs[0]),len(forged[-1]))
#print(hash_based._sequence(sha256(b"toto").hexdigest()))
print("......")
print("Going Live....")
context.log_level = 'error'
i=1
while 1:
io = remote("hashsig.chall.malicecyber.com",4998)
io.send(b"c")
#receive "l"
io.recv(1)
#print(bytes("admin","utf-8"))
io.send(bytes("admin","utf-8"))
chall = io.recv(timeout=1)
#print("***",chall)
print(i,end="\r")
what,forged = hash_based.forge(chall,hashes,known_seq)
#print(what,binascii.unhexlify(forged))
if what:
io.send(binascii.unhexlify(forged))
io.send(b"g")
#print(io.recvall())
io.interactive()
input(">>>")
else:
io.close()
del io
i+=1
#io.interactive()
```
gives after 200 iterations (in average)
```
e33ec31bf913b607e4408484cbf18c32628588c259b22c061a34334aea7cd17ae185289835a6833a3fb433961a6a4a194b4207cf7a6570ed9c9a57f9eeaad0570b889c3c9a82591d621859cb83b38b2df000ba43b3fea9113d2fbdec41460991864df26441381a87c56eba01bc007bc11b2f581698'
========
......
Going Live....
m[DGA{CrYpt0#_#7&}]$
```