安卓apk信息提取,敏感信息搜集——apkinfo
作者:Sec-Labs | 发布时间:
项目介绍
一个基于Python的安卓APK分析工具,可以快速提取敏感信息
项目地址
https://github.com/saucer-man/apkinfo
使用说明
apkinfo
一键解析app信息+检查敏感信息
用法:
python3 -m pip install apkutils
python3 apkinfo -a path/to/app
python3 apkinfo -f dir/to/app
功能:
- 解压app
- 查壳
- manifest解析
- 签名查询
- 敏感信息查询
- url、ips
- 手机号、身份证、银行卡等
- 其他敏感信息,比如oss key、数据库链接地址等
- 如果想自定义关键词,直接编辑
config/keys.json即可
核心代码
import argparse
import os
import sys
import zipfile
import subprocess
from apkutils import APK
import platform
import re
import json
class APKInfo():
def __init__(self, apk_path):
self.apk_path = os.path.abspath(apk_path)
self.base_path = os.path.abspath(os.path.dirname(__file__))
self.apk = None
self.result_path = None
self.apk_name = None
self.shell_type = None
self.patterns = None
self.pattern_file = None
self.shellfeatures = None
self.check()
def shell_detect(self):
try:
zipfiles = zipfile.ZipFile(self.apk_path)
namelist = zipfiles.namelist() # 得到压缩包里所有文件
# zipfiles.extractall(path=os.path.join(
# self.result_path, "files")) # 解压zip到result目录下
for filename in namelist:
for k, v in self.shellfeatures.items():
for shell in v:
if shell in filename:
shell_type = k
return True, shell_type
except:
pass
return False, "未加壳"
def dex2jar(apk_path, savePath):
try:
subprocess.run(
f"{dex2jar_path} --force {apk_path} --output {os.path.join(savePath, 'classed-dex2jar.jar')}", shell=True)
return True
except:
pass
return False
def manifest(self):
with open(os.path.join(self.result_path, "AndroidManifest.xml"), "w") as f:
f.write(self.apk.get_manifest())
print(f"{self.apkname}基础信息:package: {self.apk.package_name} Version: {self.apk._version_name} MainActivity: {self.apk.get_manifest_main_activities()}")
def signinfo(self):
try:
# 获取签名信息
for k, _ in self.apk.get_certs():
print(f"{self.apkname}签名信息:Signer:{k}")
except:
pass
def finder(self, name, pattern, strings):
matcher = re.compile(pattern)
found = []
for string in strings:
mo = matcher.search(string)
if mo:
found.append(mo.group())
return sorted(list(set(found)))
def save(self, name, found):
if not found:
return
with open(os.path.join(self.result_path, name), "a") as f:
f.write("\n".join(found))
def detect_strings(self):
strings = set()
for string in self.apk.get_dex_strings():
try:
strings.add(string.decode(encoding="UTF-8"))
except:
pass
with open(os.path.join(self.result_path, "strings.txt"), "w") as f:
for string in strings:
f.write(f"{string}\n")
for name, pattern in self.patterns.items():
if isinstance(pattern, list):
for p in pattern:
found = self.finder(name, p, strings)
self.save(name, found)
else:
found = self.finder(name, pattern, strings)
self.save(name, found)
def check(self):
print(f"下面开始分析:{self.apk_path}")
if not os.path.exists(self.apk_path):
raise(f"当前文件不存在")
_, self.apkname = os.path.split(apk_path)
self.result_path = os.path.join(self.base_path, "result", self.apkname)
if not os.path.exists(self.result_path):
os.makedirs(self.result_path)
else:
print(f"{self.apkname}: 检测到文件已经分析过,将会覆盖")
self.pattern_file = os.path.join(self.base_path, "config", "keys.json")
with open(self.pattern_file) as f:
self.patterns = json.load(f)
shell_path = os.path.join(self.base_path, "config", "shell.json")
with open(shell_path) as f:
self.shellfeatures = json.load(f)
def analyse(self):
# 先解压 + 查壳
print(f"{self.apkname}: 开始查壳...")
flag, self.shell_type = self.shell_detect()
if flag:
print(f"{self.apkname}: 经检测,该apk使用了{self.shell_type}进行加固")
return
else:
print(f"{self.apkname}: 未检测到壳")
self.apk = APK.from_file(self.apk_path)
print(f"{self.apkname}: 下面开始解析apk...")
# 解析manifest获取apk信息
self.manifest()
# 查看签名
self.signinfo()
# 字符串解析,查找敏感字符串
self.detect_strings()
print(f"{self.apkname}: 分析结束,结果保存在{self.result_path}")
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='apk信息提取')
parser.add_argument('-a', '--apk',
help='指定apk路径')
parser.add_argument('-d', '--dir',
help='指定apk路径')
args = parser.parse_args()
apks = []
if args.apk:
apks.append(args.apk)
if args.dir:
for filename in os.listdir(args.dir):
if filename.endswith(".apk"):
apks.append(os.path.join(args.dir, filename))
if not apks:
print(f"请使用-a或者-f指定apk")
sys.exit()
for apk_path in apks:
try:
apk = APKInfo(apk_path)
apk.analyse()
except Exception as e:
print(f"{apk_path}出错: {e}")
import traceback
traceback.print_exc()
常见特征代码如下
{
"Amazon_AWS_Access_Key_ID": "([^A-Z0-9]|^)(AKIA|A3T|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{12,}",
"Amazon_AWS_S3_Bucket": [
"//s3-[a-z0-9-]+\\.amazonaws\\.com/[a-z0-9._-]+",
"//s3\\.amazonaws\\.com/[a-z0-9._-]+",
"[a-z0-9.-]+\\.s3-[a-z0-9-]\\.amazonaws\\.com",
"[a-z0-9.-]+\\.s3-website[.-](eu|ap|us|ca|sa|cn)",
"[a-z0-9.-]+\\.s3\\.amazonaws\\.com",
"amzn\\.mws\\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
],
"Artifactory_API_Token": "(?:\\s|=|:|\"|^)AKC[a-zA-Z0-9]{10,}",
"Artifactory_Password": "(?:\\s|=|:|\"|^)AP[\\dABCDEF][a-zA-Z0-9]{8,}",
"Authorization_Basic": "basic\\s[a-zA-Z0-9_\\-:\\.=]+",
"Authorization_Bearer": "bearer\\s[a-zA-Z0-9_\\-:\\.=]+",
"AWS_API_Key": "AKIA[0-9A-Z]{16}",
"Basic_Auth_Credentials": "(?<=:\/\/)[a-zA-Z0-9]+:[a-zA-Z0-9]+@[a-zA-Z0-9]+\\.[a-zA-Z]+",
"Cloudinary_Basic_Auth": "cloudinary:\/\/[0-9]{15}:[0-9A-Za-z]+@[a-z]+",
"DEFCON_CTF_Flag": "O{3}\\{.*\\}",
"Discord_BOT_Token": "((?:N|M|O)[a-zA-Z0-9]{23}\\.[a-zA-Z0-9-_]{6}\\.[a-zA-Z0-9-_]{27})$",
"Facebook_Access_Token": "EAACEdEose0cBA[0-9A-Za-z]+",
"Facebook_ClientID": "[f|F][a|A][c|C][e|E][b|B][o|O][o|O][k|K](.{0,20})?['\"][0-9]{13,17}",
"Facebook_OAuth": "[f|F][a|A][c|C][e|E][b|B][o|O][o|O][k|K].*['|\"][0-9a-f]{32}['|\"]",
"Facebook_Secret_Key": "([f|F][a|A][c|C][e|E][b|B][o|O][o|O][k|K]|[f|F][b|B])(.{0,20})?['\"][0-9a-f]{32}",
"Firebase": "[a-z0-9.-]+\\.firebaseio\\.com",
"Generic_API_Key": "[a|A][p|P][i|I][_]?[k|K][e|E][y|Y].*['|\"][0-9a-zA-Z]{32,45}['|\"]",
"Generic_Secret": "[s|S][e|E][c|C][r|R][e|E][t|T].*['|\"][0-9a-zA-Z]{32,45}['|\"]",
"GitHub": "[g|G][i|I][t|T][h|H][u|U][b|B].*['|\"][0-9a-zA-Z]{35,40}['|\"]",
"GitHub_Access_Token": "([a-zA-Z0-9_-]*:[a-zA-Z0-9_-]+@github.com*)$",
"Google_API_Key": "AIza[0-9A-Za-z\\-_]{35}",
"Google_Cloud_Platform_OAuth": "[0-9]+-[0-9A-Za-z_]{32}\\.apps\\.googleusercontent\\.com",
"Google_Cloud_Platform_Service_Account": "\"type\": \"service_account\"",
"Google_OAuth_Access_Token": "ya29\\.[0-9A-Za-z\\-_]+",
"HackerOne_CTF_Flag": "[h|H]1(?:[c|C][t|T][f|F])?\\{.*\\}",
"HackTheBox_CTF_Flag": "[h|H](?:[a|A][c|C][k|K][t|T][h|H][e|E][b|B][o|O][x|X]|[t|T][b|B])\\{.*\\}$",
"Heroku_API_Key": "[h|H][e|E][r|R][o|O][k|K][u|U].*[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}",
"IP_Address": "(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])",
"JSON_Web_Token": "(?i)^((?=.*[a-z])(?=.*[0-9])(?:[a-z0-9_=]+\\.){2}(?:[a-z0-9_\\-\\+\/=]*))$",
"LinkFinder": "(?:\"|')(((?:[a-zA-Z]{1,10}:\/\/|\/\/)[^\"'\/]{1,}\\.[a-zA-Z]{2,}[^\"']{0,})|((?:\/|\\.\\.\/|\\.\/)[^\"'><,;| *()(%%$^\/\\\\\\[\\]][^\"'><,;|()]{1,})|([a-zA-Z0-9_\\-\/]{1,}\/[a-zA-Z0-9_\\-\/]{1,}\\.(?:[a-zA-Z]{1,4}|action)(?:[\\?|#][^\"|']{0,}|))|([a-zA-Z0-9_\\-\/]{1,}\/[a-zA-Z0-9_\\-\/]{3,}(?:[\\?|#][^\"|']{0,}|))|([a-zA-Z0-9_\\-]{1,}\\.(?:php|asp|aspx|jsp|json|action|html|js|txt|xml)(?:[\\?|#][^\"|']{0,}|)))(?:\"|')",
"Mac_Address": "(([0-9A-Fa-f]{2}[:]){5}[0-9A-Fa-f]{2}|([0-9A-Fa-f]{2}[-]){5}[0-9A-Fa-f]{2}|([0-9A-Fa-f]{4}[\\.]){2}[0-9A-Fa-f]{4})$",
"MailChimp_API_Key": "[0-9a-f]{32}-us[0-9]{1,2}",
"Mailgun_API_Key": "key-[0-9a-zA-Z]{32}",
"Mailto": "(?<=mailto:)[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9.-]+",
"Password_in_URL": "[a-zA-Z]{3,10}://[^/\\s:@]{3,20}:[^/\\s:@]{3,20}@.{1,100}[\"'\\s]",
"PayPal_Braintree_Access_Token": "access_token\\$production\\$[0-9a-z]{16}\\$[0-9a-f]{32}",
"PGP_private_key_block": "-----BEGIN PGP PRIVATE KEY BLOCK-----",
"Picatic_API_Key": "sk_live_[0-9a-z]{32}",
"RSA_Private_Key": "-----BEGIN RSA PRIVATE KEY-----",
"Slack_Token": "(xox[p|b|o|a]-[0-9]{12}-[0-9]{12}-[0-9]{12}-[a-z0-9]{32})",
"Slack_Webhook": "https://hooks.slack.com/services/T[a-zA-Z0-9_]{8}/B[a-zA-Z0-9_]{8}/[a-zA-Z0-9_]{24}",
"Square_Access_Token": "sq0atp-[0-9A-Za-z\\-_]{22}",
"Square_OAuth_Secret": "sq0csp-[0-9A-Za-z\\-_]{43}",
"SSH_DSA_Private_Key": "-----BEGIN DSA PRIVATE KEY-----",
"SSH_EC_Private_Key": "-----BEGIN EC PRIVATE KEY-----",
"Stripe_API_Key": "sk_live_[0-9a-zA-Z]{24}",
"Stripe_Restricted_API_Key": "rk_live_[0-9a-zA-Z]{24}",
"TryHackMe_CTF_Flag": "[t|T](?:[r|R][y|Y][h|H][a|A][c|C][k|K][m|M][e|E]|[h|H][m|M])\\{.*\\}$",
"Twilio_API_Key": "SK[0-9a-fA-F]{32}",
"Twitter_Access_Token": "[t|T][w|W][i|I][t|T][t|T][e|E][r|R].*[1-9][0-9]+-[0-9a-zA-Z]{40}",
"Twitter_ClientID": "[t|T][w|W][i|I][t|T][t|T][e|E][r|R](.{0,20})?['\"][0-9a-z]{18,25}",
"Twitter_OAuth": "[t|T][w|W][i|I][t|T][t|T][e|E][r|R].*['|\"][0-9a-zA-Z]{35,44}['|\"]",
"Twitter_Secret_Key": "[t|T][w|W][i|I][t|T][t|T][e|E][r|R](.{0,20})?['\"][0-9a-z]{35,44}",
"OSS":"([A|a]ccess[K|k]ey[I|i][d|D]|[A|a]ccess[K|k]ey[S|s]ecret)",
"sensitives_list":["accessKey", "database", "ssh", "rdp", "smb", "mysql", "sqlserver", "oracle",
"ftp", "mongodb", "memcached", "postgresql", "telnet", "smtp", "pop3", "imap",
"vnc", "redis", "admin", "root", "config", "jdbc", ".properties", "aliyuncs",
"oss"],
"domain":["http://","https://"],
"Chinese_Bank_Card_ID":"[^0-9]([1-9]\\d{12,18})[^0-9]",
"MAC_Address" :"(^([a-fA-F0-9]{2}(:[a-fA-F0-9]{2}){5})|[^a-zA-Z0-9]([a-fA-F0-9]{2}(:[a-fA-F0-9]{2}){5}))",
"Phone_Number":"[^\\w]((?:(?:\\+|00)86)?1(?:(?:3[\\d])|(?:4[5-79])|(?:5[0-35-9])|(?:6[5-7])|(?:7[0-8])|(?:8[\\d])|(?:9[189]))\\d{8})[^\\w]",
"IDcard":"'[^0-9]((\\d{8}(0\\d|10|11|12)([0-2]\\d|30|31)\\d{3}$)|(\\d{6}(18|19|20)\\d{2}(0[1-9]|10|11|12)([0-2]\\d|30|31)\\d{3}(\\d|X|x)))[^0-9]'",
"Email":"(([a-zA-Z0-9][_|\\.])*[a-zA-Z0-9]+@([a-zA-Z0-9][-|_|\\.])*[a-zA-Z0-9]+\\.((?!js|css|jpg|jpeg|png|ico)[a-zA-Z]{2,}))"
}
标签:逆向技巧, APP分析