Showing posts with label buffer overflow. Show all posts
Showing posts with label buffer overflow. Show all posts

Friday, June 28, 2013

What SELinux is not!

If you're interested about the benefits of running SELinux, probably you read the some of documentations where was written something like that:

SElinux is not:
- antivirus software
- firewalls
- an all-in-one security solution

But what this exactly means? Where is the beginning and ends the SELinux protection? To better answering those questions I show two examples, where SELinux is work (block) and where is not. This examples is are a buffer overflow and shellcode. But first we must build a test domain where we can test this examples.


Test domain


The test domain has a minimal privileges to run a simple process via normal system user (and SELinux user "user_u"). We will create a new TE domain as a SELinux policy module. This domain is called process_test_t and we run our two codes example - the buffer overflow and shellcode.

File prosess_test.te:

module process_test 0.1;

require {
        type user_t;
        attribute domain,application_domain_type,ubac_constrained_type,file_type,exec_type,entry_type,non_security_file_type,application_exec_type;
        class file { execute entrypoint };
        class process { transition sigchld  };
}

type process_test_t,
        domain,
        application_domain_type,
        ubac_constrained_type;

type process_test_exec_t,
        file_type,
        exec_type,
        entry_type,
        non_security_file_type,
        application_exec_type;

type_transition user_t process_test_exec_t:process process_test_t;
role user_r types { process_test_exec_t process_test_t };

allow user_t process_test_t:process transition;
allow process_test_t process_test_exec_t:file entrypoint;

allow process_test_t user_t:process sigchld;


# for terminal
require {
        type user_devpts_t,sshd_t;
        class chr_file { read write getattr };
        class fd { use };
        class process { rlimitinh siginh noatsecure  };
}

allow process_test_t user_devpts_t:chr_file { read write } ;
allow process_test_t sshd_t:fd { use };
allow process_test_t user_t:fd { use };
allow user_t process_test_t:process { rlimitinh siginh noatsecure } ;

# getchar()
allow process_test_t user_devpts_t:chr_file { getattr } ;


File prosess_test.fc:

/home/test1/process_test/process_test   --      gen_context(system_u:object_r:process_test_exec_t,s0)


This domain is having only such rights:

root@SELinux:~# sesearch --allow -s process_test_t -d
Found 11 semantic av rules:
   allow process_test_t user_devpts_t : chr_file { read write getattr } ; 
   allow process_test_t sshd_t : fd use ; 
   allow process_test_t user_t : process sigchld ; 
   allow process_test_t user_t : fd use ; 
   allow process_test_t process_test_exec_t : file entrypoint ; 
   allow process_test_t process_test_t : process { fork sigchld } ; 
   allow process_test_t process_test_t : file { ioctl read write getattr lock append open } ; 
   allow process_test_t process_test_t : dir { ioctl read getattr lock search open } ; 
   allow process_test_t process_test_t : lnk_file { ioctl read getattr lock } ; 
   allow process_test_t process_test_t : unix_stream_socket { ioctl read write create getattr setattr append bind connect listen accept getopt setopt shutdown } ; 
   allow process_test_t process_test_t : association sendto ;

So it can't do any bad things and user_u:user_r can run it. The program can run via user_u and wait to user type in a character with getchar().

Ok, now we can build the module. On root (unconfined_t) - in his home directory - create a directory mod_process and write there the two module files: process_test.te and process_test.fc. Then we can compile it:

root@SELinux:~/mod_process_test# make -f /usr/share/selinux/default/include/Makefile process_test.pp

I forget, I do this in Debian Squeeze ;) Remember, you must have install SELinux policy development package.
After build you can load this module:

# semodule -i process_test.pp

Now this command work also with you:

# sesearch --allow -s process_test_t -d

As you can see in the file context (process_test.fc) I choose the location on test code in /home/test1/process_test directory. You can choose different localizations, for test I use system user was called 'test1'. If you choose different location you must change the content in process_test.fc file.

Ok! Now I login as test1 user and create the process_test directory in his home directory:

test1@SELinux:~$ id
uid=1001(test1) gid=1001(test1) groups=1001(test1) context=user_u:user_r:user_t:s0
test1@SELinux:~$ ls -lZd process_test/
drwxr-xr-x. 2 test1 test1 user_u:object_r:user_home_t:s0 4096 Jun 25 21:29 process_test/

Now we can begin the first test.


SELinux and buffer overflow


This is a buffer overflow code. Save it into process_test.c file in process_test directory.

     1 #include <stdio.h>
     2 #include <string.h>
     3
     4 #define TEST 
     5
     6 char *get_correct_pass ()
     7 {
     8 return (char*)"5pW1";
     9 }
    10
    11 int auth ( char *pass )
    12 {
    13 int _auth = 0;
    14 char buff[10];
    15 memset( buff,0,10 );
    16 strcpy( buff, pass );
    17
    18 if ( strcmp( buff,get_correct_pass() ) == 0 ) 
    19 _auth = 1;
    20
    21 return _auth;
    22 }
    23
    24 int main ( int argc, char * argv[] )
    25 {
    26 if ( auth( argv[1] ) )
    27 printf ("access\n");
    28 else
    29 printf ("denied\n");
    30 #ifdef TEST
    31 getchar();
    32 #endif
    33 return 0;
    34 }

Now I little describe this code. This program get a one parameter from line command. This parameter is a passwords. If passwords is correct the program print "access" otherwise print "denied". Correct password is defined in line number 8 ("5pW1").

In line 4 we have TEST declaration, if TEST is declared the program will be wait for a key press (line 31).

The most important function is an auth (line 11). In this function first is declared variable _auth. Second variable is a buff, is having lenght 10 characters.

Because variable buff is declared as second it is above in memory than _auth. This is the reason why overwriting the buff can write the _auth variable (line 16). Program give "access" when function auth() return true (line 26) but any value beyond 0 it's mean TRUE. So if you overwrite _auth variable in some character, each of them have diffrent code from 0.

Let's see in a debugger how overwriting it's work, but before we must compile the code:

test1@SELinux:~/process_test$ gcc -g process_test.c -o process_test

Now we check how to the hex value of "E" character, because I use this character for the test as password.

$ echo 'EEEEEEEEEE' | hexdump 
0000000 4545 4545 4545 4545 4545

Now we run this program in gdb, as parameter we type this password "EEEEEEEEEE" (10x'E').

test1@SELinux:~/process_test$ gdb process_test
GNU gdb (GDB) 7.0.1-debian
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/test1/process_test/process_test...done.
(gdb) list 1
1 #include <stdio.h>
2 #include <string.h>
3
4 #define TEST
5
6 char *get_correct_pass ()
7 {
8 return (char*)"5pW1";
9 }
10
(gdb) list
11 int auth ( char *pass )
12 {
13 int _auth = 0;
14 char buff[10];
15 memset( buff,0,10 );
16 strcpy( buff, pass );
17
18 if ( strcmp( buff,get_correct_pass() ) == 0 )
19 _auth = 1;
20
(gdb) break 18
Breakpoint 1 at 0x40066b: file process_test.c, line 18.
(gdb) run EEEEEEEEEE
Starting program: /home/test1/process_test/process_test EEEEEEEEEE

Breakpoint 1, auth (pass=0x7fffffffe979 "EEEEEEEEEE") at process_test.c:18
18 if ( strcmp( buff,get_correct_pass() ) == 0 ) 
(gdb) x/32wx &buff - 0x1
0x7fffffffe636: 0xe9790000 0x7fffffff 0x45450000 0x45454545
0x7fffffffe646: 0x45454545 0x00000000 0xe6700000 0x7fffffff
0x7fffffffe656: 0x06b90000 0x00000040 0xe7580000 0x7fffffff
0x7fffffffe666: 0x00000000 0x00020000 0x00000000 0x00000000
0x7fffffffe676: 0xbc8d0000 0x7ffff7a9 0x00000000 0x00000000
0x7fffffffe686: 0xe7580000 0x7fffffff 0x00000000 0x00020000
0x7fffffffe696: 0x06970000 0x00000040 0x00000000 0x00000000
0x7fffffffe6a6: 0xb6fe0000 0x5e236504 0x0540ad6d 0x00000040
(gdb) x/wx &_auth
0x7fffffffe64c: 0x00000000
(gdb) quit

Ok, in the red we have ten bytes from the buffer (buff). In the green we have four bytes from the _auth. A distance between them is a two bytes. So, enough add 3 characters in the password to overwrite _auth e.g.:

test1@SELinux:~/process_test$ ./process_test EEEEEEEEEEbbX
access

test1@SELinux:~/process_test$ 

And we have an bingo. So now we know that the buffer overflow it is works. Ok, the next step is a change the context of this program.

root@SELinux:~# matchpathcon /home/test1/process_test/process_test
/home/test1/process_test/process_test system_u:object_r:process_test_exec_t:s0
root@SELinux:~# ls -lZ /home/test1/process_test/process_test
-rwxr-xr-x. 1 test1 test1 user_u:object_r:user_home_t:s0 9159 Jun 26 17:55 /home/test1/process_test/process_test
root@SELinux:~# restorecon -v /home/test1/process_test/process_test
restorecon reset /home/test1/process_test/process_test context user_u:object_r:user_home_t:s0->system_u:object_r:process_test_exec_t:s0
root@SELinux:~# ls -lZ /home/test1/process_test/process_test
-rwxr-xr-x. 1 test1 test1 system_u:object_r:process_test_exec_t:s0 9159 Jun 26 17:55 /home/test1/process_test/process_test

Now we check whether SELinux work in enforcing mode.

root@SELinux:~# getenforce 
Enforcing

And the last we can run this program in DTE, and thanks getchar() function (line 31) we can see who context have program at running.

$ ./process_test EEEEEEEEEExx1
access

Don't press the any key and as root type in:

root@SELinux:~# ps -efZ |grep test1
system_u:system_r:sshd_t:s0-s0:c0.c1023 root 1084 1043  0 17:32 ?      00:00:00 sshd: test1 [priv]
system_u:system_r:sshd_t:s0-s0:c0.c1023 test1 1088 1084  0 17:32 ?     00:00:00 sshd: test1@pts/1
user_u:user_r:user_t:s0         test1     1089  1088  0 17:32 pts/1    00:00:00 -bash
user_u:user_r:process_test_t:s0 test1     1163  1089  0 21:48 pts/1    00:00:00 ./process_test EEEEEEEEEExx1
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 root 1177 1078  0 21:57 pts/0 00:00:00 grep test1


As you can see process_test run in process_test_t domain (DTE). This process is run by test1 user (user_t), but after that it's work in own domain (process_test_t) and user test1 don't have permission to e.g. kill this (own) proces. You can login as test1 user in second shell and try this:

test1@SELinux:~$ ps -A 
  PID TTY          TIME CMD
 1089 pts/1    00:00:00 bash
 1185 pts/2    00:00:00 bash
 1190 pts/2    00:00:00 ps

Even if you get the PID from root shell you still can't kill them:

test1@SELinux:~$ kill -s 15 1163
-bash: kill: (1163) - Permission denied

And you have such log:

# tail /var/log/audit/audit.log
...
type=AVC msg=audit(1372276812.345:10): avc:  denied  { signal } for  pid=1185 comm="bash" scontext=user_u:user_r:user_t:s0 tcontext=user_u:user_r:process_test_t:s0 tclass=process
...

As you can see the process_test_t domain is very confined. user_t can execute them but can't do anything else - only press the key.

But wait the minute, we run this program in enforcing mode with 13 length password and buffer overflow is work even with SELinux protection. Why this possible?

The answer is in the architecture of operating system. Everything what is going during the buffer overflow technique (at running ./process_test) is going in the process space. This technique is not required interactions with the kernel.
The next examples is better explains that.


SELinux and shellcode


This is a shellcode:

char SC[] =   "\xeb\x1d\x5b\x31\xc0\x67\x89\x43\x07\x67\x89\x5b\x08\x67\x89\x43\x0c"\
"\x31\xc0\xb0\x0b\x67\x8d\x4b\x08\x67\x8d\x53\x0c\xcd\x80\xe8\xde\xff"\
"\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68\x4e\x41\x41\x41\x41\x42\x42\x42"\
"\x42";
    
int 
main (int argc, char **argv)
{
        int (*ret)();
        ret = (int(*)())SC;
            
        (int)(*ret)();
        exit(0);
}

Description of this exploit tells that code execute system function execve(/bin/sh), but how we can check this? We must compile it and run in gdb.
Before we must save this code in shellcode.c.

test1@SELinux:~/process_test$ gcc -g shellcode.c -o shellcode
test1@SELinux:~/process_test$ gdb -q ./shellcode
Reading symbols from /home/test1/process_test/shellcode...done.
(gdb) list 1
1 char SC[] =   "\xeb\x1d\x5b\x31\xc0\x67\x89\x43\x07\x67\x89\x5b\x08\x67\x89\x43\x0c"\
2              "\x31\xc0\xb0\x0b\x67\x8d\x4b\x08\x67\x8d\x53\x0c\xcd\x80\xe8\xde\xff"\
3              "\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68\x4e\x41\x41\x41\x41\x42\x42\x42"\
4              "\x42";
5  
6 int
7 main (int argc, char **argv)
8 {
9        int (*ret)();             
10        ret = (int(*)())SC;
(gdb) disas SC
Dump of assembler code for function SC:
0x00000000006008c0 <SC+0>: jmp    0x6008df <SC+31>
0x00000000006008c2 <SC+2>: pop    %rbx
0x00000000006008c3 <SC+3>: xor    %eax,%eax
0x00000000006008c5 <SC+5>: addr32 mov %eax,0x7(%ebx)
0x00000000006008c9 <SC+9>: addr32 mov %ebx,0x8(%ebx)
0x00000000006008cd <SC+13>: addr32 mov %eax,0xc(%ebx)
0x00000000006008d1 <SC+17>: xor    %eax,%eax
0x00000000006008d3 <SC+19>: mov    $0xb,%al
0x00000000006008d5 <SC+21>: addr32 lea 0x8(%ebx),%ecx
0x00000000006008d9 <SC+25>: addr32 lea 0xc(%ebx),%edx
0x00000000006008dd <SC+29>: int    $0x80
0x00000000006008df <SC+31>: callq  0x6008c2 <SC+2>
0x00000000006008e4 <SC+36>: (bad)  
0x00000000006008e5 <SC+37>: (bad)  
0x00000000006008e6 <SC+38>: imul   $0x414e6873,0x2f(%rsi),%ebp
0x00000000006008ed <SC+45>: rex.B
0x00000000006008ee <SC+46>: rex.B
0x00000000006008ef <SC+47>: rex.B
0x00000000006008f0 <SC+48>: rex.X
0x00000000006008f1 <SC+49>: rex.X
0x00000000006008f2 <SC+50>: rex.X
0x00000000006008f3 <SC+51>: rex.X add    %al,(%rax)
End of assembler dump.
(gdb) q

This is a assembler code. In the blue I mark the code which clear EAX register and set in a 0xb hex value. 0xb in decimal number is a 11. In the red I mark the holy grail of understand when SELinux is work. In assembly language instruction 'int 0x80' is used when program wants invoke a system calls. In other words that is means the interrupt, this happens when a code flow is switch into system mode. Only then - when program interaction with the system - SELinux can control the process actions (the subject).

Below is a definitions of the system call numbers in file root/arch/x86/include/asm/unistd_32.h from kernel source. On the blue mark we have the number of execve() function.

#ifndef _ASM_X86_UNISTD_32_H
#define _ASM_X86_UNISTD_32_H

/*
 * This file contains the system call numbers.
 */

#define __NR_restart_syscall      0
#define __NR_exit    1
#define __NR_fork    2
#define __NR_read    3
#define __NR_write    4
#define __NR_open    5
#define __NR_close    6
#define __NR_waitpid    7
#define __NR_creat    8
#define __NR_link    9
#define __NR_unlink   10
#define __NR_execve   11
#define __NR_chdir   12
#define __NR_time   13
#define __NR_mknod   14
#define __NR_chmod   15
#define __NR_lchown   16
#define __NR_break   17
#define __NR_oldstat   18
#define __NR_lseek   19
#define __NR_getpid   20

The next what we do is a compile the shellcode without a process memory protections.

root@SELinux:~# rm /home/test1/process_test/process_test
...
test1@SELinux:~/process_test$ gcc -fno-stack-protector -z execstack shellcode.c  -o process_test
...
root@SELinux:~# restorecon -v /home/test1/process_test/process_test
restorecon reset /home/test1/process_test/process_test context user_u:object_r:user_home_t:s0->system_u:object_r:process_test_exec_t:s0
...
test1@SELinux:~/process_test$ ls -lZ process_test
total 24
-rwxr-xr-x. 1 test1 test1 system_u:object_r:process_test_exec_t:s0 6657 Jun 27 21:11 process_test
test1@SELinux:~/process_test$ ./process_test 
^C
test1@SELinux:~/process_test$

And we have such logs:

root@SELinux:~# tail /var/log/audit/audit.log
...
type=AVC msg=audit(1372360361.357:69779): avc:  denied  { search } for  pid=1221 comm="process_test" name="bin" dev=sda1 ino=562 scontext=user_u:user_r:process_test_t:s0 tcontext=system_u:object_r:bin_t:s0 tclass=dir
type=SYSCALL msg=audit(1372360361.357:69779): arch=40000003 syscall=11 per=400000 success=no exit=-13 a0=6008e4 a1=6008ec a2=6008f0 a3=7fff3d5bf1f8 items=0 ppid=1057 pid=1221 auid=4294967295 uid=1001 gid=1001 euid=1001 suid=1001 fsuid=1001 egid=1001 sgid=1001 fsgid=1001 tty=pts1 ses=4294967295 comm="process_test" exe="/home/test1/process_test/process_test" subj=user_u:user_r:process_test_t:s0 key=(null)

The process_test runs in process_test_t domain don't have permission to search the /bin directory. Even if it had this permission it don't have permission to execute a bin_t type object and many others permission required to run /bin/sh.


Summary


Summary of this two examples it show the SELinux is a kernel security mechanism that work only if the process (subject) must use of the system calls to do bad things.

Furthermore you must understand that if a process which has SELinux permission to e.g. write in to /etc/passwd will be crack with technique which work only in the process space the SELinux protections it's not stop it. SELinux check only if a particular process can call a particular system function, but not check when it do this.

So SELinux is not see a diffrent between a normal system call from proces (the orginaly code flows) and a crack call from process (an attacker changes the orginaly code flows). Of course the policy must allow this action for this process.

Sunday, July 24, 2011

Ochrona przed atakiem typu buffer overflow (Stack-Smashing Protector)

Jak działa metoda buffer overflow? Przyjrzyjmy się przykładowemu programowi.


Tak na marginesie to trzeba mieć talent, aby takie coś napisać. Musiałem przerobić mój przykład tak, aby atak się udał ponieważ pierwotnie nie było to możliwe.
Program działa prosto, oczekuje jako parametru hasła. Poprawnym hasłem jest "root". Autoryzacja powiedzie się tylko wtedy, gdy wpiszemy "$ ./bflow2 root". 
Cały błąd projekcyjny tego programu leży w tym, że do autoryzacji używamy w ogóle nie potrzebnej zmiennej auth. Jak by tego było małe, to jest jeszcze ona zadeklarowana przed buforem, który można przepełnić. Nie ma sprawdzania długość hasła wprowadzonego przez użytkownika.
Zanim przejdę do rzeczy wspomnę jeszcze, że najpierw skompiluję i przetestuje program na swoim systemie Debian Squeeze amd64, a później na CentOS 5. Wyniki będą różne. W systemie Debian od wersji Lenny wszystkie możliwe paczki są kompilowane z opcją ochrony stosu. Mechanizm ten stworzył IBM, to tak zwany stack-smashing protector. Dokumentacje GCC zawiera te opcje.
Do rzeczy. Najpierw uruchomimy program z parametrem, który zapełni cały bufor, następnie w debuggerze gdb sprawdzimy ile jeszcze bajtów zostało do nadpisania zmiennej auth.


Cyfrę "1" w kodzie hexadecymalnym reprezentuje liczba "31". Można to sobie sprawdzić wydając polecenie  'echo "1111111111"|hd'.
Jedynek jest 10, zapełniamy cały bufor. Podkreśliłem je na czerwono. Trzeba pamiętać, że w pamięci bajty są umieszczone w odwrotnej kolejności. Dlatego wartości "31" nie są ciągłe.
Na zielono podkreśliłem adres zmiennej auth na stosie oraz wynik działania programu.
Jeżeli słowa zawierające koniec tablicy buff i zmienną auth odwrócimy to zobaczymy ile jest między nimi bajtów:
0x00313131   0x00000000   0xffe32000
0x31313100   0x00000000   0x0020e3ff   odwrócone
Bajty stanowiące odstęp między końcem tablicy buff, a zmienną auth oznaczyłem kursywą. Natomiast samą zmienną auth pogrubiłem. Teraz nie ma już wątpliwości, należy dopisać jeszcze 6 jedynek ('1') aby nadpisać zmienną auth. (Znak '1' ani '0' nie reprezentują w pamięci liczb 1 i 0, zapisywane są odpowiednimi kodami ASCII)


Jak widać atak powiódł się. Program dotarł do końca, wypisując tekst 'end' i uzyskał dostęp. Mimo wszystko system wykrył ingerencję w stosie programu. Świadczy o tym komunikat "Segmentation fault.". Dzieje się tak ponieważ większość najważniejszych programów w systemie Debian są kompilowane, z ochroną stosu.
Po opuszczeniu debuggera wywołałem jeszcze trzy razy program. Pierwszy raz miał o jedną jedynkę za mało (15), druki 16, a trzeci raz o jedną za dużo (17). Wpisując szesnaście jedynek program sam się nie zakończył, ale kod wykonał do końca, o czym świadczy wypisanie testu 'end'.
Teraz skompilujemy program z opcją -fstack-protector.


Tym razem zamiast samych jedynek użyłem kolejnych liczb, teraz można zobaczyć jak powinno się czytać zrzut pamięci od tyłu. Jak widać kompilator odwrócił kolejność zmiennych na stosie i nie można teraz poprzez tablicę buff nadpisać zmiennej auth, ponieważ jest ona przed nią.
Nawet z opcją -fstack-protector da się nadpisac w taki sposób zmienne z funkcji main(). Aby temu zapobiec należy użyć opcji -fstack-protector-all.
Teraz przetestujemy ten sam kod na systemie CentOS 5.


Najpierw sprawdzamy jaki jest odstęp między zmiennymi. Łoł - nie ma żadnego. Wystarczy dodać tylko jeden znak, czyli zamiast jako parametr podać 10 znaków, należy podać 11 i gotowe.
Dodałem 'x', jak widać działa pięknie. Potem kompilujemy program z ochroną stosu i również działa jak powinno - przerwał program i nie pozwolił mu dalej pracować. Różnice w zachowaniach kompilatora na obu tych systemach wynikają z kompilacji z różnymi opcjami gcc i nie tylko.
W systemie Debian opcja ochrony stosu zapobiegła atakowi, a system w całości wykonała kod programu, w CentOS program został natychmiast przerwany.