Last active
March 8, 2025 03:20
-
-
Save tyfkda/86f5274fc338fc3f58e2ce043c2e9f94 to your computer and use it in GitHub Desktop.
Mach-O実行ファイル形式を自分で生成する(aarch64用)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <mach-o/compact_unwind_encoding.h> | |
#include <mach-o/fixup-chains.h> | |
#include <mach-o/ldsyms.h> | |
#include <mach-o/loader.h> | |
#include <mach-o/nlist.h> | |
#include <stdio.h> | |
#include <string.h> | |
#define ARRAYSIZE(a) (sizeof(a) / sizeof(a[0])) | |
#define ALIGN(x, a) (((x) + (a) - 1) & ~((a) - 1)) | |
#define PAGE_SIZE 0x4000 | |
// #define OUTPUT_SYMBTAB // シンボルテーブルを出力する? | |
static const uint32_t text_code[] = { | |
0xa9bf7bfd, // 0000000100003f6c stp x29, x30, [sp, #-0x10]! | |
0x910003fd, // 0000000100003f70 mov x29, sp | |
0x90000000, // 0000000100003f74 adrp x0, 0 ; 0x100003000 | |
0x913e6000, // 0000000100003f78 add x0, x0, #0xf98 ; literal pool for: "Hello, world!" | |
0x94000004, // 0000000100003f7c bl 0x100003f8c ; symbol stub for: _puts | |
0x52800000, // 0000000100003f80 mov w0, #0x0 | |
0xa8c17bfd, // 0000000100003f84 ldp x29, x30, [sp], #0x10 | |
0xd65f03c0, // 0000000100003f88 ret | |
}; | |
static const uint32_t text_stub[] = { | |
0xb0000010, // 0000000100003f8c adrp x16, 1 ; 0x1000 | |
0xf9400210, // 0000000100003f90 ldr x16, [x16] | |
0xd61f0200, // 0000000100003f94 br x16 | |
}; | |
static const char text_cstring[] = "Hello, world!"; | |
struct text_unwind_info_t { | |
struct unwind_info_section_header header; | |
struct unwind_info_section_header_index_entry index_entries[2]; | |
uint64_t reserved1; | |
struct unwind_info_compressed_second_level_page_header second_level_page_header; | |
uint32_t second_level_entry_pages[1]; | |
uint32_t second_level_encodings_pages[1]; | |
} static const text_unwind_info = { | |
.header = { | |
.version = UNWIND_SECTION_VERSION, | |
.commonEncodingsArraySectionOffset = 0, | |
.commonEncodingsArrayCount = 0x00000000, | |
.personalityArraySectionOffset = 0, | |
.personalityArrayCount = 0x00000000, | |
.indexSectionOffset = offsetof(struct text_unwind_info_t, index_entries) + sizeof(struct unwind_info_section_header_index_entry) * 0, | |
.indexCount = 0x00000002, | |
}, | |
.index_entries = { | |
{ | |
.functionOffset = 0x00003f6c, | |
.secondLevelPagesSectionOffset = offsetof(struct text_unwind_info_t, second_level_page_header), // <-- 0にしても動く | |
.lsdaIndexArraySectionOffset = offsetof(struct text_unwind_info_t, second_level_page_header), // <-- 0にしても動く | |
}, | |
{ | |
.functionOffset = 0x00003f8c, | |
.secondLevelPagesSectionOffset = 0x00000000, | |
.lsdaIndexArraySectionOffset = offsetof(struct text_unwind_info_t, second_level_page_header), // <-- 0にしても動く | |
}, | |
}, | |
.second_level_page_header = { | |
.kind = UNWIND_SECOND_LEVEL_COMPRESSED, | |
.entryPageOffset = offsetof(struct text_unwind_info_t, second_level_entry_pages) - offsetof(struct text_unwind_info_t, second_level_page_header), | |
.entryCount = 0x01, | |
.encodingsPageOffset = offsetof(struct text_unwind_info_t, second_level_encodings_pages) - offsetof(struct text_unwind_info_t, second_level_page_header), | |
.encodingsCount = 0x01, | |
}, | |
.second_level_entry_pages = { | |
0x00000000, | |
}, | |
.second_level_encodings_pages = { | |
UNWIND_ARM_MODE_DWARF | 0x000000, | |
}, | |
}; | |
static const uint64_t data_const_got[] = { | |
0x8000000000000000, // 0x004000 | |
}; | |
#ifdef OUTPUT_SYMBTAB | |
static const uint32_t indirectsym[] = { | |
2, | |
2, | |
}; | |
static const char symstring[0x28] = | |
"\0" | |
_MH_EXECUTE_SYM "\0" | |
"_main\0" | |
"_puts\0"; | |
#endif | |
static const char dylinker_name[0x14] = "/usr/lib/dyld"; | |
static const char loaddylib_name[0x20] = "/usr/lib/libSystem.B.dylib"; | |
void put_padding(FILE *fp, size_t offset) { | |
size_t pos = ftell(fp); | |
if (pos < offset) { | |
size_t n = offset - pos; | |
for (size_t i = 0; i < n; ++i) | |
fputc(0, fp); | |
} | |
} | |
int main(void) { | |
// ヘッダ | |
struct mach_header_64 header; | |
// セクション | |
struct section_64 section0s[0]; | |
struct section_64 section1s[4]; | |
struct section_64 section2s[1]; | |
struct section_64 section3s[0]; | |
struct section_64* sections[] = {section0s, section1s, section2s, section3s}; | |
size_t section_sizes[] = {sizeof(section0s), sizeof(section1s), sizeof(section2s), sizeof(section3s)}; | |
// ロードコマンド | |
struct segment_command_64 segmentcmds[ARRAYSIZE(sections)]; | |
struct linkedit_data_command dyldchainedfixupcmd; | |
#ifdef OUTPUT_SYMBTAB | |
struct symtab_command symtabcmd; | |
struct dysymtab_command dysymtabcmd; | |
#endif | |
struct dylinker_command loaddylinkercmd; | |
struct entry_point_command entrypointcmd; | |
struct dylib_command loaddyldcmd; | |
struct commanddata { | |
void *command; | |
size_t size; | |
void *extra_data; | |
size_t extra_size; | |
} const load_commands[] = { | |
{&segmentcmds[0], sizeof(segmentcmds[0]), section0s, sizeof(section0s)}, | |
{&segmentcmds[1], sizeof(segmentcmds[1]), section1s, sizeof(section1s)}, | |
{&segmentcmds[2], sizeof(segmentcmds[2]), section2s, sizeof(section2s)}, | |
{&segmentcmds[3], sizeof(segmentcmds[3]), section3s, sizeof(section3s)}, | |
{&dyldchainedfixupcmd, sizeof(dyldchainedfixupcmd)}, | |
#ifdef OUTPUT_SYMBTAB | |
{&symtabcmd, sizeof(symtabcmd)}, | |
{&dysymtabcmd, sizeof(dysymtabcmd)}, | |
#endif | |
{&loaddylinkercmd, sizeof(loaddylinkercmd), (void*)dylinker_name, sizeof(dylinker_name)}, | |
{&entrypointcmd, sizeof(entrypointcmd)}, | |
{&loaddyldcmd, sizeof(loaddyldcmd), (void*)loaddylib_name, sizeof(loaddylib_name)}, | |
}; | |
size_t sizeofcmds = 0; | |
for (size_t i = 0; i < ARRAYSIZE(load_commands); ++i) { | |
const struct commanddata *cmd = &load_commands[i]; | |
sizeofcmds += cmd->size + cmd->extra_size; | |
} | |
// ファイル内のオフセット位置を計算 | |
uint64_t vmaddr = 0x100000000; | |
const uint32_t text_start_off = 0; | |
const uint64_t text_total_size = sizeof(text_code) + sizeof(text_stub) + ALIGN(sizeof(text_cstring), 8) + sizeof(text_unwind_info); | |
const uint32_t text_code_off = ALIGN(text_start_off + sizeof(header) + sizeofcmds, PAGE_SIZE) - text_total_size; | |
const uint64_t entryoff = text_code_off + 0; // TODO | |
section1s[0] = (struct section_64){ | |
.sectname = SECT_TEXT, | |
.segname = SEG_TEXT, | |
.addr = vmaddr + text_code_off, | |
.size = sizeof(text_code), | |
.offset = text_code_off, | |
.align = 2, // 2^2 | |
.reloff = 0, //reloc_start_off, | |
.nreloc = 0, //sizeof(relocs) / sizeof(*relocs), | |
.flags = S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SOME_INSTRUCTIONS, | |
}; | |
const uint32_t text_stub_off = text_code_off + sizeof(text_code); | |
section1s[1] = (struct section_64){ | |
.sectname = "__stubs", | |
.segname = SEG_TEXT, | |
.addr = vmaddr + text_stub_off, | |
.size = sizeof(text_stub), | |
.offset = text_stub_off, | |
.align = 2, // 2^2 | |
.reloff = 0, //reloc_start_off, | |
.nreloc = 0, //sizeof(relocs) / sizeof(*relocs), | |
.flags = S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SOME_INSTRUCTIONS | S_SYMBOL_STUBS, | |
.reserved1 = 0x00000000, // <-- Indirect Sym Index? | |
.reserved2 = sizeof(text_stub), // <-- サイズ | |
}; | |
const uint32_t text_cstring_off = text_stub_off + sizeof(text_stub); | |
section1s[2] = (struct section_64){ | |
.sectname = "__cstring", | |
.segname = SEG_TEXT, | |
.addr = vmaddr + text_cstring_off, | |
.size = sizeof(text_cstring), | |
.offset = text_cstring_off, | |
.align = 0, // 2^0 | |
.reloff = 0, //reloc_start_off, | |
.nreloc = 0, //sizeof(relocs) / sizeof(*relocs), | |
.flags = S_CSTRING_LITERALS, | |
}; | |
const uint32_t text_unwind_info_off = text_cstring_off + ALIGN(sizeof(text_cstring), 8); | |
section1s[3] = (struct section_64){ | |
.sectname = "__unwind_info", | |
.segname = SEG_TEXT, | |
.addr = vmaddr + text_unwind_info_off, | |
.size = sizeof(text_unwind_info), | |
.offset = text_unwind_info_off, | |
.align = 2, // 2^2 | |
.reloff = 0, //reloc_start_off, | |
.nreloc = 0, //sizeof(relocs) / sizeof(*relocs), | |
.flags = 0, | |
}; | |
const uint32_t data_start_off = ALIGN(text_code_off + text_total_size, PAGE_SIZE); | |
const uint64_t data_total_size = sizeof(data_const_got); | |
const uint32_t data_const_got_off = data_start_off; | |
section2s[0] = (struct section_64){ | |
.sectname = "__got", | |
.segname = "__DATA_CONST", | |
.addr = vmaddr + data_const_got_off, | |
.size = sizeof(data_const_got), | |
.offset = data_const_got_off, | |
.align = 3, // 2^3 | |
.reloff = 0, //reloc_start_off, | |
.nreloc = 0, //sizeof(relocs) / sizeof(*relocs), | |
.flags = S_NON_LAZY_SYMBOL_POINTERS, | |
.reserved1 = 0x00000001, // ? | |
}; | |
const uint32_t linkedit_start_off = ALIGN(data_start_off + data_total_size, PAGE_SIZE); | |
// チェインドフィックスアップ | |
struct dyld_chained_fixups_t { | |
struct dyld_chained_fixups_header header; | |
// uint32_t reserved1; // padding | |
struct { // struct dyld_chained_starts_in_image | |
uint32_t seg_count; | |
uint32_t seg_info_offset[ALIGN(4, 2)]; | |
} starts; | |
struct dyld_chained_starts_in_segment starts_in_segment; | |
struct dyld_chained_import imports[1]; | |
// uint32_t reserved2; // padding | |
char symbol[8]; | |
} const dyld_chained_fixups = { | |
.header = { | |
.fixups_version = 0x00000000, | |
.starts_offset = offsetof(struct dyld_chained_fixups_t, starts), | |
.imports_offset = offsetof(struct dyld_chained_fixups_t, imports), | |
.symbols_offset = offsetof(struct dyld_chained_fixups_t, symbol), | |
.imports_count = 0x00000001, | |
.imports_format = DYLD_CHAINED_IMPORT, | |
.symbols_format = 0x00000000, // Uncompressed | |
}, | |
.starts = { // struct dyld_chained_starts_in_image | |
.seg_count = 4, | |
{ | |
0x00000000, | |
0x00000000, | |
offsetof(struct dyld_chained_fixups_t, starts_in_segment) - offsetof(struct dyld_chained_fixups_t, starts), | |
0x00000000, | |
}, | |
}, | |
.starts_in_segment = { | |
.size = sizeof(((struct dyld_chained_fixups_t*)NULL)->starts_in_segment), | |
.page_size = PAGE_SIZE, | |
.pointer_format = DYLD_CHAINED_PTR_64_OFFSET, | |
.segment_offset = data_start_off, | |
.max_valid_pointer = 0x00000000, | |
.page_count = (data_total_size + (PAGE_SIZE - 1)) / PAGE_SIZE, | |
.page_start = {0x0000}, | |
}, | |
.imports = { | |
{.lib_ordinal = 1, .weak_import = 0, .name_offset = 1}, // _puts | |
}, | |
.symbol = "\0_puts", | |
}; | |
// シンボル | |
#ifdef OUTPUT_SYMBTAB | |
const uint32_t null_nameofs = 0; | |
const uint32_t mh_execute_header_nameofs = null_nameofs + strlen("") + 1; | |
const uint32_t main_nameofs = mh_execute_header_nameofs + strlen(_MH_EXECUTE_SYM) + 1; | |
const uint32_t puts_nameofs = main_nameofs + strlen("_main") + 1; | |
const uint32_t nlocalsym = 0; | |
const uint32_t nextdefsym = 2; // __mh_execute_header, _main | |
const uint32_t nundefsym = 1; // _puts | |
const struct nlist_64 symbols[] = { | |
{ | |
.n_un.n_strx = mh_execute_header_nameofs, | |
.n_type = N_SECT | N_EXT, | |
.n_sect = 0x01, | |
.n_desc = 0x0010, | |
.n_value = vmaddr | |
}, | |
{ | |
.n_un.n_strx = main_nameofs, | |
.n_type = N_SECT | N_EXT, | |
.n_sect = 0x01, | |
.n_desc = 0x0000, | |
.n_value = vmaddr + text_code_off, | |
}, | |
{ | |
.n_un.n_strx = puts_nameofs, | |
.n_type = N_EXT, | |
.n_sect = NO_SECT, | |
.n_desc = (1 << 8) | 0, // ordinal=1 (index of dyld) | |
}, | |
}; | |
#endif | |
// 内容構築 | |
segmentcmds[0] = (struct segment_command_64){ // __PAGEZERO | |
.cmd = LC_SEGMENT_64, | |
.cmdsize = sizeof(segmentcmds[0]) + sizeof(section0s), | |
.segname = SEG_PAGEZERO, | |
.vmaddr = 0, | |
.vmsize = vmaddr, | |
.fileoff = 0, | |
.filesize = 0, | |
.maxprot = VM_PROT_NONE, // --- | |
.initprot = VM_PROT_NONE, // --- | |
.nsects = ARRAYSIZE(section0s), | |
.flags = 0, | |
}; | |
segmentcmds[1] = (struct segment_command_64){ // __TEXT | |
.cmd = LC_SEGMENT_64, | |
.cmdsize = sizeof(segmentcmds[1]) + sizeof(section1s), | |
.segname = SEG_TEXT, | |
.vmaddr = vmaddr + text_start_off, | |
.vmsize = ALIGN(text_total_size, PAGE_SIZE), | |
.fileoff = text_start_off, | |
.filesize = ALIGN(text_total_size, PAGE_SIZE), // 念の為ALIGN、なくても動く? | |
.maxprot = VM_PROT_EXECUTE | VM_PROT_READ, | |
.initprot = VM_PROT_EXECUTE | VM_PROT_READ, | |
.nsects = ARRAYSIZE(section1s), | |
.flags = 0, | |
}; | |
segmentcmds[2] = (struct segment_command_64){ // __DATA_CONST | |
.cmd = LC_SEGMENT_64, | |
.cmdsize = sizeof(segmentcmds[2]) + sizeof(section2s), | |
.segname = "__DATA_CONST", | |
.vmaddr = vmaddr + data_start_off, | |
.vmsize = ALIGN(data_total_size, PAGE_SIZE), | |
.fileoff = data_start_off, | |
.filesize = ALIGN(data_total_size, PAGE_SIZE), // 念の為ALIGN、なくても動く? | |
.maxprot = VM_PROT_WRITE | VM_PROT_READ, | |
.initprot = VM_PROT_WRITE | VM_PROT_READ, | |
.nsects = ARRAYSIZE(section2s), | |
.flags = SG_READ_ONLY, | |
}; | |
uint64_t linkedit_total_size = sizeof(dyld_chained_fixups); | |
#ifdef OUTPUT_SYMBTAB | |
linkedit_total_size += sizeof(symbols) + sizeof(indirectsym) + sizeof(symstring); | |
#endif | |
segmentcmds[3] = (struct segment_command_64){ // __LINKEDIT | |
.cmd = LC_SEGMENT_64, | |
.cmdsize = sizeof(segmentcmds[3]) + sizeof(section3s), | |
.segname = SEG_LINKEDIT, | |
.vmaddr = vmaddr + linkedit_start_off, | |
.vmsize = ALIGN(linkedit_total_size, PAGE_SIZE), | |
.fileoff = linkedit_start_off, | |
.filesize = linkedit_total_size, | |
.maxprot = VM_PROT_READ, | |
.initprot = VM_PROT_READ, | |
.nsects = ARRAYSIZE(section3s), | |
.flags = 0, | |
}; | |
const uint32_t dyld_chained_fixups_off = linkedit_start_off; | |
dyldchainedfixupcmd = (struct linkedit_data_command){ | |
.cmd = LC_DYLD_CHAINED_FIXUPS, | |
.cmdsize = sizeof(dyldchainedfixupcmd), | |
.dataoff = dyld_chained_fixups_off, | |
.datasize = sizeof(dyld_chained_fixups), | |
}; | |
const uint32_t symtab_off = dyld_chained_fixups_off + sizeof(dyld_chained_fixups); | |
#ifdef OUTPUT_SYMBTAB | |
const uint32_t indirectsym_off = symtab_off + sizeof(symbols); | |
const uint32_t symstring_off = indirectsym_off + sizeof(indirectsym); | |
symtabcmd = (struct symtab_command){ | |
.cmd = LC_SYMTAB, | |
.cmdsize = sizeof(symtabcmd), | |
.symoff = symtab_off, | |
.nsyms = ARRAYSIZE(symbols), | |
.stroff = symstring_off, | |
.strsize = sizeof(symstring), | |
}; | |
dysymtabcmd = (struct dysymtab_command){ | |
.cmd = LC_DYSYMTAB, | |
.cmdsize = sizeof(dysymtabcmd), | |
.ilocalsym = 0, | |
.nlocalsym = nlocalsym, | |
.iextdefsym = 0 + nlocalsym, | |
.nextdefsym = nextdefsym, | |
.iundefsym = 0 + nlocalsym + nextdefsym, | |
.nundefsym = nundefsym, | |
.tocoff = 0x00000000, | |
.ntoc = 0x00000000, | |
.modtaboff = 0x00000000, | |
.nmodtab = 0x00000000, | |
.extrefsymoff = 0x00000000, | |
.nextrefsyms = 0x00000000, | |
.indirectsymoff = indirectsym_off, | |
.nindirectsyms = ARRAYSIZE(indirectsym), | |
.extreloff = 0x00000000, | |
.nextrel = 0x00000000, | |
.locreloff = 0x00000000, | |
.nlocrel = 0x00000000, | |
}; | |
#endif | |
loaddylinkercmd = (struct dylinker_command){ | |
.cmd = LC_LOAD_DYLINKER, | |
.cmdsize = sizeof(loaddylinkercmd) + sizeof(dylinker_name), | |
.name = sizeof(loaddylinkercmd), | |
}; | |
entrypointcmd = (struct entry_point_command){ | |
.cmd = LC_MAIN, | |
.cmdsize = sizeof(entrypointcmd), | |
.entryoff = entryoff, | |
.stacksize = 0x0000000000000000, | |
}; | |
loaddyldcmd = (struct dylib_command){ | |
.cmd = LC_LOAD_DYLIB, | |
.cmdsize = sizeof(loaddyldcmd) + sizeof(loaddylib_name), | |
{ | |
.name = sizeof(struct dylib_command), | |
.timestamp = 0x00000002, | |
.current_version = 0x05470000, | |
.compatibility_version = 0x00010000, | |
}, | |
}; | |
header = (struct mach_header_64){ | |
.magic = MH_MAGIC_64, | |
.cputype = CPU_TYPE_ARM64, | |
.cpusubtype = 0, | |
.filetype = MH_EXECUTE, | |
.ncmds = ARRAYSIZE(load_commands), | |
.sizeofcmds = sizeofcmds, | |
.flags = MH_NOUNDEFS | MH_DYLDLINK | MH_TWOLEVEL | MH_PIE, | |
}; | |
// ファイル出力 | |
FILE *fp = stdout; | |
fwrite(&header, sizeof(header), 1, fp); | |
for (size_t i = 0; i < ARRAYSIZE(load_commands); ++i) { | |
const struct commanddata *cmd = &load_commands[i]; | |
fwrite(cmd->command, cmd->size, 1, fp); | |
if (cmd->extra_data != NULL) | |
fwrite(cmd->extra_data, cmd->extra_size, 1, fp); | |
} | |
struct { | |
const void *data; | |
size_t offset; | |
size_t size; | |
} const table[] = { | |
{ text_code, text_code_off, sizeof(text_code) }, | |
{ text_stub, text_stub_off, sizeof(text_stub) }, | |
{ text_cstring, text_cstring_off, sizeof(text_cstring) }, | |
{ &text_unwind_info, text_unwind_info_off, sizeof(text_unwind_info) }, | |
{ data_const_got, data_const_got_off, sizeof(data_const_got) }, | |
{ &dyld_chained_fixups, dyldchainedfixupcmd.dataoff, sizeof(dyld_chained_fixups) }, | |
#ifdef OUTPUT_SYMBTAB | |
{ symbols, symtabcmd.symoff, sizeof(symbols) }, | |
{ indirectsym, dysymtabcmd.indirectsymoff, sizeof(indirectsym) }, | |
{ symstring, symstring_off, sizeof(symstring) }, | |
#endif | |
}; | |
for (size_t i = 0; i < sizeof(table) / sizeof(table[0]); ++i) { | |
put_padding(fp, table[i].offset); | |
fwrite(table[i].data, table[i].size, 1, fp); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment