#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h> // for fchmod
#include <mach/mach.h>

#include <mach/task.h>
#include <ctype.h>


// Sample memory dumper for processes (PID=...) or kernel proper (checkrain)
// Compile with gcc-arm64 ....c -o ....
// 
// and don't forget to sign:
// JENTS=com.apple.private.security.container-required,task_for_pid-allow,com.apple.system-task-ports jtool2 --sign /tmp/memdump
//
// As an exercise, you might want to implement mach_vm_write - to turn this into a kernel/process memory patcher :-)


// CAVEAT: Reading "wrong" addresses (e.g. kernel base, unslid) can and probably will lead to a panic  since
//         mach_vm_read() has no sanity checks in it. 
//
// Instead of including <mach/mach_vm.h> which would #error unsupported
// I include the relevant functions inlined here...


kern_return_t mach_vm_read
(
        vm_map_t target_task,
        mach_vm_address_t address,
        mach_vm_size_t size,
        vm_offset_t *data,
        mach_msg_type_number_t *dataCnt
);


void hexDump (void *buf, int len, uint64_t addr);


/////
int main (int argc, char **argv){


	if (argc < 3) {
		fprintf(stderr,"Usage: %s <address> <size>\n",
			argv[0]);
		exit(0);
		
	}


	// Assume that our target is a valid task port which we obtain
	// per checkra1n via task_for_pid

	// Also rig the pid here. (if you want this as an arbitrary user mode
	// reader - just get pid as argument, or..


	pid_t	pid = 0;


	// hack - you might want pid as an arg...

	if (getenv("PID")) { pid = atoi(getenv("PID")); }
	
	mach_port_t	target_task = MACH_PORT_NULL;

	kern_return_t kr = task_for_pid (mach_task_self(), // ipc_space
					 pid, // 0 or target
					&target_task);


	if (kr != KERN_SUCCESS) {
		fprintf(stderr,"Our method of getting target for PID %d doesn't work. Sorry\n", pid);
		return (1);

		}

	uint64_t addr ;
	uint32_t len ;

	len = atoi(argv[2]);
	int rc = sscanf (argv[1], "0x%llx", &addr);
	if (rc !=1 ) { fprintf(stderr," HELLO! Usage, man! USAGE!!!!\n"); return 2;}


	fprintf(stderr,"Reading %d bytes from 0x%llx\n", len, addr);

	vm_offset_t data;
	mach_msg_type_number_t dataCnt = 0;



	// FOR KERNEL MEMORY, we might need to dump a pointer that is UNSLID
	// Or one which is SLID.
	
	// #1: how do we tell difference? 
	//  (use getenv("SLIDE");

	if (pid == 0 && (getenv("SLIDE") != NULL)) {
	
		// #2: how do we get slide?
		//  - in MacOS - easy - we discussed SEVERAL KASLR leaks
		//  - in *OS - most JBs will leave the slide value somehow.

		// CHECKRAIN ONLY!
		// Luca made the astute observation that there is a task_info (..DYLD_...)
		// Which - on the kernel task - would make no sense
		// That's where he puts the slide value.

		struct task_dyld_info dyld_info = {};
		uint64_t slide = 0; 
                mach_msg_type_number_t  count =  sizeof(struct task_dyld_info);


                kern_return_t kr =  task_info (target_task, // task_inspect_t task,
                        TASK_DYLD_INFO, (task_info_t) &dyld_info, &count);



		if (kr != KERN_SUCCESS) {

			fprintf(stderr,"Can't get slide - are you using checkrain?\n");
			// too risky. Fail
			return(3);

		}
		else { 
                	slide = dyld_info.all_image_info_size;

			printf("SLIDE is 0%llx\n", slide);

			addr+=slide;
		}


	} // pid 0

	kr = mach_vm_read (target_task, //  vm_map_t target_task,
			   addr, // mach_vm_address_t address,
			   len, // mach_vm_size_t size,
			   &data, // vm_offset_t *data,
			   &dataCnt); //mach_msg_type_number_t *dataCnt

	// Dump memory!
	if (getenv("RAW") != NULL) {
		char filename[128];
		sprintf(filename, "/tmp/mem.pid.%d.0x%llx-0x%llx.bin",
				pid,
				addr, addr +len);
		int fd = open (filename, O_CREAT | O_TRUNC | O_WRONLY);
		fchmod (fd, 0644);
		int rc = write (fd, (void *)data, dataCnt);
		printf("RC of write was %d\n", rc);
		close(fd);
		}
	else 

	hexDump ((void *)data, dataCnt, addr);

	return 0;
}



//  Simple hexdump (from disarm.c)

void hexDump(void *Mem, int Len, uint64_t Addr)
{
    unsigned char *Buf = (unsigned char *)Mem;
    int i = 0;

    
    char formatStr[80];

    strcpy(formatStr,"%06llx");
    
    int rowBegin = 0;
    for (i = 0; i < Len; i++)
    {
        if ((i % 16) == 0) {
            if (i>0) {
                // Have to print previous line
                int j = 0;
		printf (" |");
                for (j = i - 16; j < i; j++)
                {
                    if (isprint(Buf[j])) printf("%c", Buf[j]);
                    else printf(".");
                }
		printf ("|\n");
            }
            if (1) { fprintf(stdout, "%08llx  ", //formatStr,
                                Addr +i); }
            
            rowBegin = i;
        }
        
        int rowPos = i%16;
        
        if ((rowPos == 0) || (rowPos == 8))
        {

           uint64_t Address = *((uint64_t *) (Buf + rowBegin + rowPos)) ;
            
            if  ((Address & 0xFFFFFFF000000000) == 0xFFFFFFF000000000)
            {
                // Kernel address
                
                printf("   %s0x%llx%s   ",
                       "", Address, "");
                
                
                i +=7; // and +1 when we leave loop
                continue;
            }
        }
        // If we're here, we want to print normally
    	if (rowPos == 8) printf (" ");
        printf ("%02x ",  Buf[i]);
        
    } // end for
    
    if ((i % 16) == 0) {
        if (i>0) {
            // Have to print previous line
            int j = 0;
		printf (" |");
            for (j = i - 16; j < i; j++)
            {
                if (isprint(Buf[j])) printf("%c", Buf[j]);
                else printf(".");
            }
		printf ("|\n");
        }
 }

}