APP基础
格式:
*.IPA IOS系统的app文件格式
*.APK 安卓系统的app文件格式
APK静态分析
加固(壳):
目的:
防止APK被逆向,防篡改,实现数据保护
种类:
APK加壳的中了非常多,我在取证过程中所见到的加壳种类有UPX和pyintaller的加壳方式
脱壳:
一般我们用雷电挂载APK时可以直接使用雷电自带的脱壳来脱壳之后反编译。但是有些恶意文件不能用雷电来挂载,这个时候我们就要手动查壳
查壳工具:
Detect It Easy3.7(之前老的版本可能查不到pyinstaller这个加壳方式)
PEiD:偶尔用,效果不是很好
当然可以用沙箱的方法看一下他是不是有壳
脱壳工具:
知道是什么壳加固了的话,就可以对症下药了
如果是UPX壳的话就用UPX tool来脱
或者360壳,使用取证工具来脱
如果是pyinstaller壳的话用脚本来脱,将脚本和恶意文件放到一个文件夹下面,用cmd跑,会得到一个文件夹,文件夹里的文件不带pyc后缀,要自己加上,然后这些文件也缺少文件头,还要自己补充
然后可以使用在线pyc反编译
"""
PyInstaller Extractor v1.9 (Supports pyinstaller 3.3, 3.2, 3.1, 3.0, 2.1, 2.0)
Author : Extreme Coders
E-mail : extremecoders(at)hotmail(dot)com
Web : https://0xec.blogspot.com
Date : 29-November-2017
Url : https://sourceforge.net/projects/pyinstallerextractor/
For any suggestions, leave a comment on
https://forum.tuts4you.com/topic/34455-pyinstaller-extractor/
This script extracts a pyinstaller generated executable file.
Pyinstaller installation is not needed. The script has it all.
For best results, it is recommended to run this script in the
same version of python as was used to create the executable.
This is just to prevent unmarshalling errors(if any) while
extracting the PYZ archive.
Usage : Just copy this script to the directory where your exe resides
and run the script with the exe file name as a parameter
C:\path\to\exe\>python pyinstxtractor.py <filename>
$ /path/to/exe/python pyinstxtractor.py <filename>
Licensed under GNU General Public License (GPL) v3.
You are free to modify this source.
CHANGELOG
================================================
Version 1.1 (Jan 28, 2014)
-------------------------------------------------
- First Release
- Supports only pyinstaller 2.0
Version 1.2 (Sept 12, 2015)
-------------------------------------------------
- Added support for pyinstaller 2.1 and 3.0 dev
- Cleaned up code
- Script is now more verbose
- Executable extracted within a dedicated sub-directory
(Support for pyinstaller 3.0 dev is experimental)
Version 1.3 (Dec 12, 2015)
-------------------------------------------------
- Added support for pyinstaller 3.0 final
- Script is compatible with both python 2.x & 3.x (Thanks to Moritz Kroll @ Avira Operations GmbH & Co. KG)
Version 1.4 (Jan 19, 2016)
-------------------------------------------------
- Fixed a bug when writing pyc files >= version 3.3 (Thanks to Daniello Alto: https://github.com/Djamana)
Version 1.5 (March 1, 2016)
-------------------------------------------------
- Added support for pyinstaller 3.1 (Thanks to Berwyn Hoyt for reporting)
Version 1.6 (Sept 5, 2016)
-------------------------------------------------
- Added support for pyinstaller 3.2
- Extractor will use a random name while extracting unnamed files.
- For encrypted pyz archives it will dump the contents as is. Previously, the tool would fail.
Version 1.7 (March 13, 2017)
-------------------------------------------------
- Made the script compatible with python 2.6 (Thanks to Ross for reporting)
Version 1.8 (April 28, 2017)
-------------------------------------------------
- Support for sub-directories in .pyz files (Thanks to Moritz Kroll @ Avira Operations GmbH & Co. KG)
Version 1.9 (November 29, 2017)
-------------------------------------------------
- Added support for pyinstaller 3.3
- Display the scripts which are run at entry (Thanks to Michael Gillespie @ malwarehunterteam for the feature request)
"""
from __future__ import print_function
import os
import struct
import marshal
import zlib
import sys
import imp
import types
from uuid import uuid4 as uniquename
class CTOCEntry:
def __init__(self, position, cmprsdDataSize, uncmprsdDataSize, cmprsFlag, typeCmprsData, name):
self.position = position
self.cmprsdDataSize = cmprsdDataSize
self.uncmprsdDataSize = uncmprsdDataSize
self.cmprsFlag = cmprsFlag
self.typeCmprsData = typeCmprsData
self.name = name
class PyInstArchive:
PYINST20_COOKIE_SIZE = 24 # For pyinstaller 2.0
PYINST21_COOKIE_SIZE = 24 + 64 # For pyinstaller 2.1+
MAGIC = b'MEI\014\013\012\013\016' # Magic number which identifies pyinstaller
def __init__(self, path):
self.filePath = path
def open(self):
try:
self.fPtr = open(self.filePath, 'rb')
self.fileSize = os.stat(self.filePath).st_size
except:
print('[*] Error: Could not open {0}'.format(self.filePath))
return False
return True
def close(self):
try:
self.fPtr.close()
except:
pass
def checkFile(self):
print('[*] Processing {0}'.format(self.filePath))
# Check if it is a 2.0 archive
self.fPtr.seek(self.fileSize - self.PYINST20_COOKIE_SIZE, os.SEEK_SET)
magicFromFile = self.fPtr.read(len(self.MAGIC))
if magicFromFile == self.MAGIC:
self.pyinstVer = 20 # pyinstaller 2.0
print('[*] Pyinstaller version: 2.0')
return True
# Check for pyinstaller 2.1+ before bailing out
self.fPtr.seek(self.fileSize - self.PYINST21_COOKIE_SIZE, os.SEEK_SET)
magicFromFile = self.fPtr.read(len(self.MAGIC))
if magicFromFile == self.MAGIC:
print('[*] Pyinstaller version: 2.1+')
self.pyinstVer = 21 # pyinstaller 2.1+
return True
print('[*] Error : Unsupported pyinstaller version or not a pyinstaller archive')
return False
def getCArchiveInfo(self):
try:
if self.pyinstVer == 20:
self.fPtr.seek(self.fileSize - self.PYINST20_COOKIE_SIZE, os.SEEK_SET)
# Read CArchive cookie
(magic, lengthofPackage, toc, tocLen, self.pyver) = \
struct.unpack('!8siiii', self.fPtr.read(self.PYINST20_COOKIE_SIZE))
elif self.pyinstVer == 21:
self.fPtr.seek(self.fileSize - self.PYINST21_COOKIE_SIZE, os.SEEK_SET)
# Read CArchive cookie
(magic, lengthofPackage, toc, tocLen, self.pyver, pylibname) = \
struct.unpack('!8siiii64s', self.fPtr.read(self.PYINST21_COOKIE_SIZE))
except:
print('[*] Error : The file is not a pyinstaller archive')
return False
print('[*] Python version: {0}'.format(self.pyver))
# Overlay is the data appended at the end of the PE
self.overlaySize = lengthofPackage
self.overlayPos = self.fileSize - self.overlaySize
self.tableOfContentsPos = self.overlayPos + toc
self.tableOfContentsSize = tocLen
print('[*] Length of package: {0} bytes'.format(self.overlaySize))
return True
def parseTOC(self):
# Go to the table of contents
self.fPtr.seek(self.tableOfContentsPos, os.SEEK_SET)
self.tocList = []
parsedLen = 0
# Parse table of contents
while parsedLen < self.tableOfContentsSize:
(entrySize, ) = struct.unpack('!i', self.fPtr.read(4))
nameLen = struct.calcsize('!iiiiBc')
(entryPos, cmprsdDataSize, uncmprsdDataSize, cmprsFlag, typeCmprsData, name) = \
struct.unpack( \
'!iiiBc{0}s'.format(entrySize - nameLen), \
self.fPtr.read(entrySize - 4))
name = name.decode('utf-8').rstrip('\0')
if len(name) == 0:
name = str(uniquename())
print('[!] Warning: Found an unamed file in CArchive. Using random name {0}'.format(name))
self.tocList.append( \
CTOCEntry( \
self.overlayPos + entryPos, \
cmprsdDataSize, \
uncmprsdDataSize, \
cmprsFlag, \
typeCmprsData, \
name \
))
parsedLen += entrySize
print('[*] Found {0} files in CArchive'.format(len(self.tocList)))
def extractFiles(self):
print('[*] Beginning extraction...please standby')
extractionDir = os.path.join(os.getcwd(), os.path.basename(self.filePath) + '_extracted')
if not os.path.exists(extractionDir):
os.mkdir(extractionDir)
os.chdir(extractionDir)
for entry in self.tocList:
basePath = os.path.dirname(entry.name)
if basePath != '':
# Check if path exists, create if not
if not os.path.exists(basePath):
os.makedirs(basePath)
self.fPtr.seek(entry.position, os.SEEK_SET)
data = self.fPtr.read(entry.cmprsdDataSize)
if entry.cmprsFlag == 1:
data = zlib.decompress(data)
# Malware may tamper with the uncompressed size
# Comment out the assertion in such a case
assert len(data) == entry.uncmprsdDataSize # Sanity Check
with open(entry.name, 'wb') as f:
f.write(data)
if entry.typeCmprsData == b's':
print('[+] Possible entry point: {0}'.format(entry.name))
elif entry.typeCmprsData == b'z' or entry.typeCmprsData == b'Z':
self._extractPyz(entry.name)
def _extractPyz(self, name):
dirName = name + '_extracted'
# Create a directory for the contents of the pyz
if not os.path.exists(dirName):
os.mkdir(dirName)
with open(name, 'rb') as f:
pyzMagic = f.read(4)
assert pyzMagic == b'PYZ\0' # Sanity Check
pycHeader = f.read(4) # Python magic value
if imp.get_magic() != pycHeader:
print('[!] Warning: The script is running in a different python version than the one used to build the executable')
print(' Run this script in Python{0} to prevent extraction errors(if any) during unmarshalling'.format(self.pyver))
(tocPosition, ) = struct.unpack('!i', f.read(4))
f.seek(tocPosition, os.SEEK_SET)
try:
toc = marshal.load(f)
except:
print('[!] Unmarshalling FAILED. Cannot extract {0}. Extracting remaining files.'.format(name))
return
print('[*] Found {0} files in PYZ archive'.format(len(toc)))
# From pyinstaller 3.1+ toc is a list of tuples
if type(toc) == list:
toc = dict(toc)
for key in toc.keys():
(ispkg, pos, length) = toc[key]
f.seek(pos, os.SEEK_SET)
fileName = key
try:
# for Python > 3.3 some keys are bytes object some are str object
fileName = key.decode('utf-8')
except:
pass
# Make sure destination directory exists, ensuring we keep inside dirName
destName = os.path.join(dirName, fileName.replace("..", "__"))
destDirName = os.path.dirname(destName)
if not os.path.exists(destDirName):
os.makedirs(destDirName)
try:
data = f.read(length)
data = zlib.decompress(data)
except:
print('[!] Error: Failed to decompress {0}, probably encrypted. Extracting as is.'.format(fileName))
open(destName + '.pyc.encrypted', 'wb').write(data)
continue
with open(destName + '.pyc', 'wb') as pycFile:
pycFile.write(pycHeader) # Write pyc magic
pycFile.write(b'\0' * 4) # Write timestamp
if self.pyver >= 33:
pycFile.write(b'\0' * 4) # Size parameter added in Python 3.3
pycFile.write(data)
def main():
if len(sys.argv) < 2:
print('[*] Usage: pyinstxtractor.py <filename>')
else:
arch = PyInstArchive(sys.argv[1])
if arch.open():
if arch.checkFile():
if arch.getCArchiveInfo():
arch.parseTOC()
arch.extractFiles()
arch.close()
print('[*] Successfully extracted pyinstaller archive: {0}'.format(sys.argv[1]))
print('')
print('You can now use a python decompiler on the pyc files within the extracted directory')
return
arch.close()
if __name__ == '__main__':
main()
内部结构:
APK本质是一个压缩包,可以修改为.zip然后解压看到里面的内部文件
AndroidManifest.xml全局清单文件(最重要)
使用雷电我们是可以直接看到的这些内容
内含:APK安装包名、版本、SDK版本、APP运行权限、Activity主入口函数,第三方SDK调用信息
classes.dex(在虚拟机上可执行文件)Bytecode程序代码主体
是我们逆向分析的主要对象是java源码编译生成的java字节文件(是java文件经过jdk编译,再经dex编译生成classes.dex)。可查看域名,IP,加解密算法之类的信息
查看工具:雷电、JEB(看不懂的代码可以使用tab键转换为java语言)、ida等反编译工具
分析方法:先找主程序/入口程序/主函数,然后顺着往下看(合理利用查找)
META-INF(签名信息)
APK中的证书公钥、有效日期、签名以及证书的MD5、SHA-1/256、指纹信息、数字签名(不同的程序可能名字相同但数字签名不同)
resources.arsc(二进制索引文件)
图片,颜色等现在这个文件里面存储,然后在映射道Android的R文件中,形成ID,在使用程序资源是直接就可以通过ID调用
Assets(资源文件夹)
lib(存放so文件)
存放第三方库文件,这些so文件一般都是使用C或C++编写,单靠JAVA很难实现高性能,会依赖SO文件,一般一些隐藏的加密文件都会用so文件封装
根据CPU性能架构,lib分为四种分别是ARM(高通骁龙、麒麟基本都是)、ARM-V7、MIPS、x86(桌面笔记本CPU一般使用),对应目录为armeabi、armeabi-v7a、mips、x86
一般越好的程序他的架构越多,能在各种手机上面使用,甚至电脑
分析方法
混淆市代码加固方式的解决
通过修该代码函数的名字起到影响查找函数的作用,比如把函数名字改成abcdef什么的
先找主函数,可以找LAUNCHER,LAUNCHER上面那个activity名字就是主函数,然后一步步找主函数页面,找到主函数只有再找oncreate
正则表达式搜索
使用魔盾安全可以直接看到一些重要信息
APK动态分析
APK抓包概述
一般抓包使用https抓包
抓包工具
代理抓包工具(小而美):FIddler、Charles等。可以解密https的,tcp之上的都可以解析
非代理抓包:wireshark、httpdebugger
取证抓包工具:应用程序检测大师系统、雷电
FLDDLER抓包
首先下载并配置了flddler的抓包工具
端口写一个不会冲突的端口
然后我们要配置我们的雷电模拟器(注意这里必须要用雷电九,雷电四不能设置WiFi代理)
点击WiFi的齿轮设置手动代理
代理服务器主机名是我们电脑的wife地址
设置完代理之后我们用手机访问就是可以发现是能抓到报的了
确实抓到了
若要解密https流,我们要重新设置
双方都要安装假证书
两者都安装好之后,查看https的官网就可以看到了
这两个图片都有
adb.exe命令
adb.exe在雷电的文件夹里面,使用要使用cmd
使用方法:
先使用adb devices命令测试链接(出现xxx device表示测试成功可以通讯,不成功可以考虑usb模式是否打开)
在手机上打开要测试的软件程序
adb shell dumpsys window | findstr mCurrentFocus(mCurrentFocus表示鼠标当前光标位置,也就是打开的软件) 获取当前窗口特征,比如包名
adb shell ps 获取进程信息,可以找进程名
adb shell pm path 包名 获取安装路径
adb pull + 路径名,可以讲文件备份拉出来,不指定路径的话就默认存储在雷电文件夹下面
安卓文件目录查看:
APP安装包: /data/app/目录
APP存储位置: /data/data/目录
vmdk文件取证
有的时候给的不是E01或者DD镜像格式,而是给的vmdk格式的镜像,一般会给你三个:data.vmdk system.vmdk sdcard.vmdk文件,这种用火眼是不能分析的
解析方法:
手机大师解析
模拟器镜像替换解析:在雷电文件夹下的vms文件里面替换
取证大师解析