Thursday, 5 June 2014

Addictive Double-Quoting Sickness

Much as I'd love it if people who used "Scare Quotes" (see what I did there) were punished appropriately I doubt my intolerance is shared sufficiently amongst the general population. So this blog's not about that, but something security related which keeps on popping up, when it really shouldn't.

Still if I could be as loved as Mike Myers it might be worth using them myself. Wait...
This is a post about abusing ADS, but what I'm going to talk about is something I refer to as Domain-Specific Weirdness, at least when I'm bored and decide to make stuff up. The term refers to those bugs which are due to not understanding the differences between domain-specific representations. A far too common coding pattern you'll see on Windows in "Secure" systems (I really need help), is something like the following:
path = GetExecutablePath();

if(ValidAuthenticode(path)) {
    cmdline = '"' + path + '"';

    CreateProcess(NULL, cmdline, ...); 
}

The security issue appears when the executable path is influenced by untrusted code. The ValidateAuthenticode function verifies the file is signed by a specific certificate. Only if that passes will the executable be started. This hits so many bugs classes as it is, poor input validation, TOCTOU between the validation and process creation and also failing to pass the first argument to CreateProcess. But the sad thing is you see it in real software by real companies, even Microsoft.

Now to be fair the one thing the code does right is it ensures the path is double-quoted before passing it to CreateProcess. At least you won't get some hack hassling you for executing C:\Program.exe again. But process command lines and file paths are two completely different interpretation domains. CreateProcess pretty much follows how the standard C Runtime parses the process path. The CRT is open-source, so we can take a look at how the executable name is parsed out. In there you'll find the following comment (the file's stdargv.c if you're curious):
/* A quoted program name is handled here. The handling is much
   simpler than for other arguments. Basically, whatever lies
   between the leading double-quote and next one, or a terminal null
   character is simply accepted. Fancier handling is not required
   because the program name must be a legal NTFS/HPFS file name.
   Note that the double-quote characters are not copied, nor do they
   contribute to numchars. */
You've got to love how much MS still cares about OS/2. Except this is of course rubbish, the program name doesn't have to be a legal NTFS/HPFS file name in any way. Especially for CreateProcess. The rationale for ignoring illegal program names is because NTFS, like many file systems have a specific set of valid characters.

What's a valid NTFS file name you might ask? You can go look it up in MSDN, instead I put together a quick test case to find out.
#include <stdio.h>
#include <Windows.h>
#include <string>
 
int wmain(int argc, WCHAR* argv[])
{ 
    for (int i = 1; i < 65536; ++i)
    {
        std::wstring name = L".\a";  
        name += (WCHAR)i;
        name += L"a";  
 
        HANDLE hFile = CreateFile(name.c_str(), 
          GENERIC_READ | GENERIC_WRITE, FILE_SHARE_DELETE, NULL, 
          CREATE_ALWAYS, FILE_FLAG_DELETE_ON_CLOSE, NULL);

        if (hFile == INVALID_HANDLE_VALUE)
        {
            printf("Illegal Char: %d\n", i);
        }
        else
        {
            CloseHandle(hFile);
        }
    }
 
    return 0;
}
And it pretty much confirms MSDN, you can't use characters 1 through 31 (0 is implied), 32 (space) has some oddities and you can't use anything in <, >, :, \, ", \, /, |, ?, *. Notice the double-quote sitting there proud, in the middle.

Okay, let's get back to the point, what's this got to do with ADS? If you read this you'll find the following statement: "Any characters that are legal for a file name are also legal for the stream name, including spaces". If you read that and thought it meant that stream names have the same restrictions as file names in NTFS I have a surprise for you. Change the test case so instead of '.\a' we use '.\a:' we find that the only banned characters are, \, / and : quite a surprise. For our "bad" code we can now complete the circle of exploitation. You can pass a file name such as c:\abc\xyz:file" and the verification code will verify c:\abc\xyz:file" but actually execute c:\abc\xyz:file (subtle I know). And the crazy thing about this is there isn't even a way to escape it.

The moral of the story is this, you can never blindly assume that even a single interpretation domain works how you expect it to do, so when you mix domains together pain will likely ensue. This is also why Windows command line processing is so broken. At least *nix passes command line arguments separately, well unless you use something like system(3) (and for that you'll be punished). Making assumptions on the "validity" of a file path just seems inherently untrustworthy.

Tuesday, 27 May 2014

Abusive Directory Syndrome

As ever there's been some activity recently on Full Disclosure where one side believes something's a security vulnerability and the other says it's not. I'm not going to be drawn into that debate, but one interesting point did come up. Specifically that you can't create files in the root of the system drive (i.e. C:\) as a non-admin user, you can only create directories. Well this is both 100% true and false at the same time, it just depends what you mean by a "file" and who is asking at the time.

The title might give the game away, this is all to do with NTFS Alternate Data Streams (ADS). The NTFS file system supports multiple alternative data streams which can be assigned to a file, this can be used for storing additional attributes and data out-of-band. For example it is used by Internet Explorer to store the zone information for a downloaded file so the shell can warn you when you try to execute a file from Internet. Streams have names and are accessed using a special syntax, filename:streamname. You can easily create a data stream using the command prompt, type the following (in a directory you can write to):

echo Hello > abc
echo World! > abc:stm

more < abc  - Prints Hello
more < abc:stm - Prints World!

Easy no? Okay lets try it in the root of the system drive (obviously as a normal user):

echo Hello > c:\abc:stm - Prints Foxtrot Oscar
Oh well worth a try, but wait there's more...  One of the lesser known abilities of ADS, except to the odd Malware author, is you can also create streams on directories. Does this create new directories? Of course not, it creates file streams which you can access using file APIs. Let's try this again:

mkdir c:\abc
echo Fucking Awesome! > c:\abc:stm
more < c:\abc - File can't be found
more < c:\abc:stm - Prints Fucking Awesome!

Well we've created something which looks like a file to the Windows APIs, but is in the root of the system drive, something you're not supposed to be able to do. It seems deeply odd that you can:

  • Add an ADS to a directory, and 
  • The ADS is considered a file from the API perspective

Of course this doesn't help us exploit unquoted service paths, you can't have everything. Still when you consider the filename from a security perspective it has an interesting property, namely that its nominal parent in the hierarchy (when we're dealing with paths that's going to be what's separated by slashes) is C:\. A naive security verification process might assume that the file exists in a secure directory, leading to a security issue.

Take for example User Account Control (UAC), better know as the "Stop with the bloody security dialogs and let me get my work done" feature which was introduced in Vista. The service which controls this (Application Info) has the ability to automatically elevate certain executables, for controlling the UI (UIAccess) or to reduce the number of prompts you see. It verifies that the executables are in a secure directory such as c:\windows\system32 but specifically excludes writeable directories such as c:\windows\system32\tasks. But if you could write to Tasks:stm then that wouldn't be under the Tasks directory and so would be allowed? Well let's try it!

echo Hello > c:\windows\system32\tasks:stm
more < c:\windows\system32\tasks:stm - Access Denied :(

Why does it do that? We only have to take a look at the DACL to find out why, we have write access to Tasks but not read:

c:\>icacls c:\Windows\system32\tasks
c:\Windows\system32\tasks BUILTIN\Administrators:(CI)(F)
                          BUILTIN\Administrators:(OI)(R,W,D,WDAC,WO)
                          NT AUTHORITY\SYSTEM:(CI)(F)
                          NT AUTHORITY\SYSTEM:(OI)(R,W,D,WDAC,WO)
                          NT AUTHORITY\Authenticated Users:(CI)(W,Rc)
                          NT AUTHORITY\NETWORK SERVICE:(CI)(W,Rc)
                          NT AUTHORITY\LOCAL SERVICE:(CI)(W,Rc)
                          CREATOR OWNER:(OI)(CI)(IO)(F)

Oh well... Such is life. We've managed to create the stream but can't re-read it, awesome Write Only Memory. Still this does demonstrate something interesting, the DACL for the directory is applied to all the sub-streams even though the DACL might make little sense for file content. The directory DACL for Tasks doesn't allow normal users to read or list the directory, which means that you can't read or execute the file stream.

In conclusion all you need to be able to create an ADS on a directory is write access to the directory. This is normally so you can modify the contents of the directory but it also applies to creating streams. But the immediate security principal for a stream then becomes the parent directory which might not be as expected. If I find the time I might blog about other interesting abuses of ADS at a later date.

Wednesday, 21 May 2014

Impersonation and MS14-027

The recent MS14-027 patch intrigued me, a local EoP using ShellExecute. It seems it also intrigued others so I pointed out how it probably worked on Twitter but I hadn't confirmed it. This post is just a quick write up of what the patch does and doesn't fix. It turned out to be more complex than it first seemed and I'm not even sure it's correctly patched. Anyway, first a few caveats, I am fairly confident that what I'm presenting here is already known to some anyway. Also I'm not providing direct exploitation details, you'd need to find the actual mechanism to get the EoP working (at least to LocalSystem).

I theorized that the issue was due to mishandling of the registry when querying for file associations. Specifically the handling of HKEY_CLASSES_ROOT (HKCR) registry hive when under an impersonation token. When the ShellExecute function is passed a file to execute it first looks up the extension in the HKCR key. For example if you try to open a text file, it will try and open HKCR\.txt. If you know anything about the registry and how COM registration works you might know that HKCR isn't a real registry hive at all. Instead it's a merging of the keys HKEY_CURRENT_USER\Software\Classes and HKEY_LOCAL_MACHINE\Software\Classes. In most scenarios HKCU is taken to override HKLM registration as we can see in the following screenshot from Process Monitor (note PM records all access to HKLM classes as HKCR confusing the issue somewhat). 





When ShellExecute has read this registry key it tries to read a few values out it, most importantly the default value. The value of the default value represents a ProgID which determines how the shell handles the file extension. So for example the .txt extension is mapped to the ProgID 'txtfile'.

The ProgID just points ShellExecute to go reading HKCR/txtfile and we finally find what we're looking for, the shell verb registrations. The ShellExecute function also takes a verb parameter, this is the Action to perform on the file, so it could be print or edit but by far the most common one is open. There are many possible things to do here but one common action is to run another process passing the file path as an argument. As you can see below a text file defaults to being passed to NOTEPAD.

Now the crucial thing to understand here is that HKCU can be written to by a normal, unprivileged user. But HKCU is again a fake hive and is in fact just the key HKEY_USERS\SID where SID is replaced with the string SID of the current user (see pretty obvious, I guess). And even this isn't strictly 100% true when it comes to HKCR but it's close enough. Anyway, so what you might be asking? Well this is wonderful until user Impersonation gets involved. If a system or administrator process impersonates another user it's also suddenly finds when it accesses HKCU it really accesses the impersonated user's keys instead of its own. Perhaps this could lead to a system service that is  impersonating a user and then calls ShellExecute to start up the wrong handler for a file type leading to arbitrary execution at a higher privilege. 

With all this in mind lets take a look at patch in a bit more depth. The first step it to diff the patched binary with the original, sometimes easier said than done. I ran an unpatched and patched copies of shell32.dll through Patchdiff2 in IDA Pro which lead to a few interesting changes. In the function CAssocProgidElement::_InitFileAssociation a call was added to a new function CAssocProgidElement::SetPerMachineRootIfNeeded.


Digging into that revealed what the function was doing. If the current thread was impersonating another user and the current Session ID is 0 and the file extension being looked up is one a set of specific types the lookup is switched from HKCR to only HKLM. This seemed to confirm by suspicions that the patch targeted local system elevation (the only user on session 0 is likely to be LocalSystem or one of the service accounts) and it was related to impersonation.




Looking at the list of extension they all seemed to be executables (so .exe, .cmd etc.) so I knocked up a quick example program to test this out.


Running this program from a system account (using good old psexec -s) passing it the path to an executable file and the process ID of one of my user processes (say explorer.exe) I could see in process monitor it's reading the corresponding HKCU registry settings.








Okay so a last bit of exploitation is necessary I guess :) If you now register your own handler for the executable ProgID (in this case cmdfile), then no matter what the process executes it will instead run code of the attacker's choosing at what ever privilege the caller has. This is because Impersonation doesn't automatically cross to new processes, you need to call something special like CreateProcessFromUser to do that.


So how's it being exploited in the real world? I can't say for certain without knowing the original attack vector (and I don't really have the time to go wading through all the system services looking for the bad guy, assuming it's even Microsoft's code and not a third party service). Presumably there's something which calls ShellExecute on an executable file type (which you don't control the path to) during impersonating another user.

Still is it fixed? One thing I'm fairly clear on is there seems to still be a few potential attack vectors. This doesn't seem to do anything against an elevated admin user's processes being subverted. If you register a file extension as the unprivileged user it will get used by an admin process for the same user. This is ultimately by design, otherwise you would get inconsistent behaviour in elevated processes. The fix is only enabled if the current thread is impersonating and it's in session 0 (i.e. system services), and it's only enabled for certain executable file types.

This last requirement might seem odd, surely this applies to any file type? Well it does in a sense, however the way ShellExecute works is if the handling of the file type might block it runs the processing asynchronously in a new thread. Just like processes, threads don't inherit impersonation levels so the issue goes away. Turns out about the only thing it treats synchronously are executables. Well unless anyone instead uses things like FindExecutable or AssocQueryString but I digress ;-) And in my investigation I found some other stuff which perhaps I should send MS's way, let's hope I'm not too lazy to do so.


Saturday, 2 February 2013

Fun with Java Serialization and Reflection

Last year I started to have a poke at Java for security vulnerabilities, I am not really sure why, but probably because I was having some success breaking .NET and felt Java was likely to have similar issues. Shame I picked a bad time to do so if I wanted to be famed for owning Java (re: Security Explorations). Still I think I found a few things Adam Gowdiak didn't find :)

Anyway, with the recent fixing of my last Java vulnerability in 7 update 13 (CVE-2012-3213 if  you care) I felt it was a good time to describe what it did and how it worked, especially as it is a mixture of the classic Java serialization vulnerabilities mixed with the hot topic of reflection, making it an interesting vulnerability. It will also describe another source of access to protected package Class objects.

The underlying issue is in the Rhino script engine. This is a Javascript interpreter built into modern versions of the JRE (from version 6 onwards) and originally comes from a Mozilla project where it was primarily designed to run in a fully trusted environment. It has had security issues before (for example see CVE-2011-3544) as Sun/Oracle decided to make it work in a sandboxed Java environment. However to exploit what I found you have to be a bit creative.

A Quick Overview of Rhino Security

As the script engine could potentially create user-defined but trusted code one of the things that was added to the engine was some checks to prevent sandboxed code from calling into objects which might have dangerous side effects, for example gaining access to protected packages such as 'sun.*'. In order to access a native Java object the engine must first wrap it with a scriptable wrapper by using a WrapFactory. The JRE provides a custom implementation in com.sun.script.javascript.RhinoWrapFactory which does these checks before the Javascript is allowed to call methods on that object. In that code it checks things like whether the object is a ClassLoader (which might allow the script to bypass package access checks in loadClass etc.), it also checks for the package name of Class objects and classes to see if they are visible to scripts (ultimately calling the current security manager's checkPackageAcccess method). There are some exclusions though, because the core Rhino classes are actually embedded within the sun.* package which means it can at least get access to those. At any rate what this ultimately results in is I couldn't see an immediate way of using the script engine to call into protected classes to do something nasty.

Bug Hunting in the Javascript World

The fact that these wrapping mechanisms are needed are a good example of some of the differences between Java and Javascript. You could consider Javascript to have one of the most flexible reflection implementations, all objects are reflectable (well of course depending on the implementation), for example to determine what properties and functions an object supports you can do something as simple as:

for(x in obj)
{
    System.println(x+": " + typeof(obj[x])");
}
You can dispatch methods or read properties by just using the obj[x] syntax. The Rhino script engine aims to  replicate this functionality even for native Java objects by providing isolated scriptable wrappers around common reflection primitives such as methods, fields and constructors. You can find these under the sun.org.mozilla.javascript.internal package with classes such as NativeJavaConstructor and NativeJavaMethod. The interesting thing I noticed was these were not performing any further reflection checks on the classes they were interacting with, presumably if you could get access to one of these classes you must have already gone through the object wrapping process and that would have blocked the package access. And so it seems, after a bit of digging I managed to find the syntax for getting access to the constructor object using the following code:

importClass(Packages.sun.swing.SwingLazyValue); 

SwingLazyValue['(java.lang.String,java.lang.String,java.lang.Object[])'];

This would get you a constructor on the SwingLazyValue class (which is a useful execution pivot in the JRE to call internal static functions, especially as it is based on a public interface we can access and call through). But if you try this in a sandboxed environment it fails with an exception due to the package access of the Class object, so close but so far. Still there is clearly a way of exploiting it otherwise I wouldn't be documenting it.

Serialization to the Rescue

If you look at the class hierarchy of the NativeConstructor class you will notice something interesting (or at least you would have done prior to update 13), it implemented the Serializable interface. So perhaps instead of using Javascript to access the constructor we could instead use serialization. The advantage of this approach is we might be able to reconstruct the object in native Java code first (which won't necessarily complain about it) and then pass it back into Javascript for the final exploitation.

I knocked up a simple full trust application which would capture a NativeConstructor object, serialize it then check it deserialized correctly. I went to run it, but it threw an exception because some internal fields could not be serialized. Damn... Looking through the documentation it looks like serialization was a vestigial feature of the original Mozilla implementation, Sun had not bothered to ensure it still worked correctly, so I followed a hunch, perhaps the object doesn't actually need those unserializable fields, perhaps I can just remove them. Fortunately Java makes it relatively easy to do this by implementing the replaceObject method on the java.io.ObjectOutputStream class.

A few minutes later I had:

class MyObjectOutputStream extends ObjectOutputStream {
   public MyObjectOutputStream(OutputStream stm) throws Throwable {
       super(stm);
       enableReplaceObject(true);
   }

   protected Object replaceObject(Object o) {
       String name = o.getClass().getName();
       if(name.startsWith("com.sun.script.javascript.")) {
          return null;
       }
       return o;
   }
}

Running this in my full trust application my hunch was proven correct, the script didn't in fact need those internal fields to work and the NativeConstructor object could be used freely when deserialized. Feeling the end was in sight I plugged it into an Applet, I took the binary output from the full trust application and deserialized it, I was dissappointed to be greeted with:

java.security.AccessControlException: access denied 
   ("java.lang.RuntimePermission" 
    "accessClassInPackage.sun.org.mozilla.javascript.internal")
at java.security.AccessControlContext.checkPermission(AccessControlContext.java)
at java.security.AccessController.checkPermission(AccessController.java)
at java.lang.SecurityManager.checkPermission(SecurityManager.java)
at java.lang.SecurityManager.checkPackageAccess(SecurityManager.java)
at sun.applet.AppletSecurity.checkPackageAccess(AppletSecurity.java)
at sun.applet.AppletClassLoader.loadClass(AppletClassLoader.java)
at java.lang.ClassLoader.loadClass(ClassLoader.java)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java)
at java.io.ObjectInputStream.resolveClass(ObjectInputStream.java)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java)
at Demo.doExploit(Demo.java)

So I seemed to have gained nothing, I traded a package access check for one type to one from the Rhino implementation. Still all was not lost, I had a plan.

Ask and You Shall Receive

Now if you look at the stack trace of the exception one method frame is clearly responsible for it, the java.io.ObjectInputStream.resolveClass method. If you go and look at the implementation of that it is passing the current class loader into the forName method, which in an Applet is the AppletClassLoader which doesn't much care for handing out references to sun.* package classes. Still serialization in Java (compared to .NET with which I have bit of experience) is an unprivileged operation, even sandboxed code can do it, and there are also some things you can do to modify the process of serialization to do some funky things, like overriding the resolveClass implementation. So this led me to the realization that perhaps if I could get the protected Class objects from somewhere else then I could implement my own ObjectInputStream, override the resolveClass method and return what I needed. So I put together:

class MyObjectInputStream extends ObjectInputStream {
   Hashtable dict;

   public MyObjectInputStream(InputStream stm, Hashtable dict) 
          throws IOException {
      super(stm);
      this.dict = dict;
   }

   protected Class resolveClass(ObjectStreamClass clazz) 
         throws Throwable {
      
      if(dict.containsKey(clazz.getName())) {
          return (Class)dict.get(clazz.getName());
      } else {
          return super.resolveClass(clazz);
      }
   }
}

This class would take a Hashtable containing some classes, if we already had the required Class object we could return it as is so we don't get the security exception, but of course this begs the question, how will we populate the dictionary? Well the key is in serialization itself.

We already know that the the NativeJavaConstructor class is serializable, we also know untrusted code can perform the serialization process and that untrusted code can create the NativeJavaConstructor objects as long as they point to non-privileged classes. So can we not use the serialization process against itself to get access to all the classes we need?

Turns out we cannot directly use the ObjectOutputStream with the overloaded replaceObject method as that is actually one of the few privileged operations in the serialization process (something to do with access to private fields or something). Anyway so we will go to the source of how ObjectOutputStream determines what to serialize, the ObjectStreamClass class. You can call this from untrusted code and it will return you a description of the object, including the classes of the object's fields. From this you can take copies of the classes and keep them for the input stream to use. Such as:

public void captureTypes(Object o) {
   try {
       Class c = o.getClass();

       while(c != Object.class) {
            dict.putClass(c);

            ObjectStreamClass stmClass = ObjectStreamClass.lookup(c);
            ObjectStreamField[] fs = stmClass.getFields();

            for(int i = 0; i < fs.length; ++i) {
                Class fc = fs[i].getType();
                dict.putClass(fc);
            } 

            c = c.getSuperclass();
        }

  } catch(Throwable e) {}
}

This fills in the dictionary and you are good to go. And amazingly this does work... From that I could do things like create my SwingLazyValue which would call a static method such as SunToolkit.getField and that was the end of the road for the sandbox. Of course how you actually go about getting the sun.swing.SwingLazyValue Class object is left as an exercise for the reader ;)

Conclusion

Well it looks like the fix was simple in 7u13, anything within Rhino which could be serialized now cannot which considering they never were without substantial effort I guess isn't an issue from a break-compatibility point of view. But it does once again show that package access restrictions, especially with serialization are not adequate to protect Java from itself.

Sunday, 6 June 2010

The Quest : Part 2

So the last try at making a small Mach-O binary didn't really work. Now I could start fiddling with the linker to see if I can make things smaller but I am not particularly up on my Apple linker usage so instead lets just straight to the binary assembler :)

Fortunately Apple choose to install nasm by default (well when you install the developers' tools), so we just need to understand how a mach-o binary is laid out. This is some official documentation about the file format (and there is the referenced header files installed with the dev tools). Using the otool utility also gives a good idea of what is in a real binary (try running with the -l command on the previous static binary to see what you get).

Anyway as with last time I still want this executable to not require any dirty tricks, although looking at the file format there isn't many obvious ones we could employ (at least compared to some of the stunts you can play with ELFs).

So what needs to be in a valid mach-o? The header, obviously, is required, then a number of load commands. It turns out (through a bit of reading the source) that you only actually need a LC_UNIXTHREAD (or LC_THREAD) command and the executable will load, seems unlike most other executable formats the entry point is not a field in the headers but is inferred by specifying the initial thread context.

Of course without any code in memory this isn't exactly that useful (well not immediately) so we also need to specify an LC_SEGMENT load command. This will map some of our binary into memory and we are ready to go. As a short aside if you look at the output of otool -l under most segments there are also sections, these are as far as I can tell unnecessary, and are more meta-data to make linking more consistent.
; A basic Mach-O executable
; (c) Tyranid 2010
BITS 32

ORG 0x1000

_program_start:

; mach_header
dd 0xfeedface ; MH_MAGIC
dd 7 ; cputype
dd 3 ; cpusubtype
dd 2 ; filetype
dd 2 ; ncmds
dd _cmd_end-_cmd_start ; sizeofcmds
dd 0x2001 ; flags

_cmd_start:

_segment_cmd:
dd 1 ; LC_SEGMENT
dd _segment_cmd_end-_segment_cmd ; sizeofcmd
_segment_name: ; segname
db "__TEXT"
times 16-$+_segment_name db 0
dd _program_start ; vmaddr
dd ((_program_end-_program_start)+4095)&~4095 ; vmsize
dd 0 ; fileofs
dd _program_end-_program_start ; filesize
dd 7 ; maxprot
dd 5 ; initprot
dd 0 ; nsects
dd 4 ; flags

_segment_cmd_end:

_thread_cmd_start:
dd 5 ; LC_UNIXTHREAD
dd _thread_cmd_end-_thread_cmd_start ; sizeofcmd
dd 1 ; flavor (i386_THREAD_STATE)
dd (_registers_end-_registers_start)/4 ; count

_registers_start:
dd 0 ; unsigned int __eax;
dd 0 ; unsigned int __ebx;
dd 0 ; unsigned int __ecx;
dd 0 ; unsigned int __edx;
dd 0 ; unsigned int __edi;
dd 0 ; unsigned int __esi;
dd 0 ; unsigned int __ebp;
dd 0 ; unsigned int __esp;
dd 0x1F ; unsigned int __ss;
dd 0 ; unsigned int __eflags;
dd _start ; unsigned int __eip;
dd 0x17 ; unsigned int __cs;
dd 0x1F ; unsigned int __ds;
dd 0x1F ; unsigned int __es;
dd 0 ; unsigned int __fs;
dd 0 ; unsigned int __gs;
_registers_end:

_thread_cmd_end:

_cmd_end:

_start:
; Call exit(42)
push byte 42
push byte 1
pop eax
push eax
int 0x80

_program_end:

Throw it through nasm in binary mode and what do we get? 172 bytes, far smaller. There are some further tricks you could play with this, such as embedding the code inside the thread context (as only EIP and probably the segment registers are important) or actually store a few of the necessary values in the context to slightly reduce the pushes. Still 172 is alright for now, can it go any lower?

Thursday, 3 June 2010

The Quest for a Small Mach-O

For my sins I have recently actually enjoyed using OS X. There is just something about its unix'ness which appeals to me (though I would rather not have to pay for it to begin with). Anyway one of the first things I tend to do on an OS is to try and write as small an executable as possible and this is not the time to change that.

So this is maybe the first post of many on creating something small :) Note: I am working on Snow Leopard and producing 32bit code, YMMV.

Step 1: What can we do with basic tools?

So lets start with a normal development environment to see what we can get without having to writing anything custom. Before we can do anything we need some code, here is a simple entry point with no reliance on external libraries, just straight into the exit syscall.
void start(void) {
// Call exit(0)
__asm__ volatile (
"push $0\n"
"movl $1, %eax\n"
"int $0x80\n"
);
}
It is worth pointing out that without this exit syscall your new application will just SIGBUS, not exactly optimal.

Now just need to link it, we will choose to link statically (which should get rid of anything to do with the dynamic linker, which might have to change as we go along).
all: test1

test1: test1.c
$(CC) -c -o test1.o test1.c
$(LD) -o test1 -s -static -e _start test1.o

clean:
rm -f test1 *.o
And our survey says? 4096 bytes, bugger. Well I guess page alignment is a killer. Of course using hexdump shows that over 3/4 of the file is empty. Still there is some hope for the future, running otool -lv over the output application shows that the entire 4k is being loaded into memory, a classic trick in making small binaries. Some nice sounding options in the ld man page (such as -pagezero_size and -seg_page_size) just don't seem to work as expected so no doubt something more custom is required next time.

Onwards and upwards.