Calling printf in OSX - The overkill way
#include <string.h> | |
#include <stdlib.h> | |
#include <unistd.h> | |
#include <mach-o/dyld.h> | |
#include <mach-o/nlist.h> | |
#include <mach-o/dyld_images.h> | |
#include <mach/mach_vm.h> | |
/* Dyld is the OSX Dynamic Linker | |
* /usr/include//mach-o/loader.h | |
* | |
* Tested on OSX 10.10.5 (Yosemite) | |
* and macOS 10.12.1 (Sierra) | |
* | |
* build with: | |
* gcc -D KERNEL_VERSION=$(uname -r | cut -d. -f1) call_printf.c | |
* | |
*/ | |
// Dyld shared cache structures | |
typedef struct { | |
char magic[16]; | |
uint32_t mappingOffset; | |
uint32_t mappingCount; | |
uint32_t imagesOffset; | |
uint32_t imagesCount; | |
uint64_t dyldBaseAddress; | |
uint64_t codeSignatureOffset; | |
uint64_t codeSignatureSize; | |
uint64_t slideInfoOffset; | |
uint64_t slideInfoSize; | |
uint64_t localSymbolsOffset; | |
uint64_t localSymbolsSize; | |
char uuid[16]; | |
#if KERNEL_VERSION >= 16 | |
// New addition for macOS Sierra | |
char sierra_reserved[0x30]; | |
#endif | |
} dyld_cache_header; | |
typedef struct { | |
uint64_t address; | |
uint64_t size; | |
uint64_t file_offset; | |
uint32_t max_prot; | |
uint32_t init_prot; | |
} shared_file_mapping_np; | |
static char *find_libc_and_cache_base(char *program_name, char **cache_rx_base) | |
{ | |
// Get DYLD task infos | |
struct task_dyld_info dyld_info; | |
mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT; | |
kern_return_t ret; | |
ret = task_info(mach_task_self_, | |
TASK_DYLD_INFO, | |
(task_info_t)&dyld_info, | |
&count); | |
if (ret != KERN_SUCCESS) { | |
return NULL; | |
} | |
// Get image array's size and address | |
mach_vm_address_t image_infos = dyld_info.all_image_info_addr; | |
struct dyld_all_image_infos *infos = (struct dyld_all_image_infos *)image_infos; | |
uint32_t image_count = infos->infoArrayCount; | |
struct dyld_image_info *image_array = infos->infoArray; | |
// Find libsystem_c.dylib among them | |
struct dyld_image_info *image; | |
char *libc = NULL; | |
char *first_lib = NULL; | |
for (int i = 0; i < image_count; ++i) { | |
image = image_array + i; | |
// Find libsystem_c.dylib's load address | |
if (strstr(image->imageFilePath, "libsystem_c.dylib")) { | |
libc = (char*)image->imageLoadAddress; | |
//printf("libc base @ %p\n", libc); | |
#if KERNEL_VERSION >= 16 | |
// Thanks Sierra | |
*cache_rx_base = (char*)infos->sharedCacheBaseAddress; | |
return libc; | |
#endif | |
} | |
// Look for first dynamic library | |
if (!strstr(image->imageFilePath, program_name)) { | |
char *load_addr = (char*)image->imageLoadAddress; | |
if (!first_lib || first_lib > load_addr) { | |
first_lib = load_addr; | |
} | |
} | |
} | |
// find beginning of RX mapping | |
// this method sucks | |
while (memcmp(first_lib, "dyld_v1 x86_64h", 16)) { | |
--first_lib; | |
} | |
*cache_rx_base = first_lib; | |
return libc; | |
} | |
static char *find_printf(char *libc, char *shared_cache_rx_base) | |
{ | |
struct mach_header_64 *libc_header = (struct mach_header_64 *)libc; | |
uint32_t ncmds = libc_header->ncmds; | |
struct symtab_command *symcmd = NULL; | |
struct segment_command_64 *cmd; | |
cmd = (struct segment_command_64*)(libc + sizeof(struct mach_header_64)); | |
// Get symtab and dysymtab | |
for (uint32_t i = 0; i < ncmds; ++i) { | |
if (cmd->cmd == LC_SYMTAB) { | |
symcmd = (struct symtab_command*)cmd; | |
break; | |
} | |
cmd = (struct segment_command_64*)((char *)cmd + cmd->cmdsize); | |
} | |
dyld_cache_header *h = (dyld_cache_header*) shared_cache_rx_base; | |
shared_file_mapping_np *mapping = (void*)(h + 1); | |
/* | |
* DYLD SHARED CACHE MAPPINGS* | |
* =========================== | |
* | |
* (*): Without ASLR slide | |
* | |
* ## OSX Yosemite | |
* | |
* ---------------------- 0x7fff70000000 | |
* | | | |
* | | | |
* | | | |
* | | | |
* | RW- | | |
* | | | |
* | | | |
* | | | |
* |----------------------| 0x7fff70000000 + [RW-].size | |
* | Junk | | |
* |----------------------| 0x7fff80000000 | |
* | Cache Header | | |
* |----------------------| | |
* | | | |
* | R-X | | |
* | | | |
* | ... | | |
* | libsystem_c.dylib | | |
* | ... | | |
* | | | |
* | | | |
* |----------------------| 0x7fff80000000 + [R-X].size | |
* | | | |
* | | | |
* | | | |
* | R-- | | |
* | | | |
* | | | |
* | | | |
* | | | |
* ---------------------- 0x7fff80000000 + [R-X].size + [R--].size | |
* | |
* cache.base = [R-X].address + [R-X].size - [R--].offset | |
* | |
* | |
* | |
* ## macOS Sierra | |
* | |
* Memory mapping follows file layout, so nothing strange to | |
* take into account. The only thing to take into account is | |
* presence of junk between each mapping, so offsets have to be | |
* handled cleverly. | |
* | |
*/ | |
char *shared_cache_base = shared_cache_rx_base; | |
size_t rx_size; | |
size_t rw_size; | |
uint64_t rx_addr; | |
uint64_t ro_addr; | |
off_t ro_off; | |
for (int i = 0; i < h->mappingCount; ++i) { | |
if (mapping[i].init_prot & VM_PROT_EXECUTE) { | |
// Get size and address of [R-X] mapping | |
rx_size = mapping[i].size; | |
rx_addr = mapping[i].address; | |
} else if (mapping[i].init_prot & VM_PROT_WRITE) { | |
// Get size of [RW-] mapping | |
rw_size = mapping[i].size; | |
} else if (mapping[i].init_prot == VM_PROT_READ) { | |
// Get file offset of [R--] mapping | |
ro_off = mapping[i].file_offset; | |
ro_addr = mapping[i].address; | |
} | |
} | |
// Can be determined by dyld_all_image_info->sharedCacheSlide but meh. | |
uint64_t aslr_slide = (uint64_t)shared_cache_rx_base - rx_addr; | |
/* | |
* Previously 'shared_cache_base + symcmd->XXXX', but since there is some | |
* gap between each mapping, it would only work on Yosemite out of luck and | |
* segfault in Sierra. Uglier, but it works on both versions. | |
*/ | |
char *shared_cache_ro = (char*)(ro_addr + aslr_slide); | |
uint64_t stroff_from_ro = symcmd->stroff - rx_size - rw_size; | |
uint64_t symoff_from_ro = symcmd->symoff - rx_size - rw_size; | |
struct nlist_64 *symtab; | |
char *strtab = shared_cache_ro + stroff_from_ro; | |
symtab = (struct nlist_64 *)(shared_cache_ro + symoff_from_ro); | |
for(uint32_t i = 0; i < symcmd->nsyms; ++i){ | |
uint32_t strtab_off = symtab[i].n_un.n_strx; | |
uint64_t func = symtab[i].n_value; | |
if(strcmp(&strtab[strtab_off], "_printf") == 0) { | |
return (char*)(func + aslr_slide); | |
} | |
} | |
return NULL; | |
} | |
int main (int argc, char *argv[]) | |
{ | |
char *shared_cache_rx_base = NULL; | |
char *libc = find_libc_and_cache_base(argv[0], &shared_cache_rx_base); | |
if (!libc) { | |
//printf("libsystem_c not found - aborting\n"); | |
return 1; | |
} | |
if (!shared_cache_rx_base) { | |
//printf("shared cache base not found - aborting\n"); | |
return 1; | |
} | |
char *printf_addr = find_printf(libc, shared_cache_rx_base); | |
if (!printf_addr) { | |
//printf("printf not found - aborting\n"); | |
return 1; | |
} | |
void (*print)(const char *fmt, ...) = (void *)printf_addr; | |
print("Gotcha\n"); | |
return 0; | |
} |
This comment has been minimized.
This comment has been minimized.
Oh indeed. But that would have defeated the "don't use syscalls" rule ;) (that I already broke once actually) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This comment has been minimized.
FWIW you can get the
cache_rx_base
even pre-Sierra the same way dyld gets it: