最小の実行可能なMach-Oは、少なくとも0x1000
バイトでなければなりません。XNUの制限により、ファイルは少なくともでなければなりませんPAGE_SIZE
。xnu-4570.1.46/bsd/kern/mach_loader.c
1600行目を参照してください。
ただし、そのパディングをカウントせず、意味のあるペイロードのみをカウントする場合、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_X86
しx86_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_SIZE
、fileoff
である必要があり0
、filesize
ファイルのサイズより小さいが、より大きくなければならないmach_header
(少なくともsizeof(header) + header.sizeofcmds
私が使用したものです)。
maxprot
とするinitprot
必要がありますVM_PROT_READ | VM_PROT_EXECUTE
。maxport
通常も持っていVM_PROT_WRITE
ます。
nsects
セクションは必要ないため、合計するとサイズが大きくなるため、0 私はflags
0に設定しました。
ここで、いくつかのコードを実行する必要があります。そこそのための2つのロード・コマンドは、次のとおりentry_point_command
とthread_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を機能させる必要があります。つまり、、__LINKEDIT
空symtab_command
、およびが必要dysymtab_command
にdylinker_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_UNIXTHREAD
、cmdsize
となり0x50
ます(下記参照)。
flavour
でありx86_THREAD_STATE32
、カウントはx86_THREAD_STATE32_COUNT
(0x10
)です。
今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 Frand
とexit(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
ます。
別の「空き」スペースは、スレッド状態レジスタです。
それらのほとんどに何でも設定でき、残りのペイロードをそこに配置します。
また、移動するよりも短いため、文字列を__eax
and __ebx
に配置します。
そこで、我々は使用することができ__ecx
、__edx
、__edi
私たちのペイロードの残りの部分を合わせて。のアドレスthread_cmd.state.__ecx
と最後のアドレスの違いを見ると、の最後の2バイトに(または)segment_cmd.segname
を入れる必要があると計算されます。jmp 0x3a
EB38
segname
したがって、アセンブルされるペイロードは53 50 31C0 89E7 6A08 57 6A01 50 B004
、最初の部分、EB38
jmp、およびCD80 6A00 B001 50 CD80
2番目の部分用です。
そして最後のステップ-を設定し__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ビットバイナリが必要です__PAGEZERO
(VM_PROT_NONE
0x0に保護カバーページがあるマッピング)。IIRC彼らは[Apple]がそれを32ビットモードで必要としないようにしたのは、一部のレガシーソフトウェアにそれがなく、それを壊すことを恐れているからです。