/* m_mach_ppc32.c -- loader stub for Mach-o PowerPC32 This file is part of the UPX executable compressor. Copyright (C) 1996-2004 Markus Franz Xaver Johannes Oberhumer Copyright (C) 1996-2004 Laszlo Molnar Copyright (C) 2000-2005 John F. Reiser All Rights Reserved. UPX and the UCL library are free software; you can redistribute them and/or modify them under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Markus F.X.J. Oberhumer Laszlo Molnar John F. Reiser */ typedef unsigned int size_t; typedef unsigned long long off_t; typedef unsigned char nrv_byte; typedef unsigned int nrv_uint; #define UPX_MAGIC_LE32 0x21585055 // "UPX!" #define PAGE_MASK (~0u<<12) #define PAGE_SIZE -PAGE_MASK #define O_RDONLY 0 /*extern int exit(int) __attribute__ ((__noreturn__));*/ /************************************************************************* // configuration section **************************************************************************/ // In order to make it much easier to move this code at runtime and execute // it at an address different from it load address: there must be no // static data, and no string constants. /************************************************************************* // "file" util **************************************************************************/ typedef struct { size_t size; // must be first to match size[0] uncompressed size char *buf; } Extent; static void xread(Extent *x, char *buf, size_t count) { char *p=x->buf, *q=buf; size_t j; if (x->size < count) { exit(127); } for (j = count; 0!=j--; ++p, ++q) { *q = *p; } x->buf += count; x->size -= count; } /************************************************************************* // util **************************************************************************/ #if 1 //{ save space #define ERR_LAB error: exit(127); #define err_exit(a) goto error #else //}{ save debugging time #define ERR_LAB static void err_exit(int a) { (void)a; // debugging convenience exit(127); } #endif //} /************************************************************************* // UPX & NRV stuff **************************************************************************/ struct l_info { // 12-byte trailer for loader (after macho headers) unsigned l_checksum; unsigned l_magic; // UPX_MAGIC_LE32 unsigned short l_lsize; unsigned char l_version; unsigned char l_format; }; struct p_info { // 12-byte packed program header unsigned p_progid; unsigned p_filesize; unsigned p_blocksize; }; struct b_info { // 12-byte header before each compressed block unsigned sz_unc; // uncompressed_size unsigned sz_cpr; // compressed_size unsigned char b_method; // compression algorithm unsigned char b_ftid; // filter id unsigned char b_cto8; // filter parameter unsigned char b_unused; }; typedef void f_unfilter( nrv_byte *, // also addvalue nrv_uint, unsigned cto8, // junk in high 24 bits unsigned ftid ); typedef int f_expand( const nrv_byte *, nrv_uint, nrv_byte *, nrv_uint *, unsigned ); static void unpackExtent( Extent *const xi, // input Extent *const xo, // output f_expand *const f_decompress, f_unfilter *f_unf ) { while (xo->size) { struct b_info h; // Note: if h.sz_unc == h.sz_cpr then the block was not // compressible and is stored in its uncompressed form. // Read and check block sizes. xread(xi, (char *)&h, sizeof(h)); if (h.sz_unc == 0) { // uncompressed size 0 -> EOF if (h.sz_cpr != UPX_MAGIC_LE32) // h.sz_cpr must be h->magic err_exit(2); if (xi->size != 0) // all bytes must be written err_exit(3); break; } if (h.sz_cpr <= 0) { err_exit(4); ERR_LAB } if (h.sz_cpr > h.sz_unc || h.sz_unc > xo->size ) { err_exit(5); } // Now we have: // assert(h.sz_cpr <= h.sz_unc); // assert(h.sz_unc > 0 && h.sz_unc <= blocksize); // assert(h.sz_cpr > 0 && h.sz_cpr <= blocksize); if (h.sz_cpr < h.sz_unc) { // Decompress block nrv_uint out_len; int const j = (*f_decompress)(xi->buf, h.sz_cpr, xo->buf, &out_len, h.b_method); if (j != 0 || out_len != (nrv_uint)h.sz_unc) err_exit(7); if (h.b_ftid!=0 && f_unf) { // have filter (*f_unf)(xo->buf, out_len, h.b_cto8, h.b_ftid); } xi->buf += h.sz_cpr; xi->size -= h.sz_cpr; } else { // copy literal block xread(xi, xo->buf, h.sz_cpr); } xo->buf += h.sz_unc; xo->size -= h.sz_unc; } } static void upx_bzero(char *p, size_t len) { if (len) do { *p++= 0; } while (--len); } #define bzero upx_bzero // The PF_* and PROT_* bits are {1,2,4}; the conversion table fits in 32 bits. #define REP8(x) \ ((x)|((x)<<4)|((x)<<8)|((x)<<12)|((x)<<16)|((x)<<20)|((x)<<24)|((x)<<28)) #define EXP8(y) \ ((1&(y)) ? 0xf0f0f0f0 : (2&(y)) ? 0xff00ff00 : (4&(y)) ? 0xffff0000 : 0) #define PF_TO_PROT(pf) \ ((PROT_READ|PROT_WRITE|PROT_EXEC) & ( \ ( (REP8(PROT_EXEC ) & EXP8(PF_X)) \ |(REP8(PROT_READ ) & EXP8(PF_R)) \ |(REP8(PROT_WRITE) & EXP8(PF_W)) \ ) >> ((pf & (PF_R|PF_W|PF_X))<<2) )) typedef struct { unsigned magic; unsigned cputype; unsigned cpysubtype; unsigned filetype; unsigned ncmds; unsigned sizeofcmds; unsigned flags; } Mach_header; enum e0 { MH_MAGIC = 0xfeedface }; enum e1 { CPU_TYPE_POWERPC = 18 }; enum e2 { MH_EXECUTE = 2 }; enum e3 { MH_NOUNDEFS = 1 }; typedef struct { unsigned cmd; unsigned cmdsize; } Mach_load_command; enum e4 { LC_SEGMENT = 0x1, LC_THREAD = 0x4, LC_UNIXTHREAD = 0x5, LC_LOAD_DYLINKER = 0xe }; typedef struct { unsigned cmd; unsigned cmdsize; char segname[16]; unsigned vmaddr; unsigned vmsize; unsigned fileoff; unsigned filesize; unsigned maxprot; unsigned initprot; unsigned nsects; unsigned flags; } Mach_segment_command; enum e5 { VM_PROT_READ = 1, VM_PROT_WRITE = 2, VM_PROT_EXECUTE = 4 }; typedef struct { unsigned srr0; /* Instruction address register (PC; entry addr) */ unsigned srr1; /* Machine state register (supervisor) */ unsigned r0, r1, r2, r3, r4, r5, r6, r7; unsigned r8, r9,r10,r11,r12,r13,r14,r15; unsigned r16,r17,r18,r19,r20,r21,r22,r23; unsigned r24,r25,r26,r27,r28,r29,r30,r31; unsigned cr; /* Condition register */ unsigned xer; /* User's integer exception register */ unsigned lr; /* Link register */ unsigned ctr; /* Count register */ unsigned mq; /* MQ register (601 only) */ unsigned vrsave; /* Vector Save Register */ } Mach_ppc_thread_state; typedef struct { unsigned cmd; /* LC_THREAD or LC_UNIXTHREAD */ unsigned cmdsize; /* total size of this command */ unsigned flavor; unsigned count; /* sizeof(following_thread_state)/4 */ Mach_ppc_thread_state state; } Mach_thread_command; enum e6 { PPC_THREAD_STATE = 1 }; enum e7 { PPC_THREAD_STATE_COUNT = sizeof(Mach_ppc_thread_state)/4 }; typedef union { unsigned long offset; /* from start of load command to string */ char *ptr; } Mach_lc_str; #define MAP_FIXED 0x10 #define MAP_PRIVATE 0x02 #define MAP_ANON 0x1000 #define PROT_READ 1 #define PROT_WRITE 2 #define PROT_EXEC 4 /* bug in crosstool/powerpc-750-linux-gnu/gcc-3.4.1-glibc-20040827: unsigned long long off_t goes into registers (9,10) instead of (8,9). Adjust in mmap(). */ extern char *mmap(char *, size_t, unsigned, unsigned, int, /*off_t*/size_t); static Mach_ppc_thread_state const * do_xmap( Mach_header const *const mhdr, Extent *const xi, int const fdi, Mach_header **mhdrpp, f_expand *const f_decompress, f_unfilter *const f_unf ) { Mach_segment_command const *sc = (Mach_segment_command const *)(1+ mhdr); Mach_ppc_thread_state const *entry = 0; int j; for ( j=0; j < mhdr->ncmds; ++j, (sc = (Mach_segment_command const *)(sc->cmdsize + (char const *)sc)) ) if (LC_SEGMENT==sc->cmd) { Extent xo; size_t mlen = xo.size = sc->filesize; char *addr = xo.buf = (char *)sc->vmaddr; char *haddr = sc->vmsize + addr; size_t frag = (int)addr &~ PAGE_MASK; addr -= frag; mlen += frag; if (0!=mlen && addr != mmap(addr, mlen, VM_PROT_READ | VM_PROT_WRITE, MAP_FIXED | MAP_PRIVATE | ((xi || 0==sc->filesize) ? MAP_ANON : 0), ((0==sc->filesize) ? -1 : fdi), sc->fileoff) ) { err_exit(8); } if (xi && 0!=sc->filesize) { if (0==sc->fileoff /*&& 0!=mhdrpp*/) { *mhdrpp = (Mach_header *)addr; } unpackExtent(xi, &xo, f_decompress, f_unf); } /*bzero(addr, frag);*/ // fragment at lo end frag = (-mlen) &~ PAGE_MASK; // distance to next page boundary bzero(mlen+addr, frag); // fragment at hi end if (0!=mlen && 0!=mprotect(addr, mlen, sc->initprot)) { err_exit(10); ERR_LAB } addr += mlen + frag; /* page boundary on hi end */ if (addr < haddr) { // need pages for .bss if (addr != mmap(addr, haddr - addr, sc->initprot, MAP_FIXED | MAP_PRIVATE | MAP_ANON, -1, 0 ) ) { err_exit(9); } } } else if (LC_UNIXTHREAD==sc->cmd || LC_THREAD==sc->cmd) { Mach_thread_command const *const thrc = (Mach_thread_command const *)sc; if (PPC_THREAD_STATE ==thrc->flavor && PPC_THREAD_STATE_COUNT==thrc->count ) { entry = &thrc->state; } } return entry; } /************************************************************************* // upx_main - called by our entry code // **************************************************************************/ Mach_ppc_thread_state const * upx_main( struct l_info const *const li, size_t volatile sz_compressed, // total length Mach_header *const mhdr, // temp char[sz_mhdr] for decompressing size_t const sz_mhdr, f_expand *const f_decompress, f_unfilter *const f_unf, Mach_header **const mhdrpp // Out: *mhdrpp= &real Mach_header ) { Mach_ppc_thread_state const *entry; Extent xi, xo, xi0; xi.buf = (char *)(1+ (struct p_info const *)(1+ li)); // &b_info xi.size = sz_compressed - (sizeof(struct l_info) + sizeof(struct p_info)); xo.buf = (char *)mhdr; xo.size = ((struct b_info const *)xi.buf)->sz_unc; xi0 = xi; // Uncompress Macho headers unpackExtent(&xi, &xo, f_decompress, 0); // never filtered? entry = do_xmap(mhdr, &xi0, -1, mhdrpp, f_decompress, f_unf); { // Map dyld dynamic loader Mach_load_command const *lc = (Mach_load_command const *)(1+ mhdr); int j; for (j=0; j < mhdr->ncmds; ++j, (lc = (Mach_load_command const *)(lc->cmdsize + (char const *)lc)) ) if (LC_LOAD_DYLINKER==lc->cmd) { char const *const dyld_name = ((Mach_lc_str const *)(1+ lc))->offset + (char const *)lc; int const fdi = open(dyld_name, O_RDONLY, 0); if (0 > fdi) { err_exit(18); } if (sz_mhdr!=read(fdi, (void *)mhdr, sz_mhdr)) { ERR_LAB err_exit(19); } entry = do_xmap(mhdr, 0, fdi, 0, 0, 0); close(fdi); } } return entry; } /* vi:ts=4:et:nowrap */