TSG CTF Recorded [Misc] write-up

以下のスクリプトを使って`input`を解読した。

```c
#include <assert.h>
#include <fcntl.h>
#include <linux/input.h>
#include <stdio.h>
#include <sys/time.h>

int main(void)
{
  int fd = open("input", O_RDWR);
  assert(fd != -1);

  for (;;) {
    struct input_event event;
    assert(read(fd, &event, sizeof(event)) == sizeof(event));
    if (event.type == 0x1 && event.value == 0x1) {
      switch (event.code) {
        case KEY_1:
          putchar('1');
          break;
        case KEY_2:
          putchar('2');
          break;
        case KEY_3:
          putchar('3');
          break;
        case KEY_4:
          putchar('4');
          break;
        case KEY_5:
          putchar('5');
          break;
        case KEY_6:
          putchar('6');
          break;
        case KEY_7:
          putchar('7');
          break;
        case KEY_8:
          putchar('8');
          break;
        case KEY_9:
          putchar('9');
          break;
        case KEY_0:
          putchar('0');
          break;
        case KEY_MINUS:
          putchar('-');
          break;
        case KEY_EQUAL:
          putchar('=');
          break;
        case KEY_Q:
          putchar('q');
          break;
        case KEY_W:
          putchar('w');
          break;
        case KEY_E:
          putchar('e');
          break;
        case KEY_R:
          putchar('r');
          break;
        case KEY_T:
          putchar('t');
          break;
        case KEY_Y:
          putchar('y');
          break;
        case KEY_U:
          putchar('u');
          break;
        case KEY_I:
          putchar('i');
          break;
        case KEY_O:
          putchar('o');
          break;
        case KEY_P:
          putchar('p');
          break;
        case KEY_LEFTBRACE:
          putchar('(');
          break;
        case KEY_RIGHTBRACE:
          putchar(')');
          break;
        case KEY_ENTER:
          putchar('\n');
          break;
        case KEY_A:
          putchar('a');
          break;
        case KEY_S:
          putchar('s');
          break;
        case KEY_D:
          putchar('d');
          break;
        case KEY_F:
          putchar('f');
          break;
        case KEY_G:
          putchar('g');
          break;
        case KEY_H:
          putchar('h');
          break;
        case KEY_J:
          putchar('j');
          break;
        case KEY_K:
          putchar('k');
          break;
        case KEY_L:
          putchar('l');
          break;
        case KEY_SEMICOLON:
          putchar(';');
          break;
        case KEY_APOSTROPHE:
          putchar('\'');
          break;
        case KEY_GRAVE:
          putchar('`');
          break;
        case KEY_BACKSLASH:
          putchar('\\');
          break;
        case KEY_Z:
          putchar('z');
          break;
        case KEY_X:
          putchar('x');
          break;
        case KEY_C:
          putchar('c');
          break;
        case KEY_V:
          putchar('v');
          break;
        case KEY_B:
          putchar('b');
          break;
        case KEY_N:
          putchar('n');
          break;
        case KEY_M:
          putchar('m');
          break;
        case KEY_COMMA:
          putchar(',');
          break;
        case KEY_DOT:
          putchar('.');
          break;
        case KEY_SLASH:
          putchar('/');
          break;
        case KEY_SPACE:
          putchar(' ');
          break;
        case KEY_LEFTSHIFT:
          printf("<shift>");
          break;
        case KEY_TAB:
          printf("<tab>");
          break;
        case KEY_BACKSPACE:
          printf("<bs>");
          break;
        default:
          printf("<%d>", event.code);
      }
      //printf(": %lu\n", event.time.tv_sec);
    }
  }
  close(fd);
}
```
結果は以下の通り。

```
rm /dev/ura<tab>
rm /dev/ra<tab>
<shift>lang-c date --utc <shift>. /dev/random
echo nyan <shift>.. /dev/ra<tab>
curl -<shift>o https'//www.openssl.org/source/openssl-1.1.1b.tar.gz
tar xzvf ope<tab>
cd ope<tab><tab>
vim cry<tab>ra<tab>rand<shift><89>unix.c
637<shift>gd17d621<shift>gd2d603<shift>gdd480<shift>gd30d'wq
vim cr<tab>ra<tab>ra<tab><shift><89>li<tab>
250<shift>gd2d'wq
./co<tab>
make -j 4
cd ..
<shift>ld<89>library<89>path-./ope<tab> ./ope<tab>app<tab>ope<tab>genrsa 1024 <shift>. key.pem
<shift>ld<89>library<89>path-./ope<tab> ./ope<tab>ap<tab>ope<tab>rsautl -encrypt -inkey key.<tab>-in fl<tab>-out encrypted
fd<bs>g 1
```
guessしながら結果を整形した。

```
rm /dev/ura<tab>
rm /dev/ra<tab>
LANG=C date --utc > /dev/random (enter key tv_sec = 1556368668)
echo nyan >> /dev/ra<tab>
curl -O https://www.openssl.org/source/openssl-1.1.1b.tar.gz
tar xzvf ope<tab>
cd ope<tab><tab>
vim cry<tab>ra<tab>rand_unix.c
637Gd17d621Gd2d603Gdd480Gd30d:wq
vim cr<tab>ra<tab>ra<tab>_li<tab>
250Gd2d:wq
./co<tab>
make -j 4
cd ..
LD_LIBRARY_PATH=./ope<tab> ./ope<tab>app<tab>ope<tab>genrsa 1024 > key.pem (enter key tv_sec = 1556368884)
LD_LIBRARY_PATH=./ope<tab> ./ope<tab>ap<tab>ope<tab>rsautl -encrypt -inkey key.<tab>-in fl<tab>-out encrypted
fg 1
^C
```
与えられた`Dockerfile`を使って`problem`イメージを生成し`problem`上でコンテナを開始した。
コンテナの中で、以下のコマンドで`/dev/urandom`と`/dev/random`を削除し`/dev/random`を再生成した。

```shell
# rm /dev/urandom
# rm /dev/random
# LANG=C date --utc --date="@1556368668" > /dev/random
# echo nyan >> /dev/random
```
解読結果に従って改造されたOpenSSLバイナリを作った。しかし、`crypto/rand/rand_unix.c`の中の`get_time_stamp()`を以下のように再改造した。

```c
static uint64_t get_time_stamp(void)
{
    return 1556368884;
}
```
OpenSSLのソースコードを読んで`key.pem`生成をランダム化する3つの因子があることを理解した。それは、`/dev/random`、生成時刻、生成するプロセスのIDであった。
解読結果から正しい`/dev/random`と正しい生成時刻を知った。しかし、正しいPIDは分からないままだった。
したがって、`encrypted`をコンテナの中へコピーし、以下のコマンドで総当たり攻撃を行った。

```shell-session
# for ((i = 0; i < 40000; i++)); do LD_LIBRARY_PATH=./openssl-1.1.1b ./openssl-1.1.1b/apps/openssl genrsa 1024 > key.pem && LD_LIBRARY_PATH=./openssl-1.1.1b ./openssl-1.1.1b/apps/openssl rsautl -decrypt -inkey key.pem -in encrypted -out flag-${i}.txt; done
# ls -lS | more
```
こうして、flagを見つけた。

```shell-session
# cat flag-2936.txt 
TSGCTF{openssl_genrsa_is_hardly_predictable}
```