Skip to content

Instantly share code, notes, and snippets.

@tyfkda
Last active March 8, 2025 03:20
Show Gist options
  • Save tyfkda/86f5274fc338fc3f58e2ce043c2e9f94 to your computer and use it in GitHub Desktop.
Save tyfkda/86f5274fc338fc3f58e2ce043c2e9f94 to your computer and use it in GitHub Desktop.
Mach-O実行ファイル形式を自分で生成する(aarch64用)
#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