Posted at

TSG CTF Recorded [Misc] write-up

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

#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を再生成した。

# 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()を以下のように再改造した。

static uint64_t get_time_stamp(void)

{
return 1556368884;
}

OpenSSLのソースコードを読んでkey.pem生成をランダム化する3つの因子があることを理解した。それは、/dev/random、生成時刻、生成するプロセスのIDであった。

解読結果から正しい/dev/randomと正しい生成時刻を知った。しかし、正しいPIDは分からないままだった。

したがって、encryptedをコンテナの中へコピーし、以下のコマンドで総当たり攻撃を行った。

# 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を見つけた。

# cat flag-2936.txt 

TSGCTF{openssl_genrsa_is_hardly_predictable}