Skip to content

Instantly share code, notes, and snippets.

@4ch12dy
Forked from P1kachu/calling_printf_osx.c
Created April 20, 2018 16:13
Show Gist options
  • Select an option

  • Save 4ch12dy/0c3f33ebf9052254cd0f27bda035685c to your computer and use it in GitHub Desktop.

Select an option

Save 4ch12dy/0c3f33ebf9052254cd0f27bda035685c to your computer and use it in GitHub Desktop.

Revisions

  1. @P1kachu P1kachu created this gist Nov 24, 2016.
    257 changes: 257 additions & 0 deletions calling_printf_osx.c
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,257 @@
    #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;
    }