Skip to content

Instantly share code, notes, and snippets.

@tomleejumah
Created April 3, 2025 19:10
Show Gist options
  • Save tomleejumah/0175e551848e7f19b6fb6a9c50ac1038 to your computer and use it in GitHub Desktop.
Save tomleejumah/0175e551848e7f19b6fb6a9c50ac1038 to your computer and use it in GitHub Desktop.
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 &lt;certificates src="user" /&gt; in network_security_config.xml</p>network_security_config.xml file Location:'
+ stringsFile,
)
else:
writePassResults(
filename,
'</br>[Info] --- Not found &lt;certificates src="user" /&gt; 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 &lt;certificates src="@raw/*"/&gt; in network_security_config.xml</p>network_security_config.xml file Location:'
+ stringsFile,
)
else:
writePassResults(
filename,
'</br>[Info] --- Not found &lt;certificates src="@raw/*"/&gt; 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 &lt;domain-config cleartextTrafficPermitted="true"&gt; 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