/*
 This util will allow you to dump an Apple crypted binary.
 The technique consists in attaching to the process and dumping the whole __TEXT segment and then replacing that content back in the main binary.
  
 Steps:
 1) Parse the binary header to find where the __TEXT segment is located
    We want to grab the addr for __text/__TEXT section and the filesize from that load command
 2) Attach to the process and dump the code
 3) Replace the original code with the dumped code
 4) Fix the mach-o header by removing the 0x8 flag from the __TEXT segment

 (c) Fractal Guru - 2009 - reverser@put.as - http://reverse.put.as
 
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 
 Dump code based on Nemo's paper (http://uninformed.org/index.cgi?v=4&a=3&p=1)
 Mach-o header processing ripped from Ghalen's ocalc.c (http://kmem.se/code/ocalc.c)
 
 Greets to Nemo, Ghalen and everyone else at #osxre @ freenode !
 
 -> Sorry for the lame code, you already know I suck at coding ;) <-
 
*/

#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <sys/types.h> 
#include <sys/mman.h> 
#include <mach/mach.h> 
#include <dlfcn.h> 
#include <getopt.h>
#include <ctype.h>
#include <strings.h>
// stuff to parse mach-o header
#include <mach-o/loader.h>
#include <mach-o/nlist.h>
#include <mach-o/fat.h>
// for ptrace
#include <signal.h>
#include <sys/wait.h>
#include <sys/ptrace.h>

#define VERSION "1.0" // only google likes beta crap :x

struct offsetinfo {
	uint32_t maxaddr;
	uint32_t maxsize;
	uint32_t maxoffset;
	uint32_t minaddr;
	uint32_t minsize;
	uint32_t minoffset;
	uint32_t offset;
};

// enable or disable debug messages
int debug=0;

void usage()
{
	printf(
		   "\nApple crypted binary dumper using ptrace v%s (c) fG! 2009 (reverser@put.as - http://reverse.put.as)\n"
		   "----------------------------------------------------------------------------------------------------\n\n"
		   "usage: dumpme_ptrace [--debug] --binary=<original binary> --cpu=<cpu type>\n\n"
		   "Where:\n"
		   "<original binary : path to the original binary\n"
		   "<cpu type>	 : i386 or ppc\n"
		   "\nIMPORTANT: Must be run as root or have procmod group id set\n"
		   "\nExample usage: (assuming that /bin/ls is crypted)\n"
		   "dumpme_ptrace --binary=/bin/ls --cpu=i386\n\n"
		   "A cryptor is available at http://osxbook.com/book/bonus/chapter7/tpmdrmmyth/\n\n", VERSION
		   );
	exit(1);
}

struct offsetinfo
get_offset(const char *buf, uint32_t base, uint32_t my_offset, uint32_t arch)
{
	int i, j;
	uint32_t addr = 0x0, size = 0x0, offset = 0x0, real_offset = 0x0;
	uint32_t ncmds, nsects, cmdsize, cmd;
	
	struct mach_header *mh; /* mach header */
	struct load_command *lc; /* load commands */
	struct segment_command *sc; /* segment command */
	struct section *st; /* section */
		
	struct offsetinfo myoffsetinfo;
	
	mh = (struct mach_header *)(buf + base);
	lc = (struct load_command*)((char*)mh + sizeof(struct mach_header));
	
	// DON'T FORGET DIFFERENT ENDIANNESS BETWEEN i386 and PPC
	ncmds = (arch == CPU_TYPE_I386)?(mh->ncmds):(ntohl(mh->ncmds));
	
	for (i = 0; i < ncmds; i++) {
		cmd = (arch == CPU_TYPE_I386)?(lc->cmd):(ntohl(lc->cmd));
		if (cmd != LC_SEGMENT)
			goto end;
		
		sc = (struct segment_command *)lc;
		// skip other segments except __TEXT
		if (debug) printf("Debug: %s\n", sc->segname);
		if (strcmp(sc->segname, "__TEXT"))
			goto end;
		
	    if (debug) printf("__TEXT segment flags are: 0x%x\n", sc->flags);
		
		// let's remove the encrypted flag from the segment
		// it's much easier to do it here then later, since we are already messing with the header
		if (sc->flags == 0x8)
				sc->flags = 0x0;
		
		st = (struct section *)((char *)sc + sizeof(struct segment_command));
		
		nsects = (arch == CPU_TYPE_I386)?(sc->nsects):(ntohl(sc->nsects));
		
		myoffsetinfo.maxaddr = (arch == CPU_TYPE_I386)?(st[0].addr):(ntohl(st[0].addr));
		myoffsetinfo.minaddr = (arch == CPU_TYPE_I386)?(st[0].addr):(ntohl(st[0].addr));
		myoffsetinfo.maxsize = (arch == CPU_TYPE_I386)?(st[0].size):(ntohl(st[0].size));
		myoffsetinfo.minsize = (arch == CPU_TYPE_I386)?(st[0].size):(ntohl(st[0].size));
		// find the maximum and minimum addresses for this segment
		// this is due to the fact that there's no guarantee that sections are in the correct order
		// most of the times they are, but macho-o loader has no problems if sections aren't ordered
		if (arch == CPU_TYPE_I386) {
			for (j =0; j < nsects; j++) {
				if (st[j].addr > myoffsetinfo.maxaddr) {
					myoffsetinfo.maxaddr = st[j].addr;
					myoffsetinfo.maxsize = st[j].size;
				}
				if (st[j].addr < myoffsetinfo.minaddr) {
					myoffsetinfo.minaddr = st[j].addr;
					myoffsetinfo.minsize = st[j].size;
				}											
			}
		}
		
		if (arch == CPU_TYPE_POWERPC) {
			for (j =0; j < nsects; j++) {
				if (ntohl(st[j].addr) > myoffsetinfo.maxaddr) {
					myoffsetinfo.maxaddr = ntohl(st[j].addr);
					myoffsetinfo.maxsize = ntohl(st[j].size);
				}
				if (ntohl(st[j].addr) < myoffsetinfo.minaddr) {
					myoffsetinfo.minaddr = ntohl(st[j].addr);
					myoffsetinfo.minsize = ntohl(st[j].size);
				}											
			}
		}

		for (j = 0; j < nsects; j++) {
			if (!strcmp(st[j].sectname, "__text"))
				break;
		}
		
		addr = (arch == CPU_TYPE_I386)?(st[j].addr):(ntohl(st[j].addr));
		size = (arch == CPU_TYPE_I386)?(st[j].size):(ntohl(st[j].size));
		offset = (arch == CPU_TYPE_I386)?(st[j].offset):(ntohl(st[j].offset));
		
	end:
		cmdsize = (arch == CPU_TYPE_I386)?(lc->cmdsize):(ntohl(lc->cmdsize));
		lc = (struct load_command*)((char*)lc + cmdsize);
	}

	my_offset = myoffsetinfo.minaddr;
	
	if (debug) printf("Max: %x Size: %x Min: %x Size: %x\n", myoffsetinfo.maxaddr, myoffsetinfo.maxsize, myoffsetinfo.minaddr, myoffsetinfo.minsize);
	// calculate real offset in the binary
	real_offset = base + offset + my_offset - addr;
	if (debug) fprintf(stderr, " -> actual offset for this arch: %d (%p)\n", real_offset, (void *)real_offset);
	
	myoffsetinfo.offset=real_offset;
	
	return myoffsetinfo;
}

int main (int argc, char * argv[])
{
	// required structure for long options
	static struct option long_options[]={
        { "pid", required_argument, NULL, 'p' },
        { "binary", required_argument, NULL, 'b' },
		{ "cpu", required_argument, NULL, 'c' },
        { "debug", no_argument, NULL, 'd' },
        { NULL, 0, NULL, 0 }
	};
	int option_index = 0, c;
	char *binarylocation, *cputype;
		
	// process command line options
	while ((c = getopt_long (argc, argv, "d:b:c:", long_options, &option_index)) != -1)
	{
        switch (c)
        {
			case ':':
				usage();
				break;
			case '?':
				usage();
				break;
			case 'd':
				debug = 1;
				break;
			case 'b':
				binarylocation = optarg;
				break;
			case 'c':
				cputype = optarg;
				break;
			default:
				usage();
		}
	}
	
	// check required arguments
	if (binarylocation == NULL || strlen(binarylocation) == 0)
		usage();
	if (cputype == NULL || strlen(cputype) == 0)
		usage();
	// parse MACH-O HEADER so we know what to dump
	unsigned int sz;
	uint32_t intel_base = 0x0, my_offset=0x0, narchs = 0x0, ppc_base = 0x0;
	int i=0;
	char *buf;
	FILE *fp;
	
	struct fat_header *fh; /* fat header */
	struct fat_arch *fa; /* fat arch */
	struct mach_header *mh; /* mach header */
	struct offsetinfo myoffsetinfo;
	
	printf("[!] INFO: Opening file %s to read mach-o header...\n", binarylocation);
	fp = fopen(binarylocation, "r");
	if (!fp) {
		fprintf(stderr, "[!] ERROR: Could not open file to read %s\n", binarylocation);
		exit(1);
	}

	// find file size
	fseek(fp, 0, SEEK_END);
	sz = ftell(fp);
	fseek(fp, 0, SEEK_SET);
	// alloc space
	buf = malloc(sz);
	fread(buf, sz, 1, fp);
	fclose(fp);
	
	/* do we have a fat binary?
	 * if that's the case, start parsing the fat header
	 */
	if (ntohl(*(unsigned int*)buf) == FAT_MAGIC) {
		fh = (struct fat_header *)(buf);
		narchs = ntohl(fh->nfat_arch);
		if (debug) fprintf(stderr, " -> found fat header with %x archs\n", narchs);
		
		fa = (struct fat_arch *)(buf + sizeof(struct fat_header));
		// grab the offsets from the header			   
		for (i = 0; i < narchs; i++) {
			switch (ntohl(fa->cputype)) {
				case CPU_TYPE_I386:
					intel_base = ntohl(fa->offset);
					if (debug) fprintf(stderr, " + intel offset: %x\n", intel_base);
					break;
				case CPU_TYPE_POWERPC:
					ppc_base = ntohl(fa->offset);
					if(debug) fprintf(stderr, " + ppc offset: %x\n", ppc_base);
					break;
				default:
					break;
			}
			
			fa += ((sizeof(struct fat_arch) * i) + 1);
		}
	// retrieve the offset information for current cpu
	printf("[!] INFO: Reading, processing and patching mach-o header...\n");
	
	if (strcasecmp(cputype, "i386") == 0) {
		myoffsetinfo = get_offset(buf, intel_base, my_offset, CPU_TYPE_I386);
	}
	if (strcasecmp(cputype, "ppc") == 0) {
		myoffsetinfo = get_offset(buf, ppc_base, my_offset, CPU_TYPE_POWERPC);
	}
		
	} else { /* if not, check the mach header */
		if(debug) fprintf(stderr, " -> this is not a fat binary, checking arch ... ");
		fflush(stderr);

		// retrieve the offset information for current cpu
		if (strcasecmp(cputype, "i386") == 0) {
			myoffsetinfo = get_offset(buf, 0, my_offset, CPU_TYPE_I386);
		}
		if (strcasecmp(cputype, "ppc") == 0) {
			myoffsetinfo = get_offset(buf, 0, my_offset, CPU_TYPE_POWERPC);
		}
	}
	
	if (debug) {
		printf("maxaddr: %x maxsize: %x maxoffset: %x\n", myoffsetinfo.maxaddr, myoffsetinfo.maxsize, myoffsetinfo.maxoffset);
		printf("minaddr: %x minsize: %x minoffset: %x\n", myoffsetinfo.minaddr, myoffsetinfo.minsize, myoffsetinfo.minoffset);
		printf("offset: %x\n", myoffsetinfo.offset);
	}
	
	// launch process, attach and dump
	int pid, status;
	// task stuff
	mach_port_t port; 
	mach_msg_type_number_t datacount;
	vm_offset_t localaddress;
	kern_return_t err;
	//	mach_msg_type_number_t local_size = vm_page_size;

	// easier if we alloc mem for everything (with malloc or xmalloc)
	// check at gdb in src/gdb/cli/cli-dump.c , function dump_memory_to_file
	
	unsigned int sizetodump=(myoffsetinfo.maxaddr-myoffsetinfo.minaddr)+myoffsetinfo.maxsize;
	vm_offset_t *storedump;
	unsigned int local_size;
	
	storedump = (vm_offset_t *) malloc(sizetodump);
	
	printf("[!] INFO: Forking and executing the process to be dumped...\n");
	/*
	 use ptrace to stop the process and mach vm_read to dump the memory area we are interested in
	 there is no problem using ptrace because it will break before executing the first instruction
	 so there isn't yet any anti-debug installed by the program
	 from ptrace man page:
	 When a process has used this request and calls execve(2) or any of the routines built on it (such as execv(3)), it will stop before executing
	 the first instruction of the new image.
	 
	 we have no problem using the trick because the binary is decrypted before execution so we already have a decrypted copy before the first instruction
	 is executed. not exactly the best design ;)
	*/
	
	if((pid = fork()) == 0) {
		// install ptrace into the child process
        ptrace(PT_TRACE_ME, 0, 0, 0);
		// execute the process
        execl(binarylocation, "", 0);
        fprintf(stderr,"[!] ERROR: exec failed...\n");
		exit(1);
    } else {
		// get the task for the child process
        err = task_for_pid(mach_task_self(), pid, &port);
        wait(&status);
//        if(WIFSTOPPED(status))
//            fprintf(stderr, "child has stopped...\n");
        
		// grab the data from memory !
		printf("[!] INFO: Dumping the decrypted memory location...\n");
		if(vm_read(port, (mach_vm_address_t) myoffsetinfo.minaddr, sizetodump, (vm_size_t *)&storedump, &local_size)) {
			fprintf(stderr, "[!] ERROR: Can't read memory from pid %d\n", pid);
			exit(1);
		}
	//  no need to continue the child process
	//	ptrace(PT_CONTINUE, pid, 0, 0);
    }
	// kill the child task
	err = task_terminate(port);		
	
	// write the memory dump to the decrypted binary
	char filenameout[] = "decrypted.binary";
	
	printf("[!] INFO: Writing the decrypted binary named %s...\n", filenameout);
	fp = fopen(filenameout, "w+");
	if (!fp) {
		fprintf(stderr, "[!] ERROR: Could not open file to write %s\n", filenameout);
		exit(1);
	}
	
	fwrite(buf, sz, 1, fp);
	// find file size
	fseek(fp, myoffsetinfo.offset, SEEK_SET);
	fwrite(storedump, sizetodump, 1, fp);
	fclose(fp);
	
	// no need to do anything else, mach header was already fixed in memory
	printf("[!] INFO: All done ! Have fun...\n");
	exit(0);
}
