本章将介绍移动设备上的Python数字取证及其涉及的概念.
简介
移动设备取证是数字取证的一个分支它涉及移动设备的获取和分析,以恢复调查兴趣的数字证据.这个分支与计算机取证不同,因为移动设备有一个内置的通信系统,可用于提供与位置相关的有用信息.
尽管智能手机在数字取证方面的使用日益增加 - 由于其异质性,它仍然被认为是非标准的.另一方面,计算机硬件(例如硬盘)被认为是标准的并且也被发展为稳定的规则.在数字取证行业,对非标准设备使用的技术存在很多争议,有短暂的证据,如智能手机.
可从移动设备中提取的工件
与仅具有通话记录或SMS消息的旧手机相比,现代移动设备拥有大量数字信息.因此,移动设备可以为调查人员提供有关其用户的大量见解.可以从移动设备中提取的一些工件如下所述 :
消息 : ;这些是有用的工件,可以揭示所有者的心态,甚至可以向研究者提供一些以前未知的信息.
位置历史 : 去;位置历史数据是一个有用的工件,调查人员可以使用它来验证人的特定位置.
已安装的应用程序 : 通过访问安装的应用程序类型,调查人员可以深入了解移动用户的习惯和想法.
证据来源和处理Python
智能手机将SQLite数据库和PLIST文件作为主要证据来源.在本节中,我们将在python中处理证据来源.
分析PLIST文件
PLIST(属性列表)是一个灵活的用于存储应用数据的便捷格式,尤其是在iPhone设备上它使用扩展名 .plist .这种文件用于存储有关包和应用程序的信息.它可以采用两种格式: XML 和二进制.以下Python代码将打开并读取PLIST文件.请注意,在继续此操作之前,我们必须创建自己的 Info.plist 文件.
首先,安装名为 biplist 通过以下命令 :
Pip install biplist
现在,导入一些有用的库来处理plist文件 :
import biplist import os import sys
现在,在main方法下使用以下命令可以将plist文件读入变量 :
def main(plist): try: data = biplist.readPlist(plist) except (biplist.InvalidPlistException,biplist.NotBinaryPlistException) as e:print("[-] Invalid PLIST file - unable to be opened by biplist")sys.exit(1)
现在,我们可以从这个变量读取控制台上的数据或直接打印它.
SQLite Databas es
SQLite充当移动设备上的主要数据存储库. SQLite是一个进程内库,它实现了一个独立的,无服务器,零配置的事务SQL数据库引擎.它是一个零配置的数据库,您不需要在系统中配置它,与其他数据库不同.
如果您是新手或不熟悉SQLite数据库,您可以按照链接 www.it1352.comhttps://www.tutorialspoint.com/sqlite/index.htm 此外,您可以点击链接 www.it1352.comhttps://www.tutorialspoint.com/sqlite/sqlite_python.htm ,以便您详细了解SQLite与Python.
在移动取证期间,我们可以与移动设备的 sms.db 文件进行交互,并可以从消息中提取有价值的信息表. Python有一个名为 sqlite3 的内置库,用于连接SQLite数据库.您可以使用以下命令导入相同的内容 :
import sqlite3
现在,在以下命令的帮助下,我们可以连接数据库,例如 sms.db ,如果是移动设备 :
Conn = sqlite3.connect('sms.db') C = conn.cursor()
在这里, C是游标对象,借助它我们可以与数据库交互.
现在,假设我们想要执行特定命令,比如从获取详细信息abc table ,它可以在以下命令的帮助下完成;
c.execute("Select * from abc" ) c.close()
上述命令的结果将存储在游标对象中.类似地,我们可以使用 fetchall()方法将结果转储到我们可以操作的变量中.
我们可以使用以下命令获取消息的列名数据表中 sms.db :
c.execute("pragma table_info(message)") table_data = c.fetchall() columns = [x [1] for table in the table_data
注意这里我们使用的是SQLite PRAGMA命令这是用于在SQLite环境中控制各种环境变量和状态标志的特殊命令.在上面的命令中, fetchall()方法返回结果元组.每个列的名称都存储在每个元组的第一个索引中.
现在,在以下命令的帮助下,我们可以在表中查询其所有数据并将其存储在名为
c.execute("Select * from message") data_msg = c.fetchall ()
上面的命令会将数据存储在变量中,我们还可以使用 csv.writer将上述数据写入CSV文件中()方法.
iTunes备份
可以对iTunes进行的备份执行iPhone移动取证.法医检查员依靠分析通过iTunes获得的iPhone逻辑备份. iTunes使用AFC(Apple文件连接)协议进行备份.此外,除了托管密钥记录之外,备份过程不会修改iPhone上的任何内容.
现在,问题出现了为什么数字取证专家理解iTunes上的技术很重要备份?重要的是,如果我们直接访问嫌疑人的计算机而不是iPhone,因为当使用计算机与iPhone同步时,iPhone上的大部分信息都可能会在计算机上备份.
备份过程及其位置
每当Apple产品备份到计算机时,它都会与iTunes同步,并且会有一个带有设备唯一ID的特定文件夹.在最新的备份格式中,文件存储在包含文件名的前两个十六进制字符的子文件夹中.从这些备份文件中,有一些文件,如info.plist,它们与名为Manifest.db的数据库一起使用.下表显示了备份位置,这些位置因iTunes备份的操作系统而有所不同;
OS | 备份位置 |
---|---|
Win7 | C:\ Users \ [用户名] \ AppData \Roaming\AppleComputer \ MobileSync \ Backup \ |
MAC OS X | 〜/Library/Application Suport/MobileSync/Backup/ |
要使用Python处理iTunes备份,我们需要首先识别所有备份根据我们的操作系统备份位置.然后我们将遍历每个备份并读取数据库Manifest.db.
现在,在下面的Python代码的帮助下,我们可以做同样的 :
首先,按以下方式导入必要的库;
from __future__ import print_functionimport argparseimport loggingimport osfrom shutil import copyfileimport sqlite3import syslogger = logging.getLogger(__name__)
现在,提供两个位置参数,即INPUT_DIR和OUTPUT_DIR,它代表iTunes备份和所需的输出文件夹 :
if __name__ == "__main__": parser.add_argument("INPUT_DIR",help = "Location of folder containing iOS backups, ""e.g. ~\Library\Application Support\MobileSync\Backup folder") parser.add_argument("OUTPUT_DIR", help = "Output Directory") parser.add_argument("-l", help = "Log file path",default = __file__[:-2] + "log") parser.add_argument("-v", help = "Increase verbosity",action = "store_true") args = parser.parse_args()
现在,设置日志如下 :
if args.v: logger.setLevel(logging.DEBUG)else: logger.setLevel(logging.INFO)
现在,设置此日志的消息格式如下 :
msg_fmt = logging.Formatter("%(asctime)-15s %(funcName)-13s""%(levelname)-8s %(message)s")strhndl = logging.StreamHandler(sys.stderr)strhndl.setFormatter(fmt = msg_fmt)fhndl = logging.FileHandler(args.l, mode = 'a')fhndl.setFormatter(fmt = msg_fmt)logger.addHandler(strhndl)logger.addHandler(fhndl)logger.info("Starting iBackup Visualizer")logger.debug("Supplied arguments: {}".format(" ".join(sys.argv[1:])))logger.debug("System: " + sys.platform)logger.debug("Python Version: " + sys.version)
以下代码行将使用 os.makedirs()函数 :
if not os.path.exists(args.OUTPUT_DIR): os.makedirs(args.OUTPUT_DIR)
现在,传递提供的输入并将目录输出到main()函数,如下所示 :
if os.path.exists(args.INPUT_DIR) and os.path.isdir(args.INPUT_DIR): main(args.INPUT_DIR, args.OUTPUT_DIR)else: logger.error("Supplied input directory does not exist or is not ""a directory") sys.exit(1)
现在,写一下 main()函数,它将进一步调用 backup_summary()用于标识输入文件夹中存在的所有备份的函数 :
def main(in_dir, out_dir): backups = backup_summary(in_dir)def backup_summary(in_dir): logger.info("Identifying all iOS backups in {}".format(in_dir)) root = os.listdir(in_dir) backups = {} for x in root: temp_dir = os.path.join(in_dir, x) if os.path.isdir(temp_dir) and len(x) == 40: num_files = 0 size = 0 for root, subdir, files in os.walk(temp_dir): num_files += len(files) size += sum(os.path.getsize(os.path.join(root, name)) for name in files) backups[x] = [temp_dir, num_files, size] return backups
现在,将每个备份的摘要打印到控制台,如下所示;
print("Backup Summary")print("=" * 20)if len(backups) > 0: for i, b in enumerate(backups): print("Backup No.: {} \n""Backup Dev. Name: {} \n""# Files: {} \n""Backup Size (Bytes): {}\n".format(i, b, backups[b][1], backups[b][2]))
现在,将Manifest.db文件的内容转储到名为db_items的变量.
try: db_items = process_manifest(backups[b][0]) except IOError: logger.warn("Non-iOS 10 backup encountered or " "invalid backup. Continuing to next backup.")continue
现在,让我们定义一个将采用目录路径的函数of backup :
def process_manifest(backup): manifest = os.path.join(backup, "Manifest.db") if not os.path.exists(manifest): logger.error("Manifest DB not found in {}".format(manifest)) raise IOError
现在,使用SQLite3,我们将通过名为c : 的游标连接到数据库;
c = conn.cursor()items = {}for row in c.execute("SELECT * from Files;"): items[row[0]] = [row[2], row[1], row[3]]return itemscreate_files(in_dir, out_dir, b, db_items) print("=" * 20)else: logger.warning("No valid backups found. The input directory should be " "the parent-directory immediately above the SHA-1 hash " "iOS device backups") sys.exit(2)
现在,将 create_files()方法定义如下 :
def create_files(in_dir, out_dir, b, db_items): msg = "Copying Files for backup {} to {}".format(b, os.path.join(out_dir, b)) logger.info(msg)
现在,遍历 db_items 字典中的每个键 :
for x, key in enumerate(db_items): if db_items[key][0] is None or db_items[key][0] == "": continue else: dirpath = os.path.join(out_dir, b,os.path.dirname(db_items[key][0])) filepath = os.path.join(out_dir, b, db_items[key][0]) if not os.path.exists(dirpath): os.makedirs(dirpath) original_dir = b + "/" + key[0:2] + "/" + key path = os.path.join(in_dir, original_dir) if os.path.exists(filepath): filepath = filepath + "_{}".format(x)
现在,使用 shutil.copyfile()方法将备份文件复制如下 :
try: copyfile(path, filepath) except IOError: logger.debug("File not found in backup: {}".format(path)) files_not_found += 1 if files_not_found > 0: logger.warning("{} files listed in the Manifest.db not" "found inbackup".format(files_not_found)) copyfile(os.path.join(in_dir, b, "Info.plist"), os.path.join(out_dir, b,"Info.plist")) copyfile(os.path.join(in_dir, b, "Manifest.db"), os.path.join(out_dir, b,"Manifest.db")) copyfile(os.path.join(in_dir, b, "Manifest.plist"), os.path.join(out_dir, b,"Manifest.plist")) copyfile(os.path.join(in_dir, b, "Status.plist"),os.path.join(out_dir, b,"Status.plist"))
使用上面的Python脚本,我们可以获得更新的备份文件结构在我们的输出文件夹中我们可以使用 pycrypto python库来解密备份.
Wi-Fi
移动设备可用于连接通过无处不在的Wi-Fi网络连接到外部世界.有时设备会自动连接到这些开放网络.
如果是iPhone,设备已连接的开放式Wi-Fi连接列表存储在名为
我们需要使用Python从标准Cellebrite XML报告中提取Wi-Fi详细信息.为此,我们需要使用来自无线地理日志引擎(WIGLE)的API,这是一个流行的平台,可用于使用Wi-Fi网络的名称查找设备的位置.
我们可以使用名为 requests 的Python库从WIGLE访问API.它可以安装如下 :
pip install requests
来自WIGLE的API
我们需要在WIGLE的网站上注册 https://wigle.net/account 从WIGLE获取免费API.用于通过WIGEL的API获取有关用户设备及其连接的信息的Python脚本将在下面讨论 :
首先,导入以下库以处理不同的东西和减号;
from __future__ import print_functionimport argparseimport csvimport osimport sysimport xml.etree.ElementTree as ETimport requests
现在,提供两个位置参数,即 INPUT_FILE 和 OUTPUT_CSV 分别表示具有Wi-Fi MAC地址和所需输出CSV文件的输入文件 :
if __name__ == "__main__": parser.add_argument("INPUT_FILE", help = "INPUT FILE with MAC Addresses") parser.add_argument("OUTPUT_CSV", help = "Output CSV File") parser.add_argument("-t", help = "Input type: Cellebrite XML report or TXTfile",choices = ('xml', 'txt'), default = "xml") parser.add_argument('--api', help = "Path to API key file",default = os.path.expanduser("~/.wigle_api"), type = argparse.FileType('r')) args = parser.parse_args()
现在,下面的代码行将检查输入文件是否存在且是否为文件.如果没有,它退出脚本 :
if not os.path.exists(args.INPUT_FILE) or \ not os.path.isfile(args.INPUT_FILE): print("[-] {} does not exist or is not afile".format(args.INPUT_FILE)) sys.exit(1)directory = os.path.dirname(args.OUTPUT_CSV)if directory != '' and not os.path.exists(directory): os.makedirs(directory)api_key = args.api.readline().strip().split(":")
现在,将参数传递给main,如下所示 :
main(args.INPUT_FILE, args.OUTPUT_CSV, args.t, api_key)def main(in_file, out_csv, type, api_key): if type == 'xml': wifi = parse_xml(in_file) else: wifi = parse_txt(in_file)query_wigle(wifi, out_csv, api_key)
现在,我们将解析XML文件,如下所示;
def parse_xml(xml_file): wifi = {} xmlns = "{http://pa.cellebrite.com/report/2.0}" print("[+] Opening {} report".format(xml_file)) xml_tree = ET.parse(xml_file) print("[+] Parsing report for all connected WiFi addresses") root = xml_tree.getroot()
现在,循环遍历根的子元素,如下所示;
for child in root.iter(): if child.tag == xmlns + "model": if child.get("type") == "Location": for field in child.findall(xmlns + "field"): if field.get("name") == "TimeStamp": ts_value = field.find(xmlns + "value") try: ts = ts_value.text except AttributeError:continue
现在,我们将检查'ssid'字符串是否存在于值的文本中或不是 :
if "SSID" in value.text: bssid, ssid = value.text.split("\t") bssid = bssid[7:] ssid = ssid[6:]
现在,我们需要在wifi字典中添加BSSID,SSID和时间戳,如下所示;
if bssid in wifi.keys():wifi[bssid]["Timestamps"].append(ts) wifi[bssid]["SSID"].append(ssid)else: wifi[bssid] = {"Timestamps": [ts], "SSID":[ssid],"Wigle": {}}return wifi
文本解析器更简单,XML解析器显示在下面 :
def parse_txt(txt_file): wifi = {} print("[+] Extracting MAC addresses from {}".format(txt_file)) with open(txt_file) as mac_file: for line in mac_file: wifi[line.strip()] = {"Timestamps": ["N/A"], "SSID":["N/A"],"Wigle": {}}return wifi
现在,让我们使用请求模块进行 WIGLE API 调用,然后需要转到 query_wigle()方法 :
def query_wigle(wifi_dictionary, out_csv, api_key): print("[+] Querying Wigle.net through Python API for {} ""APs".format(len(wifi_dictionary))) for mac in wifi_dictionary: wigle_results = query_mac_addr(mac, api_key)def query_mac_addr(mac_addr, api_key): query_url = "https://api.wigle.net/api/v2/network/search?" \"onlymine = false&freenet = false&paynet = false" \ "&netid = {}".format(mac_addr) req = requests.get(query_url, auth = (api_key[0], api_key[1])) return req.json()
实际上WIGLE API调用每天都有一个限制,如果该限制超过那么它必须显示如下错误;
try: if wigle_results["resultCount"] == 0: wifi_dictionary[mac]["Wigle"]["results"] = [] continue else: wifi_dictionary[mac]["Wigle"] = wigle_resultsexcept KeyError: if wigle_results["error"] == "too many queries today": print("[-] Wigle daily query limit exceeded") wifi_dictionary[mac]["Wigle"]["results"] = [] continue else: print("[-] Other error encountered for " "address {}: {}".format(mac,wigle_results['error'])) wifi_dictionary[mac]["Wigle"]["results"] = [] continueprep_output(out_csv, wifi_dictionary)
现在,我们将使用 prep_output() 将字典展平为易于写入的块和减号的方法;
def prep_output(output, data): csv_data = {} google_map = https://www.google.com/maps/search/
现在,访问我们收集的所有数据,以便远至如下:
for x, mac in enumerate(data): for y, ts in enumerate(data[mac]["Timestamps"]): for z, result in enumerate(data[mac]["Wigle"]["results"]): shortres = data[mac]["Wigle"]["results"][z] g_map_url = "{}{},{}".format(google_map, shortres["trilat"],shortres["trilong"])
现在,我们可以像在本章前面的脚本中那样使用 write_csv()函数在CSV文件中编写输出.