ios-deeplink-fuzzing

by
4 views 21720ac6...

Description

Rebase of an existing script. Created for personal use.

How to Use

Run this script using Frida CLI:

frida --codeshare ivan-sincek/ios-deeplink-fuzzing -f YOUR_BINARY

Source Code

JavaScript
/************************************************************************
 * Name: iOS Deeplink Fuzzing
 * OS: iOS
 * Author: @dki (Credits to the author!)
 * Source: https://codeshare.frida.re/@dki/ios-url-scheme-fuzzing
 * Edited: https://github.com/ivan-sincek/ios-penetration-testing-cheat-sheet/blob/main/scripts/ios-deeplink-fuzzing.js (heavy rebase)
 ************************************************************************
 *
 * Usage:
 *     frida -U --no-pause -l ios-deeplink-fuzzing.js -f com.someapp.dev
 *
 * Get the hooked app's URL schemes:
 *     getSchemes();
 *
 * Open a URL (deeplink) system-wide:
 *     openURL("somescheme://com.someapp.dev/somepath");
 *
 * Get all apps for a given URL scheme:
 *     getApps("somescheme");
 *
 * Emulate a single home button click (for app backgrounding):
 *     homeSinglePress();
 *
 * Move all crash logs matching the app's name to "/tmp/" directory:
 *     moveCrashLogs("someapp");
 *
 * Specify a wordlist to use for fuzzing:
 *     addFuzzStringsFromFile("/tmp/wordlist.txt");
 *
 * Fuzz a URL (deeplink) - wehere '{0}' is the placeholder for insertion point:
 *     fuzz("somescheme://com.someapp.dev/action?param={0}");
 *
 * You might want to call 'openURL()' once for the target URL (deeplink) before fuzzing to dismiss any prompt that might appear:
 *     openURL("somescheme://com.someapp.dev/action");
 *     fuzz("somescheme://com.someapp.dev/action?param={0}");
 *
 ************************************************************************/
function getSchemes() {
	var dictionary = ObjC.classes.NSBundle.mainBundle().infoDictionary().objectForKey_("CFBundleURLTypes");
	if (!dictionary) {
		console.log("Hooked app does not have any URL schemes.");
	} else {
		dictionary = dictionary.objectAtIndex_(0);
		var keys = dictionary.allKeys();
		for (var i = 0; i < keys.count(); i++) {
			var key = keys.objectAtIndex_(i);
			if (key == "CFBundleURLName") {
				console.log("URL Scheme Name: " + dictionary.objectForKey_(key));
			} else if (key == "CFBundleURLSchemes") {
				var schemes = [];
				var tmp = dictionary.objectForKey_(key);
				for (var j = 0; j < tmp.count(); j++) {
					schemes.push(tmp.objectAtIndex_(j));
				}
				console.log("URL Schemes: [" + schemes.join(", ") + "]");
			}
		}
	}
}
function openURL(url) {
	var workspace = ObjC.classes.LSApplicationWorkspace.defaultWorkspace();
	return workspace.openSensitiveURL_withOptions_(ObjC.classes.NSURL.URLWithString_(url), null);
}
function getApps(scheme) {
	var apps = [];
	var tmp = ObjC.classes.LSApplicationWorkspace.defaultWorkspace().applicationsAvailableForHandlingURLScheme_(scheme);
	for (var i = 0; i < tmp.count(); i++) {
		apps.push(tmp.objectAtIndex_(i).bundleExecutable());
	}
	return apps;
}
function homeSinglePress() {
	var controller = ObjC.classes.SBUIController;
	if (controller) {
		var version = ObjC.classes.UIDevice.currentDevice().systemVersion().toString();
		ObjC.schedule(ObjC.mainQueue, function() {
			if (version.startsWith("9")) {
				controller.sharedInstance().clickedMenuButton();
			} else {
				// doesn't work on iOS 13, need to find a solution; should work on iOS 10 and 11
				controller.sharedInstance().handleHomeButtonSinglePressUp();
			}
		});
	}
}
function moveCrashLogs(app) {
	var fm = ObjC.classes.NSFileManager.defaultManager();
	var dir = "/private/var/mobile/Library/Logs/CrashReporter/";
	var predicate = ObjC.classes.NSPredicate.predicateWithFormat_("SELF like \"" + app + "*.ips\"");
	var files = fm.contentsOfDirectoryAtPath_error_(dir, NULL).filteredArrayUsingPredicate_(predicate);
	for (var i = 0; i < files.count(); i++) {
		var file = files.objectAtIndex_(i);
		fm.moveItemAtPath_toPath_error_(dir + file, "/tmp/" + file, NULL);
	}
	return files.count();
}
if (!String.format) {
	String.format = function(format) {
		var args = Array.prototype.slice.call(arguments, 1);
		return format.replace(/{(\d+)}/g, function(match, number) {
			return typeof args[number] != 'undefined' ? args[number] : match;
		});
	};
}
// add or modify fuzz strings here
var fuzzStrings = [
	"0",
	"000",
	"0.00",
	"-1",
	"1",
	"NaN",
	"-NaN",
	"Infinity",
	"-Infinity",
	"inf",
	"-inf",
	"0b0",
	"0x0",
	"0b00111101110011001100110011001101",
	"0x1.999999999999ap-4",
	"&h00",
	"&hff",
	"0.00000000000000000000000000000000000000000000000001",
	"1e-50",
	"0e0",
	"true",
	"false",
	"+1",
	"0e-1",
	"0e1",
	"null",
	"None",
	"nil",
	"An Array",
	"%20%090",
	"0%20%00%00",
	"-2147483648",
	"2147483647",
	"4294967295",
	"-2147483649",
	"2147483648",
	"4294967296",
	Array(256).join("9"),
	Array(512).join("9"),
	Array(1024).join("9")
];
fuzzStrings.iter = function() {
	var index = 0;
	var data = this;
	return {
		next: function() {
			return {
				value: data[index],
				done: index++ == (data.length - 1)
			};
		}
	}
};
function addFuzzStringsFromFile(path) {
	var fm = ObjC.classes.NSFileManager.defaultManager();
	if (!fm.isReadableFileAtPath_(path)) {
		console.error("Cannot read the wordlist. Make sure the wordlist is on the iOS device and has the read permission!");
	} else {
		var lines = ObjC.classes.NSString.stringWithContentsOfFile_(path, "NSUTF8StringEncoding", NULL).componentsSeparatedByString_("\n");
		if (!lines.count()) {
			console.warn("Wordlist is empty! Moving on with the built-in list...");
		} else {
			fuzzStrings.length = 0;
			for (var i = 0; i < lines.count(); i++) {
				fuzzStrings.push(lines.objectAtIndex_(i));
			}
			console.log("Wordlist has been loaded successfully.");
		}
	}
}
function fuzz(url) {
	var apps = getApps(url.split('://')[0]);
	if (apps.length > 1) {
		console.error("Multiple apps handle this URL scheme, script will now exit!\nApps: [" + apps.join(", " + "]"));
		return;
	}
	var app = apps[0];
	function Fuzzer(url, app, iter, sleep) {
		this.url = url;
		this.app = app;
		this.iter = iter;
		this.sleep = sleep;
	}
	Fuzzer.prototype.checkForCrash = function(done) {
		homeSinglePress();
		if (moveCrashLogs(this.app)) {
			console.error("Crashed!");
		}
		if (!done) {
			this.fuzz();
		}
	};
	Fuzzer.prototype.fuzz = function() {
		var iter = this.iter.next();
		var fuzzedURL = String.format(this.url, iter.value);
		if (openURL(fuzzedURL)) {
			console.log("Opened URL: " + fuzzedURL);
		} else {
			console.error("URL refused by SpringBoard: " + fuzzedURL);
		}
		ObjC.classes.NSThread.sleepForTimeInterval_(this.sleep);
		this.checkForCrash(iter.done);
	};
	console.warn("Monitoring crashes for \"" + app + "\"...");
	var count = moveCrashLogs(app);
	if (count) {
		console.warn("Number of crash logs moved to \"/tmp/\": " + count.toString());
	}
	var fuzzer = new Fuzzer(url, app, fuzzStrings.iter(), 2); // change the sleep between attempts here
	fuzzer.fuzz();
}
setTimeout(function() {
	if (ObjC.available) {
		console.log("");
		// --------------------
		getSchemes();
		// --------------------
		// modify the code below as necessary, you can also paste the whole above code directly into Frida and call each method as needed
		// openURL("somescheme://com.someapp.dev/action");
		// addFuzzStringsFromFile("/tmp/wordlist.txt"); // load fuzz strings from a wordlist
		// fuzzStrings.push("somestring");              // append an additional fuzz string
		// fuzz("somescheme://com.someapp.dev/action?param={0}");
	} else {
		console.log("Objective-C Runtime is not available!");
	}
}, 250);
Share this script:
Twitter LinkedIn

Comments

Login or Sign up to leave a comment.
Loading comments...