Analysis of the bug =================== The program contains an information leak bug. The `get_data()` function sends the contents of the uninitialized reply local variable when `cmd` is neither `p`, nor `n`. By sending an unexpected `cmd`, an attacker can read part of the stack frame of other functions called before `get_data()`. Attack implementation ===================== The `check_pin()` function checks that the input pin matches the secret pin. The check stops at the first non matching character, with `i` containing the index of this character. The value of `i`, if known to the attacker, could be used to discover the PIN, by trial and error, one character at a time. The idea is to use the information leak bug to steal the value of the `i` variable in the `check_pin()` function. An analysis of the binary reveals that the compiler has not optimized-out the variable and has placed it at the same relative position in `check_pin()`'s stack frame as the reply variable in the `get_data()`'s stack frame. The attack plan is therefore as follows: repeatedly send a `p$PIN` command, with a variable `$PIN`, immediately followed by a `gg` command, where `g` is used to trigger the bug in `get_data()`. Extract the value of `i` from the reply to the `gg` command to learn how many bytes in the prefix of `$PIN` where correct. If `i` is larger than the one extracted in the previous cycle, we have found a new digit of the PIN. Otherwise, select a new digit for the `i`-th position and try again. The entire PIN will be revealed in a very short time. The attacker must be sure that the contents of `i` are not overwritten between the call to `check_pin()` and the call to `get_data()`. This can be easily accomplished, since the `child()` function can process several commands, one after the other, in the same input line. Note that, for this to work, each command must be exactly `CMDSZ` bytes, so we must also add enough padding bytes. The following python3 script implements the attack just described: ``` python import socket host = 'lettieri.iet.unipi.it' port = 10000 pinsz = 15 s = socket.create_connection((host, port)) pin = '' for _ in range(pinsz): for j in range(10): c = chr(j + 0x30) p = "p" + pin + c + "-" * (16 - len(pin) - 2) + "g" * 16 + "\n" s.send(p.encode()) r = s.recv(100).decode() if r.startswith("PIN OK"): print("PIN: " + pin + c) break if int(r) > len(pin): pin += c break ``` Suggested fix ============= The `reply` variable in `send_data()` should be initialized to a constant value. The function should probably also return an error if the subcommand is not recognized. As an additional security measure, the `check_pin()` function should strive to perform the same amount of work for any input, e.g., by completing the loop and continuing to check the input PIN digits even after the first failed test. This could be useful (in a more complex program) against attempts to extract the value of `i` using time-based side channels.