Điều tra thiết bị di động kỹ thuật số Python
Chương này sẽ giải thích pháp y kỹ thuật số Python trên thiết bị di động và các khái niệm liên quan.
Giới thiệu
Pháp y thiết bị di động là nhánh của pháp y kỹ thuật số liên quan đến việc thu thập và phân tích các thiết bị di động để khôi phục bằng chứng kỹ thuật số mà điều tra quan tâm. Nhánh này khác với pháp y máy tính vì các thiết bị di động có hệ thống liên lạc sẵn có rất hữu ích để cung cấp thông tin hữu ích liên quan đến vị trí.
Mặc dù việc sử dụng điện thoại thông minh ngày càng tăng trong pháp y kỹ thuật số, nhưng nó vẫn được coi là không đạt tiêu chuẩn do tính không đồng nhất của nó. Mặt khác, phần cứng máy tính, chẳng hạn như đĩa cứng, được coi là tiêu chuẩn và được phát triển như một kỷ luật ổn định. Trong ngành pháp y kỹ thuật số, có rất nhiều tranh luận về các kỹ thuật được sử dụng cho các thiết bị phi tiêu chuẩn, có bằng chứng thoáng qua, chẳng hạn như điện thoại thông minh.
Phần mềm có thể chiết xuất từ thiết bị di động
Các thiết bị di động hiện đại có rất nhiều thông tin kỹ thuật số so với các điện thoại cũ chỉ có nhật ký cuộc gọi hoặc tin nhắn SMS. Do đó, thiết bị di động có thể cung cấp cho các nhà điều tra rất nhiều thông tin chi tiết về người dùng của nó. Một số hiện vật có thể được trích xuất từ thiết bị di động như được đề cập bên dưới -
Messages - Đây là những hiện vật hữu ích có thể tiết lộ tâm trạng của chủ sở hữu và thậm chí có thể cung cấp một số thông tin chưa biết trước đó cho người điều tra.
Location History- Dữ liệu lịch sử vị trí là một hiện vật hữu ích có thể được các nhà điều tra sử dụng để xác thực về vị trí cụ thể của một người.
Applications Installed - Bằng cách truy cập vào loại ứng dụng được cài đặt, điều tra viên có được một số thông tin chi tiết về thói quen và suy nghĩ của người dùng di động.
Nguồn bằng chứng và xử lý bằng Python
Điện thoại thông minh có cơ sở dữ liệu SQLite và tệp PLIST là nguồn bằng chứng chính. Trong phần này, chúng tôi sẽ xử lý các nguồn bằng chứng trong trăn.
Phân tích tệp PLIST
PLIST (Danh sách tài sản) là một định dạng linh hoạt và thuận tiện để lưu trữ dữ liệu ứng dụng, đặc biệt là trên các thiết bị iPhone. Nó sử dụng phần mở rộng.plist. Loại tệp này được sử dụng để lưu trữ thông tin về gói và ứng dụng. Nó có thể ở hai định dạng:XML và binary. Mã Python sau sẽ mở và đọc tệp PLIST. Lưu ý rằng trước khi tiếp tục việc này, chúng ta phải tạoInfo.plist tập tin.
Đầu tiên, hãy cài đặt thư viện của bên thứ ba có tên biplist bằng lệnh sau -
Pip install biplist
Bây giờ, hãy nhập một số thư viện hữu ích để xử lý tệp plist -
import biplist
import os
import sys
Bây giờ, sử dụng lệnh sau theo phương thức main có thể được sử dụng để đọc tệp plist thành một biến -
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)
Bây giờ, chúng ta có thể đọc dữ liệu trên bảng điều khiển hoặc in trực tiếp từ biến này.
Cơ sở dữ liệu SQLite
SQLite đóng vai trò là kho dữ liệu chính trên thiết bị di động. SQLite một thư viện trong quy trình triển khai một công cụ cơ sở dữ liệu SQL giao dịch độc lập, ít máy chủ, không cấu hình, không giao dịch. Nó là một cơ sở dữ liệu, được cấu hình bằng không, bạn không cần phải cấu hình nó trong hệ thống của mình, không giống như các cơ sở dữ liệu khác.
Nếu bạn là người mới làm quen hoặc chưa quen với cơ sở dữ liệu SQLite, bạn có thể theo liên kết www.tutorialspoint.com/sqlite/index.htm Ngoài ra, bạn có thể theo liên kết www.tutorialspoint.com/sqlite/sqlite_python.htm trong trường hợp bạn muốn tìm hiểu chi tiết về SQLite với Python.
Trong quá trình pháp y di động, chúng tôi có thể tương tác với sms.db tệp của thiết bị di động và có thể trích xuất thông tin có giá trị từ messagebàn. Python có một thư viện tích hợp có tênsqlite3để kết nối với cơ sở dữ liệu SQLite. Bạn có thể nhập tương tự bằng lệnh sau:
import sqlite3
Bây giờ, với sự trợ giúp của lệnh sau, chúng ta có thể kết nối với cơ sở dữ liệu, nói sms.db trong trường hợp thiết bị di động -
Conn = sqlite3.connect(‘sms.db’)
C = conn.cursor()
Ở đây, C là đối tượng con trỏ với sự trợ giúp mà chúng ta có thể tương tác với cơ sở dữ liệu.
Bây giờ, giả sử nếu chúng ta muốn thực hiện một lệnh cụ thể, hãy nói để lấy chi tiết từ abc table, nó có thể được thực hiện với sự trợ giúp của lệnh sau:
c.execute(“Select * from abc”)
c.close()
Kết quả của lệnh trên sẽ được lưu trữ trong cursorvật. Tương tự, chúng ta có thể sử dụngfetchall() phương thức để kết xuất kết quả vào một biến mà chúng ta có thể thao tác.
Chúng ta có thể sử dụng lệnh sau để lấy dữ liệu tên cột của bảng thông báo trong sms.db -
c.execute(“pragma table_info(message)”)
table_data = c.fetchall()
columns = [x[1] for x in table_data
Hãy quan sát rằng ở đây chúng ta đang sử dụng lệnh SQLite PRAGMA là lệnh đặc biệt được sử dụng để điều khiển các biến môi trường và cờ trạng thái khác nhau trong môi trường SQLite. Trong lệnh trên,fetchall()phương thức trả về nhiều kết quả. Tên của mỗi cột được lưu trữ trong chỉ mục đầu tiên của mỗi bộ.
Bây giờ, với sự trợ giúp của lệnh sau, chúng ta có thể truy vấn bảng cho tất cả dữ liệu của nó và lưu trữ nó trong biến có tên data_msg -
c.execute(“Select * from message”)
data_msg = c.fetchall()
Lệnh trên sẽ lưu trữ dữ liệu trong biến và hơn nữa chúng ta cũng có thể ghi dữ liệu trên vào tệp CSV bằng cách sử dụng csv.writer() phương pháp.
Sao lưu iTunes
Pháp y điện thoại di động iPhone có thể được thực hiện trên các bản sao lưu do iTunes tạo. Các giám định viên pháp y dựa vào việc phân tích các bản sao lưu logic của iPhone có được thông qua iTunes. Giao thức AFC (kết nối tệp của Apple) được iTunes sử dụng để sao lưu. Bên cạnh đó, quá trình sao lưu không sửa đổi bất cứ điều gì trên iPhone ngoại trừ các bản ghi khóa ký quỹ.
Bây giờ, câu hỏi được đặt ra là tại sao điều quan trọng đối với một chuyên gia pháp y kỹ thuật số là phải hiểu các kỹ thuật sao lưu iTunes? Điều quan trọng là chúng ta có thể truy cập trực tiếp vào máy tính của nghi phạm thay vì iPhone vì khi máy tính được sử dụng để đồng bộ hóa với iPhone, thì hầu hết thông tin trên iPhone có khả năng được sao lưu trên máy tính.
Quy trình sao lưu và vị trí của nó
Bất cứ khi nào một sản phẩm của Apple được sao lưu vào máy tính, sản phẩm đó sẽ đồng bộ với iTunes và sẽ có một thư mục cụ thể với ID duy nhất của thiết bị. Ở định dạng sao lưu mới nhất, các tệp được lưu trữ trong các thư mục con chứa hai ký tự thập lục phân đầu tiên của tên tệp. Từ các tệp sao lưu này, có một số tệp như info.plist rất hữu ích cùng với cơ sở dữ liệu có tên Manifest.db. Bảng sau đây cho thấy các vị trí sao lưu, thay đổi theo hệ điều hành của các bản sao lưu iTunes:
Hệ điều hành | Vị trí dự phòng |
---|---|
Win7 | C: \ Users \ [tên người dùng] \ AppData \ Roaming \ AppleComputer \ MobileSync \ Backup \ |
MAC OS X | ~ / Thư viện / Ứng dụng Suport / MobileSync / Backup / |
Để xử lý bản sao lưu iTunes bằng Python, trước tiên chúng ta cần xác định tất cả các bản sao lưu ở vị trí sao lưu theo hệ điều hành của chúng ta. Sau đó, chúng tôi sẽ lặp lại từng bản sao lưu và đọc cơ sở dữ liệu Manifest.db.
Bây giờ, với sự trợ giúp của mã Python sau đây, chúng ta có thể làm tương tự -
Đầu tiên, nhập các thư viện cần thiết như sau:
from __future__ import print_function
import argparse
import logging
import os
from shutil import copyfile
import sqlite3
import sys
logger = logging.getLogger(__name__)
Bây giờ, cung cấp hai đối số vị trí là INPUT_DIR và OUTPUT_DIR đại diện cho thư mục sao lưu iTunes và đầu ra mong muốn -
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()
Bây giờ, thiết lập nhật ký như sau:
if args.v:
logger.setLevel(logging.DEBUG)
else:
logger.setLevel(logging.INFO)
Bây giờ, hãy thiết lập định dạng thông báo cho nhật ký này như sau:
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)
Dòng mã sau sẽ tạo các thư mục cần thiết cho thư mục đầu ra mong muốn bằng cách sử dụng os.makedirs() chức năng -
if not os.path.exists(args.OUTPUT_DIR):
os.makedirs(args.OUTPUT_DIR)
Bây giờ, hãy chuyển các thư mục đầu vào và đầu ra được cung cấp cho hàm main () như sau:
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)
Bây giờ viết main() chức năng sẽ gọi thêm backup_summary() chức năng xác định tất cả các bản sao lưu có trong thư mục đầu vào -
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
Bây giờ, in bản tóm tắt của mỗi bản sao lưu vào bảng điều khiển như sau:
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]))
Bây giờ, hãy kết xuất nội dung của tệp Manifest.db vào biến có tên 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
Bây giờ, chúng ta hãy xác định một hàm sẽ lấy đường dẫn thư mục của bản sao lưu -
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
Bây giờ, sử dụng SQLite3, chúng tôi sẽ kết nối với cơ sở dữ liệu bằng con trỏ có tên là c -
c = conn.cursor()
items = {}
for row in c.execute("SELECT * from Files;"):
items[row[0]] = [row[2], row[1], row[3]]
return items
create_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)
Bây giờ, xác định create_files() phương pháp như sau -
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)
Bây giờ, hãy lặp lại từng khóa trong db_items từ điển -
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)
Bây giờ, sử dụng shutil.copyfile() phương pháp sao chép tệp đã sao lưu như sau:
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 in
backup".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"))
Với tập lệnh Python ở trên, chúng ta có thể nhận cấu trúc tệp sao lưu được cập nhật trong thư mục đầu ra của chúng ta. Chúng ta có thể sử dụngpycrypto thư viện python để giải mã các bản sao lưu.
Wifi
Các thiết bị di động có thể được sử dụng để kết nối với thế giới bên ngoài bằng cách kết nối thông qua mạng Wi-Fi có sẵn ở khắp mọi nơi. Đôi khi thiết bị được kết nối tự động với các mạng mở này.
Trong trường hợp iPhone, danh sách các kết nối Wi-Fi đang mở mà thiết bị đã kết nối được lưu trữ trong tệp PLIST có tên com.apple.wifi.plist. Tệp này sẽ chứa Wi-Fi SSID, BSSID và thời gian kết nối.
Chúng tôi cần trích xuất chi tiết Wi-Fi từ báo cáo XML Cellebrite tiêu chuẩn bằng Python. Để làm được điều này, chúng tôi cần sử dụng API từ Công cụ ghi nhật ký địa lý không dây (WIGLE), một nền tảng phổ biến có thể được sử dụng để tìm vị trí của thiết bị bằng cách sử dụng tên của mạng Wi-Fi.
Chúng ta có thể sử dụng thư viện Python có tên requestsđể truy cập API từ WIGLE. Nó có thể được cài đặt như sau:
pip install requests
API từ WIGLE
Chúng tôi cần đăng ký trên trang web của WIGLE https://wigle.net/accountđể nhận API miễn phí từ WIGLE. Tập lệnh Python để lấy thông tin về thiết bị người dùng và kết nối của nó thông qua API của WIGEL được thảo luận bên dưới:
Đầu tiên, nhập các thư viện sau để xử lý những thứ khác nhau -
from __future__ import print_function
import argparse
import csv
import os
import sys
import xml.etree.ElementTree as ET
import requests
Bây giờ, cung cấp hai đối số vị trí cụ thể là INPUT_FILE và OUTPUT_CSV sẽ đại diện cho tệp đầu vào với địa chỉ MAC Wi-Fi và tệp CSV đầu ra mong muốn tương ứng -
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 TXT
file",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()
Bây giờ các dòng mã sau sẽ kiểm tra xem tệp đầu vào có tồn tại và là tệp hay không. Nếu không, nó sẽ thoát khỏi tập lệnh -
if not os.path.exists(args.INPUT_FILE) or \ not os.path.isfile(args.INPUT_FILE):
print("[-] {} does not exist or is not a
file".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(":")
Bây giờ, chuyển đối số vào main như sau:
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)
Bây giờ, chúng ta sẽ phân tích cú pháp tệp XML như sau:
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()
Bây giờ, hãy lặp lại phần tử con của root như sau:
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
Bây giờ, chúng tôi sẽ kiểm tra xem chuỗi 'ssid' có trong văn bản của giá trị hay không -
if "SSID" in value.text:
bssid, ssid = value.text.split("\t")
bssid = bssid[7:]
ssid = ssid[6:]
Bây giờ, chúng ta cần thêm BSSID, SSID và dấu thời gian vào từ điển wifi như sau:
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
Trình phân tích cú pháp văn bản đơn giản hơn nhiều mà trình phân tích cú pháp XML được hiển thị bên dưới:
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
Bây giờ, chúng ta hãy sử dụng mô-đun yêu cầu để thực hiện WIGLE APIcuộc gọi và cần chuyển sang query_wigle() phương pháp -
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()
Trên thực tế, có một giới hạn mỗi ngày cho các lệnh gọi API WIGLE, nếu vượt quá giới hạn đó thì nó phải hiển thị lỗi như sau:
try:
if wigle_results["resultCount"] == 0:
wifi_dictionary[mac]["Wigle"]["results"] = []
continue
else:
wifi_dictionary[mac]["Wigle"] = wigle_results
except 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"] = []
continue
prep_output(out_csv, wifi_dictionary)
Bây giờ, chúng ta sẽ sử dụng prep_output() phương pháp làm phẳng từ điển thành các phần có thể ghi dễ dàng -
def prep_output(output, data):
csv_data = {}
google_map = https://www.google.com/maps/search/
Bây giờ, hãy truy cập tất cả dữ liệu chúng tôi đã thu thập được cho đến nay như sau:
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"])
Bây giờ, chúng ta có thể viết đầu ra trong tệp CSV như chúng ta đã thực hiện trong các tập lệnh trước đó trong chương này bằng cách sử dụng write_csv() chức năng.