Minimal Mach-o 64 binary
Asked Answered
A

1

6

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)

Anatolian answered 8/9, 2015 at 9:17 Comment(0)
O
15

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:
Oogonium answered 18/9, 2015 at 19:31 Comment(1)
Yes, I know the PAGEZERO is not necessary, but I've NOT find the limitation after Yosemite 10.10.5 :(. Thanks for your sharing~Anatolian

© 2022 - 2024 — McMap. All rights reserved.