mikeash.com: just this guy, you know?

Posted at 2009-01-30 18:34 | RSS feed (Full text feed) | Blog Index
Next article: Friday Q&A 2009-02-06: Profiling With Shark
Previous article: Friday Q&A 2009-01-23
Tags: codeinjection evil fridayqna hack
Friday Q&A 2009-01-30: Code Injection
by Mike Ash  

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) 
And if we look in our Console (where Safari's stderr goes), we see:
    [0x0-0x17f97f8].com.apple.Safari[23558]: Here's your injected code!
And that's code injection in a nutshell. You bang into another process, load some of your own code, and get it to run. What you do there is up to you!

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:

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
What's It Good For?
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:

  1. 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.
  2. 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.
  3. 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.)
  4. 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.
  5. 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.)

Did you enjoy this article? I'm selling whole books full of them! Volumes II and III are now out! They're available as ePub, PDF, print, and on iBooks and Kindle. Click here for more information.

Comments:

One option you left out is letting dyld (the dynamic linker) insert your code in. Setting DYLD_INSERT_LIBRARIES can be useful in getting dyld to load your library. I've used this to override functions found in system libraries in certain apps.
Yeah, that's a good option that I just plain forgot about. It has the downside of requiring you to launch the target application directly (can't grab an app that's already running, or one that the user starts himself) but it has the great upside of being well supported.
one awk for the price of two greps an one awk: ;-)

`ps x | awk '/[S]afari/ {print $1}'`
Note: All code injection techniques have been blocked by 10.5 so that root privileges are required. The following is true:

 - 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/.


It's not true that all code injection functionality has been blocked to always require root. As two examples, scripting additions can be installed in ~ without root privileges, and DYLD_INSERT_LIBRARIES can be used without root as long as it's applied individually, not globally.

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.
Anyone who uses DYLD_INSERT_LIBRARIES should be aware of one particular danger it holds, because it requires DYLD_FORCE_FLAT_NAMESPACE.

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

Are you sure about that? The man page states fairly explicitly that DYLD_INSERT_LIBRARIES won't allow overriding of existing symbols unless flat namespace is forced, but it doesn't say that DYLD_INSERT_LIBRARIES forces a flat namespace. Indeed it would make no sense to have such a warning if it did.

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.
Yeah, DYLD_FORCE_FLAT_NAMESPACE is not strictly required, just most interesting uses I've seen of DYLD_INSERT_LIBRARIES have been to overload symbols and so have indeed used a flat namespace. You can of course set globals or swizzle methods from a constructor function without needing DYLD_FORCE_FLAT_NAMESPACE. Maybe even use mach_override to safely overload symbols?

Yeah, method swizzling or class posing would be a way of avoiding flat namespaces, as would simply not overriding stuff (sometimes you just want to sit around and pop up windows, listen for notifications, or whatever). Mach_override would be a safe way to do it too, well, as safe as mach_override ever is. Safer than forcing your target into flat namespace, anyway.
Contextual menu plugins will load in any program that has a contextual menu and not only the Finder. I think Apple also indicated that these won't be supported in the near future.
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.


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.
That's a nice idea but I don't buy it at all:

- 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.
- 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?


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?

- 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.


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.

- 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.


And a certificate lets you positively identify a piece of code as coming from Adobe or Microsoft.

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.


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.
Hi,
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?
Damien: Revoking a certificate requires being damned sure that they're revoking something that needs it. It also requires becoming aware of the problem, getting that awareness to the right people, convincing them to put it at the top of their priority list, communicating with the CA, etc. Big organizations move slowly. And this all assumes that Apple has some person dedicated to dealing with revoking certificates of malicious programs, which I am virtually certain they do not. I find the idea that they could revoke a certificate and propagate said revocation out to the majority of Macs within 2-3 days of a worm showing up completely ridiculous.

"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.
Revoking a certificate requires being damned sure that they're revoking something that needs it. It also requires becoming aware of the problem, getting that awareness to the right people, convincing them to put it at the top of their priority list, communicating with the CA, etc. Big organizations move slowly. And this all assumes that Apple has some person dedicated to dealing with revoking certificates of malicious programs, which I am virtually certain they do not. I find the idea that they could revoke a certificate and propagate said revocation out to the majority of Macs within 2-3 days of a worm showing up completely ridiculous.


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.

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.


Not true. You can preemptively not trust certificates from them and/or their issuing CAs.

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.


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.
Thanks Mike,

Yes, I did give up then, but was and i'am still hoping for a trick.

LA
You can preemptively not trust certificates from them and/or their issuing CAs.

Damien, that's a great idea! Maybe we should delete every system root cert in our keychains!

Anti-virus companies are built around rapid reaction to threats. It is their core competence. Companies can move quickly, sometimes, if it is their core competence. Responding to malware is so far outside Apple's core competence you need a telescope to see it. And I don't know how rapidly CAs respond to revocation requests but I see no reason for that to be particularly fast either.

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.
I just remembered a much worse code-signing shenanigan on Leopard: the firewall, when placed in "Set access for specific services and applications" mode, always allows incoming connections to applications that have been signed with a certificate issued by a trusted authority, no matter what applications you actually have in the list*.

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
"Yeah, DYLD_FORCE_FLAT_NAMESPACE is not strictly required, just most interesting uses I've seen of DYLD_INSERT_LIBRARIES have been to overload symbols and so have indeed used a flat namespace [...] "

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);
}
the firewall...always allows incoming connections to applications that have been signed


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
Good catch! I never thought it all the way through. End result: the Leopard firewall is 100% useless against all malicious code and any merely misguided code that is signed. The only protection it affords is against well-intentioned, unsigned apps.

Nice job, Apple!
Turns out that /usr/bin/nc is excepted from the free pass for signed applications. In /Library/Preferences/com.apple.alf.plist there's a key "explicitauths" which lists signed applications which should still prompt, and /usr/bin/nc is on it. And indeed, if you set the firewall to a restrictive mode and tell nc to listen on a port, you still get prompted.

This firewall is still foolishly holey, but it turns out that it's not that holey.
Third time's the charm, I hope....

/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.)
The "evil list" is just plain short, too: again I haven't tested these, but I'm fairly sure /bin/zsh and /usr/bin/tcl can be asked to listen to anything.
"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."

Signed apps with trusted certs must acquire "system.privilege.taskport" authorization to use task_for_pid.
Just checked this to make sure. You definitely still need to acquire the authorization right to use TFP, even if you're signed by a trusted cert.
Thanks for the additional information. Too bad they have not seen fit to update the man page with this restriction sometime in the intervening eighteen months.
How did you figured what are the function name inside the library which our custom functions would be replacing to?

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.

Name:
The Answer to the Ultimate Question of Life, the Universe, and Everything?
Comment:
Formatting: <i> <b> <blockquote> <code>.
NOTE: Due to an increase in spam, URLs are forbidden! Please provide search terms or fragment your URLs so they don't look like URLs.
Code syntax highlighting thanks to Pygments.
Hosted at DigitalOcean.