diff --git a/lib/linguist/languages.yml b/lib/linguist/languages.yml index 5996fc8c..c72b1857 100644 --- a/lib/linguist/languages.yml +++ b/lib/linguist/languages.yml @@ -479,6 +479,12 @@ Cuda: - .cu - .cuh +Cycript: + type: programming + lexer: JavaScript + extensions: + - .cy + Cython: type: programming group: Python diff --git a/samples/Cycript/utils.cy b/samples/Cycript/utils.cy new file mode 100644 index 00000000..72e677b7 --- /dev/null +++ b/samples/Cycript/utils.cy @@ -0,0 +1,580 @@ +(function(utils) { + // Load C functions declared in utils.loadFuncs + var shouldLoadCFuncs = true; + // Expose the C functions to cycript's global scope + var shouldExposeCFuncs = true; + // Expose C constants to cycript's global scope + var shouldExposeConsts = true; + // Expose functions defined here to cycript's global scope + var shouldExposeFuncs = true; + // Which functions to expose + var funcsToExpose = ["exec", "include", "sizeof", "logify", "apply", "str2voidPtr", "voidPtr2str", "double2voidPtr", "voidPtr2double", "isMemoryReadable", "isObject", "makeStruct"]; + + // C functions that utils.loadFuncs loads + var CFuncsDeclarations = [ + // + "void *calloc(size_t num, size_t size)", + // + "char *strcpy(char *restrict dst, const char *restrict src)", + "char *strdup(const char *s1)", + "void* memset(void* dest, int ch, size_t count)", + // + "FILE *fopen(const char *, const char *)", + "int fclose(FILE *)", + "size_t fread(void *restrict, size_t, size_t, FILE *restrict)", + "size_t fwrite(const void *restrict, size_t, size_t, FILE *restrict)", + // + "mach_port_t mach_task_self()", + "kern_return_t task_for_pid(mach_port_name_t target_tport, int pid, mach_port_name_t *tn)", + "kern_return_t mach_vm_protect(vm_map_t target_task, mach_vm_address_t address, mach_vm_size_t size, boolean_t set_maximum, vm_prot_t new_protection)", + "kern_return_t mach_vm_write(vm_map_t target_task, mach_vm_address_t address, vm_offset_t data, mach_msg_type_number_t dataCnt)", + "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)", + ]; + + /* + Replacement for eval that can handle @encode etc. + + Usage: + cy# utils.exec("@encode(void *(int, char))") + @encode(void*(int,char)) + */ + utils.exec = function(str) { + var mkdir = @encode(int (const char *, int))(dlsym(RTLD_DEFAULT, "mkdir")); + var tempnam = @encode(char *(const char *, const char *))(dlsym(RTLD_DEFAULT, "tempnam")); + var fopen = @encode(void *(const char *, const char *))(dlsym(RTLD_DEFAULT, "fopen")); + var fclose = @encode(int (void *))(dlsym(RTLD_DEFAULT, "fclose")); + var fwrite = @encode(int (const char *, int, int, void *))(dlsym(RTLD_DEFAULT, "fwrite")); + var symlink = @encode(int (const char *, const char *))(dlsym(RTLD_DEFAULT, "symlink")); + var unlink = @encode(int (const char *))(dlsym(RTLD_DEFAULT, "unlink")); + var getenv = @encode(const char *(const char *))(dlsym(RTLD_DEFAULT, "getenv")); + var setenv = @encode(int (const char *, const char *, int))(dlsym(RTLD_DEFAULT, "setenv")); + + var libdir = "/usr/lib/cycript0.9"; + var dir = libdir + "/tmp"; + + mkdir(dir, 0777); + + // This is needed because tempnam seems to ignore the first argument on i386 + var old_tmpdir = getenv("TMPDIR"); + setenv("TMPDIR", dir, 1); + + // No freeing :( + var f = tempnam(dir, "exec-"); + setenv("TMPDIR", old_tmpdir, 1); + if(!f) { + return false; + } + + symlink(f, f + ".cy"); + + str = "exports.result = " + str; + + var handle = fopen(f, "w"); + fwrite(str, str.length, 1, handle); + fclose(handle); + + var r; + var except = null; + try { + r = require(f.replace(libdir + "/", "")); + } catch(e) { + except = e; + } + + unlink(f + ".cy"); + unlink(f); + + if(except !== null) { + throw except; + } + + return r.result; + }; + + /* + Applies known typedefs + Used in utils.include and utils.makeStruct + + Usage: + cy# utils.applyTypedefs("mach_vm_address_t") + "uint64_t" + */ + utils.applyTypedefs = function(str) { + var typedefs = { + "struct": "", + "restrict": "", + "FILE": "void", + "size_t": "uint64_t", + "uintptr_t": "unsigned long", + "kern_return_t": "int", + "mach_port_t": "unsigned int", + "mach_port_name_t": "unsigned int", + "vm_offset_t": "unsigned long", + "vm_size_t": "unsigned long", + "mach_vm_address_t": "uint64_t", + "mach_vm_offset_t": "uint64_t", + "mach_vm_size_t": "uint64_t", + "vm_map_offset_t": "uint64_t", + "vm_map_address_t": "uint64_t", + "vm_map_size_t": "uint64_t", + "mach_port_context_t": "uint64_t", + "vm_map_t": "unsigned int", + "boolean_t": "unsigned int", + "vm_prot_t": "int", + "mach_msg_type_number_t": "unsigned int", + "cpu_type_t": "int", + "cpu_subtype_t": "int", + "cpu_threadtype_t": "int", + }; + + for(var k in typedefs) { + str = str.replace(new RegExp("(\\s|\\*|,|\\(|^)" + k + "(\\s|\\*|,|\\)|$)", "g"), "$1" + typedefs[k] + "$2"); + } + + return str; + }; + + /* + Parses a C function declaration and returns the function name and cycript type + If load is true, tries to load it into cycript using utils.exec + + Usage: + cy# var str = "void *calloc(size_t num, size_t size)"; + "void *calloc(size_t num, size_t size)" + cy# utils.include(str) + ["calloc","@encode(void *(uint64_t num, uint64_t size))(140735674376857)"] + cy# var ret = utils.include(str, true) + ["calloc",0x7fff93e0e299] + cy# ret[1].type + @encode(void*(unsigned long long int,unsigned long long int)) + cy# ret[1](100, 1) + 0x100444100 + */ + utils.include = function(str, load) { + var re = /^\s*([^(]*(?:\s+|\*))(\w*)\s*\(([^)]*)\)\s*;?\s*$/; + var match = re.exec(str); + if(!match) { + return -1; + } + var rType = utils.applyTypedefs(match[1]); + var name = match[2]; + var args = match[3]; + + var argsRe = /([^,]+)(?:,|$)/g; + var argsTypes = []; + while((match = argsRe.exec(args)) !== null) { + var type = utils.applyTypedefs(match[1]); + argsTypes.push(type); + } + + var encodeString = "@encode("; + encodeString += rType + "("; + encodeString += argsTypes.join(", ") + "))"; + + var fun = dlsym(RTLD_DEFAULT, name); + if(fun !== null) { + encodeString += "(" + fun + ")"; + if(load) { + return [name, utils.exec(encodeString)]; + } + } else if(load) { + throw "Function couldn't be found with dlsym!"; + } + + return [name, encodeString]; + }; + + /* + Loads the function declaration in the defs array using utils.exec and exposes to cycript's global scope + Is automatically called if shouldLoadCFuncs is true + */ + utils.funcs = {}; + utils.loadfuncs = function(expose) { + for(var i = 0; i < CFuncsDeclarations.length; i++) { + try { + var o = utils.include(CFuncsDeclarations[i], true); + utils.funcs[o[0]] = o[1]; + if(expose) { + Cycript.all[o[0]] = o[1]; + } + } catch(e) { + system.print("Failed to load function: " + i); + try { + system.print(utils.include(CFuncsDeclarations[i])); + } catch(e2) { + + } + } + } + }; + + /* + Calculates the size of a type like the C operator sizeof + + Usage: + cy# utils.sizeof(int) + 4 + cy# utils.sizeof(@encode(void *)) + 8 + cy# utils.sizeof("mach_vm_address_t") + 8 + */ + utils.sizeof = function(type) { + if(typeof type === "string") { + type = utils.applyTypedefs(type); + type = utils.exec("@encode(" + type + ")"); + } + + // (const) char * has "infinite" preceision + if(type.toString().slice(-1) === "*") { + return utils.sizeof(@encode(void *)); + } + + // float and double + if(type.toString() === @encode(float).toString()) { + return 4; + } else if (type.toString() === @encode(double).toString()) { + return 8; + } + + var typeInstance = type(0); + + if(typeInstance instanceof Object) { + // Arrays + if("length" in typeInstance) { + return typeInstance.length * utils.sizeof(typeInstance.type); + } + + // Structs + if(typeInstance.toString() === "[object Struct]") { + var typeStr = type.toString(); + var arrayTypeStr = "[2" + typeStr + "]"; + var arrayType = new Type(arrayTypeStr); + + var arrayInstance = new arrayType; + + return @encode(void *)(&(arrayInstance[1])) - @encode(void *)(&(arrayInstance[0])); + } + } + + for(var i = 0; i < 5; i++) { + var maxSigned = Math.pow(2, 8 * Math.pow(2, i) - 1) - 1; + if(i === 3) { + // Floating point fix ;^) + maxSigned /= 1000; + } + + // can't use !== or sizeof(void *) === 0.5 + if(type(maxSigned) != maxSigned) { + return Math.pow(2, i - 1); + } + } + }; + + /* + Logs a specific message sent to an instance of a class like logify.pl in theos + Requires Cydia Substrate (com.saurik.substrate.MS) and NSLog (org.cycript.NSLog) modules + Returns the old message returned by MS.hookMessage (Note: this is not just the old message!) + + Usage: + cy# var oldm = utils.logify(objc_getMetaClass(NSNumber), @selector(numberWithDouble:)) + ... + cy# var n = [NSNumber numberWithDouble:1.5] + 2014-07-28 02:26:39.805 cycript[71213:507] +[ numberWithDouble:1.5] + 2014-07-28 02:26:39.806 cycript[71213:507] = 1.5 + @1.5 + */ + utils.logify = function(cls, sel) { + @import com.saurik.substrate.MS; + @import org.cycript.NSLog; + + var oldm = {}; + + MS.hookMessage(cls, sel, function() { + var args = [].slice.call(arguments); + + var selFormat = sel.toString().replace(/:/g, ":%@ ").trim(); + var logFormat = "%@[<%@: 0x%@> " + selFormat + "]"; + + var standardArgs = [logFormat, class_isMetaClass(cls)? "+": "-", cls.toString(), (&this).valueOf().toString(16)]; + var logArgs = standardArgs.concat(args); + + NSLog.apply(null, logArgs); + + var r = oldm->apply(this, arguments); + + if(r !== undefined) { + NSLog(" = %@", r); + } + + return r; + }, oldm); + + return oldm; + }; + + /* + Calls a C function by providing its name and arguments + Doesn't support structs + Return value is always a void pointer + + Usage: + cy# utils.apply("printf", ["%s %.3s, %d -> %c, float: %f\n", "foo", "barrrr", 97, 97, 1.5]) + foo bar, 97 -> a, float: 1.500000 + 0x22 + */ + utils.apply = function(fun, args) { + if(!(args instanceof Array)) { + throw "Args needs to be an array!"; + } + + var argc = args.length; + var voidPtr = @encode(void *); + var argTypes = []; + for(var i = 0; i < argc; i++) { + var argType = voidPtr; + + var arg = args[i]; + if(typeof arg === "string") { + argType = @encode(char *); + } + if(typeof arg === "number" && arg % 1 !== 0) { + argType = @encode(double); + } + + argTypes.push(argType); + } + + var type = voidPtr.functionWith.apply(voidPtr, argTypes); + + if(typeof fun === "string") { + fun = dlsym(RTLD_DEFAULT, fun); + } + + if(!fun) { + throw "Function not found!"; + } + + return type(fun).apply(null, args); + }; + + /* + Converts a string (char *) to a void pointer (void *) + You can't cast to strings to void pointers and vice versa in cycript. Blame saurik. + + Usage: + cy# var voidPtr = utils.str2voidPtr("foobar") + 0x100331590 + cy# utils.voidPtr2str(voidPtr) + "foobar" + */ + utils.str2voidPtr = function(str) { + var strdup = @encode(void *(char *))(dlsym(RTLD_DEFAULT, "strdup")); + return strdup(str); + }; + + /* + The inverse function of str2voidPtr + */ + utils.voidPtr2str = function(voidPtr) { + var strdup = @encode(char *(void *))(dlsym(RTLD_DEFAULT, "strdup")); + return strdup(voidPtr); + }; + + /* + Converts a double into a void pointer + This can be used to view the binary representation of a floating point number + + Usage: + cy# var n = utils.double2voidPtr(-1.5) + 0xbff8000000000000 + cy# utils.voidPtr2double(n) + -1.5 + */ + utils.double2voidPtr = function(n) { + var doublePtr = new double; + *doublePtr = n; + + var voidPtrPtr = @encode(void **)(doublePtr); + + return *voidPtrPtr; + }; + + /* + The inverse function of double2voidPtr + */ + utils.voidPtr2double = function(voidPtr) { + var voidPtrPtr = new @encode(void **); + *voidPtrPtr = voidPtr; + + var doublePtr = @encode(double *)(voidPtrPtr); + + return *doublePtr; + }; + + /* + Determines in a safe way if a memory location is readable + + Usage: + cy# utils.isMemoryReadable(0) + false + cy# utils.isMemoryReadable(0x1337) + false + cy# utils.isMemoryReadable(NSObject) + true + cy# var a = malloc(100); utils.isMemoryReadable(a) + true + */ + utils.isMemoryReadable = function(ptr) { + if(typeof ptr === "string") { + return true; + } + + var fds = new @encode(int [2]); + utils.apply("pipe", [fds]); + var result = utils.apply("write", [fds[1], ptr, 1]) == 1; + + utils.apply("close", [fds[0]]); + utils.apply("close", [fds[1]]); + + return result; + }; + + /* + Determines in a safe way if the memory location contains an Objective-C object + + Usage: + cy# utils.isObject(0) + false + cy# utils.isObject(0x1337) + false + cy# utils.isObject(NSObject) + true + cy# utils.isObject(objc_getMetaClass(NSObject)) + true + cy# utils.isObject([new NSObject init]) + true + cy# var a = malloc(100); utils.isObject(a) + false + cy# *@encode(void **)(a) = NSObject; utils.isObject(a) + true + */ + utils.isObject = function(obj) { + obj = @encode(void *)(obj); + var lastObj = -1; + + function objc_isa_ptr(obj) { + // See http://www.sealiesoftware.com/blog/archive/2013/09/24/objc_explain_Non-pointer_isa.html + var objc_debug_isa_class_mask = 0x00000001fffffffa; + obj = (obj & 1)? (obj & objc_debug_isa_class_mask): obj; + + if((obj & (utils.sizeof(@encode(void *)) - 1)) != 0) { + return null; + } else { + return obj; + } + } + + function ptrValue(obj) { + return obj? obj.valueOf(): null; + } + + var foundMetaClass = false; + + for(obj = objc_isa_ptr(obj); utils.isMemoryReadable(obj); ) { + obj = *@encode(void **)(obj); + + if(ptrValue(obj) == ptrValue(lastObj)) { + foundMetaClass = true; + break; + } + + lastObj = obj; + } + + if(!foundMetaClass) { + return false; + } + + if(lastObj === -1 || lastObj === null) { + return false; + } + + var obj_class = objc_isa_ptr(@encode(void **)(obj)[1]); + + if(!utils.isMemoryReadable(obj_class)) { + return false; + } + + var metaclass = objc_isa_ptr(@encode(void **)(obj_class)[0]); + var superclass = objc_isa_ptr(@encode(void **)(obj_class)[1]); + + return ptrValue(obj) == ptrValue(metaclass) && superclass == null; + }; + + /* + Creates a cycript struct type from a C struct definition + + Usage: + cy# var foo = makeStruct("int a; short b; char c; uint64_t d; double e;", "foo"); + @encode(foo) + cy# var f = new foo + &{a:0,b:0,c:0,d:0,e:0} + cy# f->a = 100; f + &{a:100,b:0,c:0,d:0,e:0} + cy# *@encode(int *)(f) + 100 + */ + utils.makeStruct = function(str, name) { + var fieldRe = /(?:\s|\n)*([^;]+\s*(?:\s|\*))([^;]+)\s*;/g; + + if(!name) { + name = "struct" + Math.floor(Math.random() * 100000); + } + var typeStr = "{" + name + "="; + + while((match = fieldRe.exec(str)) !== null) { + var fieldType = utils.applyTypedefs(match[1]); + var fieldName = match[2]; + var encodedType = utils.exec("@encode(" + fieldType + ")").toString(); + + typeStr += '"' + fieldName + '"' + encodedType; + } + + typeStr += "}"; + + return new Type(typeStr); + }; + + // Various constants + utils.constants = { + VM_PROT_NONE: 0x0, + VM_PROT_READ: 0x1, + VM_PROT_WRITE: 0x2, + VM_PROT_EXECUTE: 0x4, + VM_PROT_NO_CHANGE: 0x8, + VM_PROT_COPY: 0x10, + VM_PROT_WANTS_COPY: 0x10, + VM_PROT_IS_MASK: 0x40, + }; + var c = utils.constants; + c.VM_PROT_DEFAULT = c.VM_PROT_READ | c.VM_PROT_WRITE; + c.VM_PROT_ALL = c.VM_PROT_READ | c.VM_PROT_WRITE | c.VM_PROT_EXECUTE; + + if(shouldExposeConsts) { + for(var k in c) { + Cycript.all[k] = c[k]; + } + } + + if(shouldExposeFuncs) { + for(var i = 0; i < funcsToExpose.length; i++) { + var name = funcsToExpose[i]; + Cycript.all[name] = utils[name]; + } + } + + if(shouldLoadCFuncs) { + utils.loadfuncs(shouldExposeCFuncs); + } +})(exports);