Next article: Friday Q&A 2009-02-06: Profiling With Shark
Previous article: Friday Q&A 2009-01-23
Tags: codeinjection evil fridayqna hack
Welcome back to another exciting Friday Q&A.; This week I'll be taking Jonathan Mitchell's suggestion to talk about code injection, the various ways to do it, why you'd want to, and why you wouldn't want to.
What It Is
Let's start with a real easy example:
Fear:~/shell mikeash$ cat injectlib.c
#include <stdio.h>
void inject_init(void) __attribute__((constructor));
void inject_init(void)
{
fprintf(stderr, "Here's your injected code!\n");
}
Fear:~/shell mikeash$ gcc -bundle -o injectlib injectlib.c
Fear:~/shell mikeash$ gdb attach `ps x|grep Safari|grep -v grep|awk '{print $1}'`
GNU gdb 6.3.50-20050815 (Apple version gdb-962) (Sat Jul 26 08:14:40 UTC 2008)
[snip]
0x93e631c6 in mach_msg_trap ()
(gdb) p (void *)dlopen("/Users/mikeash/shell/injectlib", 2)
$1 = (void *) 0x28f3b5b0
(gdb)
[0x0-0x17f97f8].com.apple.Safari[23558]: Here's your injected code!
How to Do It
Of course, using gdb
to inject code isn't exactly what one might call practical. For one thing, gdb
is unlikely to be present on your users' systems, as it's a developer tool.
However there are better alternatives, some as part of Mac OS X and some as third-party tools.
Input Managers
Input Managers are intended to provide keyboard input mechanisms for allowing custom ways to translate keystrokes into text on the screen, for example a custom Chinese input method.
They aren't very useful for their stated purpose on Mac OS X because they only work in Cocoa apps, not Carbon apps. But because they work by loading the input manager directly into every Cocoa application, they're great for code injection. All you need to do is build a bundle with the right layout, put it in the right place, and suddenly you're loaded into every Cocoa app.
Of course Apple isn't too keen on code injection and they've threatened that they might take our toys away at some point. Input Managers still work on Leopard, although they've been restricted and now require root access to install them. They may or may not still be around on Snow Leopard, it's hard to say yet.
Input Managers are a bit troublesome. First, on Leopard, they have to be installed with some fairly precise permissions and that's annoying. Second, they load into every Cocoa app even if you only want to fiddle with one of them. The third-party SIMBL helps with both of these problems. It will load standard bundles placed in standard locations (although SIMBL itself still needs the special magic installation to function), and it allows plugins to provide a list of applications they want to load into.
Mach ports
In a previous edition, I briefly mentioned that mach ports allowed injecting code into other processes. This works because mach ports can allow essentially full control over other processes. If you can get your hands on the right port (and see the task_for_pid
function for how to do that) you can do things like map new memory into the process with custom contents and create a new thread in the target process that executes that memory. Set things up right and you have code injection.
This is pretty hard to pull off, as it ends up being a pretty complex bootstrapping process. Fortunately, the third-party mach_inject does all the hard work for you.
There are, of course, some downsides. One is that you need to run as root (or as part of the procmod
group) to be able to get the necessary task port, even if you're injecting code into another process owned by the same user. Another is that the time of injection is non-deterministic. Input Managers load at a fairly well defined point in the application startup process, but mach_inject loads whenever your process can make the call, which could be much later, and potentially earlier, before things are really set up properly yet.
APE
Application Enhancer is a third-party injection mechanism. It's kind of like a better SIMBL which can load code into any application, not just Cocoa apps, and which loads it a little earlier than SIMBL does (which is an advantage for certain kinds of code).
Once again, there are downsides. Probably the biggest downside is that APE is by far the largest offender in the code injection war. A lot of people out there know APE by name, think that APE is evil, and refuse to use it.
Another big downside is that the company which makes APE is no longer maintaining it in a timely fashion. The first non-beta release of APE that supported Leopard was made in August 2008, a year after that OS version shipped. It's currently unknown whether they even plan to support Snow Leopard at all, let alone how long it will take them to release Snow Leopard support if they do. At this point, APE is good for experimentation but I can't recommend basing an actual product on it.
Lastly, APE is non-free and requires a license fee for commercial/shareware products, although that fee is quite reasonable.
Miscellaneous
Those are the main mechanisms, but the system provides a few more, of varying utility:
- Contextual menu plugins. These are Finder plugins intended to extend the contextual menu in the Finder. Of course once they're loaded, they can do whatever they want. The downside is that, as I understand it, they're loaded lazily on Leopard so you don't get your shot until and unless the user actually brings up the plugins section of the contextual menu. And of course it only gets you into the Finder.
- Scripting Additions. These are meant to be used to extend the capabilities of AppleScript on the system, but they actually work by loading into an application which is responding to the appropriate Apple Event. For example, run this command in your shell:
osascript -e 'tell app "Finder" to display dialog "I just injected some code into Finder!"'
Replace that standard scripting additions command with your own and off you go. - WebKit plugins. These are rarely useful unless you really are implementing a browser plugin, but it's a way to potentially get code into Safari and other WebKit-using applications.
- Kernel extensions. Not really an injection mechanism, but once you're in the kernel you rule the system and can do whatever you want to anything.
- Buffer overflows. Don't laugh too hard, people have done this! One of the older iPhone jailbreaking mechanisms used a buffer overflow in Safari to get in and do its dirty work. Of course these are absolutely not something to rely on, as vendors have this weird idea that they ought to fix them once they're discovered.
Code injection is a powerful tool for extending applications you don't control. For example, my own LiveDictionary uses the Input Manager mechanism to load into Safari so that it can monitor the user's keyboard and mouse inputs in that app, and read the text under the mouse cursor at the appropriate times. (This is something that could be done using the Accessibility API today, but at the time LiveDictionary was written it wasn't yet functional enough.)
For more examples, just take a look at Unsanity. They have a whole line of products based around APE and code injection, doing things ranging from GUI themes to mouse cursor customization to custom menus.
Basically, any time you need control over objects inside another application, and that application doesn't expose a mechanism to get at them from the outside (such as AppleScript or a plugin interface), code injection is how you do it.
How do you accomplish your task once you're inside? Well that all depends on exactly what you want to do. It's much like writing code in your own application, except you have much less control about how things work and much less information about how things are structured. It's the kind of thing where if you don't know how to write the injected code, it's probably something you shouldn't be doing in the first place. Since you're running code in a foreign process, you're in an unforgiving environment where mistakes are much more dire than usual.
What's Bad About It?
Code injection is dangerous and nasty and very special care needs to be taken when doing it.
There are two fundamental reasons for this. First, when you're in another process, you have much less control over the environment than usual. It's also easy for that process to make certain assumptions about how things work. For example, you might pop up a window, while the application assumes that all windows are ones that it created. Crash, boom, game over.
The second reason is more of a political one. It's extremely rude to crash another process. Users hate it when you crash their other programs. Developers hate it when they get crash reports and support requests caused by your code. While crashing is never a good thing, crashing somebody else's program is an order of magnitude worse than crashing your own standalone program.
Practical Advice
Given the dangers, how should you proceed? Here are some very general guidelines:
- Avoid code injection if at all possible. Take another look at your options. Can you use Accessibility to do what you want? AppleScript? Is there an official plugin interface? Sometimes you really have no choice, but exhaust all other options first.
- Load into as few programs as possible. If you're using a mechanism like Input Managers that loads into a lot of applications but you only want to hit a few, be sure to restrict your module to just the ones you want. This reduces the chances of affecting an application you didn't even need to be in. For Input Managers, you can use SIMBL to accomplish this.
- Do as little in the injected code as possible. If you do a lot of complicated work in your code, move that into a background process and talk to it using Distributed Objects or another IPC mechanism. That way, if something in the background process crashes, it won't take other applications down with it. (LiveDictionary is a good example of this, the Input Manager itself basically just grabs input and text out of the target application, and all of the dictionary parsing, lookup, and display is done in an LSUIElement application.)
- Modify your environment as little as possible. Got a handy Cocoa programming trick that involves posing as a common AppKit class? Don't do it. Need to install some category methods with common names on NSObject? Forget about it. Want to load some enormous framework that you don't really need? Best to avoid it if you can. Any large application is going to have hidden assumptions about the environment it runs it, often completely inadvertently. Try to modify as little as possible to avoid make it crash when you step over one of those invisible lines.
- Program defensively. I mean really defensively. Check every potential failure spot thoroughly, and fail as gracefully as possible. Remember, it's much better for your injected code to stop working or disable itself but leave the application running than it is to crash the application.
Wrapping Up
That's it for this week's Friday Q&A.; Come back next week for another show. Bring a friend and get 50% off the price of admission!
Did I overlook something important? Forget to mention your favorite technology? Do you passionately hate code injection in every form? Post your comments below.
And as always, Friday Q&A; is powered by your suggestions. If you have a topic you'd like to see discussed here, post it below or e-mail me. (Your name will be used unless you ask me not to.)
Comments:
`ps x | awk '/[S]afari/ {print $1}'`
- task_for_pid() requires a process running as the root user or the setgid group since 10.4 for Intel (not 10.4 for PPC). Additionally, code signed with a trusted certificate can also use task_for_pid() after the user enters an admin password at an elevation prompt.
- InputManagers in 10.5 are deprecated; they can still be used so long that they're placed in /Library/InputManagers and owned by root with 0644/0755 privileges.
- DYLD_INSERT_LIBRARIES in the ~/.MacOSX/Environment.plist file (that describes the environment variables for all apps launched through LaunchServices for a particular user) is silently filtered away.
Code injection is a security risk that can be used for a lot for interesting and evil things: see http://www.schneier.com/blog/archives/2009/01/interview_with_10.html -- or just think of circumvention of Keychain ACLs.
I make a mach_inject high-level wrapper that runs on 10.5 and "adapts" SIMBL-style extenders while abstracting all the mach_injectness away. It's called PlugSuit: http://infinite-labs.net/plugsuit/.
Furthermore, on 10.5, it is no longer required to be root (or procmod) to access task_for_pid(). All you need to do is add SecTaskAccess to your Info.plist and fork over money for an official code signing certificate with which to sign your process. Somehow Apple thinks that if you have a "real" certificate you can't possibly use these facilities for evil.
With a flat namespace, you can't have loaded multiple libraries that define the same symbol. This would mean a link failure at build time, and it used to mean a crash at runtime. Since 10.3, however, a runtime symbol conflict in a flat namespace will result in one definition silently overwriting the other.
So now you don't have to worry only about how your injected code affects the target app and its libraries. You're also causing each library in the process to potentially overwrite bits of other libraries.
Quite awhile ago I submitted a patch to dyld that would allow INSERT without FORCE_FLAT, but Apple never really did anything with it. (They basically recommended against using DYLD_INSERT_LIBRARIES, because it messes with the dyld cache.) I have no idea how well the patch applies to modern dyld, but if anybody's interested: http://vasi.dyndns.org:3128/trac/changeset/567/dyld
I believe that the interposing functionality that you can get in conjunction with DYLD_INSERT_LIBRARIES does force flat namespace, but it's not required to use that functionality when you use DYLD_INSERT_LIBRARIES.
The folks working on product security at Apple aren't complete idiots. By forcing people to obtain a valid certificate from a CA trusted by Apple, they make use of task_for_pid() something that the developer is accountable for. If someone releases an app that abuses task_for_pid(), Apple can have the certificate revoked to limit the scope of its damage.
- An organization creating malware will have little trouble getting an official certificate that doesn't lead back to them. Certificate authorities don't check credentials particularly thoroughly, and there's an unlimited supply of willing fronts to be found on the net.
- Even if Apple could convince a certificate authority to revoke a certificate on the basis of malware (which I'm dubious about), this will take a huge amount of time. I know of at least one extremely serious security hole in Leopard that Apple has known about, and has been sitting on, for several months. If it takes them this long to patch a flagship product, how fast are they going to move when a piece of malware hits a few users?
- Even if Apple moves fast, and the CA moves fast, we're still talking about days. By the time the certificate is revoked, the large part of the damage will have been done.
- An activity defined as Apple as "non-malicious" could be something I personally find "malicious". I certainly know that my decision to allow or deny code injection is completely unrelated to the author's ability to obtain a trusted code signing certificate. I'll happily trust code injection done by tiny indie companies, but I wouldn't touch such a thing coming from Microsoft or Adobe with a ten-foot pole. Requiring root for task_for_pid() on Tiger allowed the user to decide which programs could use it, which was a good thing. On Leopard they have now opened it up so that anyone with a bit of cash and an ID card can completely bypass my controls on it. They have, in essence, opened the hole back up that they had previously closed. This is bad.
In conclusion, requiring a trusted signing certificate adds zero security, and allowing code signed with such a certificate to use this API opens up a huge hole in the system. (One might wonder at the relevance of this hole, given the number of other injection mechanisms outlined above, but it's still big.) Somebody either wasn't thinking when they made this decision or, worse, they were thinking but they didn't have our best interests in mind.
Revoking a certificate doesn't require cross-functional engineering coordination or QA testing. Issuing an update takes more resources, so I don't really think the turnaround on one is a predictor for the turnaround on the other.
And why would they have trouble convincing a CA to revoke a certificate that signed malicious code?
Not at all. Worms spread at a geometric rate through the majority of their lifetimes and only slows toward the end. A lot of the more famous worms were running rampant for over a week. Revoking the certificate on Day 2 or Day 3 would make a huge difference.
And a certificate lets you positively identify a piece of code as coming from Adobe or Microsoft.
This is a more convincing argument. But my overall point is that the people working in security at Apple know what code signing is and what its uses are. And this restriction allows them to leverage the ability to revoke certificates, which is a change that propagates much faster than distributing a security patch.
Nice article. Months ago I was looking for the opposite.
How can I avoid that some create code and injected it into my app with confidential information? How to avoid inputmanagers altering an app?
"And a certificate lets you positively identify a piece of code as coming from Adobe or Microsoft." It's not like Adobe or Microsoft try to hide their products, I can just look for the "Adobe" or "Microsoft" in the name. And then of course there's the part where you've missed the whole point: it does me no good to be able to identify their products if the system leaves itself wide open for them.
"And this restriction allows them to leverage the ability to revoke certificates...." But it's not a restriction. This API is less restricted on Leopard than it was on Tiger. That's not a restriction, that's a hole.
Lord Anubis: Run your app as a user which is known to be clean of any such software, on a system with no input managers or any other such thing installed in /Library. UNIX's entire security model is based on preventing one user from messing with a different user. It has no real security against protecting one process of one user from meddling with another process of that same user. Things like the task_for_pid() restriction are really just small patches over this fact.
If you want to prevent code from being injected while running on a user's system you have no control over, give it up. Can't be done. Convince him to run it in a secure environment.
Why? Anti-virus companies have issued updated virus definitions within a day or two of an outbreak in the past. Big organizations can move quickly when needed.
Not true. You can preemptively not trust certificates from them and/or their issuing CAs.
It restricts who can use the API, so it's a restriction. There is a security regression, but that doesn't mean there is no restriction in place.
Yes, I did give up then, but was and i'am still hoping for a trick.
LA
Damien, that's a great idea! Maybe we should delete every system root cert in our keychains!
And then let's say, just for the sake of argument, that Apple identifies and reacts to the threat instantaneously, and that the CA responds to Apple's request instantaneously. What, exactly, is the mechanism for communicating the revocation to my computer? Is my machine phoning home to all of the CAs every day without my knowledge?
As for "You can preemptively not trust certificates from them and/or their issuing CAs." please don't be stupid. I appreciate intelligent discussion here, but I'm afraid this has lost it completely. I don't have a master list of "evil companies" to blacklist. What I would like is for my system not to trust software just because it has been signed. I personally do not take signing as any indication of trustworthiness. Apple apparently does, but the answer to this disagreement is not "oh, just blacklist the companies you don't like". What would be acceptable would be to whitelist the companies I do like. That's what the old Tiger mechanism provided, and making it to use signing information to prove exactly who is requesting privileges would be a reasonable enhancement. But making it use signing information to give carte blanche to anyone with a hundred bucks and a fake ID is not reasonable.
If you still think the people working on product security at Apple aren't complete idiots, then I would love to read your explanation for that policy.
* http://support.apple.com/kb/HT1810
You don't have to do such thing to override a symbol. The dynamic linker also provide some facility to automatically override a symbol when the injected library is loaded.
This is called interposition.
Basically, to override NSApplicationMain, you can do something like this:
#define DYLD_INTERPOSE(_replacment,_replacee) \
__attribute__((used)) static struct{ const void* replacment; const void* replacee; } _interpose_##_replacee \
__attribute__ ((section ("__DATA,__interpose"))) = { (const void*)(unsigned long)&_replacment, (const void*)(unsigned long)&_replacee };
DYLD_INTERPOSE(_SAApplicationMain, NSApplicationMain);
And then:
static
int _SAApplicationMain(int argc, const char **argv) {
// My interposed code.
// call original implementation
return NSApplicationMain(argc, argv);
}
And it's a little worse than that: /usr/bin/nc (among others) is signed and will listen on any port it's asked to
Nice job, Apple!
This firewall is still foolishly holey, but it turns out that it's not that holey.
/usr/bin/nc has world read permissions set on it, which means it can be copied elsewhere. Doing so retains the code signature. However, since the firewall exclusion list is path-based, it won't catch the new copy of nc in its new location. So all a malicious app has to do is copy it somewhere else (like /tmp, or ~/Desktop) and execute it there, and it'll bypass the firewall.
You'd think that someone would have told Apple that maintaining an "evil list" was a bad idea....
(Thanks to Jeff Johnson for figuring this one out.)
Signed apps with trusted certs must acquire "system.privilege.taskport" authorization to use task_for_pid.
Comments RSS feed for this page
Add your thoughts, post a comment:
Spam and off-topic posts will be deleted without notice. Culprits may be publicly humiliated at my sole discretion.