diff --git a/Cargo.toml b/Cargo.toml index c8fe0c3..0c962fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ anyhow = "1.0.86" async-trait = "0.1.82" clap = { version = "4.5.17", features = ["derive"] } libpt = { version = "0.7.1", features = ["cli", "log"] } +rand = "0.8.5" serde = { version = "1.0.209", features = ["derive"] } serde_json = "1.0.128" tokio = { version = "1.40.0", features = [ diff --git a/solutions/3.py b/solutions/3.py new file mode 100644 index 0000000..c5bcd8a --- /dev/null +++ b/solutions/3.py @@ -0,0 +1,73 @@ +from io import TextIOWrapper +import socket + +REMOTE = "127.0.0.1" +PORT = 1337 +MAX_GUARD = 300 + +def calc(a: int, b: int, op: str) -> int: + match op: + case '+': return a+b + case '-': return a-b + case '*': return a*b + case '/': return int(a/b) + case _: raise Exception(f"bad op: {op}") + +def main() -> int: + response: str = "" + s = socket.socket() + s.connect((REMOTE, PORT)) + sf: TextIOWrapper = s.makefile('rw') + guard = 0 + res = 0 + + while guard < MAX_GUARD: + guard += 1 + response = recv(sf) + + if "What" in response: + parts = response.strip().replace('?',"").split(' ') + print(f"# {parts}") + a = int(parts[2]) + op = parts[3] + b = int(parts[4]) + + print(f"# {a} {op} {b}") + + res: int = calc(a,b,op) + + _ = send(sf, str(res)) + + elif "correct" in response: + print(f"# we got one right: {response}") + + elif "wrong" in response: + print(f"! our answer was not accepted: '{response}'") + s.close() + return 2 + + elif "win" in response: + print(f"# We won: '{response}'") + break + + else: + print(f"! unknown response: '{response}'") + s.close() + return 1 + s.close() + return 0 + +def send(sf: TextIOWrapper, msg: str) -> int: + print(f"> {msg}") + sent: int = sf.write(f"{msg}\n") + sf.flush() + return sent + +def recv(sf: TextIOWrapper) -> str: + response = sf.readline().strip() + print(f"< {response}") + return response + + +if __name__ == "__main__": + exit(main()) diff --git a/src/challenge/c3.rs b/src/challenge/c3.rs new file mode 100644 index 0000000..ba3bb5f --- /dev/null +++ b/src/challenge/c3.rs @@ -0,0 +1,183 @@ +use std::fmt::Display; +use std::net::SocketAddr; + +use anyhow::Result; +use async_trait::async_trait; +use libpt::log::{debug, info, warn}; +use rand::distributions::{Distribution, Standard}; +use rand::{random, Rng}; +use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; +use tokio::net::{TcpListener, TcpStream}; + +use super::Challenge; +use crate::config::Config; +use crate::has_won; +use crate::vault::VaultRef; + +const NEEDED_CORRECT: usize = 16; + +#[derive(Copy, Clone)] +enum Operation { + Add, + Sub, + Div, + Mul, +} + +impl Operation { + pub(crate) fn calc(self, a: i32, b: i32) -> i32 { + match self { + Self::Add => a.overflowing_add(b).0, + Self::Sub => a.overflowing_sub(b).0, + Self::Div => a.overflowing_div(b).0, + Self::Mul => a.overflowing_mul(b).0, + } + } +} + +impl Display for Operation { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::Add => "+", + Self::Sub => "-", + Self::Mul => "*", + Self::Div => "/", + } + ) + } +} + +impl Distribution for Standard { + fn sample(&self, rng: &mut R) -> Operation { + match rng.gen_range(0..3) { + 0 => Operation::Add, + 1 => Operation::Sub, + 2 => Operation::Mul, + 3 => Operation::Div, + _ => unreachable!(), + } + } +} + +pub struct C3 { + config: Config, + vault: VaultRef, +} + +impl C3 { + fn mk_question() -> (String, String) { + let mut rng = rand::thread_rng(); + let a: i32 = rng.gen_range(0i32..i16::MAX as i32); + let b: i32 = rng.gen_range(0i32..i16::MAX as i32); + let op: Operation = random(); + let res = op.calc(a, b); + + let question = format!("What is {a} {op} {b}?"); + let solution = format!("{}", res); + (question, solution) + } + + async fn handler(peer: (TcpStream, SocketAddr), vault: &VaultRef) -> Result<()> { + let mut stream = peer.0; + let addr = peer.1; + let mut correct: usize = 0; + + let mut buf: Vec = Vec::new(); + + let (stream_read, mut stream_write) = stream.split(); + let mut rdbuf = BufReader::new(stream_read); + + loop { + if correct >= NEEDED_CORRECT { + Self::win(vault, &mut stream, &addr).await?; + break; + } + let (question, solution) = Self::mk_question(); + debug!("sending question to {addr}: '{question}'"); + stream_write.write_all((question + "\n").as_bytes()).await?; + rdbuf.read_until(0x0A, &mut buf).await?; // read until a newline comes + let answer = String::from_utf8_lossy(&buf).trim().to_string(); + if answer == solution { + correct += 1; + debug!("{addr} got one correct"); + stream_write + .write_all(format!("correct! {correct} / {NEEDED_CORRECT}\n").as_bytes()) + .await?; + } else { + debug!("{addr} screwed up, {answer} != {solution}"); + stream_write + .write_all("wrong!\n".to_string().as_bytes()) + .await?; + correct = 0; + } + buf.clear() + } + + Ok(()) + } + + async fn win( + vault: &VaultRef, + stream: &mut TcpStream, + addr: &std::net::SocketAddr, + ) -> Result<()> { + has_won(addr); + if let Err(e) = stream + .write_all(("You win: ".to_owned() + vault.secret()).as_bytes()) + .await + { + warn!("could not write to peer {addr}: {e}"); + return Err(e.into()); + }; + if let Err(e) = stream.shutdown().await { + warn!("could end connection to peer {addr}: {e}"); + return Err(e.into()); + }; + Ok(()) + } +} + +#[async_trait] +impl Challenge for C3 { + fn new(config: Config, vault: VaultRef) -> Self { + info!("Solution: {}", Self::solution()); + Self { config, vault } + } + + fn hints() -> Vec { + vec![ + "TCP connect to 1337 and answer the questions".to_string(), + "The questions keep chaning".to_string(), + "You should try to solve the questions in an automated way".to_string(), + ] + } + + fn solution() -> String { + String::from("Connect by TCP, send 1337 as bytes (not text).") + } + + async fn serve(self) -> anyhow::Result<()> { + info!("serving challenge 3"); + let listener = TcpListener::bind(self.config.addr).await?; + + loop { + let vault = self.vault.clone(); + let peer = match listener.accept().await { + Ok(s) => s, + Err(err) => { + warn!("could not accept tcp stream: {err:?}"); + continue; + } + }; + info!("new peer: {}", peer.1); + tokio::spawn(async move { + if let Err(e) = Self::handler(peer, &vault).await { + warn!("error while handling peer: {e}"); + } + }); + } + } +} diff --git a/src/challenge/mod.rs b/src/challenge/mod.rs index 0e90fca..49ac589 100644 --- a/src/challenge/mod.rs +++ b/src/challenge/mod.rs @@ -6,6 +6,7 @@ use crate::vault::VaultRef; pub mod c1; pub mod c2; +pub mod c3; #[async_trait] pub trait Challenge @@ -22,6 +23,7 @@ pub async fn select_and_start(index: u16, config: Config, vault: VaultRef) -> an match index { 1 => c1::C1::new(config, vault).serve().await?, 2 => c2::C2::new(config, vault).serve().await?, + 3 => c3::C3::new(config, vault).serve().await?, _ => { return Err(anyhow!( "no challenge with index {index} does currently exist"