実行可能な最小のMach-O実行可能ファイル


8

x86_64で実行可能な最小の実行可能なMach-O実行可能ファイルは何ですか?プログラムは何もできません(戻りコードを返すことさえできません)が、有効な実行可能ファイルでなければなりません(エラーなしで実行する必要があります)。

私の試み:

GNUアセンブラー(null.s):

.text
.globl _main

_main:
    retq

コンパイルとリンク:

as -o null.o null.s
ld -e _main -macosx_version_min 10.12 -o null null.o -lSystem

サイズ:4248バイト

16進数の値を見ると、おそらく削除できるゼロパディングがたくさんあるようですが、方法はわかりません。また、libSystemをリンクせずにexectubaleを実行できるかどうかもわかりません...



1
@JanDvorak "teensy"のMach-Oバージョンもあります。osxbook.com/ blog
DepressedDaniel

このGitHub Gistは、Mach-Oヘッダーを独自に定義することで小さなexectubaleを返すこともできます。gist.github.com
Martin M.

回答:


8

最小の実行可能なMach-Oは、少なくとも0x1000バイトでなければなりません。XNUの制限により、ファイルは少なくともでなければなりませんPAGE_SIZExnu-4570.1.46/bsd/kern/mach_loader.c1600行目を参照してください。

ただし、そのパディングをカウントせず、意味のあるペイロードのみをカウントする場合、macOSで実行可能な最小ファイルサイズは0xA4バイトです。

それはmach_header(またはfat_header/ mach_header_64で始まる必要がありますが、それらは大きいです)。

struct mach_header {
    uint32_t    magic;      /* mach magic number identifier */
    cpu_type_t  cputype;    /* cpu specifier */
    cpu_subtype_t   cpusubtype; /* machine specifier */
    uint32_t    filetype;   /* type of file */
    uint32_t    ncmds;      /* number of load commands */
    uint32_t    sizeofcmds; /* the size of all the load commands */
    uint32_t    flags;      /* flags */
};

サイズは0x1Cバイトです。
magicでなければなりませんMH_MAGIC。実行可能ファイルな
ので使用CPU_TYPE_X86x86_32ます。
filtetypeする必要がありMH_EXECUTE、実行のために、ncmdsそしてsizeofcmdsコマンドに依存し、かつ有効である必要があります。
flagsそれはそれほど重要ではなく、他の値を提供するには小さすぎます。

次はロードコマンドです。ヘッダーは、RX権限を使用して1つのマッピングに正確に含まれている必要があります。これもXNUの制限です。
また、コードをRXマッピングに配置する必要があるため、これで問題ありません。
そのために必要segment_commandです。

定義を見てみましょう。

struct segment_command { /* for 32-bit architectures */
    uint32_t    cmd;        /* LC_SEGMENT */
    uint32_t    cmdsize;    /* includes sizeof section structs */
    char        segname[16];    /* segment name */
    uint32_t    vmaddr;     /* memory address of this segment */
    uint32_t    vmsize;     /* memory size of this segment */
    uint32_t    fileoff;    /* file offset of this segment */
    uint32_t    filesize;   /* amount to map from the file */
    vm_prot_t   maxprot;    /* maximum VM protection */
    vm_prot_t   initprot;   /* initial VM protection */
    uint32_t    nsects;     /* number of sections in segment */
    uint32_t    flags;      /* flags */
};

cmdである必要がありLC_SEGMENT、かつcmdsizeなければなりませんsizeof(struct segment_command) => 0x38
segname内容は関係ありません。後で使用します。

vmaddr有効なアドレス(私が使用しますなければならない0x1000)、vmsizeの有効&倍数である必要がありPAGE_SIZEfileoffである必要があり0filesizeファイルのサイズより小さいが、より大きくなければならないmach_header(少なくともsizeof(header) + header.sizeofcmds私が使用したものです)。

maxprotとするinitprot必要がありますVM_PROT_READ | VM_PROT_EXECUTEmaxport通常も持っていVM_PROT_WRITEます。
nsectsセクションは必要ないため、合計するとサイズが大きくなるため、0 私はflags0に設定しました。

ここで、いくつかのコードを実行する必要があります。そこそのための2つのロード・コマンドは、次のとおりentry_point_commandthread_command。1977年頃のを
entry_point_command参照してくださいxnu-4570.1.46/bsd/kern/mach_loader.c

1977    /* kernel does *not* use entryoff from LC_MAIN.  Dyld uses it. */
1978    result->needs_dynlinker = TRUE;
1979    result->using_lcmain = TRUE;

したがって、これを使用するにはDYLDを機能させる必要があります。つまり、、__LINKEDITsymtab_command、およびが必要dysymtab_commanddylinker_commandなりdyld_info_commandます。「最小」ファイルの過剰。

したがって、thread_command特にLC_UNIXTHREAD必要なスタックもセットアップするため、を使用します。

struct thread_command {
    uint32_t    cmd;        /* LC_THREAD or  LC_UNIXTHREAD */
    uint32_t    cmdsize;    /* total size of this command */
    /* uint32_t flavor         flavor of thread state */
    /* uint32_t count          count of uint32_t's in thread state */
    /* struct XXX_thread_state state   thread state for this flavor */
    /* ... */
};

cmdなるだろうLC_UNIXTHREADcmdsizeとなり0x50ます(下記参照)。
flavourでありx86_THREAD_STATE32、カウントはx86_THREAD_STATE32_COUNT0x10)です。

thread_state。私たちはx86_thread_state32_t別名が必要です_STRUCT_X86_THREAD_STATE32

#define _STRUCT_X86_THREAD_STATE32  struct __darwin_i386_thread_state
_STRUCT_X86_THREAD_STATE32
{
    unsigned int    __eax;
    unsigned int    __ebx;
    unsigned int    __ecx;
    unsigned int    __edx;
    unsigned int    __edi;
    unsigned int    __esi;
    unsigned int    __ebp;
    unsigned int    __esp;
    unsigned int    __ss;
    unsigned int    __eflags;
    unsigned int    __eip;
    unsigned int    __cs;
    unsigned int    __ds;
    unsigned int    __es;
    unsigned int    __fs;
    unsigned int    __gs;
};

したがって、uint32_tスレッドが開始される前に対応するレジスタにロードされるのは、実際には16 です。

ヘッダー、セグメントコマンド、スレッドコマンドを追加すると、0xA4バイトが得られます。

では、ペイロードを作成します。
我々はそれを印刷したいとしましょうHi Frandexit(0)

macOS x86_32のSyscall規約:

  • スタックに渡され、右から左にプッシュされた引数
  • スタック16バイト境界整列(注:8バイト境界整列で問題ないようです)
  • eaxレジスターのsyscall番号
  • 割り込みで呼び出す

macOS での syscallについて詳しくは、こちらをご覧ください

それを知って、これがアセンブリのペイロードです:

push   ebx          #; push chars 5-8
push   eax          #; push chars 1-4
xor    eax, eax     #; zero eax
mov    edi, esp     #; preserve string address on stack
push   0x8          #; 3rd param for write -- length
push   edi          #; 2nd param for write -- address of bytes
push   0x1          #; 1st param for write -- fd (stdout)
push   eax          #; align stack
mov    al, 0x4      #; write syscall number
#; --- 14 bytes at this point ---
int    0x80         #; syscall
push   0x0          #; 1st param for exit -- exit code
mov    al, 0x1      #; exit syscall number
push   eax          #; align stack
int    0x80         #; syscall

最初の前の行に注意してくださいint 0x80
segname何でもかまいません、覚えていますか?ペイロードをそこに置くことができます。ただし、それはわずか16バイトで、もう少し必要です。
したがって、14バイトにを配置しjmpます。

別の「空き」スペースは、スレッド状態レジスタです。
それらのほとんどに何でも設定でき、残りのペイロードをそこに配置します。

また、移動するよりも短いため、文字列を__eaxand __ebxに配置します。

そこで、我々は使用することができ__ecx__edx__edi私たちのペイロードの残りの部分を合わせて。のアドレスthread_cmd.state.__ecxと最後のアドレスの違いを見ると、の最後の2バイトに(または)segment_cmd.segnameを入れる必要があると計算されます。jmp 0x3aEB38segname

したがって、アセンブルされるペイロードは53 50 31C0 89E7 6A08 57 6A01 50 B004、最初の部分、EB38jmp、およびCD80 6A00 B001 50 CD802番目の部分用です。

そして最後のステップ-を設定し__eipます。ファイルは0x1000(覚えているvmaddr)に読み込まれ、ペイロードはoffsetから始まり0x24ます。

ここだxxd結果ファイルの:

00000000: cefa edfe 0700 0000 0300 0000 0200 0000  ................
00000010: 0200 0000 8800 0000 0000 2001 0100 0000  .......... .....
00000020: 3800 0000 5350 31c0 89e7 6a08 576a 0150  8...SP1...j.Wj.P
00000030: b004 eb38 0010 0000 0010 0000 0000 0000  ...8............
00000040: a400 0000 0700 0000 0500 0000 0000 0000  ................
00000050: 0000 0000 0500 0000 5000 0000 0100 0000  ........P.......
00000060: 1000 0000 4869 2046 7261 6e64 cd80 6a00  ....Hi Frand..j.
00000070: b001 50cd 8000 0000 0000 0000 0000 0000  ..P.............
00000080: 0000 0000 0000 0000 0000 0000 2410 0000  ............$...
00000090: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000000a0: 0000 0000                                ....

0x1000バイトまでの任意のものでパッドし、chmod + xして実行します:)

PS x86_64について-64ビットバイナリが必要です__PAGEZEROVM_PROT_NONE0x0に保護カバーページがあるマッピング)。IIRC彼らは[Apple]がそれを32ビットモードで必要としないようにしたのは、一部のレガシーソフトウェアにそれがなく、それを壊すことを恐れているからです。


2
これは信じられないほど完全な答えです。サイトへようこそ!:)
James

1
私はtruncate -s 4096 foo(fooが実行可能ファイルである場合)を使用して0x1000バイトに適合させ、完全に機能しました:)
Martin M.

4

28バイト、事前コンパイル済み。

以下は、Mach-Oバイナリのフォーマットされた16進ダンプです。

00 00 00 00 FF FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
|---------| |---------| |---------| |---------| |---------| |---------| |---------/
|           |           |           |           |           |           +---------- uint32_t        flags;          // Once again redundant, no flags for safety.
|           |           |           |           |           +---------------------- uint32_t        sizeofcmds;     // Size of the commands. Not sure the specifics for this, yet it doesn't particularly matter when there are 0 commands. 0 is used for safety.
|           |           |           |           +---------------------------------- uint32_t        ncmds;          // Number of commands this library proivides. 0, this is a redundant library.
|           |           |           +---------------------------------------------- uint32_t        filetype;       // Once again, documentation is lacking in this department, yet I don't think it particularly matters for our useless library.
|           |           +---------------------------------------------------------- cpu_subtype_t   cpusubtype;     // Like cputype, this suggests what systems this can run on. Here, 0 is ANY.
|           +---------------------------------------------------------------------- cpu_type_t      cputype;        // Defines what cpus this can run on, I guess. -1 is ANY. This library is definitely cross system compatible.
+---------------------------------------------------------------------------------- uint32_t        magic;          // This number seems to be provided by the compiling system, as I lack a system to compile Mach-O, I can't retrieve the actual value for this. But it will always be 4 bytes. (On 32bit systems)

完全にヘッダーで構成され、データもコマンドも必要ありません。これは本来、可能な最小のMach-Oバイナリです。考えられるハードウェアでは正しく動作しない可能性がありますが、仕様と一致しています。

実際のファイルを提供しますが、完全に印刷できない文字で構成されています。


説明の最初の行は「もう一度」で始まります。それらを別の順序で書いたと思います。
2016

左から右のように、下から上に読みます。はい、その順番で書きました。
ATaco 16

しかし、これは実際には意味のある意味で実行されません。
DepressedDaniel 2016

意味のある意味では技術的に実行されませんが、技術的に実行可能です。仕様が正しいと仮定すると、これはデータのないライブラリです。または、もっと簡単に言えば、ライブラリのヘッダーだけです。
ATaco、16

これはどのように実行できますか?バイナリファイルに入れて実行すると、「Exec形式エラー」というエラーがスローされる
Martin M.

1

(uint)0x00000007は「I386」と「X86」です(名前はXNU仕様のどこにあるかによって異なりますが、正しいアーチです)(uint)0x0x01000007はX86_64です

理論的には、任意のCPU値と0x1000000をORして64ビットバージョンにすることができます。XNUは常にそれらを離散値と見なすわけではないようです。たとえば、ARM 32と64はそれぞれ0x0000000Cと0x0100000Cです。

えっと、ここに私が数年前に把握しなければならなかったリストがあります。これらの古いOS / Xのほとんどは次のとおりです。

VAX       =          1,    // Little-Endian
ROMP      =          2,    // Big-Endian -- 24bit or 32bit
NS32032   =          4,    // Hybrid -- Treat as Little Endian -- First 32b procs on the market
NS32332   =          5,    // Hybrid -- Treat as Little Endian -- These introduced a 20 byte "instruction cache"
MC680x0   =          6,    // Big-Endian
X86       =          7,    // Little-Endian
I386      =          7,    // alias for X86 and gets used interchangeably
MIPS      =          8,    // Big-Endian
NS32532   =          9,    // Hybrid -- Treat as Little Endian -- These ran from 20MHz up to a stunning 30MHz
MC98000   =         10,    // Big-Endian
HPPA      =         11,    // Big-Endian
ARM       =         12,    // Both! -- will treat as Little-Endian by default
MC88000   =         13,    // Big-Endian
SPARC     =         14,    // Big-Endian
I860      =         15,    // Little-Endian
ALPHA     =         16,    // Big-Endian -- NB, this is a 64-bit CPU, but seems to show up w/o the ABI64 flag . . . 
POWERPC   =         18,    // Big-Endian
X86_64    =   16777223,    // Little-Endian
POWERPC64 =   16777234,    // Big-Endian
ARM_64    = 0x0100000C     // Both! -- wil treat as Little-Endian by default
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.