mirror of
https://github.com/MobSF/Mobile-Security-Framework-MobSF.git
synced 2026-10-11 22:50:09 +00:00
4.2.0 (#2450)
- Added malware lookup using SHA2 with VirusTotal, Triage, Hybrid Analysis, and MetaDefender. - Fixed permissions of extracted files to counter anti-analysis techniques. - Resolved APK parsing errors in `androguard`. - Handled exceptions in `string_on_binary`. - Optimized APK ZIP analysis for improved performance. - Fixed untar permission errors in dynamic analysis. - Added bypass for SSL pinning in Boye's `AbstractVerifier`. - Updated bypass for SSL pinning in Appmattus's `CertificateTransparencyInterceptor`. - Introduced SSL pinning detector script. - Improved Frida intent dumper script. - Added Frida intent tracer script. - Introduced timeouts for all HTTP calls. - Added `django-q2`-based asynchronous scans for Android and iOS binaries and source code. - Fixed bug in certificate analysis. - Enabled asynchronous scans in Docker Compose setup. - Performed QA for Android and iOS SAST modules. - Added Frida script for `audit-webview`. - Introduced Frida script for `trace-javascript-interface`. - Upgraded `libsast` for improved file reading, multiprocessing, and multithreading. - Fixed PNG crush issues on Darwin systems. - Performed QA on the home screen UI. - Updated `httptools` and `libsast` dependencies.
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
name: "CodeQL config"
|
||||
|
||||
queries:
|
||||
- uses: security-extended
|
||||
|
||||
query-filters:
|
||||
- exclude:
|
||||
id: py/path-injection # To much false positives
|
||||
|
||||
paths-ignore:
|
||||
- "**/.git/**"
|
||||
- "**/.github/**"
|
||||
@@ -1,61 +1,76 @@
|
||||
name: "CodeQL"
|
||||
name: "CodeQL Advanced"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ "master" ]
|
||||
schedule:
|
||||
- cron: '17 16 * * 0'
|
||||
- cron: '18 14 * * 3'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
name: Analyze (${{ matrix.language }})
|
||||
# Runner size impacts CodeQL analysis time. To learn more, please see:
|
||||
# - https://gh.io/recommended-hardware-resources-for-running-codeql
|
||||
# - https://gh.io/supported-runners-and-hardware-resources
|
||||
# - https://gh.io/using-larger-runners (GitHub.com only)
|
||||
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
|
||||
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
# required for all workflows
|
||||
security-events: write
|
||||
|
||||
# required to fetch internal or private CodeQL packs
|
||||
packages: read
|
||||
|
||||
# only required for workflows in private repositories
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'python' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
|
||||
|
||||
include:
|
||||
- language: python
|
||||
build-mode: none
|
||||
# CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
|
||||
# Use `c-cpp` to analyze code written in C, C++ or both
|
||||
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
|
||||
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
|
||||
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
|
||||
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
|
||||
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
|
||||
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
|
||||
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
config-file: .github/codeql-config.yml
|
||||
|
||||
# If the analyze step fails for one of the languages you are analyzing with
|
||||
# "We were unable to automatically build your code", modify the matrix above
|
||||
# to set the build mode to "manual" for that language. Then modify this step
|
||||
# to build your code.
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
|
||||
# If the Autobuild fails above, remove it and uncomment the following three lines.
|
||||
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
|
||||
|
||||
# - run: |
|
||||
# echo "Run, Build Application using script"
|
||||
# ./location_of_script_within_repo/buildscript.sh
|
||||
- if: matrix.build-mode == 'manual'
|
||||
shell: bash
|
||||
run: |
|
||||
echo 'If you are using a "manual" build mode for one or more of the' \
|
||||
'languages you are analyzing, replace this with the commands to build' \
|
||||
'your code, for example:'
|
||||
echo ' make bootstrap'
|
||||
echo ' make release'
|
||||
exit 1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
@@ -3,6 +3,12 @@ services:
|
||||
postgres:
|
||||
image: "postgres:13"
|
||||
restart: always
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 1G
|
||||
reservations:
|
||||
memory: 512M
|
||||
volumes:
|
||||
- $HOME/MobSF/postgresql_data:/var/lib/postgresql/data
|
||||
environment:
|
||||
@@ -15,6 +21,12 @@ services:
|
||||
nginx:
|
||||
image: nginx:latest
|
||||
restart: always
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 1G
|
||||
reservations:
|
||||
memory: 256M
|
||||
ports:
|
||||
- "80:4000"
|
||||
- "1337:4001"
|
||||
@@ -25,8 +37,31 @@ services:
|
||||
networks:
|
||||
- mobsf_network
|
||||
|
||||
djangoq:
|
||||
image: opensecurity/mobile-security-framework-mobsf:latest
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: Dockerfile
|
||||
restart: unless-stopped
|
||||
command: /home/mobsf/Mobile-Security-Framework-MobSF/scripts/qcluster.sh
|
||||
volumes:
|
||||
- $HOME/MobSF/mobsf_data:/home/mobsf/.MobSF
|
||||
environment:
|
||||
- POSTGRES_USER=postgres
|
||||
- POSTGRES_PASSWORD=password
|
||||
- POSTGRES_DB=mobsf
|
||||
- POSTGRES_HOST=postgres
|
||||
- POSTGRES_PORT=5432
|
||||
depends_on:
|
||||
- postgres
|
||||
networks:
|
||||
- mobsf_network
|
||||
|
||||
mobsf:
|
||||
image: opensecurity/mobile-security-framework-mobsf:latest
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: Dockerfile
|
||||
restart: always
|
||||
volumes:
|
||||
- $HOME/MobSF/mobsf_data:/home/mobsf/.MobSF
|
||||
@@ -36,10 +71,15 @@ services:
|
||||
- POSTGRES_DB=mobsf
|
||||
- POSTGRES_HOST=postgres
|
||||
- POSTGRES_PORT=5432
|
||||
- MOBSF_ASYNC_ANALYSIS=1
|
||||
healthcheck:
|
||||
test: curl -f http://localhost:8000/login/ || exit 1
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
depends_on:
|
||||
- postgres
|
||||
- djangoq
|
||||
networks:
|
||||
- mobsf_network
|
||||
extra_hosts:
|
||||
|
||||
@@ -241,15 +241,82 @@ Java.perform(function() {
|
||||
} catch (err) {
|
||||
send('[SSL Pinning Bypass] Cronet not found');
|
||||
}
|
||||
/* Certificate Transparency Bypass
|
||||
Ajin Abraham - opensecurity.in */
|
||||
try{
|
||||
/* Boye AbstractVerifier */
|
||||
try {
|
||||
Java.use("ch.boye.httpclientandroidlib.conn.ssl.AbstractVerifier").verify.implementation = function(host, ssl) {
|
||||
send("[SSL Pinning Bypass] Bypassing Boye AbstractVerifier" + host);
|
||||
};
|
||||
} catch (err) {
|
||||
send("[SSL Pinning Bypass] Boye AbstractVerifier not found");
|
||||
}
|
||||
/* Appmattus */
|
||||
try {
|
||||
/* Certificate Transparency Bypass Ajin Abraham - opensecurity.in */
|
||||
Java.use('com.babylon.certificatetransparency.CTInterceptorBuilder').includeHost.overload('java.lang.String').implementation = function(host) {
|
||||
send('[SSL Pinning Bypass] Bypassing Certificate Transparency check');
|
||||
return this.includeHost('nonexistent.domain');
|
||||
};
|
||||
} catch (err) {
|
||||
send('[SSL Pinning Bypass] babylon certificatetransparency.CTInterceptorBuilder not found');
|
||||
}
|
||||
try {
|
||||
Java.use("com.appmattus.certificatetransparency.internal.verifier.CertificateTransparencyInterceptor")["intercept"].implementation = function(a) {
|
||||
send("[SSL Pinning Bypass] Appmattus Certificate Transparency");
|
||||
return a.proceed(a.request());
|
||||
};
|
||||
} catch (err) {
|
||||
send("[SSL Pinning Bypass] Appmattus CertificateTransparencyInterceptor not found");
|
||||
}
|
||||
try{
|
||||
bypassOkHttp3CertificateTransparency();
|
||||
} catch (err) {
|
||||
send('[SSL Pinning Bypass] certificatetransparency.CTInterceptorBuilder not found');
|
||||
}
|
||||
|
||||
}, 0);
|
||||
|
||||
|
||||
function bypassOkHttp3CertificateTransparency() {
|
||||
// https://gist.github.com/m-rey/f2a235123908ca42395b6d3c5fe1128e
|
||||
var CertificateTransparencyInterceptor = Java.use('com.appmattus.certificatetransparency.internal.verifier.CertificateTransparencyInterceptor');
|
||||
var OkHttpClientBuilder = Java.use('okhttp3.OkHttpClient$Builder');
|
||||
|
||||
CertificateTransparencyInterceptor.intercept.implementation = function (chain) {
|
||||
var request = chain.request();
|
||||
var url = request.url();
|
||||
var host = url.host();
|
||||
|
||||
// Dynamically access the VerificationResult classes
|
||||
var VerificationResult = Java.use('com.appmattus.certificatetransparency.VerificationResult');
|
||||
var VerificationResultSuccessInsecureConnection = Java.use('com.appmattus.certificatetransparency.VerificationResult$Success$InsecureConnection');
|
||||
var VerificationResultFailureNoCertificates = Java.use('com.appmattus.certificatetransparency.VerificationResult$Failure$NoCertificates');
|
||||
|
||||
// Create instances of the desired VerificationResult classes
|
||||
var success = VerificationResultSuccessInsecureConnection.$new(host);
|
||||
var failureNoCertificates = VerificationResultFailureNoCertificates.$new();
|
||||
|
||||
// Bypass certificate transparency verification
|
||||
var certs = chain.connection().handshake().peerCertificates();
|
||||
if (certs.length === 0) {
|
||||
send('[SSL Pinning Bypass] Certificate transparency bypassed.');
|
||||
return failureNoCertificates;
|
||||
}
|
||||
|
||||
try {
|
||||
// Proceed with the original request
|
||||
return chain.proceed(request);
|
||||
} catch (e) {
|
||||
// Catch SSLPeerUnverifiedException and return intercepted response
|
||||
if (e.toString().includes('SSLPeerUnverifiedException')) {
|
||||
send('[SSL Pinning Bypass] Certificate transparency failed.');
|
||||
return failureNoCertificates;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
OkHttpClientBuilder.build.implementation = function () {
|
||||
// Intercept the OkHttpClient creation
|
||||
var client = this.build();
|
||||
return client;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
Java.perform(function () {
|
||||
send("Starting WebView configuration dump...");
|
||||
|
||||
const WebView = Java.use('android.webkit.WebView');
|
||||
|
||||
// Hook the first overload: loadUrl(String)
|
||||
WebView.loadUrl.overload('java.lang.String').implementation = function (url) {
|
||||
send("[+] WebView.loadUrl(String) called: " + url);
|
||||
|
||||
// Dump WebSettings after loading a URL
|
||||
dumpWebSettingsSafely(this);
|
||||
|
||||
// Call the original method
|
||||
this.loadUrl(url);
|
||||
};
|
||||
|
||||
// Hook the second overload: loadUrl(String, Map)
|
||||
WebView.loadUrl.overload('java.lang.String', 'java.util.Map').implementation = function (url, additionalHttpHeaders) {
|
||||
send("[+] WebView.loadUrl(String, Map) called: " + url);
|
||||
send(" Additional HTTP Headers: " + additionalHttpHeaders);
|
||||
|
||||
// Dump WebSettings after loading a URL
|
||||
dumpWebSettingsSafely(this);
|
||||
|
||||
// Call the original method
|
||||
this.loadUrl(url, additionalHttpHeaders);
|
||||
};
|
||||
|
||||
function dumpWebSettingsSafely(webView) {
|
||||
try {
|
||||
const webSettings = webView.getSettings();
|
||||
send("\n[+] Dumping WebSettings:");
|
||||
|
||||
// Security-sensitive settings
|
||||
send(" JavaScript Enabled: " + webSettings.getJavaScriptEnabled());
|
||||
send(" Allow File Access: " + webSettings.getAllowFileAccess());
|
||||
send(" Allow Content Access: " + webSettings.getAllowContentAccess());
|
||||
send(" Mixed Content Mode: " + webSettings.getMixedContentMode());
|
||||
send(" Safe Browsing Enabled: " + webSettings.getSafeBrowsingEnabled());
|
||||
send(" Dom Storage Enabled: " + webSettings.getDomStorageEnabled());
|
||||
send(" Allow Universal Access From File URLs: " + webSettings.getAllowUniversalAccessFromFileURLs());
|
||||
send(" Allow File Access From File URLs: " + webSettings.getAllowFileAccessFromFileURLs());
|
||||
// Caching and storage
|
||||
send(" Cache Mode: " + webSettings.getCacheMode());
|
||||
// User agent and other information
|
||||
send(" User Agent String: " + webSettings.getUserAgentString());
|
||||
} catch (err) {
|
||||
send("Error while dumping WebView configuration: " + err);
|
||||
}
|
||||
}
|
||||
|
||||
send("Hooks installed for WebView.");
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
try {
|
||||
var UnverifiedCertError = Java.use('javax.net.ssl.SSLPeerUnverifiedException');
|
||||
UnverifiedCertError.$init.implementation = function(str) {
|
||||
send('Unexpected SSLPeerUnverifiedException occurred');
|
||||
try {
|
||||
var stackTrace = Java.use('java.lang.Thread').currentThread().getStackTrace();
|
||||
var exceptionStackIndex = stackTrace.findIndex(stack => stack.getClassName() === "javax.net.ssl.SSLPeerUnverifiedException");
|
||||
var callingFunctionStack = stackTrace[exceptionStackIndex + 1];
|
||||
var className = callingFunctionStack.getClassName();
|
||||
var methodName = callingFunctionStack.getMethodName();
|
||||
var callingClass = Java.use(className);
|
||||
var callingMethod = callingClass[methodName];
|
||||
send('SSL exception caused: ' + className + '.' + methodName + '. Patch this method to bypass pinning.');
|
||||
if (callingMethod.implementation) {
|
||||
return;
|
||||
}
|
||||
} catch (e) {}
|
||||
return this.$init(str);
|
||||
};
|
||||
} catch (err) {}
|
||||
@@ -1,21 +1,93 @@
|
||||
// https://gist.github.com/bet4it/b62ac2d5bd45b8cb699905fa498baf5e
|
||||
Java.perform(function () {
|
||||
var act = Java.use("android.app.Activity");
|
||||
act.getIntent.overload().implementation = function () {
|
||||
var intent = this.getIntent()
|
||||
var cp = intent.getComponent()
|
||||
send("[Intent Dumper] Starting " + cp.getPackageName() + "/" + cp.getClassName())
|
||||
var ext = intent.getExtras();
|
||||
if (ext) {
|
||||
var keys = ext.keySet()
|
||||
var iterator = keys.iterator()
|
||||
while (iterator.hasNext()) {
|
||||
var k = iterator.next().toString()
|
||||
var v = ext.get(k)
|
||||
send("\t" + v.getClass().getName())
|
||||
send("\t" + k + ' : ' + v.toString())
|
||||
}
|
||||
var Activity = Java.use("android.app.Activity");
|
||||
|
||||
Activity.getIntent.overload().implementation = function () {
|
||||
var intent = this.getIntent();
|
||||
var component = intent.getComponent();
|
||||
|
||||
send("[Intent Dumper] Captured Intent for Activity:");
|
||||
|
||||
// Component (target package and class)
|
||||
if (component) {
|
||||
send(" Component:");
|
||||
send(" Package: " + component.getPackageName());
|
||||
send(" Class: " + component.getClassName());
|
||||
} else {
|
||||
send(" Component: None");
|
||||
}
|
||||
return intent;
|
||||
};
|
||||
})
|
||||
|
||||
// Action
|
||||
var action = intent.getAction();
|
||||
send(" Action: " + (action ? action : "None"));
|
||||
|
||||
// Data URI
|
||||
var dataUri = intent.getDataString();
|
||||
send(" Data URI: " + (dataUri ? dataUri : "None"));
|
||||
|
||||
// Flags
|
||||
var flags = intent.getFlags();
|
||||
send(" Flags: " + flags);
|
||||
|
||||
// Dumping extras in the Intent
|
||||
var extras = intent.getExtras();
|
||||
if (extras) {
|
||||
send(" Extras:");
|
||||
var iterator = extras.keySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
var key = iterator.next();
|
||||
var value = extras.get(key);
|
||||
if (value !== null) {
|
||||
send(" " + key + " (" + value.getClass().getName() + "): " + valueToString(value));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
send(" Extras: None");
|
||||
}
|
||||
|
||||
return intent;
|
||||
};
|
||||
|
||||
// Helper function to convert intent extras to a readable string
|
||||
function valueToString(value) {
|
||||
var valueType = value.getClass().getName();
|
||||
|
||||
if (valueType === "android.os.Bundle") {
|
||||
return bundleToString(Java.cast(value, Java.use("android.os.Bundle")));
|
||||
} else if (valueType === "java.lang.String") {
|
||||
return '"' + value + '"';
|
||||
} else if (valueType === "java.lang.Integer" || valueType === "java.lang.Float" || valueType === "java.lang.Boolean") {
|
||||
return value.toString();
|
||||
} else if (valueType === "java.util.ArrayList") {
|
||||
return arrayListToString(Java.cast(value, Java.use("java.util.ArrayList")));
|
||||
} else {
|
||||
send("Unsupported extra type for key. Type: " + valueType);
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
|
||||
// Function to handle nested Bundles
|
||||
function bundleToString(bundle) {
|
||||
var result = "{";
|
||||
var iterator = bundle.keySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
var key = iterator.next();
|
||||
var value = bundle.get(key);
|
||||
result += key + ": " + (value !== null ? valueToString(value) : "null") + ", ";
|
||||
}
|
||||
result = result.slice(0, -2); // Remove trailing comma and space
|
||||
result += "}";
|
||||
return result;
|
||||
}
|
||||
|
||||
// Function to handle ArrayLists (if any)
|
||||
function arrayListToString(arrayList) {
|
||||
var result = "[";
|
||||
for (var i = 0; i < arrayList.size(); i++) {
|
||||
var item = arrayList.get(i);
|
||||
result += valueToString(item) + ", ";
|
||||
}
|
||||
result = result.slice(0, -2); // Remove trailing comma and space
|
||||
result += "]";
|
||||
return result;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -720,69 +720,70 @@ function dynamicPatching() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
try {
|
||||
var UnverifiedCertError = Java.use('javax.net.ssl.SSLPeerUnverifiedException');
|
||||
UnverifiedCertError.$init.implementation = function(str) {
|
||||
console.log('[!] Unexpected SSLPeerUnverifiedException occurred, trying to patch it dynamically...!');
|
||||
try {
|
||||
var stackTrace = Java.use('java.lang.Thread').currentThread().getStackTrace();
|
||||
var exceptionStackIndex = stackTrace.findIndex(stack => stack.getClassName() === "javax.net.ssl.SSLPeerUnverifiedException");
|
||||
var callingFunctionStack = stackTrace[exceptionStackIndex + 1];
|
||||
var className = callingFunctionStack.getClassName();
|
||||
var methodName = callingFunctionStack.getMethodName();
|
||||
var callingClass = Java.use(className);
|
||||
var callingMethod = callingClass[methodName];
|
||||
console.log('[!] Attempting to bypass uncommon SSL Pinning method on: ' + className + '.' + methodName + '!');
|
||||
if (callingMethod.implementation) {
|
||||
return;
|
||||
}
|
||||
var returnTypeName = callingMethod.returnType.type;
|
||||
callingMethod.implementation = function() {
|
||||
rudimentaryFix(returnTypeName);
|
||||
};
|
||||
} catch (e) {
|
||||
if (String(e).includes(".overload")) {
|
||||
var splittedList = String(e).split(".overload");
|
||||
for (let i = 2; i < splittedList.length; i++) {
|
||||
var extractedOverload = splittedList[i].trim().split("(")[1].slice(0, -1).replaceAll("'", "");
|
||||
if (extractedOverload.includes(",")) {
|
||||
var argList = extractedOverload.split(", ");
|
||||
console.log('[!] Attempting overload of ' + className + '.' + methodName + ' with arguments: ' + extractedOverload + '!');
|
||||
if (argList.length == 2) {
|
||||
callingMethod.overload(argList[0], argList[1]).implementation = function(a, b) {
|
||||
rudimentaryFix(returnTypeName);
|
||||
}
|
||||
} else if (argNum == 3) {
|
||||
callingMethod.overload(argList[0], argList[1], argList[2]).implementation = function(a, b, c) {
|
||||
rudimentaryFix(returnTypeName);
|
||||
}
|
||||
} else if (argNum == 4) {
|
||||
callingMethod.overload(argList[0], argList[1], argList[2], argList[3]).implementation = function(a, b, c, d) {
|
||||
rudimentaryFix(returnTypeName);
|
||||
}
|
||||
} else if (argNum == 5) {
|
||||
callingMethod.overload(argList[0], argList[1], argList[2], argList[3], argList[4]).implementation = function(a, b, c, d, e) {
|
||||
rudimentaryFix(returnTypeName);
|
||||
}
|
||||
} else if (argNum == 6) {
|
||||
callingMethod.overload(argList[0], argList[1], argList[2], argList[3], argList[4], argList[5]).implementation = function(a, b, c, d, e, f) {
|
||||
rudimentaryFix(returnTypeName);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
callingMethod.overload(extractedOverload).implementation = function(a) {
|
||||
rudimentaryFix(returnTypeName);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log('[-] Failed to dynamically patch SSLPeerUnverifiedException ' + e + '!');
|
||||
}
|
||||
}
|
||||
return this.$init(str);
|
||||
};
|
||||
} catch (err) {}
|
||||
// try {
|
||||
// var UnverifiedCertError = Java.use('javax.net.ssl.SSLPeerUnverifiedException');
|
||||
// UnverifiedCertError.$init.implementation = function(str) {
|
||||
// console.log('[!] Unexpected SSLPeerUnverifiedException occurred, trying to patch it dynamically...!');
|
||||
// try {
|
||||
// var stackTrace = Java.use('java.lang.Thread').currentThread().getStackTrace();
|
||||
// var exceptionStackIndex = stackTrace.findIndex(stack => stack.getClassName() === "javax.net.ssl.SSLPeerUnverifiedException");
|
||||
// var callingFunctionStack = stackTrace[exceptionStackIndex + 1];
|
||||
// var className = callingFunctionStack.getClassName();
|
||||
// var methodName = callingFunctionStack.getMethodName();
|
||||
// var callingClass = Java.use(className);
|
||||
// var callingMethod = callingClass[methodName];
|
||||
// console.log('[!] Attempting to bypass uncommon SSL Pinning method on: ' + className + '.' + methodName + '!');
|
||||
// if (callingMethod.implementation) {
|
||||
// return;
|
||||
// }
|
||||
// var returnTypeName = callingMethod.returnType.type;
|
||||
// callingMethod.implementation = function() {
|
||||
// rudimentaryFix(returnTypeName);
|
||||
// };
|
||||
// } catch (e) {
|
||||
// if (String(e).includes(".overload")) {
|
||||
// var splittedList = String(e).split(".overload");
|
||||
// for (let i = 2; i < splittedList.length; i++) {
|
||||
// var extractedOverload = splittedList[i].trim().split("(")[1].slice(0, -1).replaceAll("'", "");
|
||||
// if (extractedOverload.includes(",")) {
|
||||
// var argList = extractedOverload.split(", ");
|
||||
// console.log('[!] Attempting overload of ' + className + '.' + methodName + ' with arguments: ' + extractedOverload + '!');
|
||||
// if (argList.length == 2) {
|
||||
// callingMethod.overload(argList[0], argList[1]).implementation = function(a, b) {
|
||||
// rudimentaryFix(returnTypeName);
|
||||
// }
|
||||
// } else if (argNum == 3) {
|
||||
// callingMethod.overload(argList[0], argList[1], argList[2]).implementation = function(a, b, c) {
|
||||
// rudimentaryFix(returnTypeName);
|
||||
// }
|
||||
// } else if (argNum == 4) {
|
||||
// callingMethod.overload(argList[0], argList[1], argList[2], argList[3]).implementation = function(a, b, c, d) {
|
||||
// rudimentaryFix(returnTypeName);
|
||||
// }
|
||||
// } else if (argNum == 5) {
|
||||
// callingMethod.overload(argList[0], argList[1], argList[2], argList[3], argList[4]).implementation = function(a, b, c, d, e) {
|
||||
// rudimentaryFix(returnTypeName);
|
||||
// }
|
||||
// } else if (argNum == 6) {
|
||||
// callingMethod.overload(argList[0], argList[1], argList[2], argList[3], argList[4], argList[5]).implementation = function(a, b, c, d, e, f) {
|
||||
// rudimentaryFix(returnTypeName);
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// callingMethod.overload(extractedOverload).implementation = function(a) {
|
||||
// rudimentaryFix(returnTypeName);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// console.log('[-] Failed to dynamically patch SSLPeerUnverifiedException ' + e + '!');
|
||||
// }
|
||||
// }
|
||||
// return this.$init(str);
|
||||
// };
|
||||
// } catch (err) {}
|
||||
}
|
||||
|
||||
setTimeout(function() {
|
||||
Java.perform(function() {
|
||||
var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
Java.perform(function () {
|
||||
// Hook the startActivity method in the Activity class
|
||||
var Activity = Java.use("android.app.Activity");
|
||||
|
||||
Activity.startActivity.overload("android.content.Intent").implementation = function (intent) {
|
||||
send("Intercepted startActivity with Intent:");
|
||||
|
||||
// Dump the Intent details
|
||||
dumpIntent(intent);
|
||||
|
||||
// Call the original startActivity method to ensure normal behavior
|
||||
this.startActivity(intent);
|
||||
};
|
||||
|
||||
// Function to dump intent details
|
||||
function dumpIntent(intent) {
|
||||
// Action
|
||||
var action = intent.getAction();
|
||||
send(" Action: " + (action ? action : "None"));
|
||||
|
||||
// Data URI
|
||||
var dataUri = intent.getDataString();
|
||||
send(" Data URI: " + (dataUri ? dataUri : "None"));
|
||||
|
||||
// Component (target package and class)
|
||||
var component = intent.getComponent();
|
||||
if (component) {
|
||||
send(" Component:");
|
||||
send(" Package: " + component.getPackageName());
|
||||
send(" Class: " + component.getClassName());
|
||||
} else {
|
||||
send(" Component: None");
|
||||
}
|
||||
|
||||
// Flags
|
||||
var flags = intent.getFlags();
|
||||
send(" Flags: " + flags);
|
||||
|
||||
// Extras
|
||||
var extras = intent.getExtras();
|
||||
if (extras) {
|
||||
send(" Extras:");
|
||||
var iterator = extras.keySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
var key = iterator.next();
|
||||
var value = extras.get(key);
|
||||
if (value !== null) {
|
||||
send(" " + key + ": " + valueToString(value));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
send(" Extras: None");
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to convert intent extras to string for logging
|
||||
function valueToString(value) {
|
||||
// Check if the value is a Bundle and handle it accordingly
|
||||
if (value.getClass().getName() === "android.os.Bundle") {
|
||||
return bundleToString(Java.cast(value, Java.use("android.os.Bundle")));
|
||||
}
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
// Function to handle nested Bundles (if any)
|
||||
function bundleToString(bundle) {
|
||||
var result = "{";
|
||||
var iterator = bundle.keySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
var key = iterator.next();
|
||||
var value = bundle.get(key);
|
||||
result += key + ": " + (value !== null ? value.toString() : "null") + ", ";
|
||||
}
|
||||
result = result.slice(0, -2); // Remove trailing comma and space
|
||||
result += "}";
|
||||
return result;
|
||||
}
|
||||
});
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
Java.perform(function () {
|
||||
send("Starting JavaScript bridge enumeration...");
|
||||
|
||||
// Hook the WebView class
|
||||
const WebView = Java.use('android.webkit.WebView');
|
||||
|
||||
// Hook the addJavascriptInterface method
|
||||
WebView.addJavascriptInterface.overload('java.lang.Object', 'java.lang.String').implementation = function (obj, interfaceName) {
|
||||
send("[+] addJavascriptInterface called");
|
||||
send(" Interface Name: " + interfaceName);
|
||||
send(" Methods exposed:");
|
||||
|
||||
// Reflect on the object to enumerate methods
|
||||
const objectClass = obj.getClass();
|
||||
const methods = objectClass.getDeclaredMethods();
|
||||
for (let i = 0; i < methods.length; i++) {
|
||||
send(" - " + methods[i].getName());
|
||||
}
|
||||
|
||||
// Call the original method
|
||||
this.addJavascriptInterface(obj, interfaceName);
|
||||
};
|
||||
|
||||
send("Hook installed for WebView.addJavascriptInterface.");
|
||||
});
|
||||
@@ -3,6 +3,7 @@
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import errno
|
||||
import json
|
||||
import tarfile
|
||||
import shutil
|
||||
@@ -54,18 +55,35 @@ def safe_paths(tar_meta):
|
||||
yield fh
|
||||
|
||||
|
||||
def onerror(func, path, exc_info):
|
||||
_, exc, _ = exc_info
|
||||
if exc.errno == errno.EACCES: # Permission error
|
||||
try:
|
||||
os.chmod(path, 0o777)
|
||||
func(path)
|
||||
except Exception:
|
||||
pass
|
||||
elif exc.errno == errno.ENOTEMPTY: # Directory not empty
|
||||
try:
|
||||
func(path)
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
|
||||
|
||||
def untar_files(tar_loc, untar_dir):
|
||||
"""Untar files."""
|
||||
logger.info('Extracting Tar files')
|
||||
# Extract Device Data
|
||||
if not tar_loc.exists():
|
||||
return False
|
||||
if untar_dir.exists():
|
||||
# fix for permission errors
|
||||
shutil.rmtree(untar_dir)
|
||||
else:
|
||||
os.makedirs(untar_dir)
|
||||
try:
|
||||
# Extract Device Data
|
||||
if not tar_loc.exists():
|
||||
return False
|
||||
if untar_dir.exists():
|
||||
# fix for permission errors
|
||||
shutil.rmtree(untar_dir, onerror=onerror)
|
||||
else:
|
||||
os.makedirs(untar_dir)
|
||||
with tarfile.open(tar_loc.as_posix(), errorlevel=1) as tar:
|
||||
|
||||
def is_within_directory(directory, target):
|
||||
|
||||
@@ -29,6 +29,7 @@ CORELLIUM_API_KEY = getattr(
|
||||
settings,
|
||||
'CORELLIUM_API_KEY', '')
|
||||
logger = logging.getLogger(__name__)
|
||||
TIMEOUT = 20
|
||||
|
||||
|
||||
class CorelliumInit:
|
||||
@@ -54,7 +55,7 @@ class CorelliumAPI(CorelliumInit):
|
||||
"""Check API Availability."""
|
||||
try:
|
||||
r = requests.get(f'{self.api}/ready',
|
||||
timeout=5,
|
||||
timeout=TIMEOUT,
|
||||
proxies=self.proxies,
|
||||
verify=self.verify)
|
||||
if r.status_code in SUCCESS_RESP:
|
||||
@@ -74,7 +75,7 @@ class CorelliumAPI(CorelliumInit):
|
||||
return False
|
||||
r = requests.get(
|
||||
f'{self.api}/projects',
|
||||
timeout=5,
|
||||
timeout=TIMEOUT,
|
||||
headers=self.headers,
|
||||
proxies=self.proxies,
|
||||
verify=self.verify)
|
||||
@@ -91,7 +92,7 @@ class CorelliumAPI(CorelliumInit):
|
||||
ids = []
|
||||
r = requests.get(
|
||||
f'{self.api}/projects?ids_only=true',
|
||||
timeout=5,
|
||||
timeout=TIMEOUT,
|
||||
headers=self.headers,
|
||||
proxies=self.proxies,
|
||||
verify=self.verify)
|
||||
@@ -107,7 +108,7 @@ class CorelliumAPI(CorelliumInit):
|
||||
"""Get SSH public keys associated with a project."""
|
||||
r = requests.get(
|
||||
f'{self.api}/projects/{self.project_id}/keys',
|
||||
timeout=5,
|
||||
timeout=TIMEOUT,
|
||||
headers=self.headers,
|
||||
proxies=self.proxies,
|
||||
verify=self.verify)
|
||||
@@ -128,7 +129,7 @@ class CorelliumAPI(CorelliumInit):
|
||||
}
|
||||
r = requests.post(
|
||||
f'{self.api}/projects/{self.project_id}/keys',
|
||||
timeout=5,
|
||||
timeout=TIMEOUT,
|
||||
headers=self.headers,
|
||||
json=data,
|
||||
proxies=self.proxies,
|
||||
@@ -154,7 +155,7 @@ class CorelliumAPI(CorelliumInit):
|
||||
instances = []
|
||||
r = requests.get(
|
||||
f'{self.api}/instances',
|
||||
timeout=5,
|
||||
timeout=TIMEOUT,
|
||||
headers=self.headers,
|
||||
proxies=self.proxies,
|
||||
verify=self.verify)
|
||||
@@ -174,7 +175,7 @@ class CorelliumAPI(CorelliumInit):
|
||||
}
|
||||
r = requests.post(
|
||||
f'{self.api}/instances',
|
||||
timeout=5,
|
||||
timeout=TIMEOUT,
|
||||
headers=self.headers,
|
||||
json=data,
|
||||
proxies=self.proxies,
|
||||
@@ -189,7 +190,7 @@ class CorelliumModelsAPI(CorelliumInit):
|
||||
def get_models(self):
|
||||
r = requests.get(
|
||||
f'{self.api}/models',
|
||||
timeout=5,
|
||||
timeout=TIMEOUT,
|
||||
headers=self.headers,
|
||||
proxies=self.proxies,
|
||||
verify=self.verify)
|
||||
@@ -210,7 +211,7 @@ class CorelliumModelsAPI(CorelliumInit):
|
||||
return False
|
||||
r = requests.get(
|
||||
f'{self.api}/models/{model}/software',
|
||||
timeout=5,
|
||||
timeout=TIMEOUT,
|
||||
headers=self.headers,
|
||||
proxies=self.proxies,
|
||||
verify=self.verify)
|
||||
@@ -232,7 +233,7 @@ class CorelliumInstanceAPI(CorelliumInit):
|
||||
data = {'paused': False}
|
||||
r = requests.post(
|
||||
f'{self.api}/instances/{self.instance_id}/start',
|
||||
timeout=5,
|
||||
timeout=TIMEOUT,
|
||||
headers=self.headers,
|
||||
json=data,
|
||||
proxies=self.proxies,
|
||||
@@ -248,7 +249,7 @@ class CorelliumInstanceAPI(CorelliumInit):
|
||||
data = {'soft': True}
|
||||
r = requests.post(
|
||||
f'{self.api}/instances/{self.instance_id}/stop',
|
||||
timeout=5,
|
||||
timeout=TIMEOUT,
|
||||
headers=self.headers,
|
||||
json=data,
|
||||
proxies=self.proxies,
|
||||
@@ -263,7 +264,7 @@ class CorelliumInstanceAPI(CorelliumInit):
|
||||
"""Unpause instance."""
|
||||
r = requests.post(
|
||||
f'{self.api}/instances/{self.instance_id}/unpause',
|
||||
timeout=5,
|
||||
timeout=TIMEOUT,
|
||||
headers=self.headers,
|
||||
proxies=self.proxies,
|
||||
verify=self.verify)
|
||||
@@ -277,7 +278,7 @@ class CorelliumInstanceAPI(CorelliumInit):
|
||||
"""Reboot instance."""
|
||||
r = requests.post(
|
||||
f'{self.api}/instances/{self.instance_id}/reboot',
|
||||
timeout=5,
|
||||
timeout=TIMEOUT,
|
||||
headers=self.headers,
|
||||
proxies=self.proxies,
|
||||
verify=self.verify)
|
||||
@@ -304,7 +305,7 @@ class CorelliumInstanceAPI(CorelliumInit):
|
||||
"""Check instance status."""
|
||||
r = requests.get(
|
||||
f'{self.api}/instances/{self.instance_id}',
|
||||
timeout=5,
|
||||
timeout=TIMEOUT,
|
||||
headers=self.headers,
|
||||
proxies=self.proxies,
|
||||
verify=self.verify)
|
||||
@@ -320,7 +321,7 @@ class CorelliumInstanceAPI(CorelliumInit):
|
||||
r = requests.get(
|
||||
(f'{self.api}/instances/{self.instance_id}'
|
||||
'/screenshot.png?scale=1'),
|
||||
timeout=5,
|
||||
timeout=TIMEOUT,
|
||||
headers=self.headers,
|
||||
stream=True,
|
||||
proxies=self.proxies,
|
||||
@@ -337,7 +338,7 @@ class CorelliumInstanceAPI(CorelliumInit):
|
||||
"""Start network capture."""
|
||||
r = requests.post(
|
||||
f'{self.api}/instances/{self.instance_id}/sslsplit/enable',
|
||||
timeout=5,
|
||||
timeout=TIMEOUT,
|
||||
headers=self.headers,
|
||||
proxies=self.proxies,
|
||||
verify=self.verify)
|
||||
@@ -354,7 +355,7 @@ class CorelliumInstanceAPI(CorelliumInit):
|
||||
"""Stop network capture."""
|
||||
r = requests.post(
|
||||
f'{self.api}/instances/{self.instance_id}/sslsplit/disable',
|
||||
timeout=5,
|
||||
timeout=TIMEOUT,
|
||||
headers=self.headers,
|
||||
proxies=self.proxies,
|
||||
verify=self.verify)
|
||||
@@ -368,7 +369,7 @@ class CorelliumInstanceAPI(CorelliumInit):
|
||||
"""Download network capture."""
|
||||
r = requests.get(
|
||||
f'{self.api}/instances/{self.instance_id}/networkMonitor.pcap',
|
||||
timeout=5,
|
||||
timeout=TIMEOUT,
|
||||
headers=self.headers,
|
||||
proxies=self.proxies,
|
||||
verify=self.verify)
|
||||
@@ -382,7 +383,7 @@ class CorelliumInstanceAPI(CorelliumInit):
|
||||
"""Get Console Log."""
|
||||
r = requests.get(
|
||||
f'{self.api}/instances/{self.instance_id}/consoleLog',
|
||||
timeout=5,
|
||||
timeout=TIMEOUT,
|
||||
headers=self.headers,
|
||||
proxies=self.proxies,
|
||||
verify=self.verify)
|
||||
@@ -396,7 +397,7 @@ class CorelliumInstanceAPI(CorelliumInit):
|
||||
"""Get SSH connection string."""
|
||||
r = requests.get(
|
||||
f'{self.api}/instances/{self.instance_id}/quickConnectCommand',
|
||||
timeout=5,
|
||||
timeout=TIMEOUT,
|
||||
headers=self.headers,
|
||||
proxies=self.proxies,
|
||||
verify=self.verify)
|
||||
@@ -484,7 +485,7 @@ class CorelliumInstanceAPI(CorelliumInit):
|
||||
{'buttons': [], 'wait': 100}]
|
||||
r = requests.post(
|
||||
f'{self.api}/instances/{self.instance_id}/input',
|
||||
timeout=5,
|
||||
timeout=TIMEOUT,
|
||||
headers=self.headers,
|
||||
json=data,
|
||||
proxies=self.proxies,
|
||||
@@ -506,7 +507,7 @@ class CorelliumAgentAPI(CorelliumInit):
|
||||
"""Agent ready."""
|
||||
r = requests.get(
|
||||
f'{self.api}/instances/{self.instance_id}/agent/v1/app/ready',
|
||||
timeout=5,
|
||||
timeout=TIMEOUT,
|
||||
headers=self.headers,
|
||||
proxies=self.proxies,
|
||||
verify=self.verify)
|
||||
@@ -522,7 +523,7 @@ class CorelliumAgentAPI(CorelliumInit):
|
||||
"""Unlock iOS device."""
|
||||
r = requests.post(
|
||||
f'{self.api}/instances/{self.instance_id}/agent/v1/system/unlock',
|
||||
timeout=5,
|
||||
timeout=TIMEOUT,
|
||||
headers=self.headers,
|
||||
proxies=self.proxies,
|
||||
verify=self.verify)
|
||||
@@ -556,7 +557,7 @@ class CorelliumAgentAPI(CorelliumInit):
|
||||
"""Install IPA."""
|
||||
r = requests.post(
|
||||
f'{self.api}/instances/{self.instance_id}/agent/v1/app/install',
|
||||
timeout=5,
|
||||
timeout=TIMEOUT,
|
||||
headers=self.headers,
|
||||
json={'path': '/tmp/app.ipa'},
|
||||
proxies=self.proxies,
|
||||
@@ -572,7 +573,7 @@ class CorelliumAgentAPI(CorelliumInit):
|
||||
r = requests.post(
|
||||
(f'{self.api}/instances/{self.instance_id}'
|
||||
f'/agent/v1/app/apps/{bundle_id}/run'),
|
||||
timeout=5,
|
||||
timeout=TIMEOUT,
|
||||
headers=self.headers,
|
||||
proxies=self.proxies,
|
||||
verify=self.verify)
|
||||
@@ -587,7 +588,7 @@ class CorelliumAgentAPI(CorelliumInit):
|
||||
r = requests.post(
|
||||
(f'{self.api}/instances/{self.instance_id}'
|
||||
f'/agent/v1/app/apps/{bundle_id}/kill'),
|
||||
timeout=5,
|
||||
timeout=TIMEOUT,
|
||||
headers=self.headers,
|
||||
proxies=self.proxies,
|
||||
verify=self.verify)
|
||||
@@ -602,7 +603,7 @@ class CorelliumAgentAPI(CorelliumInit):
|
||||
r = requests.post(
|
||||
(f'{self.api}/instances/{self.instance_id}'
|
||||
f'/agent/v1/app/apps/{bundle_id}/uninstall'),
|
||||
timeout=5,
|
||||
timeout=TIMEOUT,
|
||||
headers=self.headers,
|
||||
proxies=self.proxies,
|
||||
verify=self.verify)
|
||||
@@ -616,7 +617,7 @@ class CorelliumAgentAPI(CorelliumInit):
|
||||
"""List all apps installed."""
|
||||
r = requests.get(
|
||||
f'{self.api}/instances/{self.instance_id}/agent/v1/app/apps',
|
||||
timeout=5,
|
||||
timeout=TIMEOUT,
|
||||
headers=self.headers,
|
||||
proxies=self.proxies,
|
||||
verify=self.verify)
|
||||
@@ -631,7 +632,7 @@ class CorelliumAgentAPI(CorelliumInit):
|
||||
r = requests.get(
|
||||
(f'{self.api}/instances/{self.instance_id}'
|
||||
f'/agent/v1/app/icons?{bundleids}'),
|
||||
timeout=5,
|
||||
timeout=TIMEOUT,
|
||||
headers=self.headers,
|
||||
proxies=self.proxies,
|
||||
verify=self.verify)
|
||||
|
||||
@@ -7,37 +7,24 @@ from django.conf import settings
|
||||
|
||||
from mobsf.MobSF.utils import (
|
||||
append_scan_status,
|
||||
get_android_src_dir,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.sast_engine import (
|
||||
scan,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def analyze(checksum, app_dir, typ):
|
||||
def analyze(checksum, sast, data):
|
||||
"""Perform behaviour analysis."""
|
||||
try:
|
||||
root = Path(settings.BASE_DIR) / 'MalwareAnalyzer' / 'views'
|
||||
rules = root / 'android' / 'rules' / 'behaviour_rules.yaml'
|
||||
app_dir = Path(app_dir)
|
||||
src = get_android_src_dir(app_dir, typ)
|
||||
skp = settings.SKIP_CLASS_PATH
|
||||
msg = 'Android Behaviour Analysis Started'
|
||||
logger.info(msg)
|
||||
append_scan_status(checksum, msg)
|
||||
# Behaviour Analysis
|
||||
findings = scan(
|
||||
checksum,
|
||||
rules.as_posix(),
|
||||
{'.java', '.kt'},
|
||||
[src.as_posix() + '/'],
|
||||
skp)
|
||||
behaviour_finds = sast.run_rules(data, rules.as_posix())
|
||||
msg = 'Android Behaviour Analysis Completed'
|
||||
logger.info(msg)
|
||||
append_scan_status(checksum, msg)
|
||||
return findings
|
||||
return behaviour_finds
|
||||
except Exception as exp:
|
||||
msg = 'Failed to perform behaviour analysis'
|
||||
logger.exception(msg)
|
||||
|
||||
+6
-6
@@ -18,13 +18,13 @@ from mobsf.install.windows.setup import windows_config_local
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
VERSION = '4.1.9'
|
||||
VERSION = '4.2.0'
|
||||
BANNER = r"""
|
||||
__ __ _ ____ _____ _ _ _
|
||||
| \/ | ___ | |__/ ___|| ___|_ _| || | / |
|
||||
| |\/| |/ _ \| '_ \___ \| |_ \ \ / / || |_ | |
|
||||
| | | | (_) | |_) |__) | _| \ V /|__ _|| |
|
||||
|_| |_|\___/|_.__/____/|_| \_/ |_|(_)_|
|
||||
__ __ _ ____ _____ _ _ ____
|
||||
| \/ | ___ | |__/ ___|| ___|_ _| || | |___ \
|
||||
| |\/| |/ _ \| '_ \___ \| |_ \ \ / / || |_ __) |
|
||||
| | | | (_) | |_) |__) | _| \ V /|__ _| / __/
|
||||
|_| |_|\___/|_.__/____/|_| \_/ |_|(_)_____|
|
||||
""" # noqa: W291
|
||||
# ASCII Font: Standard
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from django_q.models import (
|
||||
OrmQ,
|
||||
Task,
|
||||
)
|
||||
|
||||
from mobsf.StaticAnalyzer.models import EnqueuedTask
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Deletes all tasks in Django Q' # noqa: A003
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
Task.objects.all().delete()
|
||||
OrmQ.objects.all().delete()
|
||||
EnqueuedTask.objects.all().delete()
|
||||
self.stdout.write(self.style.SUCCESS('Successfully deleted all Django Q tasks'))
|
||||
@@ -2,6 +2,7 @@
|
||||
import subprocess
|
||||
import functools
|
||||
import logging
|
||||
import re
|
||||
import sys
|
||||
from shutil import which
|
||||
from pathlib import Path
|
||||
@@ -209,3 +210,15 @@ def sanitize_redirect(url):
|
||||
elif url.startswith('/'):
|
||||
return url
|
||||
return root
|
||||
|
||||
|
||||
def sanitize_filename(filename):
|
||||
"""Sanitize Filename."""
|
||||
# Remove any characters
|
||||
# that are not alphanumeric, hyphens, underscores, or dots
|
||||
safe_filename = re.sub(r'[^a-zA-Z0-9._-]', '_', filename)
|
||||
# Merge multiple underscores into one
|
||||
safe_filename = re.sub(r'__+', '_', safe_filename)
|
||||
# Remove leading and trailing underscores
|
||||
safe_filename = safe_filename.strip('_')
|
||||
return safe_filename
|
||||
|
||||
@@ -180,6 +180,7 @@ ALLOWED_HOSTS = ['127.0.0.1', 'mobsf', '*']
|
||||
# Application definition
|
||||
INSTALLED_APPS = (
|
||||
# 'django.contrib.admin',
|
||||
'django_q',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
@@ -307,6 +308,11 @@ LOGGING = {
|
||||
'level': 'DEBUG',
|
||||
'propagate': True,
|
||||
},
|
||||
'django_q': {
|
||||
'handlers': ['console', 'logfile'],
|
||||
'level': 'DEBUG',
|
||||
'propagate': True,
|
||||
},
|
||||
'django.db.backends': {
|
||||
'handlers': ['console', 'logfile'],
|
||||
# DEBUG will log all queries, so change it to WARNING.
|
||||
@@ -335,6 +341,20 @@ LOGGING = {
|
||||
},
|
||||
},
|
||||
}
|
||||
Q_CLUSTER = {
|
||||
'name': 'scan_queue',
|
||||
'workers': int(os.getenv('MOBSF_ASYNC_WORKERS', 3)),
|
||||
'recycle': 10,
|
||||
'timeout': 3600,
|
||||
'retry': 3700,
|
||||
'compress': True,
|
||||
'label': 'scan_queue',
|
||||
'orm': 'default',
|
||||
'max_attempts': 2,
|
||||
}
|
||||
QUEUE_MAX_SIZE = 100
|
||||
ASYNC_ANALYSIS = bool(os.getenv('MOBSF_ASYNC_ANALYSIS', '0') == '1')
|
||||
MULTIPROCESSING = os.getenv('MOBSF_MULTIPROCESSING')
|
||||
JADX_TIMEOUT = int(os.getenv('MOBSF_JADX_TIMEOUT', 1000))
|
||||
SAST_TIMEOUT = int(os.getenv('MOBSF_SAST_TIMEOUT', 1000))
|
||||
BINARY_ANALYSIS_TIMEOUT = int(os.getenv('MOBSF_BINARY_ANALYSIS_TIMEOUT', 600))
|
||||
|
||||
+4
-1
@@ -34,6 +34,7 @@ from mobsf.MobSF.views.api import api_ios_dynamic_analysis as api_idz
|
||||
from mobsf.StaticAnalyzer import tests
|
||||
from mobsf.StaticAnalyzer.views.common import (
|
||||
appsec,
|
||||
async_task,
|
||||
pdf,
|
||||
shared_func,
|
||||
suppression,
|
||||
@@ -204,7 +205,9 @@ if settings.API_ONLY == '0':
|
||||
re_path(r'^error/$', home.error, name='error'),
|
||||
re_path(r'^zip_format/$', home.zip_format),
|
||||
re_path(r'^dynamic_analysis/$', home.dynamic_analysis, name='dynamic'),
|
||||
|
||||
re_path(r'^tasks$',
|
||||
async_task.list_tasks,
|
||||
name='list_tasks'),
|
||||
# Static Analysis
|
||||
# Android
|
||||
re_path(fr'^static_analyzer/{checksum_regex}/$',
|
||||
|
||||
+27
-16
@@ -22,6 +22,10 @@ import unicodedata
|
||||
import threading
|
||||
from urllib.parse import urlparse
|
||||
from pathlib import Path
|
||||
from concurrent.futures import (
|
||||
ThreadPoolExecutor,
|
||||
TimeoutError as ThreadPoolTimeoutError,
|
||||
)
|
||||
|
||||
from packaging.version import Version
|
||||
|
||||
@@ -948,21 +952,28 @@ class TaskTimeoutError(Exception):
|
||||
|
||||
|
||||
def run_with_timeout(func, limit, *args, **kwargs):
|
||||
def run_func(result, *args, **kwargs):
|
||||
result.append(func(*args, **kwargs))
|
||||
with ThreadPoolExecutor(max_workers=1) as executor:
|
||||
future = executor.submit(func, *args, **kwargs)
|
||||
try:
|
||||
return future.result(timeout=limit)
|
||||
except ThreadPoolTimeoutError:
|
||||
msg = f'function <{func.__name__}> timed out after {limit} seconds'
|
||||
raise TaskTimeoutError(msg)
|
||||
|
||||
result = []
|
||||
thread = threading.Thread(
|
||||
target=run_func,
|
||||
args=(result, *args),
|
||||
kwargs=kwargs)
|
||||
thread.start()
|
||||
thread.join(limit)
|
||||
|
||||
if thread.is_alive():
|
||||
msg = (f'function <{func.__name__}> '
|
||||
f'timed out after {limit} seconds')
|
||||
raise TaskTimeoutError(msg)
|
||||
if result:
|
||||
return result[0]
|
||||
return None
|
||||
def set_permissions(path):
|
||||
base_path = Path(path)
|
||||
# Read/Write for directories without execute
|
||||
perm_dir = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
|
||||
# Read/Write for files
|
||||
perm_file = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
|
||||
|
||||
# Set permissions for directories and files
|
||||
for item in base_path.rglob('*'):
|
||||
try:
|
||||
if item.is_dir():
|
||||
item.chmod(perm_dir)
|
||||
elif item.is_file():
|
||||
item.chmod(perm_file)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -33,6 +33,7 @@ from mobsf.MobSF.utils import (
|
||||
python_dict,
|
||||
)
|
||||
from mobsf.MobSF.init import api_key
|
||||
from mobsf.MobSF.security import sanitize_filename
|
||||
from mobsf.MobSF.views.helpers import FileType
|
||||
from mobsf.MobSF.views.scanning import Scanning
|
||||
from mobsf.MobSF.views.apk_downloader import apk_download
|
||||
@@ -430,7 +431,7 @@ def download_binary(request, checksum, api=False):
|
||||
status=HTTP_STATUS_404)
|
||||
return file_download(
|
||||
dwd_file,
|
||||
filename,
|
||||
sanitize_filename(robj.FILE_NAME),
|
||||
allowed_exts[file_ext])
|
||||
except Exception:
|
||||
logger.exception('Download Binary Failed')
|
||||
|
||||
@@ -2,6 +2,7 @@ from datetime import datetime
|
||||
from enum import Enum
|
||||
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
class DjangoPermissions(Enum):
|
||||
@@ -167,3 +168,16 @@ class SuppressFindings(models.Model):
|
||||
SUPPRESS_RULE_ID = models.TextField(default=[])
|
||||
SUPPRESS_FILES = models.TextField(default={})
|
||||
SUPPRESS_TYPE = models.TextField(default='')
|
||||
|
||||
|
||||
class EnqueuedTask(models.Model):
|
||||
task_id = models.CharField(max_length=255)
|
||||
checksum = models.CharField(max_length=255)
|
||||
file_name = models.CharField(max_length=255)
|
||||
created_at = models.DateTimeField(default=timezone.now)
|
||||
status = models.CharField(max_length=50, default='Enqueued')
|
||||
completed_at = models.DateTimeField(null=True)
|
||||
app_name = models.CharField(max_length=255, default='')
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.name} ({self.status})'
|
||||
|
||||
@@ -0,0 +1,518 @@
|
||||
|
||||
"""Android APK and Source Analysis."""
|
||||
import logging
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
import mobsf.MalwareAnalyzer.views.Trackers as Trackers
|
||||
import mobsf.MalwareAnalyzer.views.VirusTotal as VirusTotal
|
||||
from mobsf.MalwareAnalyzer.views.android import (
|
||||
apkid,
|
||||
permissions,
|
||||
)
|
||||
from mobsf.MalwareAnalyzer.views.MalwareDomainCheck import MalwareDomainCheck
|
||||
|
||||
from django.conf import settings
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import render
|
||||
|
||||
from mobsf.MobSF.utils import (
|
||||
append_scan_status,
|
||||
file_size,
|
||||
print_n_send_error_response,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.models import (
|
||||
StaticAnalyzerAndroid,
|
||||
StaticAnalyzerIOS,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.common.binary.lib_analysis import (
|
||||
library_analysis,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.android.app import (
|
||||
get_app_name,
|
||||
parse_apk,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.android.cert_analysis import (
|
||||
cert_info,
|
||||
get_hardcoded_cert_keystore,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.android.code_analysis import code_analysis
|
||||
from mobsf.StaticAnalyzer.views.android.converter import (
|
||||
apk_2_java,
|
||||
dex_2_smali,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.android.db_interaction import (
|
||||
get_context_from_db_entry,
|
||||
save_get_ctx,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.android.icon_analysis import (
|
||||
get_icon_apk,
|
||||
get_icon_from_src,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.android.manifest_analysis import (
|
||||
manifest_analysis,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.android.manifest_utils import (
|
||||
get_manifest,
|
||||
manifest_data,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.android.playstore import get_app_details
|
||||
from mobsf.StaticAnalyzer.views.android.strings import (
|
||||
get_strings_metadata,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.common.shared_func import (
|
||||
get_avg_cvss,
|
||||
hash_gen,
|
||||
unzip,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.common.firebase import (
|
||||
firebase_analysis,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.common.appsec import (
|
||||
get_android_dashboard,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.common.async_task import (
|
||||
async_analysis,
|
||||
update_enqueued_task,
|
||||
)
|
||||
from mobsf.MobSF.views.authorization import (
|
||||
Permissions,
|
||||
has_permission,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def initialize_app_dic(checksum, app_dic, file_ext):
|
||||
app_dic['app_file'] = f'{checksum}.{file_ext}'
|
||||
app_dic['app_path'] = (app_dic['app_dir'] / app_dic['app_file']).as_posix()
|
||||
app_dic['app_dir'] = app_dic['app_dir'].as_posix() + '/'
|
||||
app_dic['size'] = str(file_size(app_dic['app_path'])) + 'MB'
|
||||
app_dic['sha1'], app_dic['sha256'] = hash_gen(checksum, app_dic['app_path'])
|
||||
return app_dic
|
||||
|
||||
|
||||
def get_manifest_data(checksum, app_dic, andro_apk=None):
|
||||
"""Get Manifest Data."""
|
||||
# Manifest XML
|
||||
mani_file, ns, mani_xml = get_manifest(
|
||||
checksum,
|
||||
app_dic['app_path'],
|
||||
app_dic['app_dir'],
|
||||
app_dic['tools_dir'],
|
||||
app_dic['zipped'],
|
||||
andro_apk,
|
||||
)
|
||||
app_dic['manifest_file'] = mani_file
|
||||
app_dic['parsed_xml'] = mani_xml
|
||||
# Manifest data extraction
|
||||
man_data = manifest_data(
|
||||
checksum,
|
||||
app_dic['parsed_xml'],
|
||||
ns)
|
||||
# Manifest Analysis
|
||||
man_analysis = manifest_analysis(
|
||||
checksum,
|
||||
app_dic['parsed_xml'],
|
||||
ns,
|
||||
man_data,
|
||||
app_dic['zipped'],
|
||||
app_dic['app_dir'])
|
||||
return man_data, man_analysis
|
||||
|
||||
|
||||
def print_scan_subject(checksum, app_dic, man_data):
|
||||
"""Log scan subject."""
|
||||
app_name = app_dic['real_name']
|
||||
pkg_name = man_data['packagename']
|
||||
subject = ''
|
||||
if app_name and pkg_name:
|
||||
subject = f'{app_name} ({pkg_name})'
|
||||
elif pkg_name:
|
||||
subject = pkg_name
|
||||
elif app_name:
|
||||
subject = app_name
|
||||
msg = f'Performing Static Analysis on: {subject}'
|
||||
logger.info(msg)
|
||||
append_scan_status(checksum, msg)
|
||||
return subject
|
||||
|
||||
|
||||
def apk_analysis_task(checksum, app_dic, rescan, queue=False):
|
||||
"""APK Analysis Task."""
|
||||
if queue:
|
||||
settings.ASYNC_ANALYSIS = True
|
||||
append_scan_status(checksum, 'init')
|
||||
initialize_app_dic(checksum, app_dic, 'apk')
|
||||
msg = 'Extracting APK'
|
||||
logger.info(msg)
|
||||
append_scan_status(checksum, msg)
|
||||
app_dic['files'] = unzip(
|
||||
checksum,
|
||||
app_dic['app_path'],
|
||||
app_dic['app_dir'])
|
||||
logger.info('APK Extracted')
|
||||
if not app_dic['files']:
|
||||
# Can't Analyze APK, bail out.
|
||||
msg = 'APK file is invalid or corrupt'
|
||||
logger.error(msg)
|
||||
append_scan_status(checksum, msg)
|
||||
if queue:
|
||||
return update_enqueued_task(
|
||||
checksum, 'Failed', 'Failed')
|
||||
return None, msg
|
||||
app_dic['zipped'] = 'apk'
|
||||
app_dic['certz'] = get_hardcoded_cert_keystore(
|
||||
checksum,
|
||||
app_dic['files'])
|
||||
# Parse APK with Androguard
|
||||
andro_apk = parse_apk(
|
||||
checksum,
|
||||
app_dic['app_path'])
|
||||
# Manifest Data
|
||||
man_data, man_analysis = get_manifest_data(
|
||||
checksum,
|
||||
app_dic,
|
||||
andro_apk)
|
||||
# Get App name
|
||||
app_dic['real_name'] = get_app_name(
|
||||
andro_apk,
|
||||
app_dic['app_dir'],
|
||||
True)
|
||||
# Print scan subject
|
||||
subject = print_scan_subject(checksum, app_dic, man_data)
|
||||
app_dic['playstore'] = get_app_details(
|
||||
checksum,
|
||||
man_data['packagename'])
|
||||
# Malware Permission check
|
||||
mal_perms = permissions.check_malware_permission(
|
||||
checksum,
|
||||
man_data['perm'])
|
||||
man_analysis['malware_permissions'] = mal_perms
|
||||
# Get icon
|
||||
# apktool should run before this
|
||||
get_icon_apk(andro_apk, app_dic)
|
||||
elf_dict = library_analysis(
|
||||
checksum,
|
||||
app_dic['app_dir'],
|
||||
'elf')
|
||||
cert_dic = cert_info(
|
||||
andro_apk,
|
||||
app_dic,
|
||||
man_data)
|
||||
apkid_results = apkid.apkid_analysis(
|
||||
checksum,
|
||||
app_dic['app_path'])
|
||||
trackers = Trackers.Trackers(
|
||||
checksum,
|
||||
app_dic['app_dir'],
|
||||
app_dic['tools_dir']).get_trackers()
|
||||
apk_2_java(
|
||||
checksum,
|
||||
app_dic['app_path'],
|
||||
app_dic['app_dir'],
|
||||
settings.DOWNLOADED_TOOLS_DIR)
|
||||
dex_2_smali(
|
||||
checksum,
|
||||
app_dic['app_dir'],
|
||||
app_dic['tools_dir'])
|
||||
code_an_dic = code_analysis(
|
||||
checksum,
|
||||
app_dic['app_dir'],
|
||||
app_dic['zipped'],
|
||||
app_dic['manifest_file'],
|
||||
man_data['perm'])
|
||||
# Get the strings and metadata
|
||||
get_strings_metadata(
|
||||
checksum,
|
||||
andro_apk,
|
||||
app_dic['app_dir'],
|
||||
elf_dict['elf_strings'],
|
||||
app_dic['zipped'],
|
||||
['.java'],
|
||||
code_an_dic)
|
||||
# Firebase DB Check
|
||||
code_an_dic['firebase'] = firebase_analysis(
|
||||
checksum,
|
||||
code_an_dic)
|
||||
# Domain Extraction and Malware Check
|
||||
code_an_dic['domains'] = MalwareDomainCheck().scan(
|
||||
checksum,
|
||||
code_an_dic['urls_list'])
|
||||
context = save_get_ctx(
|
||||
app_dic,
|
||||
man_data,
|
||||
man_analysis,
|
||||
code_an_dic,
|
||||
cert_dic,
|
||||
elf_dict['elf_analysis'],
|
||||
apkid_results,
|
||||
trackers,
|
||||
rescan,
|
||||
)
|
||||
if queue:
|
||||
return update_enqueued_task(
|
||||
checksum, subject, 'Success')
|
||||
return context, None
|
||||
|
||||
|
||||
def generate_dynamic_context(request, app_dic, checksum, context, api):
|
||||
"""Generate Dynamic Context."""
|
||||
context['appsec'] = get_android_dashboard(context, True)
|
||||
context['average_cvss'] = get_avg_cvss(context['code_analysis'])
|
||||
logcat_file = Path(app_dic['app_dir']) / 'logcat.txt'
|
||||
context['dynamic_analysis_done'] = logcat_file.exists()
|
||||
context['virus_total'] = None
|
||||
if settings.VT_ENABLED:
|
||||
vt = VirusTotal.VirusTotal(checksum)
|
||||
context['virus_total'] = vt.get_result(app_dic['app_path'])
|
||||
template = 'static_analysis/android_binary_analysis.html'
|
||||
return context if api else render(request, template, context)
|
||||
|
||||
|
||||
def apk_analysis(request, app_dic, rescan, api):
|
||||
"""APK Analysis."""
|
||||
checksum = app_dic['md5']
|
||||
db_entry = StaticAnalyzerAndroid.objects.filter(MD5=checksum)
|
||||
if db_entry.exists() and not rescan:
|
||||
context = get_context_from_db_entry(db_entry)
|
||||
return generate_dynamic_context(request, app_dic, checksum, context, api)
|
||||
else:
|
||||
# APK Analysis
|
||||
if not has_permission(request, Permissions.SCAN, api):
|
||||
return print_n_send_error_response(request, 'Permission Denied', False)
|
||||
if settings.ASYNC_ANALYSIS:
|
||||
return async_analysis(
|
||||
checksum,
|
||||
app_dic.get('app_name', ''),
|
||||
apk_analysis_task, checksum, app_dic, rescan)
|
||||
context, err = apk_analysis_task(checksum, app_dic, rescan)
|
||||
if err:
|
||||
return print_n_send_error_response(request, err, api)
|
||||
return generate_dynamic_context(request, app_dic, checksum, context, api)
|
||||
|
||||
|
||||
def src_analysis_task(checksum, app_dic, rescan, pro_type, queue=False):
|
||||
"""Android ZIP Source Code Analysis Begins."""
|
||||
if queue:
|
||||
settings.ASYNC_ANALYSIS = True
|
||||
cert_dic = {
|
||||
'certificate_info': '',
|
||||
'certificate_status': '',
|
||||
'description': '',
|
||||
}
|
||||
app_dic['strings'] = []
|
||||
app_dic['secrets'] = []
|
||||
# Above fields are only available for APK and not ZIP
|
||||
app_dic['zipped'] = pro_type
|
||||
app_dic['certz'] = get_hardcoded_cert_keystore(
|
||||
checksum,
|
||||
app_dic['files'])
|
||||
# Manifest Data
|
||||
man_data, man_analysis = get_manifest_data(
|
||||
checksum,
|
||||
app_dic)
|
||||
# Get app name
|
||||
app_dic['real_name'] = get_app_name(
|
||||
None,
|
||||
app_dic['app_dir'],
|
||||
False)
|
||||
# Print scan subject
|
||||
subject = print_scan_subject(checksum, app_dic, man_data)
|
||||
app_dic['playstore'] = get_app_details(
|
||||
checksum,
|
||||
man_data['packagename'])
|
||||
# Malware Permission check
|
||||
mal_perms = permissions.check_malware_permission(
|
||||
checksum,
|
||||
man_data['perm'])
|
||||
man_analysis['malware_permissions'] = mal_perms
|
||||
# Get icon
|
||||
get_icon_from_src(
|
||||
app_dic,
|
||||
man_data['icons'])
|
||||
code_an_dic = code_analysis(
|
||||
checksum,
|
||||
app_dic['app_dir'],
|
||||
app_dic['zipped'],
|
||||
app_dic['manifest_file'],
|
||||
man_data['perm'])
|
||||
# Get the strings and metadata
|
||||
get_strings_metadata(
|
||||
checksum,
|
||||
None,
|
||||
app_dic['app_dir'],
|
||||
None,
|
||||
app_dic['zipped'],
|
||||
['.java', '.kt'],
|
||||
code_an_dic)
|
||||
# Firebase DB Check
|
||||
code_an_dic['firebase'] = firebase_analysis(
|
||||
checksum,
|
||||
code_an_dic)
|
||||
# Domain Extraction and Malware Check
|
||||
code_an_dic['domains'] = MalwareDomainCheck().scan(
|
||||
checksum,
|
||||
code_an_dic['urls_list'])
|
||||
# Extract Trackers from Domains
|
||||
trackers = Trackers.Trackers(
|
||||
checksum,
|
||||
None,
|
||||
app_dic['tools_dir']).get_trackers_domains_or_deps(
|
||||
code_an_dic['domains'], [])
|
||||
context = save_get_ctx(
|
||||
app_dic,
|
||||
man_data,
|
||||
man_analysis,
|
||||
code_an_dic,
|
||||
cert_dic,
|
||||
[],
|
||||
{},
|
||||
trackers,
|
||||
rescan,
|
||||
)
|
||||
if queue:
|
||||
return update_enqueued_task(
|
||||
checksum, subject, 'Success')
|
||||
return context
|
||||
|
||||
|
||||
def generate_dynamic_src_context(request, context, api):
|
||||
"""Generate Dynamic Source Context."""
|
||||
context['appsec'] = get_android_dashboard(context, True)
|
||||
context['average_cvss'] = get_avg_cvss(context['code_analysis'])
|
||||
template = 'static_analysis/android_source_analysis.html'
|
||||
return context if api else render(request, template, context)
|
||||
|
||||
|
||||
def src_analysis(request, app_dic, rescan, api):
|
||||
"""Source Code Analysis."""
|
||||
checksum = app_dic['md5']
|
||||
ret = f'/static_analyzer_ios/{checksum}/'
|
||||
db_entry = StaticAnalyzerAndroid.objects.filter(
|
||||
MD5=checksum)
|
||||
ios_db_entry = StaticAnalyzerIOS.objects.filter(
|
||||
MD5=checksum)
|
||||
if db_entry.exists() and not rescan:
|
||||
context = get_context_from_db_entry(db_entry)
|
||||
return generate_dynamic_src_context(request, context, api)
|
||||
elif ios_db_entry.exists() and not rescan:
|
||||
return {'type': 'ios'} if api else HttpResponseRedirect(ret)
|
||||
else:
|
||||
# Initialize for both Android and iOS Source Analysis
|
||||
append_scan_status(checksum, 'init')
|
||||
initialize_app_dic(checksum, app_dic, 'zip')
|
||||
msg = 'Extracting ZIP'
|
||||
logger.info(msg)
|
||||
append_scan_status(checksum, msg)
|
||||
app_dic['files'] = unzip(
|
||||
checksum,
|
||||
app_dic['app_path'],
|
||||
app_dic['app_dir'])
|
||||
# Check if Valid Directory Structure and get ZIP Type
|
||||
pro_type, valid = valid_source_code(
|
||||
checksum,
|
||||
app_dic['app_dir'])
|
||||
msg = f'Source code type - {pro_type}'
|
||||
logger.info(msg)
|
||||
append_scan_status(checksum, msg)
|
||||
# Handle iOS Source Code
|
||||
if valid and pro_type == 'ios':
|
||||
msg = 'Redirecting to iOS Source Code Analyzer'
|
||||
logger.info(msg)
|
||||
append_scan_status(checksum, msg)
|
||||
ret = f'{ret}?rescan={str(int(rescan))}'
|
||||
return {'type': 'ios'} if api else HttpResponseRedirect(ret)
|
||||
# Android Source Code Analysis
|
||||
if not has_permission(request, Permissions.SCAN, api):
|
||||
return print_n_send_error_response(
|
||||
request,
|
||||
'Permission Denied',
|
||||
False)
|
||||
if valid and (pro_type in ['eclipse', 'studio']):
|
||||
if settings.ASYNC_ANALYSIS:
|
||||
return async_analysis(
|
||||
checksum,
|
||||
app_dic.get('app_name', ''),
|
||||
src_analysis_task, checksum, app_dic, rescan, pro_type)
|
||||
context = src_analysis_task(checksum, app_dic, rescan, pro_type)
|
||||
return generate_dynamic_src_context(request, context, api)
|
||||
else:
|
||||
msg = 'This ZIP Format is not supported'
|
||||
if api:
|
||||
return print_n_send_error_response(
|
||||
request,
|
||||
msg,
|
||||
True)
|
||||
else:
|
||||
print_n_send_error_response(request, msg, False)
|
||||
ctx = {
|
||||
'title': 'Invalid ZIP archive',
|
||||
'version': settings.MOBSF_VER,
|
||||
}
|
||||
template = 'general/zip.html'
|
||||
return render(request, template, ctx)
|
||||
|
||||
|
||||
def is_android_source(app_path):
|
||||
"""Detect Android Source and IDE Type."""
|
||||
# Eclipse
|
||||
man = app_path / 'AndroidManifest.xml'
|
||||
src = app_path / 'src'
|
||||
if man.is_file() and src.exists():
|
||||
return 'eclipse', True
|
||||
|
||||
# Studio
|
||||
man = app_path / 'app' / 'src' / 'main' / 'AndroidManifest.xml'
|
||||
java = app_path / 'app' / 'src' / 'main' / 'java'
|
||||
kotlin = app_path / 'app' / 'src' / 'main' / 'kotlin'
|
||||
if man.is_file() and (java.exists() or kotlin.exists()):
|
||||
return 'studio', True
|
||||
|
||||
return None, False
|
||||
|
||||
|
||||
def move_to_parent(inside_path, app_path):
|
||||
"""Move contents of inside to app dir."""
|
||||
for item in inside_path.iterdir():
|
||||
shutil.move(str(item), str(app_path))
|
||||
shutil.rmtree(inside_path)
|
||||
|
||||
|
||||
def valid_source_code(checksum, app_dir):
|
||||
"""Test if this is a valid source code zip."""
|
||||
try:
|
||||
msg = 'Detecting source code type'
|
||||
logger.info(msg)
|
||||
append_scan_status(checksum, msg)
|
||||
|
||||
app_path = Path(app_dir)
|
||||
ide, is_and = is_android_source(app_path)
|
||||
|
||||
if ide:
|
||||
return ide, is_and
|
||||
|
||||
# Relaxed Android Source check, one level down
|
||||
for subdir in app_path.iterdir():
|
||||
if subdir.is_dir() and subdir.exists():
|
||||
ide, is_and = is_android_source(subdir)
|
||||
if ide:
|
||||
move_to_parent(subdir, app_path)
|
||||
return ide, is_and
|
||||
|
||||
# iOS Source
|
||||
xcode = [f for f in app_path.iterdir() if f.suffix == '.xcodeproj']
|
||||
if xcode:
|
||||
return 'ios', True
|
||||
|
||||
# Relaxed iOS Source Check
|
||||
for subdir in app_path.iterdir():
|
||||
if subdir.is_dir() and subdir.exists():
|
||||
if any(f.suffix == '.xcodeproj' for f in subdir.iterdir()):
|
||||
return 'ios', True
|
||||
|
||||
return '', False
|
||||
except Exception as exp:
|
||||
msg = 'Error identifying source code type from zip'
|
||||
logger.exception(msg)
|
||||
append_scan_status(checksum, msg, repr(exp))
|
||||
@@ -33,12 +33,15 @@ def get_app_name(a, app_dir, is_apk):
|
||||
base = Path(app_dir)
|
||||
if is_apk:
|
||||
if a:
|
||||
# Parsed Androguard APK Object
|
||||
return a.get_app_name()
|
||||
else:
|
||||
# Look for app_name in values folder.
|
||||
val = base / 'apktool_out' / 'res' / 'values'
|
||||
if val.exists():
|
||||
return get_app_name_from_values_folder(val.as_posix())
|
||||
else:
|
||||
# For source code
|
||||
strings_path = base / 'app' / 'src' / 'main' / 'res' / 'values'
|
||||
eclipse_path = base / 'res' / 'values'
|
||||
if strings_path.exists():
|
||||
|
||||
@@ -167,6 +167,7 @@ def apksigtool_cert(checksum, apk_path, tools_dir):
|
||||
certs_no = 0
|
||||
min_sdk = None
|
||||
av1, av2, av3, av4 = None, None, None, None
|
||||
v1, v2, v3, v4 = None, None, None, None
|
||||
try:
|
||||
from apksigtool import (
|
||||
APKSignatureSchemeBlock,
|
||||
|
||||
@@ -20,8 +20,11 @@ from mobsf.StaticAnalyzer.views.common.shared_func import (
|
||||
url_n_email_extract,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.sast_engine import (
|
||||
niap_scan,
|
||||
scan,
|
||||
ChoiceEngine,
|
||||
SastEngine,
|
||||
)
|
||||
from mobsf.MalwareAnalyzer.views.android import (
|
||||
behaviour_analysis,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -75,6 +78,8 @@ def code_analysis(checksum, app_dir, typ, manifest_file, android_permissions):
|
||||
code_findings = {}
|
||||
api_findings = {}
|
||||
perm_mappings = {}
|
||||
behaviour_findings = {}
|
||||
niap_findings = {}
|
||||
email_n_file = []
|
||||
url_n_file = []
|
||||
url_list = []
|
||||
@@ -84,59 +89,78 @@ def code_analysis(checksum, app_dir, typ, manifest_file, android_permissions):
|
||||
msg = f'Code Analysis Started on - {filename_from_path(src)}'
|
||||
logger.info(msg)
|
||||
append_scan_status(checksum, msg)
|
||||
|
||||
options = {
|
||||
'match_rules': code_rules.as_posix(),
|
||||
'match_extensions': {'.java', '.kt'},
|
||||
'ignore_paths': skp,
|
||||
}
|
||||
# Code Analysis
|
||||
code_findings = scan(
|
||||
checksum,
|
||||
code_rules.as_posix(),
|
||||
{'.java', '.kt'},
|
||||
[src],
|
||||
skp)
|
||||
sast = SastEngine(options, src)
|
||||
# Read data once and pass it to all the analysis
|
||||
file_data = sast.read_files()
|
||||
code_findings = sast.run_rules(file_data, code_rules.as_posix())
|
||||
msg = 'Android SAST Completed'
|
||||
logger.info(msg)
|
||||
append_scan_status(checksum, msg)
|
||||
|
||||
# API Analysis
|
||||
msg = 'Android API Analysis Started'
|
||||
logger.info(msg)
|
||||
append_scan_status(checksum, msg)
|
||||
api_findings = scan(
|
||||
checksum,
|
||||
api_rules.as_posix(),
|
||||
{'.java', '.kt'},
|
||||
[src],
|
||||
skp)
|
||||
sast = SastEngine(options, src)
|
||||
api_findings = sast.run_rules(file_data, api_rules.as_posix())
|
||||
msg = 'Android API Analysis Completed'
|
||||
logger.info(msg)
|
||||
append_scan_status(checksum, msg)
|
||||
|
||||
# Permission Mapping
|
||||
rule_file = get_perm_rules(checksum, perm_rules, android_permissions)
|
||||
if rule_file:
|
||||
msg = 'Android Permission Mapping Started'
|
||||
logger.info(msg)
|
||||
append_scan_status(checksum, msg)
|
||||
perm_mappings = permission_transform(scan(
|
||||
checksum,
|
||||
rule_file.name,
|
||||
{'.java', '.kt'},
|
||||
[src],
|
||||
{}))
|
||||
sast = SastEngine(options, src)
|
||||
perm_mappings = permission_transform(
|
||||
sast.run_rules(file_data, rule_file.name))
|
||||
msg = 'Android Permission Mapping Completed'
|
||||
logger.info(msg)
|
||||
append_scan_status(checksum, msg)
|
||||
os.unlink(rule_file.name)
|
||||
|
||||
# Behavior Analysis
|
||||
sast = SastEngine(options, src)
|
||||
behaviour_findings = behaviour_analysis.analyze(
|
||||
checksum, sast, file_data)
|
||||
|
||||
# NIAP Scan
|
||||
niap_findings = niap_scan(
|
||||
checksum,
|
||||
niap_rules.as_posix(),
|
||||
{'.java', '.xml'},
|
||||
[src],
|
||||
manifest_file,
|
||||
None)
|
||||
if settings_enabled('NIAP_ENABLED'):
|
||||
msg = 'Running NIAP Analyzer'
|
||||
logger.info(msg)
|
||||
append_scan_status(checksum, msg)
|
||||
niap_options = {
|
||||
'choice_rules': niap_rules.as_posix(),
|
||||
'alternative_path': manifest_file if manifest_file else '',
|
||||
'choice_extensions': {'.java', '.xml'},
|
||||
'ignore_paths': skp,
|
||||
}
|
||||
cengine = ChoiceEngine(niap_options, src)
|
||||
file_data = cengine.read_files()
|
||||
niap_findings = cengine.run_rules(file_data, niap_rules.as_posix())
|
||||
msg = 'NIAP Analysis Completed'
|
||||
logger.info(msg)
|
||||
append_scan_status(checksum, msg)
|
||||
|
||||
# Extract URLs and Emails
|
||||
msg = 'Extracting Emails and URLs from Source Code'
|
||||
logger.info(msg)
|
||||
append_scan_status(checksum, msg)
|
||||
for pfile in Path(src).rglob('*'):
|
||||
if (
|
||||
(pfile.suffix in ('.java', '.kt')
|
||||
and any(skip_path in pfile.as_posix()
|
||||
for skip_path in skp) is False)
|
||||
for skip_path in skp) is False
|
||||
and pfile.is_file())
|
||||
):
|
||||
content = None
|
||||
try:
|
||||
@@ -155,6 +179,7 @@ def code_analysis(checksum, app_dir, typ, manifest_file, android_permissions):
|
||||
append_scan_status(checksum, msg)
|
||||
code_an_dic = {
|
||||
'api': api_findings,
|
||||
'behaviour': behaviour_findings,
|
||||
'perm_mappings': perm_mappings,
|
||||
'findings': code_findings,
|
||||
'niap': niap_findings,
|
||||
|
||||
@@ -102,7 +102,6 @@ def get_context_from_analysis(app_dic,
|
||||
cert_dic,
|
||||
bin_anal,
|
||||
apk_id,
|
||||
behaviour_an,
|
||||
trackers) -> dict:
|
||||
"""Get the context for APK/ZIP from analysis results."""
|
||||
try:
|
||||
@@ -157,7 +156,7 @@ def get_context_from_analysis(app_dic,
|
||||
'files': app_dic['files'],
|
||||
'exported_count': man_an_dic['exported_cnt'],
|
||||
'apkid': apk_id,
|
||||
'behaviour': behaviour_an,
|
||||
'behaviour': code_an_dic['behaviour'],
|
||||
'trackers': trackers,
|
||||
'playstore_details': app_dic['playstore'],
|
||||
'secrets': code_an_dic['secrets'],
|
||||
@@ -178,7 +177,6 @@ def save_or_update(update_type,
|
||||
cert_dic,
|
||||
bin_anal,
|
||||
apk_id,
|
||||
behaviour_an,
|
||||
trackers) -> None:
|
||||
"""Save/Update an APK/ZIP DB entry."""
|
||||
try:
|
||||
@@ -223,7 +221,7 @@ def save_or_update(update_type,
|
||||
'FILES': app_dic['files'],
|
||||
'EXPORTED_COUNT': man_an_dic['exported_cnt'],
|
||||
'APKID': apk_id,
|
||||
'QUARK': behaviour_an,
|
||||
'QUARK': code_an_dic['behaviour'],
|
||||
'TRACKERS': trackers,
|
||||
'PLAYSTORE_DETAILS': app_dic['playstore'],
|
||||
'NETWORK_SECURITY': man_an_dic['network_security'],
|
||||
@@ -255,7 +253,7 @@ def save_or_update(update_type,
|
||||
append_scan_status(app_dic['md5'], msg, repr(exp))
|
||||
|
||||
|
||||
def save_get_ctx(app, man, m_anal, code, cert, elf, apkid, behaviour, trk, rscn):
|
||||
def save_get_ctx(app, man, m_anal, code, cert, elf, apkid, trk, rscn):
|
||||
# SAVE TO DB
|
||||
if rscn:
|
||||
msg = 'Updating Database...'
|
||||
@@ -277,7 +275,6 @@ def save_get_ctx(app, man, m_anal, code, cert, elf, apkid, behaviour, trk, rscn)
|
||||
cert,
|
||||
elf,
|
||||
apkid,
|
||||
behaviour,
|
||||
trk,
|
||||
)
|
||||
return get_context_from_analysis(
|
||||
@@ -288,6 +285,5 @@ def save_get_ctx(app, man, m_anal, code, cert, elf, apkid, behaviour, trk, rscn)
|
||||
cert,
|
||||
elf,
|
||||
apkid,
|
||||
behaviour,
|
||||
trk,
|
||||
)
|
||||
|
||||
@@ -8,7 +8,6 @@ from django.shortcuts import render
|
||||
import mobsf.MalwareAnalyzer.views.Trackers as Trackers
|
||||
import mobsf.MalwareAnalyzer.views.VirusTotal as VirusTotal
|
||||
from mobsf.MalwareAnalyzer.views.android import (
|
||||
behaviour_analysis,
|
||||
permissions,
|
||||
)
|
||||
from mobsf.MobSF.utils import (
|
||||
@@ -209,10 +208,6 @@ def common_analysis(request, app_dic, rescan, api, analysis_type):
|
||||
checksum,
|
||||
app_dic['app_dir'],
|
||||
code_an_dic)
|
||||
behaviour_an = behaviour_analysis.analyze(
|
||||
checksum,
|
||||
app_dic['app_dir'],
|
||||
APK_TYPE)
|
||||
# Get the strings and metadata
|
||||
get_strings_metadata(
|
||||
checksum,
|
||||
@@ -240,7 +235,6 @@ def common_analysis(request, app_dic, rescan, api, analysis_type):
|
||||
cert_dic,
|
||||
elf_dict['elf_analysis'],
|
||||
{},
|
||||
behaviour_an,
|
||||
tracker_res,
|
||||
rescan,
|
||||
)
|
||||
|
||||
@@ -58,6 +58,9 @@ def get_android_manifest_androguard(apk, app_dir):
|
||||
"""Get AndroidManifest.xml using Androguard."""
|
||||
try:
|
||||
logger.info('Extracting AndroidManifest.xml with Androguard')
|
||||
if not apk:
|
||||
logger.warning('Androgaurd APK parsing failed')
|
||||
return
|
||||
manifest = apk.get_android_manifest_axml()
|
||||
if not manifest:
|
||||
return
|
||||
|
||||
@@ -23,7 +23,7 @@ def read_netsec_config(checksum, app_dir, config, src_type):
|
||||
config_file = None
|
||||
config = config.replace('@xml/', '', 1)
|
||||
base = Path(app_dir)
|
||||
if src_type:
|
||||
if src_type == 'studio':
|
||||
# Support only android studio source files
|
||||
xml_dir = base / 'app' / 'src' / 'main' / 'res' / 'xml'
|
||||
else:
|
||||
|
||||
@@ -116,6 +116,7 @@ def so_analysis(request, app_dic, rescan, api):
|
||||
apkid_results = {}
|
||||
code_an_dic = {
|
||||
'api': {},
|
||||
'behaviour': {},
|
||||
'perm_mappings': {},
|
||||
'findings': {},
|
||||
'niap': {},
|
||||
@@ -123,7 +124,6 @@ def so_analysis(request, app_dic, rescan, api):
|
||||
'urls': [],
|
||||
'emails': [],
|
||||
}
|
||||
behaviour_an = []
|
||||
# Get the strings and metadata from shared object
|
||||
get_strings_metadata(
|
||||
checksum,
|
||||
@@ -157,7 +157,6 @@ def so_analysis(request, app_dic, rescan, api):
|
||||
cert_dic,
|
||||
elf_dict['elf_analysis'],
|
||||
apkid_results,
|
||||
behaviour_an,
|
||||
trackers,
|
||||
rescan,
|
||||
)
|
||||
|
||||
@@ -2,30 +2,14 @@
|
||||
"""Android Static Code Analysis."""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
import mobsf.MalwareAnalyzer.views.Trackers as Trackers
|
||||
import mobsf.MalwareAnalyzer.views.VirusTotal as VirusTotal
|
||||
from mobsf.MalwareAnalyzer.views.android import (
|
||||
apkid,
|
||||
behaviour_analysis,
|
||||
permissions,
|
||||
)
|
||||
from mobsf.MalwareAnalyzer.views.MalwareDomainCheck import MalwareDomainCheck
|
||||
|
||||
from django.conf import settings
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import render
|
||||
from django.template.defaulttags import register
|
||||
|
||||
from mobsf.MobSF.utils import (
|
||||
android_component,
|
||||
append_scan_status,
|
||||
file_size,
|
||||
is_dir_exists,
|
||||
is_file_exists,
|
||||
is_md5,
|
||||
key,
|
||||
pathify,
|
||||
@@ -34,49 +18,16 @@ from mobsf.MobSF.utils import (
|
||||
)
|
||||
from mobsf.StaticAnalyzer.models import (
|
||||
RecentScansDB,
|
||||
StaticAnalyzerAndroid,
|
||||
StaticAnalyzerIOS,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.common.binary.lib_analysis import (
|
||||
library_analysis,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.android.app import (
|
||||
get_app_name,
|
||||
parse_apk,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.android.cert_analysis import (
|
||||
cert_info,
|
||||
get_hardcoded_cert_keystore,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.android.code_analysis import code_analysis
|
||||
from mobsf.StaticAnalyzer.views.android.converter import (
|
||||
apk_2_java,
|
||||
dex_2_smali,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.android.db_interaction import (
|
||||
get_context_from_db_entry,
|
||||
save_get_ctx,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.android.icon_analysis import (
|
||||
get_icon_apk,
|
||||
get_icon_from_src,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.android.manifest_analysis import (
|
||||
manifest_analysis,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.android.manifest_utils import (
|
||||
get_manifest,
|
||||
manifest_data,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.android.playstore import get_app_details
|
||||
from mobsf.StaticAnalyzer.views.android.strings import (
|
||||
get_strings_metadata,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.android.xapk import (
|
||||
handle_aab,
|
||||
handle_split_apk,
|
||||
handle_xapk,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.android.apk import (
|
||||
apk_analysis,
|
||||
src_analysis,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.android.jar_aar import (
|
||||
aar_analysis,
|
||||
jar_analysis,
|
||||
@@ -84,24 +35,9 @@ from mobsf.StaticAnalyzer.views.android.jar_aar import (
|
||||
from mobsf.StaticAnalyzer.views.android.so import (
|
||||
so_analysis,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.common.shared_func import (
|
||||
get_avg_cvss,
|
||||
hash_gen,
|
||||
unzip,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.common.firebase import (
|
||||
firebase_analysis,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.common.appsec import (
|
||||
get_android_dashboard,
|
||||
)
|
||||
from mobsf.MobSF.views.authentication import (
|
||||
login_required,
|
||||
)
|
||||
from mobsf.MobSF.views.authorization import (
|
||||
Permissions,
|
||||
has_permission,
|
||||
)
|
||||
|
||||
APK_TYPE = 'apk'
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -172,187 +108,9 @@ def static_analyzer(request, checksum, api=False):
|
||||
if not handle_aab(app_dic):
|
||||
raise Exception('Invalid AAB File')
|
||||
typ = APK_TYPE
|
||||
# Route to respective analysis
|
||||
if typ == APK_TYPE:
|
||||
app_dic['app_file'] = f'{checksum}.apk'
|
||||
app_dic['app_path'] = (
|
||||
app_dic['app_dir'] / app_dic['app_file']).as_posix()
|
||||
app_dic['app_dir'] = app_dic['app_dir'].as_posix() + '/'
|
||||
# Check if in DB
|
||||
# pylint: disable=E1101
|
||||
db_entry = StaticAnalyzerAndroid.objects.filter(MD5=checksum)
|
||||
if db_entry.exists() and not rescan:
|
||||
context = get_context_from_db_entry(db_entry)
|
||||
else:
|
||||
if not has_permission(request, Permissions.SCAN, api):
|
||||
return print_n_send_error_response(
|
||||
request,
|
||||
'Permission Denied',
|
||||
False)
|
||||
# ANALYSIS BEGINS
|
||||
append_scan_status(checksum, 'init')
|
||||
app_dic['size'] = str(
|
||||
file_size(app_dic['app_path'])) + 'MB' # FILE SIZE
|
||||
app_dic['sha1'], app_dic['sha256'] = hash_gen(
|
||||
checksum,
|
||||
app_dic['app_path'])
|
||||
msg = 'Extracting APK'
|
||||
logger.info(msg)
|
||||
append_scan_status(checksum, msg)
|
||||
app_dic['files'] = unzip(
|
||||
checksum,
|
||||
app_dic['app_path'],
|
||||
app_dic['app_dir'])
|
||||
logger.info('APK Extracted')
|
||||
if not app_dic['files']:
|
||||
# Can't Analyze APK, bail out.
|
||||
msg = 'APK file is invalid or corrupt'
|
||||
append_scan_status(checksum, msg)
|
||||
return print_n_send_error_response(
|
||||
request,
|
||||
msg,
|
||||
api)
|
||||
app_dic['certz'] = get_hardcoded_cert_keystore(
|
||||
checksum,
|
||||
app_dic['files'])
|
||||
# Parse APK with Androguard
|
||||
apk = parse_apk(
|
||||
checksum,
|
||||
app_dic['app_path'])
|
||||
# get app_name
|
||||
app_dic['real_name'] = get_app_name(
|
||||
apk,
|
||||
app_dic['app_dir'],
|
||||
True,
|
||||
)
|
||||
# Manifest XML
|
||||
mani_file, ns, mani_xml = get_manifest(
|
||||
checksum,
|
||||
app_dic['app_path'],
|
||||
app_dic['app_dir'],
|
||||
app_dic['tools_dir'],
|
||||
APK_TYPE,
|
||||
apk,
|
||||
)
|
||||
app_dic['manifest_file'] = mani_file
|
||||
app_dic['parsed_xml'] = mani_xml
|
||||
# Manifest data extraction
|
||||
man_data_dic = manifest_data(
|
||||
checksum,
|
||||
app_dic['parsed_xml'],
|
||||
ns)
|
||||
# Get App name
|
||||
app_name = app_dic['real_name']
|
||||
pkg_name = man_data_dic['packagename']
|
||||
if app_name or pkg_name:
|
||||
if app_name and pkg_name:
|
||||
subject = f'{app_name} ({pkg_name})'
|
||||
elif app_name:
|
||||
subject = app_name
|
||||
elif pkg_name:
|
||||
subject = pkg_name
|
||||
msg = f'Performing Static Analysis on: {subject}'
|
||||
logger.info(msg)
|
||||
append_scan_status(checksum, msg)
|
||||
app_dic['playstore'] = get_app_details(
|
||||
checksum,
|
||||
man_data_dic['packagename'])
|
||||
man_an_dic = manifest_analysis(
|
||||
checksum,
|
||||
app_dic['parsed_xml'],
|
||||
ns,
|
||||
man_data_dic,
|
||||
'',
|
||||
app_dic['app_dir'])
|
||||
# Malware Permission check
|
||||
mal_perms = permissions.check_malware_permission(
|
||||
checksum,
|
||||
man_data_dic['perm'])
|
||||
man_an_dic['malware_permissions'] = mal_perms
|
||||
# Get icon
|
||||
# apktool should run before this
|
||||
get_icon_apk(apk, app_dic)
|
||||
elf_dict = library_analysis(
|
||||
checksum,
|
||||
app_dic['app_dir'],
|
||||
'elf')
|
||||
cert_dic = cert_info(
|
||||
apk,
|
||||
app_dic,
|
||||
man_data_dic)
|
||||
apkid_results = apkid.apkid_analysis(
|
||||
checksum,
|
||||
app_dic['app_path'])
|
||||
tracker = Trackers.Trackers(
|
||||
checksum,
|
||||
app_dic['app_dir'],
|
||||
app_dic['tools_dir'])
|
||||
tracker_res = tracker.get_trackers()
|
||||
apk_2_java(
|
||||
checksum,
|
||||
app_dic['app_path'],
|
||||
app_dic['app_dir'],
|
||||
settings.DOWNLOADED_TOOLS_DIR)
|
||||
dex_2_smali(
|
||||
checksum,
|
||||
app_dic['app_dir'],
|
||||
app_dic['tools_dir'])
|
||||
code_an_dic = code_analysis(
|
||||
checksum,
|
||||
app_dic['app_dir'],
|
||||
APK_TYPE,
|
||||
app_dic['manifest_file'],
|
||||
man_data_dic['perm'])
|
||||
behaviour_an = behaviour_analysis.analyze(
|
||||
checksum,
|
||||
app_dic['app_dir'],
|
||||
APK_TYPE)
|
||||
# Get the strings and metadata
|
||||
get_strings_metadata(
|
||||
checksum,
|
||||
apk,
|
||||
app_dic['app_dir'],
|
||||
elf_dict['elf_strings'],
|
||||
APK_TYPE,
|
||||
['.java'],
|
||||
code_an_dic)
|
||||
# Firebase DB Check
|
||||
code_an_dic['firebase'] = firebase_analysis(
|
||||
checksum,
|
||||
code_an_dic)
|
||||
# Domain Extraction and Malware Check
|
||||
code_an_dic['domains'] = MalwareDomainCheck().scan(
|
||||
checksum,
|
||||
code_an_dic['urls_list'])
|
||||
|
||||
app_dic['zipped'] = APK_TYPE
|
||||
context = save_get_ctx(
|
||||
app_dic,
|
||||
man_data_dic,
|
||||
man_an_dic,
|
||||
code_an_dic,
|
||||
cert_dic,
|
||||
elf_dict['elf_analysis'],
|
||||
apkid_results,
|
||||
behaviour_an,
|
||||
tracker_res,
|
||||
rescan,
|
||||
)
|
||||
context['appsec'] = get_android_dashboard(context, True)
|
||||
context['average_cvss'] = get_avg_cvss(
|
||||
context['code_analysis'])
|
||||
context['dynamic_analysis_done'] = is_file_exists(
|
||||
os.path.join(app_dic['app_dir'], 'logcat.txt'))
|
||||
|
||||
context['virus_total'] = None
|
||||
if settings.VT_ENABLED:
|
||||
vt = VirusTotal.VirusTotal(checksum)
|
||||
context['virus_total'] = vt.get_result(
|
||||
app_dic['app_path'])
|
||||
template = 'static_analysis/android_binary_analysis.html'
|
||||
if api:
|
||||
return context
|
||||
else:
|
||||
return render(request, template, context)
|
||||
return apk_analysis(request, app_dic, rescan, api)
|
||||
elif typ == 'jar':
|
||||
return jar_analysis(request, app_dic, rescan, api)
|
||||
elif typ == 'aar':
|
||||
@@ -360,195 +118,7 @@ def static_analyzer(request, checksum, api=False):
|
||||
elif typ == 'so':
|
||||
return so_analysis(request, app_dic, rescan, api)
|
||||
elif typ == 'zip':
|
||||
ret = f'/static_analyzer_ios/{checksum}/'
|
||||
app_dic['app_file'] = f'{checksum}.zip'
|
||||
app_dic['app_path'] = (
|
||||
app_dic['app_dir'] / app_dic['app_file']).as_posix()
|
||||
app_dic['app_dir'] = app_dic['app_dir'].as_posix() + '/'
|
||||
db_entry = StaticAnalyzerAndroid.objects.filter(
|
||||
MD5=checksum)
|
||||
ios_db_entry = StaticAnalyzerIOS.objects.filter(
|
||||
MD5=checksum)
|
||||
if db_entry.exists() and not rescan:
|
||||
context = get_context_from_db_entry(db_entry)
|
||||
elif ios_db_entry.exists() and not rescan:
|
||||
if api:
|
||||
return {'type': 'ios'}
|
||||
else:
|
||||
return HttpResponseRedirect(ret)
|
||||
else:
|
||||
append_scan_status(checksum, 'init')
|
||||
msg = 'Extracting ZIP'
|
||||
logger.info(msg)
|
||||
append_scan_status(checksum, msg)
|
||||
app_dic['files'] = unzip(
|
||||
checksum,
|
||||
app_dic['app_path'],
|
||||
app_dic['app_dir'])
|
||||
# Check if Valid Directory Structure and get ZIP Type
|
||||
pro_type, valid = valid_source_code(
|
||||
checksum,
|
||||
app_dic['app_dir'])
|
||||
msg = f'Source code type - {pro_type}'
|
||||
logger.info(msg)
|
||||
append_scan_status(checksum, msg)
|
||||
if valid and pro_type == 'ios':
|
||||
msg = 'Redirecting to iOS Source Code Analyzer'
|
||||
logger.info(msg)
|
||||
append_scan_status(checksum, msg)
|
||||
if api:
|
||||
return {'type': 'ios'}
|
||||
else:
|
||||
ret += f'?rescan={str(int(rescan))}'
|
||||
return HttpResponseRedirect(ret)
|
||||
if not has_permission(request, Permissions.SCAN, api):
|
||||
return print_n_send_error_response(
|
||||
request,
|
||||
'Permission Denied',
|
||||
False)
|
||||
# Android ZIP Source Code Analysis Begins
|
||||
if valid and (pro_type in ['eclipse', 'studio']):
|
||||
cert_dic = {
|
||||
'certificate_info': '',
|
||||
'certificate_status': '',
|
||||
'description': '',
|
||||
}
|
||||
app_dic['strings'] = []
|
||||
app_dic['secrets'] = []
|
||||
# Above fields are only available for APK and not ZIP
|
||||
app_dic['zipped'] = pro_type
|
||||
app_dic['size'] = str(
|
||||
file_size(app_dic['app_path'])) + 'MB' # FILE SIZE
|
||||
app_dic['sha1'], app_dic['sha256'] = hash_gen(
|
||||
checksum,
|
||||
app_dic['app_path'])
|
||||
app_dic['certz'] = get_hardcoded_cert_keystore(
|
||||
checksum,
|
||||
app_dic['files'])
|
||||
# get app_name
|
||||
app_dic['real_name'] = get_app_name(
|
||||
app_dic['app_path'],
|
||||
app_dic['app_dir'],
|
||||
False,
|
||||
)
|
||||
# Manifest XML
|
||||
mani_file, ns, mani_xml = get_manifest(
|
||||
checksum,
|
||||
'',
|
||||
app_dic['app_dir'],
|
||||
app_dic['tools_dir'],
|
||||
pro_type,
|
||||
None,
|
||||
)
|
||||
app_dic['manifest_file'] = mani_file
|
||||
app_dic['parsed_xml'] = mani_xml
|
||||
# Get manifest data
|
||||
man_data_dic = manifest_data(
|
||||
checksum,
|
||||
app_dic['parsed_xml'],
|
||||
ns)
|
||||
# Get app name
|
||||
app_name = app_dic['real_name']
|
||||
pkg_name = man_data_dic['packagename']
|
||||
if app_name or pkg_name:
|
||||
if app_name and pkg_name:
|
||||
subject = f'{app_name} ({pkg_name})'
|
||||
elif app_name:
|
||||
subject = app_name
|
||||
elif pkg_name:
|
||||
subject = pkg_name
|
||||
msg = f'Performing Static Analysis on: {subject}'
|
||||
logger.info(msg)
|
||||
|
||||
app_dic['playstore'] = get_app_details(
|
||||
checksum,
|
||||
man_data_dic['packagename'])
|
||||
man_an_dic = manifest_analysis(
|
||||
checksum,
|
||||
app_dic['parsed_xml'],
|
||||
ns,
|
||||
man_data_dic,
|
||||
pro_type,
|
||||
app_dic['app_dir'],
|
||||
)
|
||||
# Malware Permission check
|
||||
mal_perms = permissions.check_malware_permission(
|
||||
checksum,
|
||||
man_data_dic['perm'])
|
||||
man_an_dic['malware_permissions'] = mal_perms
|
||||
# Get icon
|
||||
get_icon_from_src(
|
||||
app_dic,
|
||||
man_data_dic['icons'])
|
||||
code_an_dic = code_analysis(
|
||||
checksum,
|
||||
app_dic['app_dir'],
|
||||
pro_type,
|
||||
app_dic['manifest_file'],
|
||||
man_data_dic['perm'])
|
||||
behaviour_an = behaviour_analysis.analyze(
|
||||
checksum,
|
||||
app_dic['app_dir'],
|
||||
pro_type)
|
||||
# Get the strings and metadata
|
||||
get_strings_metadata(
|
||||
checksum,
|
||||
None,
|
||||
app_dic['app_dir'],
|
||||
None,
|
||||
pro_type,
|
||||
['.java', '.kt'],
|
||||
code_an_dic)
|
||||
# Firebase DB Check
|
||||
code_an_dic['firebase'] = firebase_analysis(
|
||||
checksum,
|
||||
code_an_dic)
|
||||
# Domain Extraction and Malware Check
|
||||
code_an_dic['domains'] = MalwareDomainCheck().scan(
|
||||
checksum,
|
||||
code_an_dic['urls_list'])
|
||||
# Extract Trackers from Domains
|
||||
trk = Trackers.Trackers(
|
||||
checksum,
|
||||
None,
|
||||
app_dic['tools_dir'])
|
||||
trackers = trk.get_trackers_domains_or_deps(
|
||||
code_an_dic['domains'], [])
|
||||
context = save_get_ctx(
|
||||
app_dic,
|
||||
man_data_dic,
|
||||
man_an_dic,
|
||||
code_an_dic,
|
||||
cert_dic,
|
||||
[],
|
||||
{},
|
||||
behaviour_an,
|
||||
trackers,
|
||||
rescan,
|
||||
)
|
||||
else:
|
||||
msg = 'This ZIP Format is not supported'
|
||||
if api:
|
||||
return print_n_send_error_response(
|
||||
request,
|
||||
msg,
|
||||
True)
|
||||
else:
|
||||
print_n_send_error_response(request, msg, False)
|
||||
ctx = {
|
||||
'title': 'Invalid ZIP archive',
|
||||
'version': settings.MOBSF_VER,
|
||||
}
|
||||
template = 'general/zip.html'
|
||||
return render(request, template, ctx)
|
||||
context['appsec'] = get_android_dashboard(context, True)
|
||||
context['average_cvss'] = get_avg_cvss(
|
||||
context['code_analysis'])
|
||||
template = 'static_analysis/android_source_analysis.html'
|
||||
if api:
|
||||
return context
|
||||
else:
|
||||
return render(request, template, context)
|
||||
return src_analysis(request, app_dic, rescan, api)
|
||||
else:
|
||||
err = ('Only APK, JAR, AAR, SO and Zipped '
|
||||
'Android/iOS Source code supported now!')
|
||||
@@ -561,65 +131,3 @@ def static_analyzer(request, checksum, api=False):
|
||||
exp = exp.__doc__
|
||||
append_scan_status(checksum, errmsg, repr(exp))
|
||||
return print_n_send_error_response(request, repr(exp), api, exp)
|
||||
|
||||
|
||||
def is_android_source(app_dir):
|
||||
"""Detect Android Source and IDE Type."""
|
||||
# Eclipse
|
||||
man = os.path.isfile(os.path.join(app_dir, 'AndroidManifest.xml'))
|
||||
src = os.path.exists(os.path.join(app_dir, 'src/'))
|
||||
if man and src:
|
||||
return 'eclipse', True
|
||||
# Studio
|
||||
man = os.path.isfile(
|
||||
os.path.join(app_dir, 'app/src/main/AndroidManifest.xml'),
|
||||
)
|
||||
java = os.path.exists(os.path.join(app_dir, 'app/src/main/java/'))
|
||||
kotlin = os.path.exists(os.path.join(app_dir, 'app/src/main/kotlin/'))
|
||||
if man and (java or kotlin):
|
||||
return 'studio', True
|
||||
return None, False
|
||||
|
||||
|
||||
def valid_source_code(checksum, app_dir):
|
||||
"""Test if this is an valid source code zip."""
|
||||
try:
|
||||
msg = 'Detecting source code type'
|
||||
logger.info(msg)
|
||||
append_scan_status(checksum, msg)
|
||||
ide, is_and = is_android_source(app_dir)
|
||||
if ide:
|
||||
return ide, is_and
|
||||
# Relaxed Android Source check, one level down
|
||||
for x in os.listdir(app_dir):
|
||||
obj = os.path.join(app_dir, x)
|
||||
if not is_dir_exists(obj):
|
||||
continue
|
||||
ide, is_and = is_android_source(obj)
|
||||
if ide:
|
||||
move_to_parent(obj, app_dir)
|
||||
return ide, is_and
|
||||
# iOS Source
|
||||
xcode = [f for f in os.listdir(app_dir) if f.endswith('.xcodeproj')]
|
||||
if xcode:
|
||||
return 'ios', True
|
||||
# Relaxed iOS Source Check
|
||||
for x in os.listdir(app_dir):
|
||||
obj = os.path.join(app_dir, x)
|
||||
if not is_dir_exists(obj):
|
||||
continue
|
||||
if [f for f in os.listdir(obj) if f.endswith('.xcodeproj')]:
|
||||
return 'ios', True
|
||||
return '', False
|
||||
except Exception as exp:
|
||||
msg = 'Error identifying source code type from zip'
|
||||
logger.exception(msg)
|
||||
append_scan_status(checksum, msg, repr(exp))
|
||||
|
||||
|
||||
def move_to_parent(inside, app_dir):
|
||||
"""Move contents of inside to app dir."""
|
||||
for x in os.listdir(inside):
|
||||
full_path = os.path.join(inside, x)
|
||||
shutil.move(full_path, app_dir)
|
||||
shutil.rmtree(inside)
|
||||
|
||||
@@ -114,6 +114,8 @@ def a_analysis(request, app_dict, rescan, api):
|
||||
'bundle_supported_platforms': [],
|
||||
'bundle_version_name': '',
|
||||
}
|
||||
app_dict['infoplist'] = infoplist_dict
|
||||
app_dict['all_files'] = all_files
|
||||
app_dict['appstore'] = ''
|
||||
app_dict['secrets'] = []
|
||||
bin_dict = {
|
||||
@@ -172,10 +174,8 @@ def a_analysis(request, app_dict, rescan, api):
|
||||
code_dict['trackers'] = trackers
|
||||
context = save_get_ctx(
|
||||
app_dict,
|
||||
infoplist_dict,
|
||||
code_dict,
|
||||
bin_dict,
|
||||
all_files,
|
||||
rescan)
|
||||
context['virus_total'] = None
|
||||
if settings.VT_ENABLED:
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
"""Views to handle asynchronous tasks."""
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
from django.utils import timezone
|
||||
from django.shortcuts import render
|
||||
from django.conf import settings
|
||||
from django.http import (
|
||||
HttpResponseRedirect,
|
||||
JsonResponse,
|
||||
)
|
||||
from django.views.decorators.http import require_http_methods
|
||||
|
||||
from django_q.tasks import async_task
|
||||
|
||||
from mobsf.StaticAnalyzer.models import EnqueuedTask
|
||||
from mobsf.MobSF.views.authentication import (
|
||||
login_required,
|
||||
)
|
||||
from mobsf.MobSF.utils import (
|
||||
append_scan_status,
|
||||
get_scan_logs,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def async_analysis(checksum, app_name, func, *args):
|
||||
# Check if there is any task with the same checksum
|
||||
# created within the last 1 minute
|
||||
recent_task_exists = EnqueuedTask.objects.filter(
|
||||
checksum=checksum,
|
||||
created_at__gte=timezone.now() - timedelta(minutes=1),
|
||||
).exists()
|
||||
if recent_task_exists:
|
||||
logger.info('Analysis already in progress')
|
||||
return HttpResponseRedirect('/tasks')
|
||||
# Clear old tasks
|
||||
queue_size = settings.QUEUE_MAX_SIZE
|
||||
task_count = EnqueuedTask.objects.count()
|
||||
if task_count > queue_size:
|
||||
logger.info('Deleting oldest enqueued tasks')
|
||||
# Get IDs of tasks to delete (keep the latest queue_size)
|
||||
oldest_task_ids = list(
|
||||
EnqueuedTask.objects.order_by('created_at')
|
||||
.values_list('id', flat=True)[:task_count - queue_size])
|
||||
# Delete tasks by IDs
|
||||
EnqueuedTask.objects.filter(id__in=oldest_task_ids).delete()
|
||||
# Enqueue the task
|
||||
task_id = async_task(
|
||||
func,
|
||||
*args,
|
||||
queue=True,
|
||||
save=False)
|
||||
EnqueuedTask.objects.create(
|
||||
task_id=task_id,
|
||||
checksum=checksum,
|
||||
file_name=app_name[:254])
|
||||
msg = f'Scan Queued with ID: {task_id}'
|
||||
logger.info(msg)
|
||||
append_scan_status(checksum, msg)
|
||||
return HttpResponseRedirect('/tasks')
|
||||
|
||||
|
||||
def update_enqueued_task(checksum, app_name, status):
|
||||
"""Update the Enqueued Task and others that matches the checksum."""
|
||||
EnqueuedTask.objects.filter(checksum=checksum).update(
|
||||
app_name=app_name,
|
||||
completed_at=timezone.now(),
|
||||
status=status,
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
def get_live_status(enq):
|
||||
"""Get Live Status of the Task."""
|
||||
if enq.status not in {'Success', 'Failed'}:
|
||||
logs = get_scan_logs(enq.checksum)
|
||||
if logs:
|
||||
return logs[-1]
|
||||
return enq.status
|
||||
|
||||
|
||||
@login_required
|
||||
@require_http_methods(['POST', 'GET'])
|
||||
def list_tasks(request):
|
||||
if request.method == 'POST':
|
||||
enqueued = EnqueuedTask.objects.all().order_by('-created_at')
|
||||
task_data = []
|
||||
for enq in enqueued:
|
||||
# Enqueued task is not in the completed tasks
|
||||
task_data.append({
|
||||
'task_id': enq.task_id,
|
||||
'file_name': enq.file_name,
|
||||
'app_name': enq.app_name,
|
||||
'checksum': enq.checksum,
|
||||
'created_at': enq.created_at,
|
||||
'completed_at': enq.completed_at,
|
||||
'status': get_live_status(enq),
|
||||
})
|
||||
return JsonResponse(task_data, safe=False)
|
||||
context = {
|
||||
'title': 'Scan Tasks',
|
||||
'version': settings.MOBSF_VER,
|
||||
}
|
||||
return render(request, 'general/tasks.html', context)
|
||||
@@ -34,3 +34,4 @@ def strings_on_binary(bin_path):
|
||||
return list(set(strings_util(bin_path)))
|
||||
except Exception:
|
||||
logger.exception('Extracting strings from binary')
|
||||
return []
|
||||
|
||||
@@ -4,7 +4,6 @@ Shared Functions.
|
||||
|
||||
Module providing the shared functions for iOS and Android
|
||||
"""
|
||||
import io
|
||||
import hashlib
|
||||
import logging
|
||||
import os
|
||||
@@ -30,6 +29,7 @@ from mobsf.MobSF.utils import (
|
||||
is_path_traversal,
|
||||
is_safe_path,
|
||||
print_n_send_error_response,
|
||||
set_permissions,
|
||||
)
|
||||
from mobsf.MobSF.views.scanning import (
|
||||
add_to_recent_scan,
|
||||
@@ -62,7 +62,7 @@ def hash_gen(checksum, app_path) -> tuple:
|
||||
sha1 = hashlib.sha1()
|
||||
sha256 = hashlib.sha256()
|
||||
block_size = 65536
|
||||
with io.open(app_path, mode='rb') as afile:
|
||||
with open(app_path, mode='rb') as afile:
|
||||
buf = afile.read(block_size)
|
||||
while buf:
|
||||
sha1.update(buf)
|
||||
@@ -108,6 +108,9 @@ def unzip(checksum, app_path, ext_path):
|
||||
unzip_b = shutil.which('unzip')
|
||||
subprocess.call(
|
||||
[unzip_b, '-o', '-q', app_path, '-d', ext_path])
|
||||
# Set permissions, packed files
|
||||
# may not have proper permissions
|
||||
set_permissions(ext_path)
|
||||
dat = subprocess.check_output([unzip_b, '-qq', '-l', app_path])
|
||||
dat = dat.decode('utf-8').split('\n')
|
||||
files_det = ['Length Date Time Name']
|
||||
|
||||
@@ -84,6 +84,7 @@ def binary_analysis(checksum, src, tools_dir, app_dir, executable_name):
|
||||
'strings': [],
|
||||
'bin_info': {},
|
||||
'bin_type': '',
|
||||
'bin_path': None,
|
||||
}
|
||||
try:
|
||||
binary_findings = {}
|
||||
@@ -133,8 +134,8 @@ def binary_analysis(checksum, src, tools_dir, app_dir, executable_name):
|
||||
bin_dict['bin_info'] = bin_info
|
||||
bin_dict['bin_type'] = bin_type
|
||||
logger.info('Running strings against the Binary')
|
||||
bin_dict['strings'] = strings_on_binary(
|
||||
bin_path.as_posix())
|
||||
bin_dict['strings'] = strings_on_binary(bin_path.as_posix())
|
||||
bin_dict['bin_path'] = bin_path
|
||||
except Exception as exp:
|
||||
msg = 'Failed to run IPA Binary Analysis'
|
||||
logger.exception(msg)
|
||||
|
||||
@@ -10,7 +10,7 @@ from mobsf.MalwareAnalyzer.views.MalwareDomainCheck import (
|
||||
from mobsf.StaticAnalyzer.views.common.shared_func import (
|
||||
url_n_email_extract,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.sast_engine import scan
|
||||
from mobsf.StaticAnalyzer.views.sast_engine import SastEngine
|
||||
from mobsf.MobSF.utils import (
|
||||
append_scan_status,
|
||||
)
|
||||
@@ -54,49 +54,75 @@ def ios_source_analysis(checksum, src):
|
||||
domains = {}
|
||||
source_type = ''
|
||||
source_types = set()
|
||||
skp = settings.SKIP_CLASS_PATH
|
||||
msg = 'iOS Source Code Analysis Started'
|
||||
logger.info(msg)
|
||||
append_scan_status(checksum, msg)
|
||||
|
||||
# Code and API Analysis
|
||||
objc_findings = scan(
|
||||
checksum,
|
||||
objective_c_rules.as_posix(),
|
||||
{'.m'},
|
||||
[src],
|
||||
settings.SKIP_CLASS_PATH)
|
||||
# Objective C Analysis
|
||||
options = {
|
||||
'match_rules': objective_c_rules.as_posix(),
|
||||
'match_extensions': {'.m'},
|
||||
'ignore_paths': skp,
|
||||
}
|
||||
objc_findings = SastEngine(options, src).scan()
|
||||
if objc_findings:
|
||||
source_types.add(_SourceType.objc)
|
||||
swift_findings = scan(
|
||||
checksum,
|
||||
swift_rules.as_posix(),
|
||||
{'.swift'},
|
||||
[src],
|
||||
settings.SKIP_CLASS_PATH)
|
||||
|
||||
# Swift Analysis
|
||||
options = {
|
||||
'match_rules': swift_rules.as_posix(),
|
||||
'match_extensions': {'.swift'},
|
||||
'ignore_paths': skp,
|
||||
}
|
||||
swift_findings = SastEngine(options, src).scan()
|
||||
if swift_findings:
|
||||
source_types.add(_SourceType.swift)
|
||||
code_findings = merge_findings(swift_findings, objc_findings)
|
||||
msg = 'iOS Source Code Analysis Completed'
|
||||
logger.info(msg)
|
||||
append_scan_status(checksum, msg)
|
||||
|
||||
# API Analysis
|
||||
api_findings = scan(
|
||||
checksum,
|
||||
api_rules.as_posix(),
|
||||
{'.m', '.swift'},
|
||||
[src],
|
||||
settings.SKIP_CLASS_PATH)
|
||||
msg = 'iOS API Analysis Started'
|
||||
logger.info(msg)
|
||||
append_scan_status(checksum, msg)
|
||||
options = {
|
||||
'match_rules': api_rules.as_posix(),
|
||||
'match_extensions': {'.m', '.swift'},
|
||||
'ignore_paths': skp,
|
||||
}
|
||||
api_findings = SastEngine(options, src).scan()
|
||||
msg = 'iOS API Analysis Completed'
|
||||
logger.info(msg)
|
||||
append_scan_status(checksum, msg)
|
||||
|
||||
# Extract URLs and Emails
|
||||
skp = settings.SKIP_CLASS_PATH
|
||||
msg = 'Extracting Emails and URLs from Source Code'
|
||||
logger.info(msg)
|
||||
append_scan_status(checksum, msg)
|
||||
for pfile in Path(src).rglob('*'):
|
||||
if (
|
||||
(pfile.suffix in ('.m', '.swift')
|
||||
and any(skip_path in pfile.as_posix()
|
||||
for skip_path in skp) is False
|
||||
and pfile.is_dir() is False)
|
||||
and pfile.is_file())
|
||||
):
|
||||
relative_java_path = pfile.as_posix().replace(src, '')
|
||||
content = None
|
||||
try:
|
||||
content = pfile.read_text('utf-8', 'ignore')
|
||||
# Certain file path cannot be read in windows
|
||||
except Exception:
|
||||
continue
|
||||
relative_src_path = pfile.as_posix().replace(src, '')
|
||||
urls, urls_nf, emails_nf = url_n_email_extract(
|
||||
pfile.read_text('utf-8', 'ignore'), relative_java_path)
|
||||
content, relative_src_path)
|
||||
url_list.extend(urls)
|
||||
url_n_file.extend(urls_nf)
|
||||
email_n_file.extend(emails_nf)
|
||||
|
||||
msg = 'Email and URL Extraction Completed'
|
||||
logger.info(msg)
|
||||
append_scan_status(checksum, msg)
|
||||
if not source_types:
|
||||
source_type = _SourceType.nocode.value
|
||||
elif len(source_types) > 1:
|
||||
|
||||
@@ -81,12 +81,12 @@ def get_context_from_db_entry(db_entry):
|
||||
|
||||
|
||||
def get_context_from_analysis(app_dict,
|
||||
info_dict,
|
||||
code_dict,
|
||||
bin_dict,
|
||||
all_files):
|
||||
bin_dict):
|
||||
"""Get the context for IPA/ZIP from analysis results."""
|
||||
try:
|
||||
info_dict = app_dict['infoplist']
|
||||
all_files = app_dict['all_files']
|
||||
bundle_id = info_dict['id']
|
||||
code = process_suppression(
|
||||
code_dict['code_anal'],
|
||||
@@ -146,12 +146,12 @@ def get_context_from_analysis(app_dict,
|
||||
|
||||
def save_or_update(update_type,
|
||||
app_dict,
|
||||
info_dict,
|
||||
code_dict,
|
||||
bin_dict,
|
||||
all_files):
|
||||
bin_dict):
|
||||
"""Save/Update an IPA/ZIP DB entry."""
|
||||
try:
|
||||
info_dict = app_dict['infoplist']
|
||||
all_files = app_dict['all_files']
|
||||
values = {
|
||||
'FILE_NAME': app_dict['file_name'],
|
||||
'APP_NAME': info_dict['bin_name'],
|
||||
@@ -218,7 +218,7 @@ def save_or_update(update_type,
|
||||
append_scan_status(app_dict['md5_hash'], msg, repr(exp))
|
||||
|
||||
|
||||
def save_get_ctx(app_dict, pdict, code_dict, bin_dict, all_files, rescan):
|
||||
def save_get_ctx(app_dict, code_dict, bin_dict, rescan):
|
||||
# Saving to DB
|
||||
logger.info('Connecting to DB')
|
||||
if rescan:
|
||||
@@ -235,13 +235,9 @@ def save_get_ctx(app_dict, pdict, code_dict, bin_dict, all_files, rescan):
|
||||
save_or_update(
|
||||
action,
|
||||
app_dict,
|
||||
pdict,
|
||||
code_dict,
|
||||
bin_dict,
|
||||
all_files)
|
||||
bin_dict)
|
||||
return get_context_from_analysis(
|
||||
app_dict,
|
||||
pdict,
|
||||
code_dict,
|
||||
bin_dict,
|
||||
all_files)
|
||||
bin_dict)
|
||||
|
||||
@@ -101,6 +101,8 @@ def dylib_analysis(request, app_dict, rescan, api):
|
||||
'bundle_supported_platforms': [],
|
||||
'bundle_version_name': '',
|
||||
}
|
||||
app_dict['infoplist'] = infoplist_dict
|
||||
app_dict['all_files'] = all_files
|
||||
app_dict['appstore'] = ''
|
||||
app_dict['secrets'] = []
|
||||
bin_dict = {
|
||||
@@ -154,10 +156,8 @@ def dylib_analysis(request, app_dict, rescan, api):
|
||||
code_dict['trackers'] = trackers
|
||||
context = save_get_ctx(
|
||||
app_dict,
|
||||
infoplist_dict,
|
||||
code_dict,
|
||||
bin_dict,
|
||||
all_files,
|
||||
rescan)
|
||||
context['virus_total'] = None
|
||||
if settings.VT_ENABLED:
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# -*- coding: utf_8 -*-
|
||||
"""iOS File Analysis."""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import logging
|
||||
from pathlib import Path
|
||||
@@ -18,7 +17,7 @@ from mobsf.MobSF.utils import (
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def ios_list_files(md5_hash, src, binary_form, mode):
|
||||
def ios_list_files(md5_hash, src, mode):
|
||||
"""List iOS files."""
|
||||
try:
|
||||
msg = 'iOS File Analysis and Normalization'
|
||||
@@ -31,63 +30,73 @@ def ios_list_files(md5_hash, src, binary_form, mode):
|
||||
full_paths = []
|
||||
database = []
|
||||
plist = []
|
||||
for dirname, _, files in os.walk(src):
|
||||
for jfile in files:
|
||||
if not jfile.endswith('.DS_Store'):
|
||||
|
||||
file_path = os.path.join(src, dirname, jfile)
|
||||
if '__MACOSX' in file_path:
|
||||
continue
|
||||
if '+' in jfile:
|
||||
plus2x = os.path.join(
|
||||
src, dirname, jfile.replace('+', 'x'))
|
||||
shutil.move(file_path, plus2x)
|
||||
file_path = plus2x
|
||||
fileparam = file_path.replace(src, '')
|
||||
filez.append(fileparam)
|
||||
full_paths.append(file_path)
|
||||
ext = Path(jfile).suffix
|
||||
if ext in ('.cer', '.pem', '.cert', '.crt',
|
||||
'.pub', '.key', '.pfx', '.p12', '.der'):
|
||||
certz.append({
|
||||
'file_path': escape(file_path.replace(src, '')),
|
||||
'type': None,
|
||||
'hash': None,
|
||||
})
|
||||
if ext in ('.db', '.sqlitedb', '.sqlite', '.sqlite3'):
|
||||
database.append({
|
||||
'file_path': escape(fileparam),
|
||||
'type': mode,
|
||||
'hash': md5_hash,
|
||||
})
|
||||
mode = 'ios' if mode == 'zip' else 'ipa'
|
||||
|
||||
if jfile.endswith('.plist'):
|
||||
if binary_form:
|
||||
convert_bin_xml(file_path)
|
||||
plist.append({
|
||||
'file_path': escape(fileparam),
|
||||
'type': mode,
|
||||
'hash': md5_hash,
|
||||
})
|
||||
# Walk through the directory
|
||||
for file_path in Path(src).rglob('*'):
|
||||
if (file_path.is_file()
|
||||
and not (file_path.name.endswith('.DS_Store')
|
||||
or '__MACOSX' in str(file_path))):
|
||||
# Normalize '+' in file names
|
||||
if '+' in file_path.name:
|
||||
normalized_path = file_path.with_name(
|
||||
file_path.name.replace('+', 'x'))
|
||||
shutil.move(file_path, normalized_path)
|
||||
file_path = normalized_path
|
||||
|
||||
if len(database) > 0:
|
||||
# Append file details
|
||||
relative_path = file_path.relative_to(src)
|
||||
filez.append(str(relative_path))
|
||||
full_paths.append(str(file_path))
|
||||
|
||||
ext = file_path.suffix.lower()
|
||||
|
||||
# Categorize files by type
|
||||
if ext in {'.cer', '.pem', '.cert', '.crt', '.pub',
|
||||
'.key', '.pfx', '.p12', '.der'}:
|
||||
certz.append({
|
||||
'file_path': escape(str(relative_path)),
|
||||
'type': None,
|
||||
'hash': None,
|
||||
})
|
||||
elif ext in {'.db', '.sqlitedb', '.sqlite', '.sqlite3'}:
|
||||
database.append({
|
||||
'file_path': escape(str(relative_path)),
|
||||
'type': mode,
|
||||
'hash': md5_hash,
|
||||
})
|
||||
elif ext in {'.plist', '.json'}:
|
||||
if mode == 'ipa' and ext == '.plist':
|
||||
convert_bin_xml(file_path.as_posix())
|
||||
plist.append({
|
||||
'file_path': escape(str(relative_path)),
|
||||
'type': mode,
|
||||
'hash': md5_hash,
|
||||
})
|
||||
|
||||
# Group special files
|
||||
if database:
|
||||
sfiles.append({
|
||||
'issue': 'SQLite Files',
|
||||
'files': database,
|
||||
})
|
||||
if len(plist) > 0:
|
||||
if plist:
|
||||
sfiles.append({
|
||||
'issue': 'Plist Files',
|
||||
'files': plist,
|
||||
})
|
||||
if len(certz) > 0:
|
||||
if certz:
|
||||
sfiles.append({
|
||||
'issue': 'Certificate/Key Files Hardcoded inside the App.',
|
||||
'files': certz,
|
||||
})
|
||||
return {'files_short': filez,
|
||||
'files_long': full_paths,
|
||||
'special_files': sfiles}
|
||||
|
||||
return {
|
||||
'files_short': filez,
|
||||
'files_long': full_paths,
|
||||
'special_files': sfiles,
|
||||
}
|
||||
except Exception as exp:
|
||||
msg = 'iOS File Analysis'
|
||||
logger.exception(msg)
|
||||
|
||||
@@ -19,9 +19,10 @@ from mobsf.MobSF.utils import (
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_icon_from_ipa(app_dict, binary):
|
||||
def get_icon_from_ipa(app_dict):
|
||||
"""Get app icon from IPA."""
|
||||
try:
|
||||
binary = app_dict['infoplist'].get('bin')
|
||||
md5 = app_dict['md5_hash']
|
||||
bin_dir = app_dict['bin_dir']
|
||||
msg = 'Fetching IPA icon path'
|
||||
@@ -37,8 +38,8 @@ def get_icon_from_ipa(app_dict, binary):
|
||||
return
|
||||
icon_file = icons.pop()
|
||||
outfile = Path(settings.DWD_DIR) / f'{md5}-icon.png'
|
||||
app_dict['icon_path'] = outfile.name
|
||||
tools_dir = Path(settings.BASE_DIR) / 'StaticAnalyzer' / 'tools' / 'ios'
|
||||
cgbipng_bin = None
|
||||
arch = platform.machine()
|
||||
system = platform.system()
|
||||
# Uncrush PNG. CgBI -> PNG
|
||||
@@ -54,24 +55,27 @@ def get_icon_from_ipa(app_dict, binary):
|
||||
raise ValueError('PNG is not CgBI')
|
||||
except Exception:
|
||||
shutil.copy2(icon_file, outfile.as_posix())
|
||||
elif system == 'Windows' and arch in ('AMD64', 'x86'):
|
||||
cgbipng_bin = 'CgbiPngFix.exe'
|
||||
elif system == 'Linux' and arch == 'x86_64':
|
||||
cgbipng_bin = 'CgbiPngFix_amd64'
|
||||
elif system == 'Linux' and arch == 'aarch64':
|
||||
cgbipng_bin = 'CgbiPngFix_arm64'
|
||||
if cgbipng_bin:
|
||||
cbin = tools_dir / 'CgbiPngFix' / cgbipng_bin
|
||||
args = [cbin.as_posix(), '-i',
|
||||
icon_file, '-o', outfile.as_posix()]
|
||||
try:
|
||||
out = subprocess.run(args, capture_output=True)
|
||||
except Exception:
|
||||
# Fails or PNG is not crushed
|
||||
shutil.copy2(icon_file, outfile.as_posix())
|
||||
else:
|
||||
shutil.copy2(icon_file, outfile.as_posix())
|
||||
app_dict['icon_path'] = outfile.name
|
||||
# Windows/Linux
|
||||
cgbipng_bin = None
|
||||
if system == 'Windows' and arch in ('AMD64', 'x86'):
|
||||
cgbipng_bin = 'CgbiPngFix.exe'
|
||||
elif system == 'Linux' and arch == 'x86_64':
|
||||
cgbipng_bin = 'CgbiPngFix_amd64'
|
||||
elif system == 'Linux' and arch == 'aarch64':
|
||||
cgbipng_bin = 'CgbiPngFix_arm64'
|
||||
if cgbipng_bin:
|
||||
cbin = tools_dir / 'CgbiPngFix' / cgbipng_bin
|
||||
args = [cbin.as_posix(), '-i',
|
||||
icon_file, '-o', outfile.as_posix()]
|
||||
try:
|
||||
out = subprocess.run(args, capture_output=True)
|
||||
except Exception:
|
||||
# Fails or PNG is not crushed
|
||||
shutil.copy2(icon_file, outfile.as_posix())
|
||||
else:
|
||||
logger.warning('CgbiPngFix not available for %s %s', system, arch)
|
||||
shutil.copy2(icon_file, outfile.as_posix())
|
||||
except Exception as exp:
|
||||
msg = 'Error Fetching IPA icon'
|
||||
logger.exception(msg)
|
||||
|
||||
@@ -0,0 +1,330 @@
|
||||
# -*- coding: utf_8 -*-
|
||||
"""iOS Analysis."""
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
import mobsf.MalwareAnalyzer.views.Trackers as Trackers
|
||||
import mobsf.MalwareAnalyzer.views.VirusTotal as VirusTotal
|
||||
|
||||
from django.conf import settings
|
||||
from django.shortcuts import render
|
||||
|
||||
from mobsf.MobSF.utils import (
|
||||
append_scan_status,
|
||||
file_size,
|
||||
print_n_send_error_response,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.models import (
|
||||
StaticAnalyzerIOS,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.ios.appstore import app_search
|
||||
from mobsf.StaticAnalyzer.views.ios.binary_analysis import (
|
||||
binary_analysis,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.common.binary.lib_analysis import (
|
||||
library_analysis,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.ios.code_analysis import ios_source_analysis
|
||||
from mobsf.StaticAnalyzer.views.ios.db_interaction import (
|
||||
get_context_from_db_entry,
|
||||
save_get_ctx,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.ios.file_analysis import ios_list_files
|
||||
from mobsf.StaticAnalyzer.views.ios.icon_analysis import (
|
||||
get_icon_from_ipa,
|
||||
get_icon_source,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.ios.plist_analysis import (
|
||||
get_plist_secrets,
|
||||
plist_analysis,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.ios.strings import (
|
||||
get_strings_metadata,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.common.shared_func import (
|
||||
get_avg_cvss,
|
||||
hash_gen,
|
||||
strings_and_entropies,
|
||||
unzip,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.common.firebase import (
|
||||
firebase_analysis,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.common.appsec import (
|
||||
get_ios_dashboard,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.common.async_task import (
|
||||
async_analysis,
|
||||
update_enqueued_task,
|
||||
)
|
||||
from mobsf.MalwareAnalyzer.views.MalwareDomainCheck import (
|
||||
MalwareDomainCheck,
|
||||
)
|
||||
from mobsf.MobSF.views.authorization import (
|
||||
Permissions,
|
||||
has_permission,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def initialize_app_dic(app_dic, checksum, scan_type):
|
||||
"""Initialize App Dictionary."""
|
||||
app_dic['app_file'] = f'{checksum}.{scan_type}'
|
||||
app_dic['app_path'] = (app_dic['app_dirp'] / app_dic['app_file']).as_posix()
|
||||
app_dic['size'] = str(file_size(app_dic['app_path'])) + 'MB'
|
||||
app_dic['sha1'], app_dic['sha256'] = hash_gen(checksum, app_dic['app_path'])
|
||||
|
||||
|
||||
def extract_and_check_ipa(checksum, app_dic):
|
||||
"""Extract and Check IPA."""
|
||||
# EXTRACT IPA
|
||||
msg = 'Extracting IPA'
|
||||
logger.info(msg)
|
||||
append_scan_status(checksum, msg)
|
||||
unzip(
|
||||
checksum,
|
||||
app_dic['app_path'],
|
||||
app_dic['app_dir'])
|
||||
# Identify Payload directory
|
||||
dirs = app_dic['app_dirp'].glob('**/*')
|
||||
for _dir in dirs:
|
||||
if 'payload' in _dir.as_posix().lower():
|
||||
app_dic['bin_dir'] = app_dic['app_dirp'] / _dir
|
||||
break
|
||||
else:
|
||||
return False
|
||||
app_dic['bin_dir'] = app_dic['bin_dir'].as_posix() + '/'
|
||||
return True
|
||||
|
||||
|
||||
def common_analysis(scan_type, app_dic, checksum):
|
||||
"""Common Analysis for ipa and zip."""
|
||||
location = app_dic['app_dir']
|
||||
if scan_type == 'ipa':
|
||||
location = app_dic['bin_dir']
|
||||
# Get Files
|
||||
app_dic['all_files'] = ios_list_files(
|
||||
checksum,
|
||||
location,
|
||||
scan_type)
|
||||
# Plist files are converted to xml/readable for ipa
|
||||
app_dic['infoplist'] = plist_analysis(
|
||||
checksum,
|
||||
location,
|
||||
scan_type)
|
||||
app_dic['appstore'] = app_search(
|
||||
checksum,
|
||||
app_dic['infoplist'].get('id'))
|
||||
app_dic['secrets'] = get_plist_secrets(
|
||||
checksum,
|
||||
location)
|
||||
|
||||
|
||||
def common_firebase_and_trackers(code_dict, app_dic, checksum):
|
||||
"""Common Firebase and Trackers."""
|
||||
# Firebase Analysis
|
||||
code_dict['firebase'] = firebase_analysis(
|
||||
checksum,
|
||||
code_dict)
|
||||
# Extract Trackers from Domains
|
||||
trk = Trackers.Trackers(
|
||||
checksum,
|
||||
None,
|
||||
app_dic['tools_dir'])
|
||||
code_dict['trackers'] = trk.get_trackers_domains_or_deps(
|
||||
code_dict['domains'], [])
|
||||
|
||||
|
||||
def get_scan_subject(app_dic, bin_dict):
|
||||
"""Get Scan Subject."""
|
||||
app_name = None
|
||||
pkg_name = None
|
||||
if bin_dict.get('bin_path'):
|
||||
app_name = bin_dict['bin_path'].name if bin_dict['bin_path'] else None
|
||||
if app_dic.get('infoplist'):
|
||||
pkg_name = app_dic['infoplist'].get('id')
|
||||
|
||||
if app_name and pkg_name:
|
||||
return f'{app_name} ({pkg_name})'
|
||||
elif pkg_name:
|
||||
return pkg_name
|
||||
elif app_name:
|
||||
return app_name
|
||||
else:
|
||||
return 'iOS Binary'
|
||||
|
||||
|
||||
def ipa_analysis_task(checksum, app_dic, rescan, queue=False):
|
||||
"""IPA Analysis Task."""
|
||||
if queue:
|
||||
settings.ASYNC_ANALYSIS = True
|
||||
scan_type = 'ipa'
|
||||
append_scan_status(checksum, 'init')
|
||||
msg = 'iOS Binary (IPA) Analysis Started'
|
||||
logger.info(msg)
|
||||
append_scan_status(checksum, msg)
|
||||
initialize_app_dic(app_dic, checksum, scan_type)
|
||||
|
||||
if not extract_and_check_ipa(checksum, app_dic):
|
||||
msg = ('IPA is malformed! MobSF cannot find Payload directory')
|
||||
append_scan_status(checksum, 'IPA is malformed', msg)
|
||||
if queue:
|
||||
return update_enqueued_task(
|
||||
checksum, 'Failed', 'Failed')
|
||||
return None, msg
|
||||
common_analysis(scan_type, app_dic, checksum)
|
||||
|
||||
# IPA Binary Analysis
|
||||
bin_dict = binary_analysis(
|
||||
checksum,
|
||||
app_dic['bin_dir'],
|
||||
app_dic['tools_dir'],
|
||||
app_dic['app_dir'],
|
||||
app_dic['infoplist'].get('bin'))
|
||||
# Analyze dylibs and frameworks
|
||||
lb = library_analysis(
|
||||
checksum,
|
||||
app_dic['bin_dir'],
|
||||
'macho')
|
||||
bin_dict['dylib_analysis'] = lb['macho_analysis']
|
||||
bin_dict['framework_analysis'] = lb['framework_analysis']
|
||||
# Extract String metadata from binary
|
||||
code_dict = get_strings_metadata(
|
||||
app_dic,
|
||||
bin_dict,
|
||||
app_dic['all_files'],
|
||||
lb['macho_strings'])
|
||||
# Domain Extraction and Malware Check
|
||||
code_dict['domains'] = MalwareDomainCheck().scan(
|
||||
checksum,
|
||||
code_dict['urls_list'])
|
||||
# Get Icon
|
||||
get_icon_from_ipa(app_dic)
|
||||
# Firebase and Trackers
|
||||
common_firebase_and_trackers(code_dict, app_dic, checksum)
|
||||
|
||||
code_dict['api'] = {}
|
||||
code_dict['code_anal'] = {}
|
||||
context = save_get_ctx(
|
||||
app_dic,
|
||||
code_dict,
|
||||
bin_dict,
|
||||
rescan)
|
||||
if queue:
|
||||
subject = get_scan_subject(app_dic, bin_dict)
|
||||
return update_enqueued_task(
|
||||
checksum, subject, 'Success')
|
||||
return context, None
|
||||
|
||||
|
||||
def generate_dynamic_context(request, app_dic, context, checksum, api):
|
||||
"""Generate Dynamic Context."""
|
||||
context['virus_total'] = None
|
||||
if settings.VT_ENABLED:
|
||||
vt = VirusTotal.VirusTotal(checksum)
|
||||
context['virus_total'] = vt.get_result(app_dic['app_path'])
|
||||
context['appsec'] = get_ios_dashboard(context, True)
|
||||
context['average_cvss'] = get_avg_cvss(context['binary_analysis'])
|
||||
template = 'static_analysis/ios_binary_analysis.html'
|
||||
return context if api else render(request, template, context)
|
||||
|
||||
|
||||
def ipa_analysis(request, app_dic, rescan, api):
|
||||
"""IPA Analysis."""
|
||||
checksum = app_dic['md5_hash']
|
||||
ipa_db = StaticAnalyzerIOS.objects.filter(MD5=checksum)
|
||||
if ipa_db.exists() and not rescan:
|
||||
context = get_context_from_db_entry(ipa_db)
|
||||
return generate_dynamic_context(request, app_dic, context, checksum, api)
|
||||
else:
|
||||
# IPA Analysis
|
||||
if not has_permission(request, Permissions.SCAN, api):
|
||||
return print_n_send_error_response(request, 'Permission Denied', False)
|
||||
if settings.ASYNC_ANALYSIS:
|
||||
return async_analysis(
|
||||
checksum,
|
||||
app_dic.get('file_name', ''),
|
||||
ipa_analysis_task, checksum, app_dic, rescan)
|
||||
context, err = ipa_analysis_task(checksum, app_dic, rescan)
|
||||
if err:
|
||||
return print_n_send_error_response(request, err, api)
|
||||
return generate_dynamic_context(request, app_dic, context, checksum, api)
|
||||
|
||||
|
||||
def ios_analysis_task(checksum, app_dic, rescan, queue=False):
|
||||
"""IOS Analysis Task."""
|
||||
if queue:
|
||||
settings.ASYNC_ANALYSIS = True
|
||||
scan_type = 'zip'
|
||||
logger.info('iOS Source Code Analysis Started')
|
||||
initialize_app_dic(app_dic, checksum, scan_type)
|
||||
|
||||
# ANALYSIS BEGINS - Already Unzipped
|
||||
# append_scan_status init done in android static analyzer
|
||||
common_analysis(scan_type, app_dic, checksum)
|
||||
|
||||
# IOS Source Code Analysis
|
||||
code_dict = ios_source_analysis(
|
||||
checksum,
|
||||
app_dic['app_dir'])
|
||||
# Extract Strings and entropies from source code
|
||||
ios_strs = strings_and_entropies(
|
||||
checksum,
|
||||
Path(app_dic['app_dir']),
|
||||
['.swift', '.m', '.h', '.plist', '.json'])
|
||||
if ios_strs['secrets']:
|
||||
app_dic['secrets'].extend(list(ios_strs['secrets']))
|
||||
# Get App Icon
|
||||
get_icon_source(app_dic)
|
||||
# Firebase and Trackers
|
||||
common_firebase_and_trackers(code_dict, app_dic, checksum)
|
||||
|
||||
bin_dict = {
|
||||
'checksec': {},
|
||||
'libraries': [],
|
||||
'bin_code_analysis': {},
|
||||
'strings': list(ios_strs['strings']),
|
||||
'bin_info': {},
|
||||
'bin_type': code_dict['source_type'],
|
||||
'dylib_analysis': {},
|
||||
'framework_analysis': {},
|
||||
}
|
||||
context = save_get_ctx(
|
||||
app_dic,
|
||||
code_dict,
|
||||
bin_dict,
|
||||
rescan)
|
||||
if queue:
|
||||
subject = get_scan_subject(app_dic, bin_dict)
|
||||
return update_enqueued_task(
|
||||
checksum, subject, 'Success')
|
||||
return context
|
||||
|
||||
|
||||
def generate_dynamic_ios_context(request, context, api):
|
||||
"""Generate Dynamic Context for IOS."""
|
||||
context['appsec'] = get_ios_dashboard(context, True)
|
||||
context['average_cvss'] = get_avg_cvss(context['code_analysis'])
|
||||
template = 'static_analysis/ios_source_analysis.html'
|
||||
return context if api else render(request, template, context)
|
||||
|
||||
|
||||
def ios_analysis(request, app_dic, rescan, api):
|
||||
"""IOS Source Code Analysis."""
|
||||
checksum = app_dic['md5_hash']
|
||||
ios_zip_db = StaticAnalyzerIOS.objects.filter(MD5=checksum)
|
||||
if ios_zip_db.exists() and not rescan:
|
||||
context = get_context_from_db_entry(ios_zip_db)
|
||||
return generate_dynamic_ios_context(request, context, api)
|
||||
else:
|
||||
# IOS Source Analysis
|
||||
if not has_permission(request, Permissions.SCAN, api):
|
||||
return print_n_send_error_response(request, 'Permission Denied', False)
|
||||
if settings.ASYNC_ANALYSIS:
|
||||
return async_analysis(
|
||||
checksum,
|
||||
app_dic.get('file_name', ''),
|
||||
ios_analysis_task, checksum, app_dic, rescan)
|
||||
context = ios_analysis_task(checksum, app_dic, rescan)
|
||||
return generate_dynamic_ios_context(request, context, api)
|
||||
@@ -108,7 +108,7 @@ def convert_bin_xml(bin_xml_file):
|
||||
logger.warning('Failed to convert plist')
|
||||
|
||||
|
||||
def plist_analysis(checksum, src, is_source):
|
||||
def plist_analysis(checksum, src, scan_type):
|
||||
"""Plist Analysis."""
|
||||
try:
|
||||
msg = 'iOS Info.plist Analysis Started'
|
||||
@@ -134,7 +134,7 @@ def plist_analysis(checksum, src, is_source):
|
||||
}
|
||||
plist_file = None
|
||||
plist_files = []
|
||||
if is_source:
|
||||
if scan_type == 'zip':
|
||||
msg = 'Finding Info.plist in iOS Source'
|
||||
logger.info(msg)
|
||||
append_scan_status(checksum, msg)
|
||||
@@ -174,7 +174,7 @@ def plist_analysis(checksum, src, is_source):
|
||||
plist_obj).decode('utf-8', 'ignore')
|
||||
plist_info['bin_name'] = (plist_obj.get('CFBundleDisplayName', '')
|
||||
or plist_obj.get('CFBundleName', ''))
|
||||
if not plist_info['bin_name'] and not is_source:
|
||||
if not plist_info['bin_name'] and scan_type == 'ipa':
|
||||
# For iOS IPA
|
||||
plist_info['bin_name'] = dot_app_dir.replace('.app', '')
|
||||
plist_info['bin'] = plist_obj.get('CFBundleExecutable', '')
|
||||
|
||||
@@ -3,82 +3,35 @@
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
import mobsf.MalwareAnalyzer.views.Trackers as Trackers
|
||||
import mobsf.MalwareAnalyzer.views.VirusTotal as VirusTotal
|
||||
|
||||
from django.conf import settings
|
||||
from django.shortcuts import render
|
||||
from django.template.defaulttags import register
|
||||
|
||||
from mobsf.MobSF.utils import (
|
||||
append_scan_status,
|
||||
file_size,
|
||||
is_md5,
|
||||
print_n_send_error_response,
|
||||
relative_path,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.models import (
|
||||
RecentScansDB,
|
||||
StaticAnalyzerIOS,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.ios.appstore import app_search
|
||||
from mobsf.StaticAnalyzer.views.ios.binary_analysis import (
|
||||
binary_analysis,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.common.binary.lib_analysis import (
|
||||
library_analysis,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.ios.code_analysis import ios_source_analysis
|
||||
from mobsf.StaticAnalyzer.views.ios.db_interaction import (
|
||||
get_context_from_db_entry,
|
||||
save_get_ctx,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.ios.dylib import dylib_analysis
|
||||
from mobsf.StaticAnalyzer.views.ios.file_analysis import ios_list_files
|
||||
from mobsf.StaticAnalyzer.views.ios.icon_analysis import (
|
||||
get_icon_from_ipa,
|
||||
get_icon_source,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.ios.plist_analysis import (
|
||||
get_plist_secrets,
|
||||
plist_analysis,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.ios.strings import (
|
||||
get_strings_metadata,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.common.a import (
|
||||
a_analysis,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.common.shared_func import (
|
||||
get_avg_cvss,
|
||||
hash_gen,
|
||||
strings_and_entropies,
|
||||
unzip,
|
||||
from mobsf.StaticAnalyzer.views.ios.dylib import (
|
||||
dylib_analysis,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.common.firebase import (
|
||||
firebase_analysis,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.common.appsec import (
|
||||
get_ios_dashboard,
|
||||
)
|
||||
from mobsf.MalwareAnalyzer.views.MalwareDomainCheck import (
|
||||
MalwareDomainCheck,
|
||||
from mobsf.StaticAnalyzer.views.ios.ipa import (
|
||||
ios_analysis,
|
||||
ipa_analysis,
|
||||
)
|
||||
from mobsf.MobSF.views.authentication import (
|
||||
login_required,
|
||||
)
|
||||
from mobsf.MobSF.views.authorization import (
|
||||
Permissions,
|
||||
has_permission,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
register.filter('relative_path', relative_path)
|
||||
|
||||
##############################################################
|
||||
# iOS Static Code Analysis IPA and Source Code
|
||||
##############################################################
|
||||
|
||||
|
||||
@login_required
|
||||
def static_analyzer_ios(request, checksum, api=False):
|
||||
@@ -120,225 +73,20 @@ def static_analyzer_ios(request, checksum, api=False):
|
||||
app_dict['directory'] = Path(settings.BASE_DIR) # BASE DIR
|
||||
app_dict['file_name'] = filename # APP ORIGINAL NAME
|
||||
app_dict['md5_hash'] = checksum # MD5
|
||||
app_dir = Path(settings.UPLD_DIR) / checksum
|
||||
app_dict['app_dir'] = app_dir.as_posix() + '/'
|
||||
app_dict['app_dirp'] = Path(settings.UPLD_DIR) / checksum
|
||||
app_dict['app_dir'] = app_dict['app_dirp'].as_posix() + '/'
|
||||
tools_dir = app_dict[
|
||||
'directory'] / 'StaticAnalyzer' / 'tools' / 'ios'
|
||||
app_dict['tools_dir'] = tools_dir.as_posix()
|
||||
app_dict['icon_path'] = ''
|
||||
if file_type == 'ipa':
|
||||
app_dict['app_file'] = f'{checksum}.ipa'
|
||||
app_dict['app_path'] = app_dir / app_dict['app_file']
|
||||
app_dict['app_path'] = app_dict['app_path'].as_posix()
|
||||
# DB
|
||||
ipa_db = StaticAnalyzerIOS.objects.filter(MD5=checksum)
|
||||
if ipa_db.exists() and not rescan:
|
||||
context = get_context_from_db_entry(ipa_db)
|
||||
else:
|
||||
if not has_permission(request, Permissions.SCAN, api):
|
||||
return print_n_send_error_response(
|
||||
request,
|
||||
'Permission Denied',
|
||||
False)
|
||||
append_scan_status(checksum, 'init')
|
||||
msg = 'iOS Binary (IPA) Analysis Started'
|
||||
logger.info(msg)
|
||||
append_scan_status(checksum, msg)
|
||||
app_dict['size'] = str(
|
||||
file_size(app_dict['app_path'])) + 'MB' # FILE SIZE
|
||||
app_dict['sha1'], app_dict['sha256'] = hash_gen(
|
||||
checksum,
|
||||
app_dict['app_path']) # SHA1 & SHA256 HASHES
|
||||
msg = 'Extracting IPA'
|
||||
logger.info(msg)
|
||||
append_scan_status(checksum, msg)
|
||||
# EXTRACT IPA
|
||||
unzip(
|
||||
checksum,
|
||||
app_dict['app_path'],
|
||||
app_dict['app_dir'])
|
||||
# Identify Payload directory
|
||||
dirs = app_dir.glob('**/*')
|
||||
for _dir in dirs:
|
||||
if 'payload' in _dir.as_posix().lower():
|
||||
app_dict['bin_dir'] = app_dict['app_dir'] / _dir
|
||||
break
|
||||
else:
|
||||
msg = ('IPA is malformed! '
|
||||
'MobSF cannot find Payload directory')
|
||||
append_scan_status(checksum, 'IPA is malformed', msg)
|
||||
return print_n_send_error_response(
|
||||
request,
|
||||
msg,
|
||||
api)
|
||||
app_dict['bin_dir'] = app_dict['bin_dir'].as_posix() + '/'
|
||||
# Get Files
|
||||
all_files = ios_list_files(
|
||||
checksum,
|
||||
app_dict['bin_dir'],
|
||||
True,
|
||||
'ipa')
|
||||
# Plist files are converted to xml/readable
|
||||
infoplist_dict = plist_analysis(
|
||||
checksum,
|
||||
app_dict['bin_dir'],
|
||||
False)
|
||||
app_dict['appstore'] = app_search(
|
||||
checksum,
|
||||
infoplist_dict.get('id'))
|
||||
app_dict['secrets'] = get_plist_secrets(
|
||||
checksum,
|
||||
app_dict['bin_dir'])
|
||||
bin_dict = binary_analysis(
|
||||
checksum,
|
||||
app_dict['bin_dir'],
|
||||
app_dict['tools_dir'],
|
||||
app_dict['app_dir'],
|
||||
infoplist_dict.get('bin'))
|
||||
# Analyze dylibs and frameworks
|
||||
lb = library_analysis(
|
||||
checksum,
|
||||
app_dict['bin_dir'],
|
||||
'macho')
|
||||
bin_dict['dylib_analysis'] = lb['macho_analysis']
|
||||
bin_dict['framework_analysis'] = lb['framework_analysis']
|
||||
# Get Icon
|
||||
get_icon_from_ipa(
|
||||
app_dict,
|
||||
infoplist_dict.get('bin'))
|
||||
# Extract String metadata
|
||||
code_dict = get_strings_metadata(
|
||||
app_dict,
|
||||
bin_dict,
|
||||
all_files,
|
||||
lb['macho_strings'])
|
||||
# Domain Extraction and Malware Check
|
||||
code_dict['domains'] = MalwareDomainCheck().scan(
|
||||
checksum,
|
||||
code_dict['urls_list'])
|
||||
# Extract Trackers from Domains
|
||||
trk = Trackers.Trackers(
|
||||
checksum,
|
||||
None,
|
||||
app_dict['tools_dir'])
|
||||
trackers = trk.get_trackers_domains_or_deps(
|
||||
code_dict['domains'], [])
|
||||
code_dict['api'] = {}
|
||||
code_dict['code_anal'] = {}
|
||||
code_dict['firebase'] = firebase_analysis(
|
||||
checksum,
|
||||
code_dict)
|
||||
code_dict['trackers'] = trackers
|
||||
context = save_get_ctx(
|
||||
app_dict,
|
||||
infoplist_dict,
|
||||
code_dict,
|
||||
bin_dict,
|
||||
all_files,
|
||||
rescan)
|
||||
context['virus_total'] = None
|
||||
if settings.VT_ENABLED:
|
||||
vt = VirusTotal.VirusTotal(checksum)
|
||||
context['virus_total'] = vt.get_result(
|
||||
app_dict['app_path'])
|
||||
context['appsec'] = get_ios_dashboard(context, True)
|
||||
context['average_cvss'] = get_avg_cvss(
|
||||
context['binary_analysis'])
|
||||
template = 'static_analysis/ios_binary_analysis.html'
|
||||
if api:
|
||||
return context
|
||||
else:
|
||||
return render(request, template, context)
|
||||
return ipa_analysis(request, app_dict, rescan, api)
|
||||
elif file_type == 'dylib':
|
||||
return dylib_analysis(request, app_dict, rescan, api)
|
||||
elif file_type == 'a':
|
||||
return a_analysis(request, app_dict, rescan, api)
|
||||
elif file_type in ('ios', 'zip'):
|
||||
ios_zip_db = StaticAnalyzerIOS.objects.filter(
|
||||
MD5=checksum)
|
||||
if ios_zip_db.exists() and not rescan:
|
||||
context = get_context_from_db_entry(ios_zip_db)
|
||||
else:
|
||||
if not has_permission(request, Permissions.SCAN, api):
|
||||
return print_n_send_error_response(
|
||||
request,
|
||||
'Permission Denied',
|
||||
False)
|
||||
logger.info('iOS Source Code Analysis Started')
|
||||
app_dict['app_file'] = app_dict[
|
||||
'md5_hash'] + '.zip' # NEW FILENAME
|
||||
app_dict['app_path'] = app_dir / app_dict['app_file']
|
||||
app_dict['app_path'] = app_dict['app_path'].as_posix()
|
||||
# ANALYSIS BEGINS - Already Unzipped
|
||||
# append_scan_status init done in android static analyzer
|
||||
app_dict['size'] = str(
|
||||
file_size(app_dict['app_path'])) + 'MB' # FILE SIZE
|
||||
app_dict['sha1'], app_dict['sha256'] = hash_gen(
|
||||
checksum,
|
||||
app_dict['app_path']) # SHA1 & SHA256 HASHES
|
||||
all_files = ios_list_files(
|
||||
checksum,
|
||||
app_dict['app_dir'],
|
||||
False,
|
||||
'ios')
|
||||
infoplist_dict = plist_analysis(
|
||||
checksum,
|
||||
app_dict['app_dir'],
|
||||
True)
|
||||
app_dict['appstore'] = app_search(
|
||||
checksum,
|
||||
infoplist_dict.get('id'))
|
||||
app_dict['secrets'] = get_plist_secrets(
|
||||
checksum,
|
||||
app_dict['app_dir'])
|
||||
code_analysis_dic = ios_source_analysis(
|
||||
checksum,
|
||||
app_dict['app_dir'])
|
||||
ios_strs = strings_and_entropies(
|
||||
checksum,
|
||||
Path(app_dict['app_dir']),
|
||||
['.swift', '.m', '.h', '.plist'])
|
||||
if ios_strs['secrets']:
|
||||
app_dict['secrets'].extend(list(ios_strs['secrets']))
|
||||
# Get App Icon
|
||||
get_icon_source(app_dict)
|
||||
# Firebase DB Check
|
||||
code_analysis_dic['firebase'] = firebase_analysis(
|
||||
checksum,
|
||||
code_analysis_dic)
|
||||
# Extract Trackers from Domains
|
||||
trk = Trackers.Trackers(
|
||||
checksum,
|
||||
None,
|
||||
app_dict['tools_dir'])
|
||||
trackers = trk.get_trackers_domains_or_deps(
|
||||
code_analysis_dic['domains'], [])
|
||||
code_analysis_dic['trackers'] = trackers
|
||||
fake_bin_dict = {
|
||||
'checksec': {},
|
||||
'libraries': [],
|
||||
'bin_code_analysis': {},
|
||||
'strings': list(ios_strs['strings']),
|
||||
'bin_info': {},
|
||||
'bin_type': code_analysis_dic['source_type'],
|
||||
'dylib_analysis': {},
|
||||
'framework_analysis': {},
|
||||
}
|
||||
context = save_get_ctx(
|
||||
app_dict,
|
||||
infoplist_dict,
|
||||
code_analysis_dic,
|
||||
fake_bin_dict,
|
||||
all_files,
|
||||
rescan)
|
||||
context['appsec'] = get_ios_dashboard(context, True)
|
||||
context['average_cvss'] = get_avg_cvss(
|
||||
context['code_analysis'])
|
||||
template = 'static_analysis/ios_source_analysis.html'
|
||||
if api:
|
||||
return context
|
||||
else:
|
||||
return render(request, template, context)
|
||||
return ios_analysis(request, app_dict, rescan, api)
|
||||
else:
|
||||
err = ('File Type not supported, '
|
||||
'Only IPA, A, DYLIB and ZIP are supported')
|
||||
|
||||
@@ -1,88 +1,118 @@
|
||||
# -*- coding: utf_8 -*-
|
||||
"""SAST engine."""
|
||||
import logging
|
||||
import platform
|
||||
|
||||
from libsast import Scanner
|
||||
from libsast.core_matcher.pattern_matcher import PatternMatcher
|
||||
from libsast.core_matcher.choice_matcher import ChoiceMatcher
|
||||
from libsast.common import get_worker_count
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from mobsf.MobSF.utils import (
|
||||
append_scan_status,
|
||||
run_with_timeout,
|
||||
settings_enabled,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def scan(checksum, rule, extensions, paths, ignore_paths=None):
|
||||
"""The libsast scan."""
|
||||
try:
|
||||
options = {
|
||||
'match_rules': rule,
|
||||
'match_extensions': extensions,
|
||||
'ignore_paths': ignore_paths,
|
||||
'show_progress': False}
|
||||
scanner = Scanner(options, paths)
|
||||
res = run_with_timeout(scanner.scan, settings.SAST_TIMEOUT)
|
||||
if res and res.get('pattern_matcher'):
|
||||
return format_findings(res['pattern_matcher'], paths[0])
|
||||
except Exception as exp:
|
||||
msg = 'libsast scan failed'
|
||||
logger.exception(msg)
|
||||
append_scan_status(checksum, msg, repr(exp))
|
||||
return {}
|
||||
def get_multiprocessing_strategy():
|
||||
"""Get the multiprocessing strategy."""
|
||||
if settings.MULTIPROCESSING:
|
||||
# Settings take precedence
|
||||
mp = settings.MULTIPROCESSING
|
||||
elif platform.system() == 'Windows' and settings.ASYNC_ANALYSIS:
|
||||
# Set to thread on Windows for async analysis
|
||||
mp = 'thread'
|
||||
elif settings.ASYNC_ANALYSIS:
|
||||
# Set to billiard for async analysis
|
||||
mp = 'billiard'
|
||||
else:
|
||||
# Defaults to processpoolexecutor for sync analysis
|
||||
mp = 'default'
|
||||
return mp
|
||||
|
||||
|
||||
def niap_scan(checksum, rule, extensions, paths, apath, ignore_paths=None):
|
||||
"""NIAP scan."""
|
||||
if not settings_enabled('NIAP_ENABLED'):
|
||||
return {}
|
||||
try:
|
||||
msg = 'Running NIAP Analyzer'
|
||||
logger.info(msg)
|
||||
append_scan_status(checksum, msg)
|
||||
if not apath:
|
||||
apath = ''
|
||||
options = {
|
||||
'choice_rules': rule,
|
||||
'alternative_path': apath,
|
||||
'choice_extensions': extensions,
|
||||
'ignore_paths': ignore_paths,
|
||||
'show_progress': False}
|
||||
scanner = Scanner(options, paths)
|
||||
res = run_with_timeout(scanner.scan, settings.SAST_TIMEOUT)
|
||||
if res and res.get('choice_matcher'):
|
||||
return res['choice_matcher']
|
||||
except Exception as exp:
|
||||
msg = 'NIAP Analyzer Failed'
|
||||
logger.exception(msg)
|
||||
append_scan_status(checksum, msg, repr(exp))
|
||||
return {}
|
||||
class SastEngine:
|
||||
def __init__(self, options, path):
|
||||
self.root = path
|
||||
mp = get_multiprocessing_strategy()
|
||||
cpu_core = get_worker_count()
|
||||
options['cpu_core'] = cpu_core
|
||||
options['multiprocessing'] = mp
|
||||
if mp != 'default':
|
||||
logger.debug(
|
||||
'Multiprocessing strategy set to %s with (%d) CPU cores', mp, cpu_core)
|
||||
self.scan_paths = Scanner(options, [path]).get_scan_files()
|
||||
self.pattern_matcher = PatternMatcher(options)
|
||||
|
||||
def scan(self):
|
||||
"""Scan the files with given rules."""
|
||||
finds = run_with_timeout(
|
||||
self.pattern_matcher.scan,
|
||||
settings.SAST_TIMEOUT,
|
||||
self.scan_paths)
|
||||
return self.format_findings(finds)
|
||||
|
||||
def read_files(self):
|
||||
"""Read the files."""
|
||||
logger.info('Reading file contents for SAST')
|
||||
return self.pattern_matcher.read_file_contents(self.scan_paths)
|
||||
|
||||
def run_rules(self, file_contents, rule_path):
|
||||
"""Run the rules."""
|
||||
finds = run_with_timeout(
|
||||
self.pattern_matcher.regex_scan,
|
||||
settings.SAST_TIMEOUT,
|
||||
file_contents,
|
||||
rule_path)
|
||||
return self.format_findings(finds)
|
||||
|
||||
def format_findings(self, findings):
|
||||
"""Format the findings."""
|
||||
for details in findings.values():
|
||||
tmp_dict = {}
|
||||
for file_meta in details['files']:
|
||||
file_meta['file_path'] = file_meta[
|
||||
'file_path'].replace(self.root, '', 1)
|
||||
file_path = file_meta['file_path']
|
||||
start = file_meta['match_lines'][0]
|
||||
end = file_meta['match_lines'][1]
|
||||
if start == end:
|
||||
match_lines = start
|
||||
else:
|
||||
exp_lines = []
|
||||
for i in range(start, end + 1):
|
||||
exp_lines.append(i)
|
||||
match_lines = ','.join(str(m) for m in exp_lines)
|
||||
if file_path not in tmp_dict:
|
||||
tmp_dict[file_path] = str(match_lines)
|
||||
elif tmp_dict[file_path].endswith(','):
|
||||
tmp_dict[file_path] += str(match_lines)
|
||||
else:
|
||||
tmp_dict[file_path] += ',' + str(match_lines)
|
||||
details['files'] = tmp_dict
|
||||
return findings
|
||||
|
||||
|
||||
def format_findings(findings, root):
|
||||
"""Format findings."""
|
||||
for details in findings.values():
|
||||
tmp_dict = {}
|
||||
for file_meta in details['files']:
|
||||
file_meta['file_path'] = file_meta[
|
||||
'file_path'].replace(root, '', 1)
|
||||
file_path = file_meta['file_path']
|
||||
start = file_meta['match_lines'][0]
|
||||
end = file_meta['match_lines'][1]
|
||||
if start == end:
|
||||
match_lines = start
|
||||
else:
|
||||
exp_lines = []
|
||||
for i in range(start, end + 1):
|
||||
exp_lines.append(i)
|
||||
match_lines = ','.join(str(m) for m in exp_lines)
|
||||
if file_path not in tmp_dict:
|
||||
tmp_dict[file_path] = str(match_lines)
|
||||
elif tmp_dict[file_path].endswith(','):
|
||||
tmp_dict[file_path] += str(match_lines)
|
||||
else:
|
||||
tmp_dict[file_path] += ',' + str(match_lines)
|
||||
details['files'] = tmp_dict
|
||||
return findings
|
||||
class ChoiceEngine:
|
||||
def __init__(self, options, path):
|
||||
self.root = path
|
||||
options['cpu_core'] = get_worker_count()
|
||||
options['multiprocessing'] = get_multiprocessing_strategy()
|
||||
self.scan_paths = Scanner(options, [path]).get_scan_files()
|
||||
self.choice_matcher = ChoiceMatcher(options)
|
||||
|
||||
def read_files(self):
|
||||
"""Read the files."""
|
||||
logger.info('Reading file contents for NIAP Scan')
|
||||
return self.choice_matcher.read_file_contents(self.scan_paths)
|
||||
|
||||
def run_rules(self, file_contents, rule_path):
|
||||
"""Run the rules."""
|
||||
return run_with_timeout(
|
||||
self.choice_matcher.regex_scan,
|
||||
settings.SAST_TIMEOUT,
|
||||
file_contents,
|
||||
rule_path)
|
||||
|
||||
@@ -194,41 +194,56 @@ progress::-webkit-progress-value {
|
||||
/* The snackbar - position it at the bottom and in the middle of the screen */
|
||||
#snackbar {
|
||||
visibility: hidden; /* Hidden by default. Visible on click */
|
||||
min-width: 250px; /* Set a default minimum width */
|
||||
width: 100%; /* Make the snackbar full width */
|
||||
background-color: #333; /* Black background color */
|
||||
color: #fff; /* White text color */
|
||||
text-align: center; /* Centered text */
|
||||
border-radius: 2px; /* Rounded borders */
|
||||
border-radius: 0; /* Remove rounded borders for full width */
|
||||
padding: 16px; /* Padding */
|
||||
z-index: 1; /* Add a z-index if needed */
|
||||
left: 50%; /* Center the snackbar */
|
||||
left: 0; /* Align to the left edge of the screen */
|
||||
bottom: 30px; /* 30px from the bottom */
|
||||
position: fixed; /* Ensure it stays at the bottom of the viewport */
|
||||
}
|
||||
|
||||
/* Show the snackbar when clicking on a button (class added with JavaScript) */
|
||||
#snackbar.show {
|
||||
visibility: visible; /* Show the snackbar */
|
||||
/* Add animation: Take 0.5 seconds to fade in and out the snackbar.
|
||||
However, delay the fade out process for 2.5 seconds */
|
||||
-webkit-animation: fadein 0.5s, fadeout 0.5s 2.5s;
|
||||
animation: fadein 0.5s, fadeout 0.5s 2.5s;
|
||||
/* Alert bar class */
|
||||
.alert-bar {
|
||||
position: fixed; /* Sticks to the bottom of the screen */
|
||||
bottom: 0; /* Aligns to the bottom */
|
||||
left: 0; /* Aligns to the left edge */
|
||||
width: 100%; /* Spans the full width of the screen */
|
||||
background-color: #00000021; /* Red background (customize as needed) */
|
||||
color: #fff; /* White text color */
|
||||
text-align: center; /* Centered text */
|
||||
padding: 16px; /* Padding inside the bar */
|
||||
font-size: 16px; /* Font size for the text */
|
||||
z-index: 9999; /* Ensure it appears above other content */
|
||||
box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.2); /* Adds a subtle shadow */
|
||||
visibility: hidden; /* Hidden by default */
|
||||
opacity: 0; /* Fully transparent */
|
||||
transition: visibility 0s, opacity 0.5s ease-in-out; /* Smooth fade-in/out */
|
||||
}
|
||||
|
||||
/* Animations to fade the snackbar in and out */
|
||||
|
||||
|
||||
@keyframes fadein {
|
||||
from {bottom: 0; opacity: 0;}
|
||||
to {bottom: 30px; opacity: 1;}
|
||||
/* Show the alert bar */
|
||||
.alert-bar.show {
|
||||
visibility: visible; /* Make it visible */
|
||||
opacity: 1; /* Fully opaque */
|
||||
}
|
||||
|
||||
/*
|
||||
@keyframes fadeout {
|
||||
from {bottom: 30px; opacity: 1;}
|
||||
to {bottom: 0; opacity: 0;}
|
||||
} */
|
||||
/* Optional: Keyframes for additional animation */
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
transform: translateY(100%);
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Snackbar ends */
|
||||
.alert-bar.slide-up {
|
||||
animation: slideUp 0.5s ease-in-out;
|
||||
}
|
||||
/* Alert bar ends */
|
||||
|
||||
/* Logo padding ^*/
|
||||
|
||||
|
||||
@@ -22,8 +22,13 @@
|
||||
<link rel="stylesheet" href="https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css">
|
||||
<!-- Google Font: Source Sans Pro -->
|
||||
<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
zoom: 0.8;
|
||||
}
|
||||
</style>
|
||||
{% block extra_css %}
|
||||
|
||||
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body class="hold-transition {% block sidebar_option %} {% endblock %} layout-fixed layout-navbar-fixed">
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
</br>
|
||||
<p>© {% now "Y" %} Mobile Security Framework - MobSF {{ version }}</p>
|
||||
</div>
|
||||
<div id="snackbar">Analysis started! Please wait to be redirected or check recent scans after sometime.</div>
|
||||
<div id="alert" class="alert-bar">Analysis started! Please wait to be redirected or check recent scans after sometime.</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
@@ -99,17 +99,17 @@
|
||||
// Result View
|
||||
function load_result(url){
|
||||
hide_loader();
|
||||
var xhr = new XMLHttpRequest()
|
||||
xhr.open('GET', url, true)
|
||||
xhr.addEventListener("loadstart", function(evt){
|
||||
var x = document.getElementById("snackbar");
|
||||
x.className = "show";
|
||||
setTimeout(function(){ x.className = x.className.replace("show", ""); }, 10000);
|
||||
})
|
||||
xhr.addEventListener("load", function (evt) {
|
||||
window.location.href = window.location.href + url;
|
||||
}, false);
|
||||
xhr.send();
|
||||
// Display alert notification
|
||||
var x = document.getElementById("alert");
|
||||
x.className = "alert-bar show slide-up";
|
||||
setTimeout(function () {
|
||||
x.className = x.className.replace("show", "");
|
||||
}, 10000);
|
||||
|
||||
// Redirect after showing alertbar
|
||||
setTimeout(function () {
|
||||
window.location.href = url;
|
||||
}, 1000); // Adjust delay as needed
|
||||
}
|
||||
|
||||
//Download and scan APK
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
|
||||
{% extends "base/base_layout.html" %}
|
||||
{% load static %}
|
||||
{% block sidebar_option %}
|
||||
sidebar-collapse
|
||||
{% endblock %}
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
/* Loader Spinner */
|
||||
.loader {
|
||||
border: 2px solid #f3f3f3; /* Light gray */
|
||||
border-top: 2px solid #3498db; /* Blue */
|
||||
border-radius: 50%;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
animation: spin 1s linear infinite;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="content-wrapper">
|
||||
<div class="content-header">
|
||||
</div>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title"><i class="fa fa-rocket"></i> Scan Queue</h3>
|
||||
<p class="lead">The scan results will be available in <strong><a href="{% url 'recent' %}">Recent Scans</a></strong> upon completion.</p>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="table-responsive">
|
||||
<table id="tasks-table" class="table table-bordered table-hover table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Scan Task</th>
|
||||
<th>Filename</th>
|
||||
<th>Queued At</th>
|
||||
<th>Completed At</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Rows will be populated dynamically -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- /.box-body -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block extra_scripts %}
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const tasksTableBody = document.querySelector("#tasks-table tbody");
|
||||
|
||||
// Fetch tasks using POST request
|
||||
async function fetchTasks() {
|
||||
const response = await fetch("{% url 'list_tasks' %}", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-CSRFToken": "{{ csrf_token }}",
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error("Failed to fetch tasks.");
|
||||
return [];
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
function addLoader(statusCell) {
|
||||
const loader = document.createElement("span");
|
||||
loader.classList.add("loader");
|
||||
loader.style.marginLeft = "10px";
|
||||
statusCell.appendChild(loader);
|
||||
}
|
||||
|
||||
function addAppName(task, statusCell) {
|
||||
if (task.app_name) {
|
||||
const strongElement = document.createElement("strong");
|
||||
strongElement.textContent = ` ${task.app_name}`;
|
||||
statusCell.appendChild(document.createElement("br"));
|
||||
statusCell.appendChild(strongElement);
|
||||
}
|
||||
}
|
||||
|
||||
function addReportLink(task, statusCell) {
|
||||
const reportLink = document.createElement("a");
|
||||
reportLink.href = `/search?query=${encodeURIComponent(task.checksum)}`;
|
||||
reportLink.textContent = ' View Report';
|
||||
|
||||
const icon = document.createElement("i");
|
||||
icon.classList.add("fa", "fa-eye");
|
||||
reportLink.prepend(icon);
|
||||
|
||||
statusCell.appendChild(document.createElement("br"));
|
||||
statusCell.appendChild(reportLink);
|
||||
}
|
||||
|
||||
function updateCellStatus(task, statusCell, completedCell) {
|
||||
statusCell.textContent = task.status.status || task.status;
|
||||
|
||||
if (!task.completed_at) {
|
||||
addLoader(statusCell);
|
||||
} else {
|
||||
completedCell.textContent = new Date(task.completed_at).toLocaleString();
|
||||
if (task.status === "Failed") {
|
||||
statusCell.classList.add("text-danger");
|
||||
statusCell.classList.remove("text-warning");
|
||||
} else if (task.status === "Success") {
|
||||
statusCell.classList.add("text-success");
|
||||
statusCell.classList.remove("text-warning");
|
||||
}
|
||||
if (task.status === "Success") {
|
||||
addReportLink(task, statusCell);
|
||||
}
|
||||
}
|
||||
addAppName(task, statusCell);
|
||||
}
|
||||
|
||||
function buildTableRow(task) {
|
||||
const row = document.createElement("tr");
|
||||
row.setAttribute("data-task-id", task.task_id);
|
||||
|
||||
// Scan Task
|
||||
const taskIdCell = document.createElement("td");
|
||||
const taskP = document.createElement("p");
|
||||
const checksumP = document.createElement("p");
|
||||
|
||||
taskP.innerHTML = '<strong>Task ID: </strong> ';
|
||||
taskP.appendChild(document.createTextNode(task.task_id));
|
||||
|
||||
checksumP.innerHTML = '<strong>Checksum: </strong> ';
|
||||
checksumP.appendChild(document.createTextNode(task.checksum));
|
||||
|
||||
taskIdCell.appendChild(taskP);
|
||||
taskIdCell.appendChild(checksumP);
|
||||
|
||||
// File Name
|
||||
const fileNameCell = document.createElement("td");
|
||||
fileNameCell.textContent = task.file_name;
|
||||
|
||||
// Queued At
|
||||
const queuedAtCell = document.createElement("td");
|
||||
const date = new Date(task.created_at);
|
||||
queuedAtCell.textContent = date.toLocaleString();
|
||||
|
||||
// Completed At
|
||||
const completedCell = document.createElement("td");
|
||||
completedCell.setAttribute("id", `completed-${task.task_id}`);
|
||||
completedCell.textContent = task.completed_at ? new Date(task.completed_at).toLocaleString() : null;
|
||||
|
||||
// Status
|
||||
const statusCell = document.createElement("td");
|
||||
statusCell.setAttribute("id", `status-${task.task_id}`);
|
||||
statusCell.classList.add(task.completed_at ? "text-success" : "text-warning");
|
||||
|
||||
updateCellStatus(task, statusCell, completedCell);
|
||||
|
||||
// Append cells to row
|
||||
row.appendChild(taskIdCell);
|
||||
row.appendChild(fileNameCell);
|
||||
row.appendChild(queuedAtCell);
|
||||
row.appendChild(completedCell);
|
||||
row.appendChild(statusCell);
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
async function renderTable() {
|
||||
const tasks = await fetchTasks();
|
||||
tasksTableBody.innerHTML = ""; // Clear existing rows
|
||||
|
||||
if (tasks.length === 0) {
|
||||
const noTasksRow = document.createElement("tr");
|
||||
const noTasksCell = document.createElement("td");
|
||||
noTasksCell.setAttribute("colspan", "5");
|
||||
noTasksCell.textContent = "No tasks in queue.";
|
||||
noTasksRow.appendChild(noTasksCell);
|
||||
tasksTableBody.appendChild(noTasksRow);
|
||||
} else {
|
||||
tasks.forEach((task) => {
|
||||
const row = buildTableRow(task);
|
||||
tasksTableBody.appendChild(row);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function updateTaskStatuses() {
|
||||
const tasks = await fetchTasks();
|
||||
tasks.forEach((task) => {
|
||||
const statusCell = document.getElementById(`status-${task.task_id}`);
|
||||
const completedCell = document.getElementById(`completed-${task.task_id}`);
|
||||
if (statusCell && completedCell) {
|
||||
updateCellStatus(task, statusCell, completedCell);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initial render
|
||||
renderTable();
|
||||
|
||||
// Periodically update statuses
|
||||
setInterval(updateTaskStatuses, 5000); // Update every 5 seconds
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -184,6 +184,12 @@
|
||||
</p>
|
||||
</a>
|
||||
<ul class="nav nav-treeview">
|
||||
<li class="nav-item">
|
||||
<a href="#malware_lookup" class="nav-link">
|
||||
<i class="fa fa-ban nav-icon"></i>
|
||||
<p>Malware Lookup</p>
|
||||
</a>
|
||||
</li>
|
||||
{% if app_type not in 'so' %}
|
||||
<li class="nav-item">
|
||||
<a href="#apkid" class="nav-link">
|
||||
@@ -206,12 +212,14 @@
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if app_type not in 'so' %}
|
||||
<li class="nav-item">
|
||||
<a href="#malware_permissions" class="nav-link">
|
||||
<i class="fa fa-braille nav-icon"></i>
|
||||
<p>Abused Permissions</p>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="nav-item">
|
||||
<a href="#serverlocations" class="nav-link">
|
||||
<i class="fas fa-globe-asia nav-icon"></i>
|
||||
@@ -1511,6 +1519,7 @@
|
||||
</div>
|
||||
</section>
|
||||
<!-- ===========================end file analysis ================================== -->
|
||||
{% endif %}
|
||||
<a id="firebase" class="anchor"></a>
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
@@ -1559,7 +1568,56 @@
|
||||
</div>
|
||||
</section>
|
||||
<!-- ===========================end firebase db analysis ================================== -->
|
||||
|
||||
<!---Malware Analysis Section-->
|
||||
<a id="malware_lookup" class="anchor"></a>
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<p>
|
||||
<strong><i class="fa fa-ban"></i> MALWARE LOOKUP</strong>
|
||||
</p>
|
||||
<div class="table-responsive">
|
||||
<div class="row">
|
||||
<div class="col-sm-3 col-6">
|
||||
<div class="description-block border-right">
|
||||
<span class="text-danger"><a href="https://www.virustotal.com/gui/file/{{ sha256 }}" target="_blank" rel="noopener"><i class="fa fa-eye"></i> VirusTotal Report</a></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 col-6">
|
||||
<div class="description-block border-right">
|
||||
<span class="text-warning"></i><a href="https://tria.ge/s?q={{ sha256 }}" target="_blank" rel="noopener"><i class="fa fa-eye"></i> Triage Report</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 col-6">
|
||||
<div class="description-block border-right">
|
||||
<span class="text-info"><a href="https://metadefender.com/results/hash/{{ sha256 }}" target="_blank" rel="noopener"><i class="fa fa-eye"></i> MetaDefender Report</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 col-6">
|
||||
<div class="description-block border-right">
|
||||
<span class="text-success"><a href="https://www.hybrid-analysis.com/search?query={{ sha256 }}" target="_blank" rel="noopener"><i class="fa fa-eye"></i> Hybrid Analysis Report</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /.card -->
|
||||
</div>
|
||||
<!-- end row -->
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- ===========================end malware lookup ================================== -->
|
||||
{% if app_type not in 'so' %}
|
||||
<!---APKiD Analysis Section-->
|
||||
<a id="apkid" class="anchor"></a>
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
@@ -1762,7 +1820,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
<!-- ===========================end virus total ================================== -->
|
||||
<a id="malware_permissions" class="anchor"></a>
|
||||
<section class="content">
|
||||
@@ -1822,6 +1879,7 @@
|
||||
</div>
|
||||
</section>
|
||||
<!-- ===========================end malware permissions ================================== -->
|
||||
{% endif %}
|
||||
<style>
|
||||
#chartdiv {
|
||||
width: 100%;
|
||||
|
||||
@@ -158,6 +158,12 @@
|
||||
</p>
|
||||
</a>
|
||||
<ul class="nav nav-treeview">
|
||||
<li class="nav-item">
|
||||
<a href="#malware_lookup" class="nav-link">
|
||||
<i class="fa fa-ban nav-icon"></i>
|
||||
<p>Malware Lookup</p>
|
||||
</a>
|
||||
</li>
|
||||
{% if virus_total %}
|
||||
<li class="nav-item">
|
||||
<a href="#virus_total" class="nav-link">
|
||||
@@ -1280,6 +1286,54 @@
|
||||
</div>
|
||||
</section>
|
||||
<!-- ===========================end firebase db analysis ================================== -->
|
||||
<!---Malware Analysis Section-->
|
||||
<a id="malware_lookup" class="anchor"></a>
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<p>
|
||||
<strong><i class="fa fa-ban"></i> MALWARE LOOKUP</strong>
|
||||
</p>
|
||||
<div class="table-responsive">
|
||||
<div class="row">
|
||||
<div class="col-sm-3 col-6">
|
||||
<div class="description-block border-right">
|
||||
<span class="text-danger"><a href="https://www.virustotal.com/gui/file/{{ sha256 }}" target="_blank" rel="noopener"><i class="fa fa-eye"></i> VirusTotal Report</a></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 col-6">
|
||||
<div class="description-block border-right">
|
||||
<span class="text-warning"></i><a href="https://tria.ge/s?q={{ sha256 }}" target="_blank" rel="noopener"><i class="fa fa-eye"></i> Triage Report</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 col-6">
|
||||
<div class="description-block border-right">
|
||||
<span class="text-info"><a href="https://metadefender.com/results/hash/{{ sha256 }}" target="_blank" rel="noopener"><i class="fa fa-eye"></i> MetaDefender Report</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 col-6">
|
||||
<div class="description-block border-right">
|
||||
<span class="text-success"><a href="https://www.hybrid-analysis.com/search?query={{ sha256 }}" target="_blank" rel="noopener"><i class="fa fa-eye"></i> Hybrid Analysis Report</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /.card -->
|
||||
</div>
|
||||
<!-- end row -->
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- ===========================end malware lookup ================================== -->
|
||||
{% if virus_total %}
|
||||
<a id="virus_total" class="anchor"></a>
|
||||
<section class="content">
|
||||
|
||||
@@ -59,6 +59,14 @@
|
||||
Binary Analysis
|
||||
</p>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="#malware_lookup" class="nav-link">
|
||||
<i class="fa fa-ban nav-icon"></i>
|
||||
<p>
|
||||
Malware Lookup
|
||||
</p>
|
||||
</a>
|
||||
</li>
|
||||
{% if virus_total %}
|
||||
<li class="nav-item">
|
||||
@@ -280,6 +288,54 @@
|
||||
</div>
|
||||
</section>
|
||||
<!-- ===========================end binary analysis ================================== -->
|
||||
<!---Malware Analysis Section-->
|
||||
<a id="malware_lookup" class="anchor"></a>
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<p>
|
||||
<strong><i class="fa fa-ban"></i> MALWARE LOOKUP</strong>
|
||||
</p>
|
||||
<div class="table-responsive">
|
||||
<div class="row">
|
||||
<div class="col-sm-3 col-6">
|
||||
<div class="description-block border-right">
|
||||
<span class="text-danger"><a href="https://www.virustotal.com/gui/file/{{ sha256 }}" target="_blank" rel="noopener"><i class="fa fa-eye"></i> VirusTotal Report</a></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 col-6">
|
||||
<div class="description-block border-right">
|
||||
<span class="text-warning"></i><a href="https://tria.ge/s?q={{ sha256 }}" target="_blank" rel="noopener"><i class="fa fa-eye"></i> Triage Report</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 col-6">
|
||||
<div class="description-block border-right">
|
||||
<span class="text-info"><a href="https://metadefender.com/results/hash/{{ sha256 }}" target="_blank" rel="noopener"><i class="fa fa-eye"></i> MetaDefender Report</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 col-6">
|
||||
<div class="description-block border-right">
|
||||
<span class="text-success"><a href="https://www.hybrid-analysis.com/search?query={{ sha256 }}" target="_blank" rel="noopener"><i class="fa fa-eye"></i> Hybrid Analysis Report</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /.card -->
|
||||
</div>
|
||||
<!-- end row -->
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- ===========================end malware lookup ================================== -->
|
||||
{% if virus_total %}
|
||||
<a id="virus_total" class="anchor"></a>
|
||||
<section class="content">
|
||||
|
||||
Generated
+405
-1084
File diff suppressed because it is too large
Load Diff
+4
-3
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "mobsf"
|
||||
version = "4.1.9"
|
||||
version = "4.2.0"
|
||||
description = "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."
|
||||
keywords = ["mobsf", "mobile security framework", "mobile security", "security tool", "static analysis", "dynamic analysis", "malware analysis"]
|
||||
authors = ["Ajin Abraham <[email protected]>"]
|
||||
@@ -40,7 +40,6 @@ shelljob = ">=0.6.2"
|
||||
asn1crypto = ">=1.4.0"
|
||||
distro = ">=1.5.0"
|
||||
ip2location = "8.10.0"
|
||||
http-tools = ">=4.0.0"
|
||||
pdfkit = ">=0.6.1"
|
||||
google-play-scraper = ">=0.1.2"
|
||||
apkid = "2.1.5"
|
||||
@@ -51,7 +50,8 @@ svgutils = "0.3.4"
|
||||
arpy = "2.3.0"
|
||||
apksigtool = "0.1.0"
|
||||
tzdata = "^2023.3"
|
||||
libsast = "^3.1.0"
|
||||
http-tools = ">=5.0.0"
|
||||
libsast = "^3.1.6"
|
||||
paramiko = "^3.3.1"
|
||||
six = ">=1.16.0"
|
||||
python3-saml = "^1.16.0"
|
||||
@@ -60,6 +60,7 @@ psycopg2-binary = "^2.9.10"
|
||||
lief = "^0.15.1"
|
||||
packaging = ">=21.3,<22.0"
|
||||
django-ratelimit = "^4.1.0"
|
||||
django-q2 = "^1.7.4"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# wait for the database to be ready and migrations to be applied
|
||||
echo "Waiting for the database to be ready..."
|
||||
sleep 10
|
||||
|
||||
echo "Starting Django Q Cluster..."
|
||||
exec python3 manage.py qcluster
|
||||
Reference in New Issue
Block a user