#APK解析基础
APK(Android Package)是Android应用程序的安装包格式,了解APK结构对于App逆向分析至关重要。
#APK文件结构详解
# apk_analyzer.py - APK文件结构分析工具
import zipfile
import xml.etree.ElementTree as ET
import os
import json
from typing import Dict, List, Optional
from pathlib import Path
import hashlib
class APKAnalyzer:
"""APK分析器 - 解析APK文件结构"""
def __init__(self, apk_path: str):
self.apk_path = apk_path
self.apk_info = {
'manifest': {},
'files': [],
'permissions': [],
'activities': [],
'services': [],
'receivers': [],
'providers': [],
'libraries': [],
'resources': {},
'metadata': {}
}
def analyze_apk(self) -> Dict:
"""分析APK文件"""
print(f"🔍 分析APK文件: {self.apk_path}")
# 检查文件是否存在
if not os.path.exists(self.apk_path):
raise FileNotFoundError(f"APK文件不存在: {self.apk_path}")
# 获取文件基本信息
self._get_file_metadata()
# 解析ZIP结构
with zipfile.ZipFile(self.apk_path, 'r') as apk:
file_list = apk.namelist()
self.apk_info['files'] = file_list
# 分析各组件
self._analyze_manifest(apk)
self._analyze_dex_files(apk)
self._analyze_resources(apk)
self._analyze_libraries(apk)
return self.apk_info
def _get_file_metadata(self):
"""获取文件元数据"""
stat = os.stat(self.apk_path)
self.apk_info['metadata'] = {
'size': stat.st_size,
'created': stat.st_ctime,
'modified': stat.st_mtime,
'sha256': self._calculate_sha256(),
'md5': self._calculate_md5()
}
def _calculate_sha256(self) -> str:
"""计算SHA256哈希值"""
sha256_hash = hashlib.sha256()
with open(self.apk_path, "rb") as f:
for byte_block in iter(lambda: f.read(4096), b""):
sha256_hash.update(byte_block)
return sha256_hash.hexdigest()
def _calculate_md5(self) -> str:
"""计算MD5哈希值"""
md5_hash = hashlib.md5()
with open(self.apk_path, "rb") as f:
for byte_block in iter(lambda: f.read(4096), b""):
md5_hash.update(byte_block)
return md5_hash.hexdigest()
def _analyze_manifest(self, apk_zip: zipfile.ZipFile):
"""分析AndroidManifest.xml"""
try:
# 读取AndroidManifest.xml内容
manifest_content = apk_zip.read('AndroidManifest.xml')
# 解析XML(注意:APK中的AndroidManifest.xml是二进制格式,需要特殊处理)
# 这里使用简化方法,实际需要使用axmlparser等工具
print("📄 解析AndroidManifest.xml...")
# 模拟解析结果
self.apk_info['manifest'] = {
'package_name': 'com.example.app',
'version_code': '1',
'version_name': '1.0',
'min_sdk': '21',
'target_sdk': '30',
'application_label': 'Example App'
}
# 解析权限
self.apk_info['permissions'] = [
'android.permission.INTERNET',
'android.permission.WRITE_EXTERNAL_STORAGE',
'android.permission.ACCESS_NETWORK_STATE'
]
# 解析Activities
self.apk_info['activities'] = [
{
'name': '.MainActivity',
'exported': 'true',
'launch_mode': 'standard'
},
{
'name': '.SplashActivity',
'exported': 'false',
'launch_mode': 'singleTop'
}
]
# 解析Services
self.apk_info['services'] = [
{
'name': '.BackgroundService',
'exported': 'false'
}
]
# 解析Receivers
self.apk_info['receivers'] = [
{
'name': '.BootReceiver',
'exported': 'false'
}
]
# 解析Content Providers
self.apk_info['providers'] = [
{
'name': '.DataProvider',
'exported': 'false',
'authorities': 'com.example.provider'
}
]
except KeyError:
print("⚠️ AndroidManifest.xml 未找到")
except Exception as e:
print(f"❌ 解析AndroidManifest.xml失败: {e}")
def _analyze_dex_files(self, apk_zip: zipfile.ZipFile):
"""分析DEX文件"""
dex_files = [f for f in apk_zip.namelist() if f.endswith('.dex')]
print(f"📱 发现 {len(dex_files)} 个DEX文件")
for dex_file in dex_files:
try:
dex_info = {
'name': dex_file,
'size': len(apk_zip.read(dex_file)),
'compressed_size': apk_zip.getinfo(dex_file).compress_size
}
self.apk_info['files_info'] = self.apk_info.get('files_info', [])
self.apk_info['files_info'].append(dex_info)
print(f" - {dex_file}: {dex_info['size']} bytes")
except Exception as e:
print(f" ❌ 读取 {dex_file} 失败: {e}")
def _analyze_resources(self, apk_zip: zipfile.ZipFile):
"""分析资源文件"""
resource_dirs = [f for f in apk_zip.namelist() if f.startswith('res/') or f.startswith('assets/')]
print(f"🎨 发现 {len(resource_dirs)} 个资源文件")
# 按类型分类资源
drawable_files = [f for f in resource_dirs if 'drawable' in f]
layout_files = [f for f in resource_dirs if 'layout' in f]
values_files = [f for f in resource_dirs if 'values' in f]
self.apk_info['resources'] = {
'drawables': len(drawable_files),
'layouts': len(layout_files),
'values': len(values_files),
'total_assets': len([f for f in apk_zip.namelist() if f.startswith('assets/')])
}
print(f" - 图片资源: {len(drawable_files)} 个")
print(f" - 布局文件: {len(layout_files)} 个")
print(f" - 配置文件: {len(values_files)} 个")
def _analyze_libraries(self, apk_zip: zipfile.ZipFile):
"""分析原生库文件"""
lib_files = [f for f in apk_zip.namelist() if f.startswith('lib/')]
print(f"⚙️ 发现 {len(lib_files)} 个原生库文件")
architectures = set()
for lib_file in lib_files:
# 提取架构信息
parts = lib_file.split('/')
if len(parts) > 1:
arch = parts[1] # lib/arm64-v8a/...
architectures.add(arch)
self.apk_info['libraries'] = {
'count': len(lib_files),
'architectures': list(architectures),
'files': lib_files[:10] # 只显示前10个
}
print(f" - 支持架构: {', '.join(architectures)}")
def generate_report(self) -> str:
"""生成分析报告"""
report = f"""
=== APK分析报告 ===
📁 文件信息:
- 文件路径: {self.apk_path}
- 文件大小: {self.apk_info['metadata']['size']} bytes
- SHA256: {self.apk_info['metadata']['sha256']}
- MD5: {self.apk_info['metadata']['md5']}
📱 应用信息:
- 包名: {self.apk_info['manifest'].get('package_name', 'N/A')}
- 版本代码: {self.apk_info['manifest'].get('version_code', 'N/A')}
- 版本名称: {self.apk_info['manifest'].get('version_name', 'N/A')}
- 最小SDK: {self.apk_info['manifest'].get('min_sdk', 'N/A')}
- 目标SDK: {self.apk_info['manifest'].get('target_sdk', 'N/A')}
🔐 权限列表 ({len(self.apk_info['permissions'])}个):
"""
for perm in self.apk_info['permissions']:
report += f"- {perm}\n"
report += f"\n📱 Activities ({len(self.apk_info['activities'])}个):\n"
for activity in self.apk_info['activities']:
report += f"- {activity['name']} (exported: {activity['exported']})\n"
report += f"\n⚙️ Services ({len(self.apk_info['services'])}个):\n"
for service in self.apk_info['services']:
report += f"- {service['name']}\n"
report += f"\n📡 Receivers ({len(self.apk_info['receivers'])}个):\n"
for receiver in self.apk_info['receivers']:
report += f"- {receiver['name']}\n"
report += f"\n📚 Providers ({len(self.apk_info['providers'])}个):\n"
for provider in self.apk_info['providers']:
report += f"- {provider['name']}\n"
report += f"\n🎨 资源统计:\n"
resources = self.apk_info['resources']
report += f"- 图片资源: {resources.get('drawables', 0)} 个\n"
report += f"- 布局文件: {resources.get('layouts', 0)} 个\n"
report += f"- 配置文件: {resources.get('values', 0)} 个\n"
report += f"- 资源总数: {resources.get('total_assets', 0)} 个\n"
report += f"\n⚙️ 原生库:\n"
libs = self.apk_info['libraries']
report += f"- 库文件数: {libs['count']}\n"
report += f"- 支持架构: {', '.join(libs['architectures'])}\n"
report += f"\n📄 文件总数: {len(self.apk_info['files'])}"
return report
def analyze_apk_file(apk_path: str) -> Dict:
"""分析APK文件的便捷函数"""
analyzer = APKAnalyzer(apk_path)
return analyzer.analyze_apk()
def main():
"""主函数示例"""
# 注意:这里只是示例,实际使用时需要提供真实的APK文件路径
print("APK分析工具示例")
print("请提供APK文件路径进行分析")
if __name__ == "__main__":
main()#APK反编译工具使用
# apk_decompiler.py - APK反编译工具集成
import subprocess
import os
import tempfile
from typing import Optional, Dict
import shutil
class APKDecompiler:
"""APK反编译工具类"""
def __init__(self):
self.tools_available = self._check_tools()
def _check_tools(self) -> Dict[str, bool]:
"""检查反编译工具是否可用"""
tools = {
'jadx': False,
'apktool': False,
'dex2jar': False
}
# 检查jadx
try:
result = subprocess.run(['jadx', '--version'],
capture_output=True, text=True, timeout=10)
tools['jadx'] = result.returncode == 0
except (subprocess.TimeoutExpired, FileNotFoundError):
tools['jadx'] = False
# 检查apktool
try:
result = subprocess.run(['apktool', '--version'],
capture_output=True, text=True, timeout=10)
tools['apktool'] = result.returncode == 0
except (subprocess.TimeoutExpired, FileNotFoundError):
tools['apktool'] = False
# 检查dex2jar (检查d2j-dex2jar.sh或.bat)
try:
result = subprocess.run(['d2j-dex2jar', '--version'],
capture_output=True, text=True, timeout=10)
tools['dex2jar'] = result.returncode == 0
except (subprocess.TimeoutExpired, FileNotFoundError):
tools['dex2jar'] = False
print("🔍 检测到的反编译工具:")
for tool, available in tools.items():
status = "✅ 可用" if available else "❌ 不可用"
print(f" {tool}: {status}")
return tools
def decompile_with_jadx(self, apk_path: str, output_dir: str = None) -> Optional[str]:
"""使用jadx反编译APK"""
if not self.tools_available['jadx']:
print("❌ jadx工具不可用")
return None
if not output_dir:
output_dir = f"jadx_output_{os.path.basename(apk_path).replace('.apk', '')}"
try:
cmd = ['jadx', '-d', output_dir, apk_path]
print(f" jadx反编译命令: {' '.join(cmd)}")
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
if result.returncode == 0:
print(f"✅ jadx反编译成功,输出目录: {output_dir}")
return output_dir
else:
print(f"❌ jadx反编译失败: {result.stderr}")
return None
except subprocess.TimeoutExpired:
print("❌ jadx反编译超时")
return None
except Exception as e:
print(f"❌ jadx反编译异常: {e}")
return None
def decompile_with_apktool(self, apk_path: str, output_dir: str = None) -> Optional[str]:
"""使用apktool反编译APK"""
if not self.tools_available['apktool']:
print("❌ apktool工具不可用")
return None
if not output_dir:
output_dir = f"apktool_output_{os.path.basename(apk_path).replace('.apk', '')}"
try:
cmd = ['apktool', 'd', apk_path, '-o', output_dir]
print(f" apktool反编译命令: {' '.join(cmd)}")
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
if result.returncode == 0:
print(f"✅ apktool反编译成功,输出目录: {output_dir}")
return output_dir
else:
print(f"❌ apktool反编译失败: {result.stderr}")
return None
except subprocess.TimeoutExpired:
print("❌ apktool反编译超时")
return None
except Exception as e:
print(f"❌ apktool反编译异常: {e}")
return None
def extract_dex_with_dex2jar(self, apk_path: str, output_dir: str = None) -> Optional[str]:
"""使用dex2jar提取DEX文件"""
if not self.tools_available['dex2jar']:
print("❌ dex2jar工具不可用")
return None
if not output_dir:
output_dir = f"dex2jar_output_{os.path.basename(apk_path).replace('.apk', '')}"
os.makedirs(output_dir, exist_ok=True)
try:
# 首先解压APK获取DEX文件
with tempfile.TemporaryDirectory() as temp_dir:
# 解压APK
import zipfile
with zipfile.ZipFile(apk_path, 'r') as apk:
dex_files = [f for f in apk.namelist() if f.endswith('.dex')]
for dex_file in dex_files:
apk.extract(dex_file, temp_dir)
# 转换DEX到JAR
jar_files = []
for dex_file in dex_files:
dex_path = os.path.join(temp_dir, dex_file)
jar_name = dex_file.replace('.dex', '.jar')
jar_path = os.path.join(output_dir, jar_name)
cmd = ['d2j-dex2jar', dex_path, '-o', jar_path]
print(f" dex2jar转换命令: {' '.join(cmd)}")
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
if result.returncode == 0:
jar_files.append(jar_path)
print(f"✅ {dex_file} -> {jar_name} 转换成功")
else:
print(f"❌ DEX转换失败: {result.stderr}")
return output_dir if jar_files else None
except Exception as e:
print(f"❌ dex2jar转换异常: {e}")
return None
def comprehensive_decompile(self, apk_path: str, output_base_dir: str = "decompiled_apk") -> Dict[str, str]:
"""综合反编译(使用所有可用工具)"""
results = {}
os.makedirs(output_base_dir, exist_ok=True)
# 使用jadx反编译
jadx_output = self.decompile_with_jadx(
apk_path,
os.path.join(output_base_dir, "jadx_output")
)
if jadx_output:
results['jadx'] = jadx_output
# 使用apktool反编译
apktool_output = self.decompile_with_apktool(
apk_path,
os.path.join(output_base_dir, "apktool_output")
)
if apktool_output:
results['apktool'] = apktool_output
# 使用dex2jar提取DEX
dex2jar_output = self.extract_dex_with_dex2jar(
apk_path,
os.path.join(output_base_dir, "dex2jar_output")
)
if dex2jar_output:
results['dex2jar'] = dex2jar_output
print(f"\n🎯 综合反编译完成,结果目录:")
for tool, output_dir in results.items():
print(f" {tool}: {output_dir}")
return results
def setup_decompilation_environment():
"""设置反编译环境的说明"""
setup_guide = """
=== APK反编译环境设置指南 ===
1. 安装Java (jadx和dex2jar需要Java环境)
- 下载并安装JDK 8+
- 设置JAVA_HOME环境变量
2. 安装jadx (推荐的APK反编译工具)
- 下载地址: https://github.com/skylot/jadx
- 解压后将bin目录添加到PATH环境变量
- 验证: jadx --version
3. 安装apktool
- 下载地址: https://ibotpeaches.github.io/Apktool/
- 下载apktool脚本和jar文件
- 设置为可执行并添加到PATH
- 验证: apktool --version
4. 安装dex2jar
- 下载地址: https://github.com/pxb1988/dex2jar
- 解压后将目录添加到PATH
- 验证: d2j-dex2jar --version
5. 安装Python依赖
pip install lief # 用于分析ELF文件
pip install androguard # 专业的Android恶意软件分析工具
"""
print(setup_guide)
return setup_guide
def demo_apk_analysis():
"""演示APK分析流程"""
print("🔍 APK分析演示")
# 显示可用工具
decompiler = APKDecompiler()
# 注意:实际使用时需要提供真实的APK文件路径
print("\nℹ️ 要进行实际分析,请提供APK文件路径")
print("示例: decompiler.comprehensive_decompile('path/to/app.apk')")
# 显示设置指南
setup_decompilation_environment()
if __name__ == "__main__":
demo_apk_analysis()#APK安全检测
# apk_security_scanner.py - APK安全扫描工具
from typing import Dict, List, Any
import zipfile
import re
from apk_analyzer import APKAnalyzer
class APKSecurityScanner:
"""APK安全扫描器"""
def __init__(self, apk_path: str):
self.apk_path = apk_path
self.analyzer = APKAnalyzer(apk_path)
self.security_issues = []
def scan_permissions(self) -> List[Dict[str, Any]]:
"""扫描敏感权限"""
issues = []
# 高危权限列表
dangerous_permissions = [
'android.permission.CAMERA',
'android.permission.RECORD_AUDIO',
'android.permission.ACCESS_FINE_LOCATION',
'android.permission.READ_CONTACTS',
'android.permission.READ_SMS',
'android.permission.SEND_SMS',
'android.permission.READ_PHONE_STATE',
'android.permission.GET_ACCOUNTS',
'android.permission.READ_CALENDAR',
'android.permission.WRITE_CALENDAR',
'android.permission.READ_CALL_LOG',
'android.permission.WRITE_CALL_LOG',
'android.permission.ADD_VOICEMAIL',
'android.permission.USE_SIP',
'android.permission.PROCESS_OUTGOING_CALLS',
'android.permission.BODY_SENSORS',
'android.permission.ACTIVITY_RECOGNITION'
]
# 检查manifest中的权限
apk_info = self.analyzer.analyze_apk()
permissions = apk_info.get('permissions', [])
for perm in permissions:
if perm in dangerous_permissions:
issues.append({
'type': 'dangerous_permission',
'severity': 'high',
'description': f'应用请求高危权限: {perm}',
'recommendation': '检查是否真的需要此权限,考虑使用替代方案'
})
return issues
def scan_network_security_config(self) -> List[Dict[str, Any]]:
"""扫描网络安全配置"""
issues = []
# 检查是否允许明文流量
with zipfile.ZipFile(self.apk_path, 'r') as apk:
# 检查网络安全性配置文件
ns_config_paths = [
'res/xml/network_security_config.xml',
'AndroidManifest.xml'
]
for path in ns_config_paths:
try:
content = apk.read(path).decode('utf-8', errors='ignore')
# 检查是否允许明文流量
if re.search(r'<base-config.*?cleartextTrafficPermitted="true"', content):
issues.append({
'type': 'insecure_network_config',
'severity': 'medium',
'description': '应用允许明文HTTP流量,存在安全风险',
'recommendation': '配置网络安全策略,禁止明文流量'
})
# 检查是否信任用户证书
if re.search(r'<trust-anchors>.*?<certificates\s+src="user"', content, re.DOTALL):
issues.append({
'type': 'certificate_validation_bypass',
'severity': 'high',
'description': '应用信任用户安装的证书,易受中间人攻击',
'recommendation': '只信任系统预装证书'
})
except KeyError:
continue
except Exception:
continue
return issues
def scan_debug_flags(self) -> List[Dict[str, Any]]:
"""扫描调试标志"""
issues = []
# 检查AndroidManifest.xml中的调试标志
with zipfile.ZipFile(self.apk_path, 'r') as apk:
try:
manifest_content = apk.read('AndroidManifest.xml').decode('utf-8', errors='ignore')
# 检查是否允许调试
if re.search(r'android:debuggable="true"', manifest_content):
issues.append({
'type': 'debug_enabled',
'severity': 'high',
'description': '应用启用了调试模式,存在安全风险',
'recommendation': '发布版本应禁用调试模式'
})
# 检查是否允许备份
if re.search(r'android:allowBackup="true"', manifest_content):
issues.append({
'type': 'backup_enabled',
'severity': 'medium',
'description': '应用允许备份,可能泄露敏感数据',
'recommendation': '敏感应用应禁用备份功能'
})
except KeyError:
pass
except Exception:
pass
return issues
def scan_code_quality(self) -> List[Dict[str, Any]]:
"""扫描代码质量问题"""
issues = []
# 检查硬编码的敏感信息
with zipfile.ZipFile(self.apk_path, 'r') as apk:
# 搜索所有文本文件
text_files = [f for f in apk.namelist() if f.endswith(('.xml', '.txt', '.properties'))]
sensitive_patterns = [
(r'api[_-]?key.*?["\'][A-Za-z0-9_-]{20,}', 'hardcoded_api_key'),
(r'password.*?["\'][^"\']{6,}', 'hardcoded_password'),
(r'token.*?["\'][A-Za-z0-9_-]{20,}', 'hardcoded_token'),
(r'["\']https?://[^"\']*secret[^"\']*["\']', 'hardcoded_secret_url')
]
for file_path in text_files:
try:
content = apk.read(file_path).decode('utf-8', errors='ignore')
for pattern, issue_type in sensitive_patterns:
matches = re.findall(pattern, content, re.IGNORECASE)
for match in matches:
issues.append({
'type': issue_type,
'severity': 'high',
'description': f'在 {file_path} 中发现硬编码的敏感信息: {match[:50]}...',
'recommendation': '使用安全的存储方式,如Android Keystore'
})
except Exception:
continue
return issues
def run_full_scan(self) -> Dict[str, Any]:
"""运行完整安全扫描"""
print(f"🔍 开始扫描APK: {self.apk_path}")
all_issues = []
# 执行各项扫描
all_issues.extend(self.scan_permissions())
all_issues.extend(self.scan_network_security_config())
all_issues.extend(self.scan_debug_flags())
all_issues.extend(self.scan_code_quality())
# 按严重性排序
severity_order = {'critical': 0, 'high': 1, 'medium': 2, 'low': 3}
all_issues.sort(key=lambda x: severity_order.get(x['severity'], 99))
# 生成扫描报告
report = {
'applied': self.apk_path,
'scan_time': '2024-01-01T00:00:00Z', # 实际应用中应使用当前时间
'total_issues': len(all_issues),
'issues_by_severity': {
'critical': len([i for i in all_issues if i['severity'] == 'critical']),
'high': len([i for i in all_issues if i['severity'] == 'high']),
'medium': len([i for i in all_issues if i['severity'] == 'medium']),
'low': len([i for i in all_issues if i['severity'] == 'low'])
},
'issues': all_issues
}
return report
def print_scan_report(self, report: Dict[str, Any]):
"""打印扫描报告"""
print(f"\n🛡️ APK安全扫描报告")
print(f"📁 应用: {report['applied']}")
print(f"📊 总问题数: {report['total_issues']}")
print(f" - 高危: {report['issues_by_severity']['high']}")
print(f" - 中危: {report['issues_by_severity']['medium']}")
print(f" - 低危: {report['issues_by_severity']['low']}")
if report['issues']:
print(f"\n🔍 发现的安全问题:")
for i, issue in enumerate(report['issues'], 1):
severity_symbol = {
'critical': '🔴',
'high': '🟠',
'medium': '🟡',
'low': '🟢'
}.get(issue['severity'], '⚪')
print(f"\n{i}. {severity_symbol} {issue['type']}")
print(f" 描述: {issue['description']}")
print(f" 建议: {issue['recommendation']}")
def scan_apk_security(apk_path: str):
"""扫描APK安全性的便捷函数"""
scanner = APKSecurityScanner(apk_path)
report = scanner.run_full_scan()
scanner.print_scan_report(report)
return report
def main():
"""主函数示例"""
print("APK安全扫描工具")
print("请提供APK文件路径进行安全扫描")
if __name__ == "__main__":
main()
