Created
April 3, 2025 19:10
-
-
Save tomleejumah/0175e551848e7f19b6fb6a9c50ac1038 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import re | |
import requests | |
import sys | |
import os | |
from os import path | |
try: | |
import sty | |
from sty import fg, bg, ef, rs | |
HAS_STY = True | |
except ImportError: | |
HAS_STY = False | |
# Create dummy color functions | |
class DummyColor: | |
def __getattr__(self, name): | |
return "" | |
fg = DummyColor() | |
bg = DummyColor() | |
ef = DummyColor() | |
rs = DummyColor() | |
def checkLinksInStringsXML(filename, stringsFile): | |
stringsFile = os.path.join(pwd, filename, stringsFile) | |
print("[Info] - Checking for URLs in strings.xml") | |
writePassResults(filename, "<br> <br><b>URL Checks</b> ") | |
try: | |
with open(stringsFile, errors="ignore") as f: | |
f1 = f.read() | |
searchObj = re.findall(r'https?://[^\s<>"\']+', f1) | |
if searchObj: | |
for url in searchObj: | |
print("[Info] - URL found: " + url) | |
try: | |
req = requests.get(url, timeout=5) | |
status = req.status_code | |
if status == 200: | |
print(f"[Info] - {url} is accessible") | |
writeResults( | |
filename, | |
f"<p style='color:green;'>[Info] --- URL <a href='{url}'>{url}</a> is accessible </p>", | |
) | |
else: | |
print( | |
f"[Info] - {url} is not accessible, Status Code: {status}" | |
) | |
writePassResults( | |
filename, | |
f"<br>[Info] --- URL <a href='{url}'>{url}</a> is not accessible, Status Code: {status}<br>", | |
) | |
except requests.RequestException as e: | |
print(f"[Error] - Unable to reach {url}: {str(e)}") | |
writePassResults( | |
filename, | |
f"<br>[Error] --- Unable to reach <a href='{url}'>{url}</a>: {str(e)}<br>", | |
) | |
else: | |
print("[Info] - No URLs found in strings.xml") | |
writePassResults( | |
filename, "<br>[Info] --- No URLs found in strings.xml" | |
) | |
except IOError: | |
writeResults(filename, "<br> Strings.xml not accessible") | |
def network_security_config_Test(filename, nscFile): | |
stringsFile = os.path.join(pwd, filename, nscFile) | |
print("[Info] - Checking for Network Security Config") | |
writePassResults(filename, "<br> <br><b>Network Security Config Checks</b>") | |
try: | |
with open(stringsFile, errors="ignore") as f: | |
fData = f.read() | |
# Search for <certificates src="user"/> | |
searchObj = re.search(r"<certificates.*src.*user.*>", fData, re.M | re.I) | |
if searchObj: | |
if HAS_STY: | |
print( | |
fg.red | |
+ "[Vuln] - Found Misconfigured network_security_config.xml\n - " | |
+ searchObj.group() | |
+ " in network_security_config.xml which leads to MITM in Android devices API24 or above.\n - network_security_config.xml file Location:" | |
+ stringsFile | |
+ fg.rs | |
) | |
else: | |
print( | |
"[Vuln] - Found Misconfigured network_security_config.xml\n - " | |
+ searchObj.group() | |
+ " in network_security_config.xml which leads to MITM in Android devices API24 or above.\n - network_security_config.xml file Location:" | |
+ stringsFile | |
) | |
writeResults( | |
filename, | |
'<p style="color:red;">[Vulnerability] --- Misconfigured network_security_config.xml. \n Found ' | |
+ searchObj.group() | |
+ ' in network_security_config.xml which leads to MITM in Android devices API24 or above.</br>Found <certificates src="user" /> in network_security_config.xml</p>network_security_config.xml file Location:' | |
+ stringsFile, | |
) | |
else: | |
writePassResults( | |
filename, | |
'</br>[Info] --- Not found <certificates src="user" /> in network_security_config.xml </br>network_security_config.xml file Location:' | |
+ stringsFile, | |
) | |
# Search for <certificates src="@raw/*"/> | |
searchObj = re.search(r"<certificates.*src.*raw.*>", fData, re.M | re.I) | |
if searchObj: | |
if HAS_STY: | |
print( | |
fg.red | |
+ "[Vuln] - Found Misconfigured network_security_config.xml\n - " | |
+ searchObj.group() | |
+ " in network_security_config.xml which leads to MITM in Android devices API24 or above.\n - network_security_config.xml file Location:" | |
+ stringsFile | |
+ fg.rs | |
) | |
else: | |
print( | |
"[Vuln] - Found Misconfigured network_security_config.xml\n - " | |
+ searchObj.group() | |
+ " in network_security_config.xml which leads to MITM in Android devices API24 or above.\n - network_security_config.xml file Location:" | |
+ stringsFile | |
) | |
writeResults( | |
filename, | |
'<p style="color:red;">[Vulnerability] --- Misconfigured network_security_config.xml. \n Found ' | |
+ searchObj.group() | |
+ ' in network_security_config.xml which leads to MITM in Android devices API24 or above.</br>Found <certificates src="@raw/*"/> in network_security_config.xml</p>network_security_config.xml file Location:' | |
+ stringsFile, | |
) | |
else: | |
writePassResults( | |
filename, | |
'</br>[Info] --- Not found <certificates src="@raw/*"/> in network_security_config.xml</br>network_security_config.xml file Location:' | |
+ stringsFile, | |
) | |
# Search for ClearTextTraffic | |
searchObj = re.search( | |
r"<domain-config.*cleartextTrafficPermitted.*true.*>", | |
fData, | |
re.M | re.I, | |
) | |
if searchObj: | |
if HAS_STY: | |
print( | |
fg.red | |
+ "[Vuln] - Found Misconfigured network_security_config.xml\n - " | |
+ searchObj.group() | |
+ " in network_security_config.xml which leads to MITM in Android devices API24 or above.\n - network_security_config.xml file Location:" | |
+ stringsFile | |
+ fg.rs | |
) | |
else: | |
print( | |
"[Vuln] - Found Misconfigured network_security_config.xml\n - " | |
+ searchObj.group() | |
+ " in network_security_config.xml which leads to MITM in Android devices API24 or above.\n - network_security_config.xml file Location:" | |
+ stringsFile | |
) | |
writeResults( | |
filename, | |
'<p style="color:red;">[Vulnerability] --- Misconfigured network_security_config.xml. \n Found ' | |
+ searchObj.group() | |
+ " in network_security_config.xml which leads to MITM in Android devices API24 or above</p> </br>network_security_config.xml file Location:" | |
+ stringsFile, | |
) | |
else: | |
writePassResults( | |
filename, | |
'</br>[Info] --- Not found <domain-config cleartextTrafficPermitted="true"> in network_security_config.xml</br>network_security_config.xml file Location:' | |
+ stringsFile, | |
) | |
except IOError: | |
print("[Info] - App doesn't have network_security_config.xml") | |
writePassResults(filename, "</br>App doesn't have network_security_config.xml") | |
def getDeepLinks(): | |
print("[Info] - Checking for Deeplinks") | |
writePassResults(filename, "</br></br> <b> Custom URL Check</b>") | |
# for AndroidManifest.xml file | |
f1 = os.path.join(pwd, filename, manifestFile) | |
writePassResults(filename, "</br>[Info]---AndroidManifest.xml file Location: " + f1) | |
try: | |
with open(f1, errors="ignore") as f: | |
f2 = f.read() | |
i = f2.count("<data android:scheme") | |
searchObj1 = re.findall(r"<data android:host=(.*)", f2) | |
j = len(searchObj1) | |
if j != 0: | |
while j > 0: | |
j = j - 1 | |
scheme1 = re.search( | |
r'android:scheme="(.*)"', searchObj1[j], re.M | re.I | |
) | |
if scheme1: | |
print(" - scheme: " + scheme1.group(1)) | |
writePassResults(filename, "</br>scheme: " + scheme1.group(1)) | |
host1 = searchObj1[j].replace(scheme1.group(), "") | |
host2 = re.search(r'"(.*)"', host1, re.M | re.I) | |
if host2: | |
print( | |
" - host: " | |
+ host2.group(1) | |
+ "\n - Deeplink: " | |
+ scheme1.group(1) | |
+ "://" | |
+ host2.group(1) | |
) | |
writePassResults( | |
filename, | |
"</br>host: " | |
+ host2.group(1) | |
+ "</br>Deeplink: " | |
+ scheme1.group(1) | |
+ "://" | |
+ host2.group(1), | |
) | |
else: | |
print( | |
" - host: Not Found \n - Deeplink: " | |
+ scheme1.group(1) | |
+ "://" | |
) | |
writePassResults( | |
filename, | |
"</br>No host found</br>Deeplink: " | |
+ scheme1.group(1) | |
+ "://", | |
) | |
else: | |
host3 = searchObj1[j].replace('"', "") | |
host4 = host3.replace("/>", "") | |
writePassResults( | |
filename, | |
"</br>no scheme found</br>host: " | |
+ host4 | |
+ "</br>Deeplink: " | |
+ "://" | |
+ host4, | |
) | |
else: | |
writePassResults(filename, "</br>NO host") | |
searchObj = re.findall(r"<data android:scheme=(.*)", f2) | |
i = len(searchObj) | |
if i != 0: | |
while i > 0: | |
i = i - 1 | |
host = re.search(r'android:host="(.*)"', searchObj[i], re.M | re.I) | |
if host: | |
writePassResults(filename, "</br>host: " + host.group(1)) | |
scheme1 = searchObj[i].replace(host.group(), "") | |
scheme = re.search(r'"(.*)"', scheme1, re.M | re.I) | |
if scheme: | |
writePassResults( | |
filename, | |
"</br>scheme: " | |
+ scheme.group(1) | |
+ "</br>Deeplink: " | |
+ scheme.group(1) | |
+ "://" | |
+ host.group(1), | |
) | |
scheme = scheme1.replace(scheme.group(), "") | |
else: | |
writePassResults( | |
filename, | |
"</br>No Scheme found</br>Deeplink: " | |
+ "://" | |
+ host.group(1), | |
) | |
else: | |
scheme = searchObj[i].replace('"', "") | |
scheme = scheme.replace("/>", "") | |
writePassResults( | |
filename, | |
"</br>no host found</br>scheme: " | |
+ scheme | |
+ "</br>Deeplink: " | |
+ scheme | |
+ "://", | |
) | |
else: | |
writePassResults(filename, "</br>No more schemes") | |
except IOError as e: | |
print(f"[Error] - Failed to open AndroidManifest.xml: {str(e)}") | |
writePassResults( | |
filename, f"</br>[Error] - Failed to open AndroidManifest.xml: {str(e)}" | |
) | |
def isDebuggableOrBackup(): | |
f1 = os.path.join(pwd, filename, manifestFile) | |
try: | |
with open(f1, errors="ignore") as f: | |
f2 = f.read() | |
print("[Info] - Checking AndroidManifest.xml") | |
searchObj = re.search(r'android:debuggable="true"', f2, re.M | re.I) | |
if searchObj: | |
if HAS_STY: | |
print( | |
fg.red | |
+ "[Vuln] - Android debuggable. Found android:debuggable=true in AndroidManifest.xml file" | |
+ fg.rs | |
) | |
else: | |
print( | |
"[Vuln] - Android debuggable. Found android:debuggable=true in AndroidManifest.xml file" | |
) | |
writeResults( | |
filename, | |
'<p style="color:red;">[Vulnerability] ---Android debuggable. \n Found android:debuggable=true in AndroidManifest.xml file</p>', | |
) | |
else: | |
writePassResults( | |
filename, | |
"</br></br><b>android:debuggable Check </b> <br>[Info] --- android:debuggable not found", | |
) | |
searchObj1 = re.search(r'android:allowBackup="true"', f2, re.M | re.I) | |
searchObj2 = re.search(r'android:allowBackup="false"', f2, re.M | re.I) | |
if searchObj1: | |
if HAS_STY: | |
print( | |
fg.red | |
+ "[Vuln] - Android backup vulnerability. Found android:allowBackup=true in AndroidManifest.xml file" | |
+ fg.rs | |
) | |
else: | |
print( | |
"[Vuln] - Android backup vulnerability. Found android:allowBackup=true in AndroidManifest.xml file" | |
) | |
writeResults( | |
filename, | |
'<p style="color:red;">[Vulnerability] --- Android backup vulnerability. \n Found android:allowBackup=true in AndroidManifest.xml file</p>', | |
) | |
elif searchObj2: | |
writePassResults( | |
filename, | |
'</br></br><b>android:allowBackup Check </b></br>[Info] --- android:allowBackup="false" found', | |
) | |
else: | |
if HAS_STY: | |
print( | |
fg.red | |
+ "[Vuln] - Android backup vulnerability. Not found android:allowbackup=true, default value is true, in AndroidManifest.xml file" | |
+ fg.rs | |
) | |
else: | |
print( | |
"[Vuln] - Android backup vulnerability. Not found android:allowbackup=true, default value is true, in AndroidManifest.xml file" | |
) | |
writeResults( | |
filename, | |
'<p style="color:red;">[Vulnerability] --- Android backup vulnerability . \n Not found android:allowbackup=true, default value is true, in AndroidManifest.xml file</p>', | |
) | |
except IOError as e: | |
print(f"[Error] - Failed to open AndroidManifest.xml: {str(e)}") | |
writeResults( | |
filename, | |
f'<p style="color:red;">[Error] - Failed to open AndroidManifest.xml: {str(e)}</p>', | |
) | |
def writeResults(filename, msg): | |
f = open(resultsHtml, "a") | |
f.write(msg) | |
f.close() | |
def writePassResults(filename, msg): | |
f = open(resultsHtmlTemp, "a") | |
f.write(msg) | |
f.close() | |
# Get command line argument for APK file | |
apkfile = sys.argv[-1] | |
# Get file extension .apk | |
filename, file_extension = os.path.splitext(apkfile) | |
pwd = os.getcwd() | |
stringsFile = os.path.join("res", "values", "strings.xml") | |
nscFile = os.path.join("res", "xml", "network_security_config.xml") | |
manifestFile = "AndroidManifest.xml" | |
resultsHtml = filename + ".html" | |
resultsHtmlTemp = filename + "Temp.html" | |
head = "<!DOCTYPE html><html><head><style>table { font-family: arial, sans-serif; border-collapse: collapse; width: 100%;} td, th { border: 1px solid #dddddd; text-align: left; padding: 8px; } tr:nth-child(even) { background-color: #dddddd; } </style> </head> <body>" | |
endhtml = "</body> </html>" | |
print( | |
"This tool analyzes Android app to find vulnerabilities in \n1. AndroidManifest.xml \n2. network_security_config.xml \n3. URLs from strings.xml. \nThis tool also shows Deeplinks used in Android app.\n" | |
) | |
writeResults( | |
filename, | |
head | |
+ "<h3>This tool analyze Android app to find vulnerabilities in AndroidManifest.xml, network_security_config.xml and URLs from strings.xml </br>This tool also shows Deeplinks used in Android app </br>Developed by Satish Patnayak <a href='https://twitter.com/satish_patnayak'>satish_patnayak</a> </br> </br>Analysis results of <u>" | |
+ apkfile | |
+ "</u></h3>", | |
) | |
if file_extension == ".apk": | |
# Decompile APK file | |
print("[Info] - Please wait while I am analyzing Android app " + apkfile) | |
if path.exists(resultsHtml): | |
os.remove(resultsHtml) | |
writeResults( | |
filename, | |
head | |
+ "<h3>This tool analyzes Android app to find vulnerabilities in AndroidManifest.xml, network_security_config.xml and URLs from strings.xml </br>This tool also shows Deeplinks used in Android app </br>Developed by Satish Patnayak <a href='https://twitter.com/satish_patnayak'>satish_patnayak</a> </br> </br>Analysis results of <u>" | |
+ apkfile | |
+ "</u></h3>", | |
) | |
# Check if apktool.jar exists | |
if not os.path.exists("apktool.jar"): | |
print("[Error] - apktool.jar not found in the current directory") | |
writeResults( | |
filename, | |
'<p style="color:red;">[Error] - apktool.jar not found in the current directory. Please download it from https://ibotpeaches.github.io/Apktool/</p>' | |
+ endhtml, | |
) | |
sys.exit(1) | |
# Run apktool to decompile the APK | |
decompile_command = 'java -jar apktool.jar d -q -f "' + apkfile + '"' | |
print(f"[Info] - Running command: {decompile_command}") | |
decompile_result = os.system(decompile_command) | |
if decompile_result != 0: | |
print("[Error] - Failed to decompile the APK file") | |
writeResults( | |
filename, | |
'<p style="color:red;">[Error] - Failed to decompile the APK file. Make sure you have Java installed and apktool.jar is configured correctly.</p>' | |
+ endhtml, | |
) | |
sys.exit(1) | |
# Verify the decompiled directory exists | |
if not os.path.exists(filename): | |
print(f"[Error] - Decompiled directory {filename} not found") | |
writeResults( | |
filename, | |
f'<p style="color:red;">[Error] - Decompiled directory {filename} not found after running apktool.</p>' | |
+ endhtml, | |
) | |
sys.exit(1) | |
# Run the analysis functions | |
isDebuggableOrBackup() | |
network_security_config_Test(filename, nscFile) | |
checkLinksInStringsXML(filename, stringsFile) | |
getDeepLinks() | |
try: | |
f11 = open(resultsHtmlTemp, "r") | |
writeResults(filename, "</br><h3>Pass cases</h3>" + f11.read() + endhtml) | |
f11.close() | |
os.remove(resultsHtmlTemp) | |
except IOError: | |
writeResults(filename, endhtml) | |
print("Results are printed in " + os.path.join(pwd, resultsHtml)) | |
# if file extension is not .apk | |
else: | |
print("[Warning] - Please use apk file only") | |
writeResults(filename, "</br>Please use apk file only" + endhtml) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment