macosx86-64mach-o

Minimal Mach-o 64 binary


I thinks this is a strange question, but now I prepare to hand-made a minimal Mach-O 64 binary, like the same problem on ELF (http://timelessname.com/elfbin/).

But currently I still sucks on how to debug my binary. otool does NOT show me the error, but I get the suck how to debug the binary. The following is the binary I make in hex view. In the current stage I've no idea how to continue. Any suggestion? or I should stop this stupid things...

0000000: cffa edfe 0700 0001 0300 0080 0200 0000  ................
0000010: 0900 0000 0002 0000 8500 0000 0000 0000  ................
0000020: 1900 0000 4800 0000 5f5f 5041 4745 5a45  ....H...__PAGEZE
0000030: 524f 0000 0000 0000 0000 0000 0000 0000  RO..............
0000040: 0000 0000 0100 0000 0000 0000 0000 0000  ................
0000050: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000060: 0000 0000 0000 0000 1900 0000 9800 0000  ................
0000070: 5f5f 5445 5854 0000 0000 0000 0000 0000  __TEXT..........
0000080: 0010 0000 0000 0000 0010 0000 0000 0000  ................
0000090: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000a0: 0700 0000 0500 0000 0100 0000 0000 0000  ................
00000b0: 5f5f 7465 7874 0000 0000 0000 0000 0000  __text..........
00000c0: 5f5f 5445 5854 0000 0000 0000 0000 0000  __TEXT..........
00000d0: 1010 0000 0000 0000 1000 0000 0000 0000  ................
00000e0: 2002 0000 0100 0000 0000 0000 0000 0000   ...............
00000f0: 0004 0080 0000 0000 0000 0000 0000 0000  ................
0000100: 1900 0000 4800 0000 5f5f 4c49 4e4b 4544  ....H...__LINKED
0000110: 4954 0000 0000 0000 0000 0000 0000 0000  IT..............
0000120: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000130: 0000 0000 0000 0000 0700 0000 0100 0000  ................
0000140: 0000 0000 0000 0000 2200 0080 3000 0000  ........"...0...
0000150: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000160: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000170: 0000 0000 0000 0000 0200 0000 1800 0000  ................
0000180: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000190: 0b00 0000 5000 0000 0000 0000 0000 0000  ....P...........
00001a0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00001b0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00001c0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00001d0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00001e0: 1b00 0000 1800 0000 506f 7765 7265 6420  ........Powered
00001f0: 6279 2063 6d6a 0000 2400 0000 1000 0000  by cmj..$.......
0000200: 000a 0a00 000a 0a00 2800 0080 1800 0000  ........(.......
0000210: 2002 0000 0000 0000 0000 0000 0000 0000   ...............
0000220: 48c7 c001 0000 0248 c7c7 0400 0000 0f05  H......H........
0000230: 0a                                       .

[UPDATE] My environment is Mac OSX 10.10 which the online information does not workable in my case (e.g. https://gist.github.com/softboysxp/1084476)


Solution

  • On a mac with x86-64 physical hardware (see below for Rosetta 2 update) you cannot go below the 4096 byte limit since Yosemite 10.10.5. Its Mach-O kernel checks are more restrictive now. Alternatively to dyld and LC_MAIN, you can go for LC_UNIXTHREAD and obviously keeping your LC_SEGMENT64. Sections are not necessary, but dropping them will make tracking the binary harder.

    Since El Capitan, PAGEZERO with nonzero size is required for 64-bit executables. Here is a working HelloWorld example, valid on Sierra 10.12.2 assembled with NASM or YASM

    ; A minimal Mach-o x64 executable for OS X Sierra
    ; $ nasm -f bin -o tiny_hello tiny_hello.s
    ; $ chmod +x tiny_hello
    ; Constants (For readability)
    %define MH_MAGIC_64                    0xfeedfacf
    %define CPU_ARCH_ABI64                0x01000000
    %define    CPU_TYPE_I386                0x00000007
    %define CPU_TYPE_X86_64                CPU_ARCH_ABI64 | CPU_TYPE_I386
    %define CPU_SUBTYPE_LIB64            0x80000000
    %define CPU_SUBTYPE_I386_ALL        0x00000003
    %define MH_EXECUTE                    0x2
    %define MH_NOUNDEFS                    0x1
    %define LC_SEGMENT_64                0x19
    %define LC_UNIXTHREAD                0x5 
    %define VM_PROT_READ                0x1
    %define VM_PROT_WRITE                0x2
    %define VM_PROT_EXECUTE                0x4
    %define x86_THREAD_STATE64            0x4
    %define    x86_EXCEPTION_STATE64_COUNT    42
    %define SYSCALL_CLASS_SHIFT            24
    %define SYSCALL_CLASS_MASK            (0xFF << SYSCALL_CLASS_SHIFT)
    %define SYSCALL_NUMBER_MASK            (~SYSCALL_CLASS_MASK)  
    %define SYSCALL_CLASS_UNIX            2
    %define SYSCALL_CONSTRUCT_UNIX(syscall_number) \
                ((SYSCALL_CLASS_UNIX << SYSCALL_CLASS_SHIFT) | \
                 (SYSCALL_NUMBER_MASK & (syscall_number)))
    %define SYS_exit                    1
    %define SYS_write                    4
    ; NASM directive, not compiled
    ; Use RIP-Relative addressing for x64
    BITS    64
    ;DEFAULT    REL
    %define __origin 0x100000000
    org __origin
    ; Mach-O header
    DD        MH_MAGIC_64                                        ; magic
    DD        CPU_TYPE_X86_64                                    ; cputype
    DD        CPU_SUBTYPE_LIB64 | CPU_SUBTYPE_I386_ALL        ; cpusubtype
    DD        MH_EXECUTE                                        ; filetype
    DD        3                                                ; ncmds
    DD        __COMMANDSend  - __COMMANDSstart                ; sizeofcmds
    DD        MH_NOUNDEFS                                        ; flags
    DD        0x0                                                ; reserved
    __COMMANDSstart:
    
    ___PAGEZEROstart:
            DD        LC_SEGMENT_64                                    ; cmd
            dd         ___PAGEZEROend - ___PAGEZEROstart                ; command size
    hello_str:
            db         '__PAGEZERO',0x0,0,0,0,0,0 ; segment name (pad to 16 bytes)
            DQ        0x0                                                ; vmaddr
            DQ        __origin                                        ; vmsize
            DQ        0                                                ; fileoff
            DQ        0                                                ; filesize
            DD        0                                                 ; maxprot
            DD        0                                                ; initprot
            DD        0x0                                                ; nsects
            DD        0x0                                                ; flags
    ___PAGEZEROend:
    ; Segment and Sections
    ___TEXTstart:
            DD        LC_SEGMENT_64                                    ; cmd
            dd ___TEXTend - ___TEXTstart    ; command size
    
            db '__TEXT',0,0,0,0,0,0,0,0,0,0 ; segment name (pad to 16 bytes)
            DQ        __origin                                        ; vmaddr
            DQ        ___codeend - __origin                ; vmsize
            DQ        0                                                ; fileoff
            DQ        ___codeend - __origin                    ; filesize
            DD        VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE    ; maxprot
            DD        VM_PROT_READ | VM_PROT_EXECUTE                            ; initprot
            DD        0x0                                                ; nsects
            DD        0x0                                                ; flags
    ___TEXTend:
    __UNIX_THREADstart:
    ; UNIX Thread Status
    DD        LC_UNIXTHREAD                                    ; cmd
    DD        __UNIX_THREADend - __UNIX_THREADstart             ; cmdsize
    DD        x86_THREAD_STATE64                                ; flavor
    DD        x86_EXCEPTION_STATE64_COUNT                        ; count
    DQ        0x0, 0x0, 0x00, 0x0                                ; rax, rbx , rcx , rdx
    DQ        0x01, hello_str, 0x00, 0x00                        ; rdi = STDOUT, rsi = address of hello_str,  rbp, rsp
    DQ        0x00, 0x00                                        ; r8 and r9
    DQ        0x00, 0x00, 0x00, 0x00, 0x00, 0x00                ; r10, r11, r12, r13, r14, r15
    DQ         ___codestart, 0x00, 0x00, 0x00, 0x00            ; rip, rflags, cs, fs, gs
    __UNIX_THREADend:
    __COMMANDSend:
    ___codestart:                                                    ; 24 bytes
        ; rdi and rsi have already been set in the initial state
        mov        rdx, 11
        mov        rax, SYSCALL_CONSTRUCT_UNIX(SYS_write)
        syscall
        mov            rdi, rax
        mov            rax, SYSCALL_CONSTRUCT_UNIX(SYS_exit)
        syscall
    ___codeend:
        times 4096-($-$$) DB  0;
        filesize    EQU    $-$$
    

    Rosetta 2 update when running Monterey 12.6.3 on M1 Max the file size minimal limit is NOT checked. In my example 399 bytes is working perfectly fine. Another observation I've made is that initial values of registers from LC_UNIXTHREAD aren't set. So the initial setup needs to be done using proper assembly instructions. So the executable part now also need include rdi & rsi setup:

        ___codestart:                                            
            mov        rdi, 0x01 ; STDOUT
            mov        rsi, hello_str
            mov        rdx, 11
            mov        rax, SYSCALL_CONSTRUCT_UNIX(SYS_write)
            syscall
            mov            rdi, rax
            mov            rax, SYSCALL_CONSTRUCT_UNIX(SYS_exit)
            syscall
        ___codeend: