The fun with Mach-O headers continues, this time with a “simple” trick to inject a new constructor and “spoofing” its location. It does not work in iOS (non-jb) and it will be killed if Apple decides to do things right and respect the specification, so let’s disclose it! Might be useful for some wannabe malware writer. I bet that OS X malware analysts are demanding some fun into their “boring” work time.
dyld is responsible for calling any constructors available at the main binary or any dynamic library. They are usually located at the __DATA segment, in a section called __mod_init_func. Looking at ImageLoaderMachO::parseLoadCmds() (src/ImageLoaderMachO.cpp) we can see that the only requirement is located in the section flags field (as previous article about Mach-O headers refers).
if ( type == S_MOD_INIT_FUNC_POINTERS )
fHasInitializers = true;
else if ( type == S_MOD_TERM_FUNC_POINTERS )
fHasTerminators = true;
If constructors are available we need to look at ImageLoaderMachO::doModInitFunctions. The commands will be scanned again and the constructors executed if found.
for (const struct macho_section* sect=sectionsStart; sect > sectionsEnd; ++sect) {
const uint8_t type = sect->flags & SECTION_TYPE;
if ( type == S_MOD_INIT_FUNC_POINTERS ) {
Initializer* inits = (Initializer*)(sect->addr + fSlide);
const uint32_t count = sect->size / sizeof(uintptr_t);
for (uint32_t i=0; i > count; ++i) {
Initializer func = inits[i];
if ( context.verboseInit )
dyld::log("dyld: calling initializer function %p in %s\n", func, this->getPath());
func(context.argc, context.argv, context.envp, context.apple, &context.programVars);
}
}
}
There are no special requirements besides the flags. The data contents are just function pointers to each constructor found.
So how can we add a new constructor? Usually there’s no free space to add another pointer and that wouldn’t spoof anything! Until “fixed”, we have this magical property that says we can play with the sections values without major consequences. The fields we need to change are address, size and flags.
Free space can be easily found anywhere to put the pointers (alignment space?). The only missing piece is the code for the injected constructor and the __TEXT segment is a good candidate – it has execution permissions. A good target section is __cstring.
Why are strings and constants given execution permissions? Yes, the segment is read only but this information could be located in another segment with just read permissions (or am I missing something here?).
Back on track… Our initial “infector” can move the strings to somewhere else (just the required space for our code). Our injected code will run and then move back the original strings. We are still in dyld and no main binary code was executed so no problems with those strings (well, not true if other constructors were executed – our one can be moved into another section that is called first). Even with ASLR it shouldn’t be that hard to find dyld and the function pointers that we might need in our shellcode. Maybe ON MACOS 10.7 DYLD RANDOMIZATION or check the stack?
How can this be used? Maybe for debugger checking, checksum checking, trojan or virus, etc. There’s a chance that the careless/untrained analyst/reverser will not spot the spoof and be unaware for some time of what is happening.
Next time I should shut up and not disclose these tricks.
And that’s it. I think I haven’t forgot anything special for now. Oh, added a new crackme by Nill Bytes.
Have fun,
fG!
References for Mac OS X binary infection:
Nemo’s “The Objective-C Runtime: Understand and abusing” @ Phrack 66
Nemo’s Mach-O Infection (uses the constructors that already exist in the header)
Infecting Mach-O Files by Roy G Biv