Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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;
}
@Siguza

This comment has been minimized.

Copy link

Siguza commented Jun 19, 2017

FWIW you can get the cache_rx_base even pre-Sierra the same way dyld gets it:

syscall(294, &cache_rx_base);
@P1kachu

This comment has been minimized.

Copy link
Owner Author

P1kachu commented Jul 2, 2017

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
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.