Skip to content

Commit

Permalink
iOS Dynamic Analysis with Corellium (#2194)
Browse files Browse the repository at this point in the history
* iOS Dynamic Analysis Support with Corellium Jailbroken iOS devices
* Corellium API layer for complete device and project management
* Frida instrumentation (attach, spawn and inject) over SSH local port forward
* Shell access over SSH
* MobSF httptools proxy integration over SSH remote port forward
* Device File upload and download over SSH
* Frida scripts for core defense bypass, monitoring, and tracing
* Helper iOS Frida scripts for pentesting and malware analysis
* Screen cast with touch, swipe and text input support from web UI
* Dynamic Analysis device data dump and  report Generation
* Android Certificate analysis, replaced oscrypto with cryptography for public key parsing
* Python minimum support is 3.10
* Bumped httptools to latest, fixes httptools repeat bug
* Added unzip to docker to fix a bug
  • Loading branch information
ajinabraham authored Dec 3, 2023
1 parent 4685d8e commit 2aecb90
Show file tree
Hide file tree
Showing 148 changed files with 10,444 additions and 1,604 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/mobsf-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-22.04, macos-latest, windows-latest]
python-version: [3.9, '3.10', '3.11']
python-version: ['3.10', '3.11']

runs-on: ${{ matrix.os }}
steps:
Expand Down
2 changes: 1 addition & 1 deletion .sonarcloud.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
sonar.sources=.
sonar.exclusions=mobsf/static/**/*,mobsf/templates/**/*
sonar.sourceEncoding=UTF-8
sonar.python.version=3.7, 3.8, 3.9, 3.10, 3.11
sonar.python.version=3.10, 3.11
4 changes: 3 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ RUN apt update -y && apt install -y --no-install-recommends \
curl \
git \
jq \
unzip \
android-tools-adb && \
locale-gen en_US.UTF-8
locale-gen en_US.UTF-8 && \
apt upgrade -y

ENV MOBSF_USER=mobsf \
MOBSF_PLATFORM=docker \
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# Mobile Security Framework (MobSF)
Version: v3.7 beta
Version: v3.8 beta

![](https://cloud.githubusercontent.com/assets/4301109/20019521/cc61f7fc-a2f2-11e6-95f3-407030d9fdde.png)

Mobile Security Framework (MobSF) is an automated, all-in-one mobile application (Android/iOS/Windows) pen-testing, malware analysis and security assessment framework capable of performing static and dynamic analysis. MobSF supports mobile app binaries (APK, XAPK, IPA & APPX) along with zipped source code and provides REST APIs for seamless integration with your CI/CD or DevSecOps pipeline.The Dynamic Analyzer helps you to perform runtime security assessment and interactive instrumented testing.

Made with ![Love](https://cloud.githubusercontent.com/assets/4301109/16754758/82e3a63c-4813-11e6-9430-6015d98aeaab.png) in India

[![python](https://img.shields.io/badge/python-3.9+-blue.svg?logo=python&labelColor=yellow)](https://www.python.org/downloads/)
[![python](https://img.shields.io/badge/python-3.10+-blue.svg?logo=python&labelColor=yellow)](https://www.python.org/downloads/)
[![PyPI version](https://badge.fury.io/py/mobsf.svg)](https://badge.fury.io/py/mobsf)
[![platform](https://img.shields.io/badge/platform-osx%2Flinux%2Fwindows-green.svg)](https://github.com/MobSF/Mobile-Security-Framework-MobSF/)
[![License](https://img.shields.io/:license-GPL--3.0--only-blue.svg)](https://www.gnu.org/licenses/gpl-3.0.html)
Expand Down
6 changes: 6 additions & 0 deletions mobsf/DynamicAnalyzer/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""File upload to iOS form."""
from django import forms


class UploadFileForm(forms.Form):
file = forms.FileField()
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ Java.performNow(function () {
};

// String.contains check
var String = Java.use('java.lang.String');
String.contains.implementation = function (name) {
var javaString = Java.use('java.lang.String');
javaString.contains.implementation = function (name) {
if (name == "test-keys") {
send("[RootDetection Bypass] test-keys check");
return false;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/* Description: Hook all the methods of a particular class
* Mode: S+A
* Version: 1.0
* Credit: https://github.com/interference-security/frida-scripts/blob/master/iOS
* Author: @interference-security
*/
//Twitter: https://twitter.com/xploresec
//GitHub: https://github.com/interference-security
function hook_class_method(class_name, method_name)
{
var hook = ObjC.classes[class_name][method_name];
Interceptor.attach(hook.implementation, {
onEnter: function(args) {
send("[AUXILIARY] Detected call to: " + class_name + " -> " + method_name);
}
});
}

function run_hook_all_methods_of_specific_class(className_arg)
{
send("Started: Hook all methods of a specific class");
send("Class Name: " + className_arg);
//Your class name here
var className = className_arg;
//var methods = ObjC.classes[className].$methods;
var methods = ObjC.classes[className].$ownMethods;
for (var i = 0; i < methods.length; i++)
{
send("[AUXILIARY] [-] "+methods[i]);
send("[AUXILIARY] \t[*] Hooking into implementation");
//eval('var className2 = "'+className+'"; var funcName2 = "'+methods[i]+'"; var hook = eval(\'ObjC.classes.\'+className2+\'["\'+funcName2+\'"]\'); Interceptor.attach(hook.implementation, { onEnter: function(args) { console.log("[*] Detected call to: " + className2 + " -> " + funcName2); } });');
var className2 = className;
var funcName2 = methods[i];
hook_class_method(className2, funcName2);
// send("[AUXILIARY] \t[*] Hooking successful");
}
send("[AUXILIARY] Completed: Hook all methods of a specific class");
}

function hook_all_methods_of_specific_class(className_arg)
{
try {
setImmediate(run_hook_all_methods_of_specific_class,[className_arg])
} catch(err) {}
}


//Your class name goes here
hook_all_methods_of_specific_class('{{CLASS}}')
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/* Description: Dump all methods inside classes owned by the app only
* Mode: S+A
* Version: 1.0
* Credit: PassionFruit (https://github.com/chaitin/passionfruit/blob/master/agent/app/classdump.js) & https://github.com/interference-security/frida-scripts/blob/master/iOS
* Author: @interference-security
*/
//Twitter: https://twitter.com/xploresec
//GitHub: https://github.com/interference-security
function run_show_app_classes_methods_only()
{
send("Started: Find App's Classes and Methods")
var free = new NativeFunction(Module.findExportByName(null, 'free'), 'void', ['pointer'])
var copyClassNamesForImage = new NativeFunction(Module.findExportByName(null, 'objc_copyClassNamesForImage'), 'pointer', ['pointer', 'pointer'])
var p = Memory.alloc(Process.pointerSize)
Memory.writeUInt(p, 0)
var path = ObjC.classes.NSBundle.mainBundle().executablePath().UTF8String()
var pPath = Memory.allocUtf8String(path)
var pClasses = copyClassNamesForImage(pPath, p)
var count = Memory.readUInt(p)
var classesArray = new Array(count)
for (var i = 0; i < count; i++)
{
var pClassName = Memory.readPointer(pClasses.add(i * Process.pointerSize))
classesArray[i] = Memory.readUtf8String(pClassName)
var className = classesArray[i]
send("[AUXILIARY] Class: " + className);
//var methods = ObjC.classes[className].$methods;
var methods = ObjC.classes[className].$ownMethods;
for (var j = 0; j < methods.length; j++)
{
send("[AUXILIARY] \t[-] Method: " + methods[j]);
try
{
send("[AUXILIARY] \t\t[-] Arguments Type: " + ObjC.classes[className][methods[j]].argumentTypes);
send("[AUXILIARY] \t\t[-] Return Type: " + ObjC.classes[className][methods[j]].returnType);
}
catch(err) {}
}
}
free(pClasses)
send("App Classes found: " + count);
send("Completed: Find App's Classes")
}

function show_app_classes_methods_only()
{
try {
setImmediate(run_show_app_classes_methods_only)
} catch(err) {}
}

show_app_classes_methods_only()
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/* Description: Dump classes owned by the app only
* Mode: S+A
* Version: 1.0
* Credit: PassionFruit (https://github.com/chaitin/passionfruit/blob/master/agent/app/classdump.js) & https://github.com/interference-security/frida-scripts/blob/master/iOS
* Author: @interference-security
*/
//Twitter: https://twitter.com/xploresec
//GitHub: https://github.com/interference-security
function run_show_app_classes_only()
{
send("Started: Find App's Classes")
var free = new NativeFunction(Module.findExportByName(null, 'free'), 'void', ['pointer'])
var copyClassNamesForImage = new NativeFunction(Module.findExportByName(null, 'objc_copyClassNamesForImage'), 'pointer', ['pointer', 'pointer'])
var p = Memory.alloc(Process.pointerSize)
Memory.writeUInt(p, 0)
var path = ObjC.classes.NSBundle.mainBundle().executablePath().UTF8String()
var pPath = Memory.allocUtf8String(path)
var pClasses = copyClassNamesForImage(pPath, p)
var count = Memory.readUInt(p)
var classesArray = new Array(count)
for (var i = 0; i < count; i++)
{
var pClassName = Memory.readPointer(pClasses.add(i * Process.pointerSize))
classesArray[i] = Memory.readUtf8String(pClassName)
send("[AUXILIARY] " + classesArray[i])
}
free(pClasses)
send("App Classes found: " + count);
send("Completed: Find App's Classes")
}

function show_app_classes_only()
{
try {
setImmediate(run_show_app_classes_only)
} catch(err) {}
}

show_app_classes_only()
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/* Description: Find a specific method in all classes in the app
* Modified for MobSF
* Mode: S+A
* Version: 1.0
* Credit: PassionFruit (https://github.com/chaitin/passionfruit/blob/master/agent/app/classdump.js) & https://github.com/interference-security/frida-scripts/blob/master/iOS
* Author: @interference-security
*/
//Twitter: https://twitter.com/xploresec
//GitHub: https://github.com/interference-security
function find_specific_method_in_all_classes(func_name)
{
send("Searching for method [" + func_name + "] in all Classes");
var free = new NativeFunction(Module.findExportByName(null, 'free'), 'void', ['pointer'])
var copyClassNamesForImage = new NativeFunction(Module.findExportByName(null, 'objc_copyClassNamesForImage'), 'pointer', ['pointer', 'pointer'])
var p = Memory.alloc(Process.pointerSize)
Memory.writeUInt(p, 0)
var path = ObjC.classes.NSBundle.mainBundle().executablePath().UTF8String()
var pPath = Memory.allocUtf8String(path)
var pClasses = copyClassNamesForImage(pPath, p)
var count = Memory.readUInt(p)
var classesArray = new Array(count)
for (var i = 0; i < count; i++)
{
var pClassName = Memory.readPointer(pClasses.add(i * Process.pointerSize))
classesArray[i] = Memory.readUtf8String(pClassName)
var className = classesArray[i]
//var methods = ObjC.classes[className].$methods;
var methods = ObjC.classes[className].$ownMethods;
for (var j = 0; j < methods.length; j++)
{
if(methods[j].includes(func_name))
{
send("[AUXILIARY] Class: " + className);
send("[AUXILIARY] \t[-] Method: " + methods[j]);
try
{
send("[AUXILIARY] \t\t[-] Arguments Type: " + ObjC.classes[className][methods[j]].argumentTypes);
send("[AUXILIARY] \t\t[-] Return Type: " + ObjC.classes[className][methods[j]].returnType);
}
catch(err) {}
}
}
}
free(pClasses)
send("Completed: Find specific Method in all Classes");
}


//Your function name goes here
var METHOD = '{{METHOD}}'
try {
find_specific_method_in_all_classes(METHOD)
} catch(err) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/* Description: Get all methods of the class
* Modified for MobSF
* Mode: S+A
* Version: 1.0
* Credit: PassionFruit (https://github.com/chaitin/passionfruit/blob/master/agent/app/classdump.js) & https://github.com/interference-security/frida-scripts/blob/master/iOS
* Author: @interference-security
*/
//Twitter: https://twitter.com/xploresec
//GitHub: https://github.com/interference-security
function run_get_app_methods_in_class()
{
var targetClass = '{{CLASS}}';
var found = false;
send("Looking for methods in: " + targetClass)

var free = new NativeFunction(Module.findExportByName(null, 'free'), 'void', ['pointer'])
var copyClassNamesForImage = new NativeFunction(Module.findExportByName(null, 'objc_copyClassNamesForImage'), 'pointer', ['pointer', 'pointer'])
var p = Memory.alloc(Process.pointerSize)
Memory.writeUInt(p, 0)
var path = ObjC.classes.NSBundle.mainBundle().executablePath().UTF8String()
var pPath = Memory.allocUtf8String(path)
var pClasses = copyClassNamesForImage(pPath, p)
var count = Memory.readUInt(p)
var classesArray = new Array(count)
for (var i = 0; i < count; i++)
{
var pClassName = Memory.readPointer(pClasses.add(i * Process.pointerSize))
classesArray[i] = Memory.readUtf8String(pClassName)
var className = classesArray[i]
if (className === targetClass)
{
found = true;
send("[AUXILIARY] Class: " + className);
//var methods = ObjC.classes[className].$methods;
var methods = ObjC.classes[className].$ownMethods;
for (var j = 0; j < methods.length; j++)
{
send("[AUXILIARY] \t[-] Method: " + methods[j]);
try
{
send("[AUXILIARY] \t\t[-] Arguments Type: " + ObjC.classes[className][methods[j]].argumentTypes);
send("[AUXILIARY] \t\t[-] Return Type: " + ObjC.classes[className][methods[j]].returnType);
}
catch(err) {}
}
}
}
free(pClasses)
if (!found)
{
send("Class not found: " + targetClass)
} else
{
send("Completed Enumerating Methods in Class: " + targetClass)
}
}


try {
run_get_app_methods_in_class()
} catch(err) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/* Description: Find classes matching a pattern
* Modified for MobSF
* Mode: S+A
* Version: 1.0
* Credit: PassionFruit (https://github.com/chaitin/passionfruit/blob/master/agent/app/classdump.js) & https://github.com/interference-security/frida-scripts/blob/master/iOS
* Author: @interference-security
*/
//Twitter: https://twitter.com/xploresec
//GitHub: https://github.com/interference-security
function findClasses(pattern)
{
var foundClasses = [];
var free = new NativeFunction(Module.findExportByName(null, 'free'), 'void', ['pointer'])
var copyClassNamesForImage = new NativeFunction(Module.findExportByName(null, 'objc_copyClassNamesForImage'), 'pointer', ['pointer', 'pointer'])
var p = Memory.alloc(Process.pointerSize)
Memory.writeUInt(p, 0)
var path = ObjC.classes.NSBundle.mainBundle().executablePath().UTF8String()
var pPath = Memory.allocUtf8String(path)
var pClasses = copyClassNamesForImage(pPath, p)
var count = Memory.readUInt(p)
var classesArray = new Array(count)
for (var i = 0; i < count; i++)
{
var pClassName = Memory.readPointer(pClasses.add(i * Process.pointerSize))
classesArray[i] = Memory.readUtf8String(pClassName)
if (classesArray[i].match(pattern)) {
foundClasses.push( classesArray[i]);
}
}
free(pClasses)
return foundClasses;
}


function getMatches(){
var matches;
try{
var pattern = /{{PATTERN}}/i;
send('Class search for pattern: ' + pattern)
matches = findClasses(pattern);
}catch (err){
send('Class pattern match [\"Error\"] => ' + err);
return;
}
if (matches.length>0)
send('Found [' + matches.length + '] matches')
else
send('No matches found')
matches.forEach(function(clz) {
send('[AUXILIARY] ' + clz)
});
}


try {
getMatches();
} catch(err) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
function captureString() {
send('Capturing strings')
Interceptor.attach(ObjC.classes.NSString['+ stringWithUTF8String:'].implementation, {
onLeave: function (retval) {
var str = new ObjC.Object(ptr(retval)).toString()
send('[AUXILIARY] [NSString stringWithUTF8String:] -> '+ str);
return retval;
}
});
}

try {
captureString();
} catch(err) {}
Loading

0 comments on commit 2aecb90

Please sign in to comment.