Apa kemungkinan Mach-O runnable terkecil yang dapat dieksekusi pada x86_64? Program tidak dapat melakukan apa-apa (bahkan tidak mengembalikan kode kembali), tetapi harus dapat dieksekusi (harus dijalankan tanpa kesalahan).
Usaha saya:
GNU Assembler ( null.s
):
.text
.globl _main
_main:
retq
Kompilasi & Penautan:
as -o null.o null.s
ld -e _main -macosx_version_min 10.12 -o null null.o -lSystem
Ukuran: 4248 byte
Melihat nilai hex sepertinya ada banyak zero padding yang mungkin bisa dihilangkan, tapi saya tidak tahu caranya. Saya juga tidak tahu apakah mungkin menjalankan exectubale tanpa menghubungkan libSystem ...
code-golf
tips
assembly
machine-code
Martin M.
sumber
sumber
Jawaban:
Mach-O runnable terkecil harus sekurang-kurangnya
0x1000
byte. Karena batasan XNU, file harus setidaknyaPAGE_SIZE
. Lihatxnu-4570.1.46/bsd/kern/mach_loader.c
, sekitar baris 1600.Namun, jika kami tidak menghitung padding itu, dan hanya menghitung payload yang berarti, maka ukuran file minimal yang bisa dijalankan pada macOS adalah
0xA4
byte.Itu harus dimulai dengan mach_header (atau
fat_header
/mach_header_64
, tetapi itu lebih besar).Ukurannya adalah
0x1C
byte.magic
harusMH_MAGIC
.Saya akan menggunakan
CPU_TYPE_X86
karena inix86_32
executable.filtetype
harusMH_EXECUTE
untuk dieksekusi,ncmds
dansizeofcmds
bergantung pada perintah, dan harus valid.flags
tidak begitu penting dan terlalu kecil untuk memberikan nilai lain.Berikutnya adalah memuat perintah. Header harus tepat dalam satu pemetaan, dengan hak RX - lagi, batasan XNU.
Kami juga perlu menempatkan kode kami di beberapa pemetaan RX, jadi ini baik-baik saja.
Untuk itu diperlukan suatu
segment_command
.Mari kita lihat definisi.
cmd
harusLC_SEGMENT
, dancmdsize
harussizeof(struct segment_command) => 0x38
.segname
konten tidak masalah, dan kami akan menggunakannya nanti.vmaddr
harus alamat yang valid (saya akan menggunakan0x1000
),vmsize
harus valid & kelipatanPAGE_SIZE
,fileoff
harus0
,filesize
harus lebih kecil dari ukuran file, tetapi lebih besar darimach_header
setidaknya (sizeof(header) + header.sizeofcmds
adalah apa yang saya gunakan).maxprot
daninitprot
harusVM_PROT_READ | VM_PROT_EXECUTE
.maxport
biasanya juga sudahVM_PROT_WRITE
.nsects
adalah 0, karena kita tidak benar-benar membutuhkan bagian apa pun dan mereka akan menambahkan hingga ukuran. Saya telah menetapkanflags
ke 0.Sekarang, kita perlu menjalankan beberapa kode. Ada dua perintah pemuatan untuk itu:
entry_point_command
danthread_command
.entry_point_command
tidak cocok untuk kita: lihatxnu-4570.1.46/bsd/kern/mach_loader.c
, sekitar baris 1977:Jadi, menggunakannya membutuhkan DYLD untuk bekerja, dan itu berarti kita perlu
__LINKEDIT
, kosongsymtab_command
dandysymtab_command
,dylinker_command
dandyld_info_command
. Berlebihan untuk file "terkecil".Jadi, kami akan gunakan
thread_command
, khususnyaLC_UNIXTHREAD
karena ia juga mengatur tumpukan yang akan kami butuhkan.cmd
akan menjadiLC_UNIXTHREAD
,cmdsize
akan0x50
(lihat di bawah).flavour
adalahx86_THREAD_STATE32
, dan hitung adalahx86_THREAD_STATE32_COUNT
(0x10
).Sekarang
thread_state
. Kami membutuhkanx86_thread_state32_t
alias_STRUCT_X86_THREAD_STATE32
:Jadi, memang 16
uint32_t
's yang akan dimuat ke register yang sesuai sebelum utas dimulai.Menambahkan header, perintah segmen dan perintah utas memberi kita
0xA4
byte.Sekarang, waktu untuk menyusun payload.
Katakanlah kita ingin mencetak
Hi Frand
danexit(0)
.Konvensi Syscall untuk macOS x86_32:
Lihat lebih lanjut tentang syscalls di macOS di sini .
Jadi, mengetahui itu, inilah muatan kami dalam perakitan:
Perhatikan baris sebelumnya
int 0x80
.segname
bisa apa saja, ingat? Jadi kita bisa menaruh muatan kita di dalamnya. Namun, ini hanya 16 byte, dan kami perlu lebih banyak.Jadi, pada
14
byte kita akan menempatkan ajmp
.Ruang "bebas" lainnya adalah register status utas.
Kami dapat mengatur apa saja di sebagian besar dari mereka, dan kami akan menaruh sisa muatan kami di sana.
Juga, kami menempatkan string kami di
__eax
dan__ebx
, karena itu lebih pendek daripada memindahkannya.Jadi, kita dapat menggunakan
__ecx
,__edx
,__edi
agar sesuai dengan sisa payload kami. Melihat perbedaan antara alamatthread_cmd.state.__ecx
dan akhirsegment_cmd.segname
kita menghitung bahwa kita perlu memasukkanjmp 0x3a
(atauEB38
) dalam dua byte terakhirsegname
.Jadi, payload kami yang dirakit adalah
53 50 31C0 89E7 6A08 57 6A01 50 B004
untuk bagian pertama,EB38
untuk jmp, danCD80 6A00 B001 50 CD80
untuk bagian kedua.Dan langkah terakhir - pengaturan
__eip
. File kami dimuat di0x1000
(ingatvmaddr
), dan payload dimulai pada offset0x24
.Inilah
xxd
file hasil:Pad dengan apa saja hingga
0x1000
byte, chmod + x dan jalankan :)PS Tentang x86_64 - binari 64bit harus memiliki
__PAGEZERO
(setiap pemetaan denganVM_PROT_NONE
halaman perlindungan meliputi pada 0x0). IIRC mereka [Apple] tidak membuatnya diperlukan pada mode 32bit hanya karena beberapa perangkat lunak lama tidak memilikinya dan mereka takut untuk merusaknya.sumber
truncate -s 4096 foo
(dengan foo menjadi file exectuable) untuk membuatnya sesuai dengan0x1000
byte dan bekerja dengan sempurna :)28 Bytes, Pra-dikompilasi.
Di bawah ini adalah dump hex yang diformat dari biner Mach-O.
Terdiri sepenuhnya dari header, dan tidak memerlukan data atau cmds. Ini, secara alami, biner Mach-O terkecil yang mungkin. Mungkin tidak berjalan dengan benar pada perangkat keras apa pun yang mungkin, tetapi cocok dengan spesifikasinya.
Saya akan menyediakan file yang sebenarnya, tetapi seluruhnya terdiri dari karakter yang tidak dapat dicetak.
sumber
(uint) 0x00000007 adalah "I386" dan "X86" (nama tergantung di mana dalam spesifikasi XNU yang Anda cari, tetapi itu adalah lengkungan yang benar) (uint) 0x0x01000007 adalah X86_64
Secara teoritis, Anda dapat ATAU nilai CPU apa pun dengan 0x1000000 untuk menjadikannya versi 64bit. XNU tampaknya tidak selalu menganggapnya sebagai nilai diskrit; misalnya, ARM 32 dan 64 masing-masing adalah 0x0000000C dan 0x0100000C.
Ah, begini, daftar saya akhirnya harus mencari tahu beberapa tahun yang lalu, perhatikan bahwa sebagian besar sebelum OS / X:
sumber