Skip to content

Instantly share code, notes, and snippets.

@stingerIO
Last active September 17, 2024 15:05
Show Gist options
  • Save stingerIO/7f9213004aa57bf8f5c7bc3974c49e3d to your computer and use it in GitHub Desktop.
Save stingerIO/7f9213004aa57bf8f5c7bc3974c49e3d to your computer and use it in GitHub Desktop.
iOS URL Scheme Fuzzer
/*
* Modified from: https://codeshare.frida.re/@dki/ios-url-scheme-fuzzing/
* Supports iOS 12 and 13, iPadOS 13!
* USAGE: frida -U -l fuzzer.js SpringBoard
*
* iOS URL Scheme Fuzzing
*
* Open the specified URL
* openURL("somescheme://test");
*
* Fuzz a particular URL - use {0} as placeholder for insertion points
* fuzz("somescheme://somepath?param={0}");
*
* List all crash logs matching a particular string
* listCrashLogs("MyApp");
*
*
* You'll typically want to call openURL for the target scheme once before
* fuzzing to dismiss the prompt that appears the first time
*
* openURL("somescheme://test");
* fuzzStrings.push("somefancyfuzzstring");
* fuzz("somescheme://test/{0}");
*
*/
// have Springboard open a URL with whatever handler has claimed it
function openURL(url) {
var w = ObjC.classes.LSApplicationWorkspace.defaultWorkspace();
var toOpen = ObjC.classes.NSURL.URLWithString_(url);
return w.openSensitiveURL_withOptions_(toOpen, null);
}
// emulate single home button press (WORKS ONLY WITH SpringBoard)
function homeSinglePress() {
var version = ObjC.classes.UIDevice.currentDevice().systemVersion().toString();
if (version.startsWith("9.")) { // iOS 9
ObjC.schedule(ObjC.mainQueue, function() {
ObjC.classes.SBUIController.sharedInstance().clickedMenuButton();
});
} else if (version.startsWith("10.") || version.startsWith("11.")) { // iOS 10//11
ObjC.schedule(ObjC.mainQueue, function() {
ObjC.classes.SBUIController.sharedInstance().handleHomeButtonSinglePressUp();
});
} else if (version.startsWith("12.") || version.startsWith("13.")) { // iOS 12//13
ObjC.schedule(ObjC.mainQueue, function() {
ObjC.classes.SBUIController.sharedInstance().handleHomeButtonSinglePressUp();
});
} else {
console.log("Untested on this device firmware version! :(");
}
}
// check for crash logs and move them to /tmp/ if they exist
// can result in false positives if the appname is a substring of another app
function listCrashLogs(appname) {
var match = appname + "*.ips";
var pred = ObjC.classes.NSPredicate.predicateWithFormat_('SELF like "' + match + '"');
var fm = ObjC.classes.NSFileManager.defaultManager();
var dirlist = fm.contentsOfDirectoryAtPath_error_("/private/var/mobile/Library/Logs/CrashReporter", NULL);
var results = dirlist.filteredArrayUsingPredicate_(pred);
if (results.count() > 0) {
for (var i = 0; i < results.count(); i++) {
var src = results.objectAtIndex_(i).toString();
console.log(src);
}
}
return results.count();
}
// check for crash logs and move them to /tmp/ if they exist
// can result in false positives if the appname is a substring of another app
function moveCrashLogs(appname) {
var match = appname + "*.ips";
var pred = ObjC.classes.NSPredicate.predicateWithFormat_('SELF like "' + match + '"');
var fm = ObjC.classes.NSFileManager.defaultManager();
var dirlist = fm.contentsOfDirectoryAtPath_error_("/private/var/mobile/Library/Logs/CrashReporter", NULL);
var results = dirlist.filteredArrayUsingPredicate_(pred);
if (results.count() > 0) {
for (var i = 0; i < results.count(); i++) {
var src = results.objectAtIndex_(i).toString();
console.log('Moving ' + src);
var result = fm.moveItemAtPath_toPath_error_("/private/var/mobile/Library/Logs/CrashReporter/" + src, "/tmp/" + src, NULL);
console.log('Result: ' + result);
}
return true;
}
return false;
}
// https://stackoverflow.com/questions/610406/javascript-equivalent-to-printf-string-format
// this is what happens when i port things from python D:
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/remove default fuzz strings here, or at the command line
var fuzzStrings = ["0",
"1",
"-1",
"null",
"nil",
"99999999999999999999999999999999999",
Array(257).join("A"),
Array(1026).join("A"),
Array(4096).join("5"),
Array(9096).join("8"),
"'",
"%20d",
"%20n",
"%20x",
"%20s"
];
fuzzStrings.iter = function() {
var index = 0;
var data = this;
return {
next: function() {
return {
value: data[index],
done: index++ == (data.length - 1)
};
},
hasNext: function() {
return index < data.length;
}
}
};
// fuzz a url for a registered scheme
// use {0} for placeholders: blah://test/{0}
function fuzz(appname, url) {
function Fuzzer(url, appname, iter) {
this.url = url;
this.appname = appname;
this.iter = iter;
}
Fuzzer.prototype.checkForCrash = function(done) {
// background in case it is still running
homeSinglePress();
// check for a crash
if (listCrashLogs(this.appname) > 0) {
console.log("Crashed!");
}
else {
console.log("OK!");
}
// fuzz next url
if (!done) {
this.fuzz();
}
};
Fuzzer.prototype.fuzz = function() {
var term = this.iter.next();
// create the url
var fuzzedURL = String.format(this.url, term.value);
// this should launch the app
if (openURL(fuzzedURL)) {
console.log("Opened URL: " + fuzzedURL);
} else {
console.log("URL refused by SpringBoard: " + fuzzedURL);
}
// wait a few seconds before backgrounding
ObjC.classes.NSThread.sleepForTimeInterval_(3);
this.checkForCrash(term.done);
};
console.log("Watching for crashes from " + appname + "...");
// start by clearing any current logs
if (moveCrashLogs(appname)) {
console.log("Moved one or more logs to /tmp/ before fuzzing!");
}
else {
console.log("No logs were moved.");
}
// get iterator for fuzz strings
var iter = fuzzStrings.iter();
var fuzzer = new Fuzzer(url, appname, iter);
fuzzer.fuzz();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment