Skip to content

alexdremov/MachOBuilder

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

38 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MachOBuilder

Executable and object mach-o file builder.

Description of an object file structure can be found in my blog:

https://alexdremov.ru/mystery-of-mach-o-object-file-builders/

Install

mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make install

Check out console output to find install directory.

To use in cmake (as an example):

SET(CMAKE_CXX_FLAGS        "${CMAKE_CXX_FLAGS}         -I/usr/local/include") # check your installation directory
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}  -L/usr/local/lib")

Mach-O format quick guide

Mach-O file is structured like this:

Important tips:

  • Load command size must be divisible by 8. It can be padded by zeros
  • Minimal structure: PAGEZERO segment + TEXT segment(+text section) + UNIXTHREAD + code payload
  • Mach-O file must be at least one page size. Framework automatically achives that.
  • Segments virtual size nust be rounded to the page size.
  • Segments payload must be page-aligned
  • UNIXTHREAD entry point type does not require anything appart from the code itself
  • MAIN segment require: PAGEZERO + TEXT + LINKEDIT + LC_DYLD_INFO_ONLY + LC_DYSYMTAB + LC_LOAD_DYLINKER + LC_LOAD_DYLIB

Usage

The main interface uses binaryFile as it's code container:

FILE *res = fopen(..., "wb");
binaryFile binary = {};
binary.init(res);
...
binary.dest();

The executable itself is generated using MachoFileBin

MachoFileBin machoFile = {};
machoFile.init();
...
machoFile.binWrite(&binary);
machoFile.dest();

Simple return zero executable

FILE *res = fopen("machoRetZeroApp", "wb"); // executable
binaryFile binary = {};
binary.init(res);

MachoFileBin machoFile = {};
machoFile.init();

machoFile.header = machHeader64::executable(); // standard header
machoFile.loadCommands.pushBack(loadCommand::pageZero()); // add PAGEZERO load command 

auto codeSection = loadCommand::code();                // __TEXT segment
codeSection.sections.pushBack(segmentSection::code()); // push __text section to __TEXT segment
codeSection.payloads.pushBack(0);                      // linked to first payload
machoFile.loadCommands.pushBack(codeSection);

machoFile.loadCommands.pushBack(loadCommand::thread(0));           // Add UNIXTHREAD load command referencing to first section (codeSection)

unsigned char asmCode[] = {
        0x48, 0x89, 0xC7, 0xB8, 0x01, 0x00, 0x00, 0x02, 0x0F, 0x05 // asm code to execute return 0 syscall
};

binPayload codePayload = {};
codePayload.payload = (char *) asmCode;
codePayload.size = sizeof(asmCode) / sizeof(asmCode[1]);
codePayload.freeable = false;             // static array asmCode, no free() required
codePayload.align = 1;                    // 2^1 alignment of payload block
machoFile.payload.pushBack(codePayload);


machoFile.binWrite(&binary); // generate executable and write to the binary

binary.dest();
machoFile.dest();

Or as simple as this:

FILE *res = fopen("machoRetZeroApp", "wb");
binaryFile binary = {};
binary.init(res);

unsigned char asmCode[] = {
        0x48, 0x89, 0xC7, 0xB8, 0x01, 0x00, 0x00, 0x02, 0x0F, 0x05
};
MachoFileBin::simpleExe(binary, (char*) asmCode, sizeof(asmCode));

binary.dest();

Simple object file

There is an API for creating an object file that can be linked using ld / gcc / clang

FILE *res = fopen("machoObjectAuto.o", "wb");
binaryFile binary = {};
binary.init(res);

ObjectMachOGen mgen = {};
mgen.init();

unsigned char asmCode[] = {
        0x55, 0x48, 0x89, 0xE5,
        0xE8, 0x00, 0x00, 0x00, 0x00, // call __Z8printTenv
        0xE8, 0x00, 0x00, 0x00, 0x00, // call __Z8printTenv
        0x8B, 0x05, 0x00, 0x00, 0x00, 0x00, // mov eax, dword ptr [rip + offset globalVar ]
        0x31, 0xC0, 0x5D,
        0xE8, 0x00, 0x00, 0x00, 0x00,
        0xC3
};

unsigned char data[] = {
        0xDE, 0xD3, 0x2D, 0xED, 0x32, 0xDE, 0xD3, 0x2D, 0xED, 0x32,
        0xDE, 0xD3, 0x2D, 0xED, 0x32, 0xDE, 0xD3, 0x2D, 0xED, 0x32,
};

mgen.addCode(asmCode, sizeof(asmCode));
mgen.addData(data, sizeof(data));

mgen.addInternalCodeSymbol("_main", 0);
mgen.addInternalDataSymbol("globalVar", 0);

mgen.bindBranchExt("__Z5printi", 0x5);
mgen.bindBranchExt("__Z5printi", 0xA);
mgen.bindSignedOffset("globalVar", 16);

mgen.dumpFile(binary);

mgen.dest();
binary.dest();

It will create such structure:

Several essential functions are used in the listing.

addInternalCodeSymbol defines symbol at offset in code section

  • symbol - symbol name
  • offset - offset from the start of the section

addInternalDataSymbol defines symbol at offset in data section

  • symbol - symbol name
  • offset - offset from the start of the section

bindBranchExt defines address position for relocation for external symbol

  • symbol - symbol name
  • offset - address offset from the start of the code section

bindSignedOffset defines address position for relocation for internal symbol

  • symbol - symbol name
  • offset - address offset from the start of the code section