/* From http://www.hanshq.net/making-executables.html */

#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


static const uint8_t rot13_program[] = {
                               /* read:                          */
0xb9,0x00,0x20,0x00,0x00,      /*   movl   $buffer,%ecx          */
0xb8,0x03,0x00,0x00,0x00,      /*   movl   $3,%eax               */
0x68,0x00,0x10,0x00,0x00,      /*   pushl  $4096                 */
0x51,                          /*   pushl  %ecx                  */
0x6a,0x00,                     /*   pushl  $0                    */
0x6a,0x00,                     /*   pushl  $0                    */
0xcd,0x80,                     /*   int    $0x80                 */
0x83,0xc4,0x10,                /*   addl   $16,%esp              */
0x85,0xc0,                     /*   testl  %eax,%eax             */
0x7e,0x25,                     /*   jle    end                   */
0x89,0xc2,                     /*   movl   %eax,%edx             */
                               /* rot13:                         */
0x0f,0xb6,0x19,                /*   movzbl (%ecx),%ebx           */
0x8a,0x9b,0x2d,0x12,0x00,0x00, /*   movb   rot13_table(%ebx),%bl */
0x88,0x19,                     /*   movb   %bl,(%ecx)            */
0x41,                          /*   incl   %ecx                  */
0x48,                          /*   decl   %eax                  */
0x75,0xf1,                     /*   jnz    rot13                 */
0x29,0xd1,                     /*   subl   %edx,%ecx             */
0xb8,0x04,0x00,0x00,0x00,      /*   movl   $4,%eax               */
0x52,                          /*   pushl  %edx                  */
0x51,                          /*   pushl  %ecx                  */
0x6a,0x01,                     /*   pushl  $1                    */
0x6a,0x00,                     /*   pushl  $0                    */
0xcd,0x80,                     /*   int    $0x80                 */
0x83,0xc4,0x10,                /*   addl   $16,%esp              */
0xeb,0xbe,                     /*   jmp    read                  */
                               /* end:                           */
0xb8,0x01,0x00,0x00,0x00,      /*   movl   $1,%eax               */
0x6a,0x00,                     /*   pushl  $0                    */
0x6a,0x00,                     /*   pushl  $0                    */
0xcd,0x80,                     /*   int    $0x80                 */
};

static const uint8_t rot13_table[] = {   0,   1,   2,   3,   4,   5,   6,
  7,   8,   9,  10,  11,  12,  13,  14,  15,  16,  17,  18,  19,  20,  21,  22,
 23,  24,  25,  26,  27,  28,  29,  30,  31,  32,  33,  34,  35,  36,  37,  38,
 39,  40,  41,  42,  43,  44,  45,  46,  47,  48,  49,  50,  51,  52,  53,  54,
 55,  56,  57,  58,  59,  60,  61,  62,  63,  64, 'N', 'O', 'P', 'Q', 'R', 'S',
'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
'J', 'K', 'L', 'M',  91,  92,  93,  94,  95,  96, 'n', 'o', 'p', 'q', 'r', 's',
't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
'j', 'k', 'l', 'm', 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134,
135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150,
151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166,
167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182,
183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198,
199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214,
215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230,
231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246,
247, 248, 249, 250, 251, 252, 253, 254, 255 };


/* Write an 8-bit value to file. */
static void write8(FILE *file, uint8_t value)
{
        if (fputc(value, file) == EOF) {
                perror("fputc");
                exit(1);
        }
}

/* Write a 32-bit value in little-endian to file. */
static void write32(FILE *file, uint32_t value)
{
        write8(file, (value >> 0)  & 0xff);
        write8(file, (value >> 8)  & 0xff);
        write8(file, (value >> 16) & 0xff);
        write8(file, (value >> 24) & 0xff);
}

/* Write a string to file, padded to 16 bytes. */
static void writestr16(FILE *file, const char *str)
{
        size_t len, i;

        len = strlen(str);
        assert(len <= 16 && "The string must fit in 16 bytes.");

        for (i = 0; i < 16; i++) {
                write8(file, i < len ? str[i] : 0);
        }
}

/* Seek to offset in file. */
static void seek(FILE *file, long offset)
{
        if (fseek(file, offset, SEEK_SET) == -1) {
                perror("fseek");
                exit(1);
        }
}

/* Round value up to alignment. */
static uint32_t align_to(uint32_t value, uint32_t alignment)
{
        uint32_t remainder = value % alignment;

        if (remainder == 0) {
                return value;
        }

        return value + (alignment - remainder);
}

#define MACH_HEADER_SZ     (7*4)      /* Size of Mach-O header.      */
#define MACH_SEGMENT_SZ    (10*4+16)  /* Size of segment struct.     */
#define MACH_SECTION_SZ    (2*16+9*4) /* Size of section struct.     */
#define MACH_UNIXTHREAD_SZ (4*4+16*4) /* Size of UNIXTHREAD command. */

#define TEXT_SZ   sizeof(rot13_program) /* Size of the code. */
#define CONST_SZ  sizeof(rot13_table)   /* Size of rot13_table. */
#define COMMON_SZ 4096                  /* Size of buffer. */

#define PAGE_SIZE 4096

int main(int argc, char **argv)
{
        FILE *f;
        size_t i;

        if (argc != 2) {
                fprintf(stderr, "Usage: %s <filename>\n", argv[0]);
                exit(1);
        }

        f = fopen(argv[1], "wb");
        if (f == NULL) {
                perror("fopen");
                exit(1);
        }

        uint32_t num_commands = 4; /* Three LC_SEGMENT and
                                      one LC_UNIXTHREAD. */

        uint32_t commands_size = 3 * MACH_SEGMENT_SZ +
                                 3 * MACH_SECTION_SZ +
                                 MACH_UNIXTHREAD_SZ;

        /* Mach-O header. */
        write32(f, 0xfeedface);    /* MH_MAGIC             */
        write32(f, 7);             /* CPU_TYPE_I386        */
        write32(f, 3);             /* CPU_SUBTYPE_I386_ALL */
        write32(f, 2);             /* MH_EXECUTE           */
        write32(f, num_commands);  /* ncmds                */
        write32(f, commands_size); /* sizeofcmds           */
        write32(f, 1);             /* MH_NOUNDEFS          */

        /* The __PAGEZERO segment. */
        write32(f, 1);               /* LC_SEGMENT   */
        write32(f, MACH_SEGMENT_SZ); /* cmdsize      */
        writestr16(f, "__PAGEZERO"); /* segname      */
        write32(f, 0x0);             /* vmaddr       */
        write32(f, 0x1000);          /* vmsize       */
        write32(f, 0);               /* fileoff      */
        write32(f, 0);               /* filesize     */
        write32(f, 0);               /* VM_PROT_NONE */
        write32(f, 0);               /* VM_PROT_NONE */
        write32(f, 0);               /* nsects       */
        write32(f, 0);               /* flags        */

        uint32_t text_cmd_size = MACH_SEGMENT_SZ + 2 * MACH_SECTION_SZ;
        uint32_t text_seg_addr = 0x1000;
        uint32_t text_seg_size = align_to(MACH_HEADER_SZ + commands_size +
                                          TEXT_SZ + CONST_SZ, PAGE_SIZE);

        /* The __TEXT segment. */
        write32(f, 1);             /* LC_SEGMENT                     */
        write32(f, text_cmd_size); /* cmdsize                        */
        writestr16(f, "__TEXT");   /* segname                        */
        write32(f, text_seg_addr); /* vmaddr                         */
        write32(f, text_seg_size); /* vmsize                         */
        write32(f, 0);             /* fileoff                        */
        write32(f, text_seg_size); /* filesize                       */
        write32(f, 0x7);           /* VM_PROT_ALL                    */
        write32(f, 0x1 | 0x4);     /* VM_PROT_READ | VM_PROT_EXECUTE */
        write32(f, 2);             /* nsects                         */
        write32(f, 0);             /* flags                          */

        uint32_t text_sec_addr = text_seg_addr + MACH_HEADER_SZ + commands_size;
        uint32_t text_sec_size = TEXT_SZ;
        uint32_t text_sec_offset = MACH_HEADER_SZ + commands_size;

        /* The __TEXT,__text section. */
        writestr16(f, "__text");     /* sectname                          */
        writestr16(f, "__TEXT");     /* segname                           */
        write32(f, text_sec_addr);   /* addr                              */
        write32(f, text_sec_size);   /* size                              */
        write32(f, text_sec_offset); /* offset                            */
        write32(f, 0);               /* alignment                         */
        write32(f, 0);               /* reloff                            */
        write32(f, 0);               /* nreloc                            */
        write32(f, 0x80000400);      /* S_ATTR_{SOME | PURE}_INSTRUCTIONS */
        write32(f, 0);               /* reserved1                         */
        write32(f, 0);               /* reserved2                         */

        uint32_t const_sec_addr = text_sec_addr + text_sec_size;
        uint32_t const_sec_size = CONST_SZ;
        uint32_t const_sec_offset = text_sec_offset + text_sec_size;

        /* The __TEXT,__const section. */
        writestr16(f, "__const");     /* sectname  */
        writestr16(f, "__TEXT");      /* segname   */
        write32(f, const_sec_addr);   /* addr      */
        write32(f, const_sec_size);   /* size      */
        write32(f, const_sec_offset); /* offset    */
        write32(f, 0);                /* alignment */
        write32(f, 0);                /* reloff    */
        write32(f, 0);                /* nreloc    */
        write32(f, 0);                /* flags     */
        write32(f, 0);                /* reserved1 */
        write32(f, 0);                /* reserved2 */

        uint32_t data_cmd_size = MACH_SEGMENT_SZ + MACH_SECTION_SZ;
        uint32_t data_seg_addr = text_seg_addr + text_seg_size;
        assert(data_seg_addr == align_to(data_seg_addr, PAGE_SIZE));
        uint32_t data_seg_size = COMMON_SZ;

        /* The __DATA segment. */
        write32(f, 1);             /* LC_SEGMENT                   */
        write32(f, data_cmd_size); /* cmdsize                      */
        writestr16(f, "__DATA");   /* segname                      */
        write32(f, data_seg_addr); /* vmaddr                       */
        write32(f, data_seg_size); /* vmsize                       */
        write32(f, 0);             /* fileoff                      */
        write32(f, 0);             /* filesize                     */
        write32(f, 0x7);           /* VM_PROT_ALL                  */
        write32(f, 0x1 | 0x2);     /* VM_PROT_READ | VM_PROT_WRITE */
        write32(f, 1);             /* nsects                       */
        write32(f, 0);             /* flags                        */

        /* The __DATA,__common section. */
        writestr16(f, "__common"); /* sectname   */
        writestr16(f, "__DATA");   /* segname    */
        write32(f, data_seg_addr); /* addr       */
        write32(f, data_seg_size); /* size       */
        write32(f, 0);             /* offset     */
        write32(f, 0);             /* alignment  */
        write32(f, 0);             /* reloff     */
        write32(f, 0);             /* nreloc     */
        write32(f, 0x1);           /* S_ZEROFILL */
        write32(f, 0);             /* reserved1  */
        write32(f, 0);             /* reserved2  */

        /* The LC_UNIXTHREAD command. */
        write32(f, 0x5);                /* LC_UNIXTHREAD                 */ 
        write32(f, MACH_UNIXTHREAD_SZ); /* cmdsize                       */
        write32(f, 1);                  /* flavor: X86_THREAD_STATE32    */
        write32(f, 16);                 /* count: x86_THREAD_STATE_COUNT */
        write32(f, 0x0);                /* eax                           */    
        write32(f, 0x0);                /* ebx                           */
        write32(f, 0x0);                /* ecx                           */
        write32(f, 0x0);                /* edx                           */
        write32(f, 0x0);                /* edi                           */
        write32(f, 0x0);                /* esi                           */
        write32(f, 0x0);                /* ebp                           */
        write32(f, 0x0);                /* esp                           */
        write32(f, 0x0);                /* ss                            */
        write32(f, 0x0);                /* eflags                        */
        write32(f, text_sec_addr);      /* eip                           */
        write32(f, 0x0);                /* cs                            */
        write32(f, 0x0);                /* ds                            */
        write32(f, 0x0);                /* es                            */
        write32(f, 0x0);                /* fs                            */
        write32(f, 0x0);                /* gs                            */


        /* Write the __TEXT,__text section. */
        for (i = 0; i < sizeof(rot13_program); i++) {
                write8(f, rot13_program[i]);
        }

        /* Write the __TEXT,__const section. */
        for (i = 0; i < sizeof(rot13_table); i++) {
                write8(f, rot13_table[i]);
        }

        /* Pad the file to match the __TEXT segment size. */
        assert(const_sec_offset + const_sec_size < text_seg_size);
        seek(f, text_seg_size - 1);
        write8(f, 0);

        if (fclose(f) == EOF) {
                perror("fclose");
                exit(1);
        }

        return 0;
}
