---
# System prepended metadata

title: CSAW CTF Qual 2021

---

# CSAW CTF Qual 2021

## Forgery (127 solves, 405pts)

Felicity and Cisco would like to hire you as an intern for a new security company that they are forming. They have given you a black box signature verification system to test out and see if you can forge a signature. Forge it and you will get a passphrase to be hired!

:::spoiler **Source Code**
```python=
from Crypto.Util.number import getPrime
from random import randint
from math import gcd

with open("flag.txt",'r') as f:
	flag = f.read()

p = getPrime(1024)
g = 3
MASK = 2**1024 - 1

def gen_keys():
	x = randint(1, p-2)
	y = pow(g, x, p)
	return (x, y)

def sign(answer: str, x: int):
	while True:
		m = int(asnwer, 16) & MASK
		k = randint(2, p-2)
		if gcd(k, p - 1) != 1:
			continue 
		r = pow(g, k, p)
		s = (m - x*r) * pow(k,-1,p-1) % (p - 1)
		if s == 0:
			continue
		return (r,s)

def verify(answer: str, r: int, s: int, y: int):
	m = int(answer, 16) & MASK
	if any([x <= 0 or x >= p-1 for x in [m,r,s]]):
		return False
	return pow(g, m, p) == (pow(y, r, p) * pow(r, s, p)) % p

def main():
	x, y = gen_keys()
	print(f"Server's public key (p,g,y): {p} {g} {y}")
	print("Who do you think is the tech wizard: Felicity or Cisco or both? Please answer it with your signnature (r,s)")
	print('Answer: ')
	answer = input()
	print('r: ')
	r = int(input())
	print('s: ')
	s = int(input())
	answer_bytes = bytes.fromhex(answer)

	if b'Felicity' not in answer_bytes and b'Cisco' not in answer_bytes and b'both' not in answer_bytes:
		print("Error: the answer is not valid!")
	elif verify(answer, r, s, y):
		if b'Felicity' in answer_bytes:
			print("I see you are a fan of Arrow!")
		elif b'Cisco' in answer_bytes:
			print("I see you are a fan of Flash!")
		else:
			print("Brown noser!")
		print(flag)
	else:
		print("Error: message does not match given signature")

if __name__ == "__main__":
	main()
```
:::

### Overview 

The server asks you to provide a tuple $(m, r, s)$ such that the corresponding bytes of $m$ contains a keyword and $$ g^{m \pmod{2^{1024}}} \equiv y^r \times r^s \pmod{p} $$

### Valid Signature Forgery

First of all, we need to find, at least, a valid tuple $(m, r, s)$. However, computing the discrete log of $y$ seems impossible. Notice that the constructable power of $y$ is $1$, a reasonable choice of $(r,s)$ is $$ (y, \quad -y \pmod{p-1}) $$

Now, for LHS, we need $0 < m < p-1$ with $g^{m} \equiv 1 \pmod{p}$. One may take $m = \frac{p-1}{2}$ and hope it works :D

### Sign Message Containing Given Keyword

This is quite straightforward. Because the server only cares the residue of $m$ divided by $2^{1024}$, we simply change our $m$ to $$ Cisco \times 2^{1064} + \frac{p-1}{2} $$

### Capture the Flag

```python=
if pow(g, (p-1) // 2, p) != 1:
        r.close()
        print("3 is primitive root of p G_G")
        exit()

answer = (p-1) // 2 + bytes_to_long(b"Ciscoo" + b"\x00" * 133)

r.sendline(hex(answer)[2:])
r.sendline(str(y))
r.sendline(str(p-1-y))

r.recvuntil("I see you are a fan of Flash!")
r.recvline()

flag = r.recvline()
print(flag)
```

:::success
flag{7h3_4rr0wv3r53_15_4w350M3!}
:::

## ECC Pop Quiz (63 solves, 478 pts)

Your crypto professor has taught y'all about ECC and now, here's a pop quiz regarding some of the attacks on it. See if you can pass the quiz!

### Overview

1. Smart Attack
2. Moving Attack
3. Singular Curve

### Capture the Flag

:::success
flag{4Ll_0f_tH353_4tT4cK5_R3lY_0N_51mPl1FY1n9_th3_D15cr3t3_l09_pr08l3m}
:::

## Bits (24 solves, 497 pts)

I wrote this oracle in rust so that it can't sue companies over java stuff.

:::spoiler **Source Code**
```rust=
use std::io::BufRead;
use getrandom::getrandom;
use rug::{
    rand::{RandGen,RandState},
    Integer
};
use sha2::{Sha256,Digest};
use aes::{Aes256,Aes256Ctr,NewBlockCipher,cipher::{FromBlockCipher,StreamCipher}};
use generic_array::GenericArray;

// Secret sauce
// N = p*q; p ≡ q ≡ 3 (mod 4); p, q prime
use hardcore::{dlog, N, G, ORDER, FLAG};

struct SystemRandom;
impl RandGen for SystemRandom {
    fn gen(&mut self) -> u32 {
        let mut buf: [u8; 4] = [0; 4];
        let _ = getrandom(&mut buf).unwrap();
        ((buf[0] as u32) << 24) | ((buf[1] as u32) << 16) | ((buf[2] as u32) << 8) | (buf[3] as u32)
    }
}

fn encrypt_flag(shared: Integer) {
    let mut hasher = Sha256::new();
    hasher.update(shared.to_string());
    let key = hasher.finalize();
    let mut cipher = Aes256Ctr::from_block_cipher(
        Aes256::new_from_slice(&key.as_slice()).unwrap(),
        &GenericArray::clone_from_slice(&[0; 16])
        );
    let mut flag = FLAG.clone();
    cipher.apply_keystream(&mut flag);
    println!("FLAG = {}", flag.iter().map(|c| format!("{:02x}", c)).collect::<String>());
}

fn main() {
    println!("+++++++++++++++++++++++++++++++++++++++++++++++\n\
              + I hear there's a mythical oracle at Delphi. +\n\
              +++++++++++++++++++++++++++++++++++++++++++++++\n");
    let mut sysrng = SystemRandom;
    let mut rnd = RandState::new_custom(&mut sysrng);
    let d = Integer::from(&*ORDER).random_below(&mut rnd);
    let publ = Integer::from(&*G).pow_mod(&d, &*N).unwrap();
    let nbits = ORDER.significant_bits();
    let alice = Integer::from(&*G).pow_mod(&Integer::from(&*ORDER).random_below(&mut rnd), &*N).unwrap();
    println!("N = {}\nG = {}\npubl = {}\nalice = {}\nnbits = {}",
        *N,
        *G,
        publ,
        alice,
        nbits);
    encrypt_flag(alice.pow_mod(&d, &N).unwrap());
    for line in std::io::stdin().lock().lines() {
        let input = line.unwrap().parse::<Integer>().unwrap();
        match dlog(input.clone()) {
            None => println!("-1"),
            Some(x) => {
                assert!(G.clone().pow_mod(&x, &*N).unwrap() == input % &*N);
                assert!(x < *ORDER);
                assert!(x >= 0);
                println!("{}", x.get_bit(nbits - 123) as i32)
            }
        }
    }
}
```
:::

### Overview

The server uses static generator and modulus $g, N$ and generates two public keys $A, B$ as follows: $$ A = g^{r_1}, \quad B = g^{r_2} \pmod{N} $$

where $r_1, r_2$ are two random numbers. Then the server calculates the shared secret $$A^{r_2} = g^{r_1r_2} = B^{r_1} \pmod{N} $$ and use it to encrypt the flag by AES256 in CTR mode.

The server also provides a discrete-log oracle. For a given input $y$, it finds $x$ so that $$ g^{x} \equiv y \pmod{N} $$ If no such $x$, then it returns $-1$. Otherwise, it return $$ \begin{cases} 1 \quad & \textit{if} \quad x \& 2^{883} > 0 \\ 0 \quad & \textit{otherwise} \end{cases} $$

### 

To decrypt the flag, it suffices find the secret exponent $r_1$. Notice that one may get a bit of $$r_1 + k \pmod{Ord_{N}(g)} $$ by sending $A \times g^{k} \pmod{N}$ to the oracle.

### Retrieve LSBs of $r_1$

Suppose the binary representation of $r_1$ is $b_{n}b_{n-1}\dots b_{0}$ or equivalently, $$ r_1 = \sum\limits_{i=0}^{n}{b_{i}\cdot 2^{i}} $$ Further, let $m$ be the smallest index such that $$ b_m \neq 0, \quad m > 883 $$ The key observation is $$ r_1 - \left(\sum\limits_{i=a+1}^{883}{b_i\cdot 2^{i}} + 2^{a}\right) = \begin{cases} \\ \\ \sum\limits_{i=m+1}^{1006}{b_i\cdot 2^{i}} + \sum\limits_{i=a}^{m-1}{\cdot 2^{i}} + \sum\limits_{i=0}^{a-1}{b_i\cdot 2^{i}} & \quad \textit{if} \quad b_a = 0 \\ \\ \sum\limits_{i=884}^{1006}{b_i\cdot 2^{i}} + \sum\limits_{i=0}^{a-1}{b_i\cdot 2^{i}} & \quad \textit{if} \quad b_a = 1 \end{cases} $$ So the oracle returns $1$ iff $b_{a} = 0$ and we can sequentially determine the lower bits of $r_1$

```python=
d_lsb = 0

for i in range(nbits - 123, -1, -1):
    k = d_lsb | (1 << i)
    payload = (A * pow(g, -k, N)) % N

    r.sendline(str(payload))

    _ = r.recvline()
    if b"0" in _:
        d_lsb |= (1 << i)
```

### Binary Search MSBs of $r_1$

Note that $$ Ord_{N}(g) \ \mid \ \phi(N) < N $$ We know that the bit-lengths of $Ord_{N}(g), N$ are $1006, 1007$, respectively. One may expect that $Ord_{N}(g)$ is exactly $\phi(N) = N - p - q + 1$ and it follows that the MSBs of $Ord_{N}(g)$ and that of $N$ are the same.

For convenience, we define two functions $MSBs$ and $LSBs$ by the following rules: $$ MSBs(x) = \left\lfloor\frac{x}{2^{884}}\right\rfloor, \quad LSBs(x) = x \pmod{2^{884}} $$

To apply binary search, we need to distinguish whether $MSBs(r_1)+MSBs(k)$ is larger than $MSBs(Ord_{N}(g))$. The following process will only work for the case that $883$-th bit of $Ord_{N}(g)$ equals to $0$.

We're going to find $MSBs(Ord_{N}(g))-MSBs(r_1)$ by binary search. Begin with bounds $L, H = 0, MSBs(Ord_{N}(g))$

* Each time, we send $$A \times g^{-LSBs(r_1)} \times g^{m \cdot 2^{884}} \pmod{N}$$ where $m=\lfloor\frac{L+H}{2}\rfloor$
* If $MSBs(r_1) + m \le MSB(Ord_{N}(g))$, then it's clear that the oracle returns $0$.
* Otherwise, the oracle returns the $883$-th bit of the following number $$ M = (MSBs(r_1)+m) \times 2^{884}-Ord_{N}(g) $$ which equals to $$ (MSBs(r_1)+m-MSBs(Ord_{N}(g))-1) \times 2^{884} + (2^{884}-LSBs(Ord_{N}(g))) $$ Assume the binary representation of $LSBs(Ord_{N}(g))$ is $c_{883}c_{882}\dots c_{0}$ Then $LSBs(M)$ is $$2^{884}-LSBs(Ord_{N}(g)) = \sum\limits_{i=0}^{883}(1-c_{i})\cdot 2^{i} + 1$$ Because $c_{883} = 0$, the oracle returns $1$
* Update $L, H$ according to the output

```python=
T = (A * pow(g, -d_lsb, N)) % N

L, H = 0, ord_msb

while L + 1 < H:

    mid = (L + H) // 2
    k = mid * (1 << (nbits - 123 + 1))
    payload = T * pow(g, k, N)

    r.sendline(str(payload))

    _ = r.recvline().strip()

    if b"1" in _:
        H = mid
    else:
        L = mid

d_msb = ord_msb - L
```

### Capture the Flag

So we have the secret exponent $$ r_1 = MSBs(r_1) \times 2^{884} + LSBs(r_1) $$ and the shared secret $$B^{r_1} \pmod{N} $$ Let's decrypt the ciphertext!

```python=
d = d_msb * (1 << (nbits - 123 + 1)) + d_lsb

shared_secret = pow(B, d, N)

Sha = hashlib.sha256()
Sha.update(str(shared_secret).encode('ascii'))
key = Sha.digest()

cipher = AES.new(key, AES.MODE_CTR, nonce = b"\x00" * 8)
flag = cipher.decrypt(encrypted_flag)
print(pt)
```

:::success
flag{https://www.youtube.com/watch?v=uhTCeZasCmc}
:::
