Python 디지털 모바일 장치 포렌식
이 장에서는 모바일 장치의 Python 디지털 포렌식과 관련된 개념을 설명합니다.
소개
모바일 장치 포렌식은 조사 관심의 디지털 증거를 복구하기 위해 모바일 장치의 획득 및 분석을 다루는 디지털 포렌식의 한 분야입니다. 모바일 기기에는 위치와 관련된 유용한 정보를 제공하는 데 유용한 통신 시스템이 내장되어 있기 때문에이 지점은 컴퓨터 포렌식과 다릅니다.
디지털 포렌식에서 스마트 폰의 사용이 날마다 증가하고 있지만 여전히 이질성으로 인해 비표준으로 간주됩니다. 반면에 하드 디스크와 같은 컴퓨터 하드웨어는 표준으로 간주되고 안정된 분야로 개발되었습니다. 디지털 포렌식 산업에서는 스마트 폰과 같은 일시적인 증거가있는 비표준 장치에 사용되는 기술에 대해 많은 논쟁이 있습니다.
모바일 장치에서 추출 가능한 아티팩트
최신 모바일 장치는 통화 기록이나 SMS 메시지 만있는 구형 전화기에 비해 많은 디지털 정보를 보유하고 있습니다. 따라서 모바일 장치는 조사자에게 사용자에 대한 많은 통찰력을 제공 할 수 있습니다. 모바일 장치에서 추출 할 수있는 일부 인공물은 다음과 같습니다.
Messages − 이것들은 소유자의 마음 상태를 드러 낼 수있는 유용한 인공물이며, 심지어 조사자에게 이전에 알려지지 않은 정보를 제공 할 수도 있습니다.
Location History− 위치 기록 데이터는 조사관이 사람의 특정 위치를 확인하는 데 사용할 수있는 유용한 인공물입니다.
Applications Installed − 설치된 애플리케이션의 종류에 액세스하여 조사자는 모바일 사용자의 습관과 생각에 대한 통찰력을 얻습니다.
Python의 증거 소스 및 처리
스마트 폰에는 SQLite 데이터베이스와 PLIST 파일이 주요 증거 자료로 사용됩니다. 이 섹션에서는 파이썬으로 증거의 소스를 처리 할 것입니다.
PLIST 파일 분석
PLIST (속성 목록)는 특히 iPhone 장치에 응용 프로그램 데이터를 저장하기위한 유연하고 편리한 형식입니다. 확장을 사용합니다..plist. 번들 및 애플리케이션에 대한 정보를 저장하는 데 사용되는 이러한 종류의 파일입니다. 두 가지 형식이 있습니다.XML 과 binary. 다음 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 데이터베이스
SQLite는 모바일 장치에서 기본 데이터 저장소 역할을합니다. SQLite는 자체 포함, 서버리스, 제로 구성, 트랜잭션 SQL 데이터베이스 엔진을 구현하는 프로세스 내 라이브러리입니다. 제로 구성되는 데이터베이스이므로 다른 데이터베이스와 달리 시스템에서 구성 할 필요가 없습니다.
당신이 SQLite는 데이터베이스와 초보자 또는 익숙를하는 경우, 당신은 링크 따를 수 www.tutorialspoint.com/sqlite/index.htm을 해당 링크를 따를 수 있습니다, 또한 www.tutorialspoint.com/sqlite/sqlite_python.htm 당신이 원하는 경우를 Python으로 SQLite에 대해 자세히 알아보십시오.
모바일 포렌식 중에 우리는 sms.db 모바일 장치의 파일을 저장하고 message표. Python에는sqlite3SQLite 데이터베이스와 연결하기 위해. 다음 명령으로 동일하게 가져올 수 있습니다-
import sqlite3
이제 다음 명령을 사용하여 데이터베이스에 연결할 수 있습니다. sms.db 모바일 기기의 경우 −
Conn = sqlite3.connect(‘sms.db’)
C = conn.cursor()
여기서 C는 데이터베이스와 상호 작용할 수있는 커서 객체입니다.
이제 특정 명령을 실행하고 싶다면 abc table, 다음 명령의 도움으로 수행 할 수 있습니다-
c.execute(“Select * from abc”)
c.close()
위 명령의 결과는 cursor목적. 마찬가지로 우리는fetchall() 우리가 조작 할 수있는 변수에 결과를 덤프하는 메소드.
다음 명령을 사용하여 메시지 테이블의 열 이름 데이터를 가져올 수 있습니다. sms.db −
c.execute(“pragma table_info(message)”)
table_data = c.fetchall()
columns = [x[1] for x in table_data
여기서 우리는 SQLite 환경 내에서 다양한 환경 변수와 상태 플래그를 제어하는 데 사용되는 특수 명령 인 SQLite PRAGMA 명령을 사용하고 있습니다. 위의 명령에서fetchall()메서드는 결과의 튜플을 반환합니다. 각 열의 이름은 각 튜플의 첫 번째 인덱스에 저장됩니다.
이제 다음 명령을 사용하여 테이블에서 모든 데이터를 쿼리하고 이름이 지정된 변수에 저장할 수 있습니다. data_msg −
c.execute(“Select * from message”)
data_msg = c.fetchall()
위의 명령은 변수에 데이터를 저장하며, 또한 다음을 사용하여 CSV 파일에 위의 데이터를 쓸 수도 있습니다. csv.writer() 방법.
iTunes 백업
iTunes에서 만든 백업에서 iPhone 모바일 포렌식을 수행 할 수 있습니다. 법의학 검사관은 iTunes를 통해 얻은 iPhone 논리적 백업 분석에 의존합니다. AFC (Apple 파일 연결) 프로토콜은 iTunes에서 백업을 수행하는 데 사용됩니다. 또한 백업 프로세스는 에스크로 키 레코드를 제외하고 iPhone에서 아무것도 수정하지 않습니다.
이제 디지털 포렌식 전문가가 iTunes 백업 기술을 이해하는 것이 왜 중요한지 의문이 생깁니다. 아이폰이 아닌 피의자의 컴퓨터에 직접 접속할 경우 중요하다. 컴퓨터를 아이폰과 동기화 할 때 아이폰에있는 대부분의 정보가 컴퓨터에 백업 될 가능성이 있기 때문이다.
백업 프로세스 및 위치
Apple 제품이 컴퓨터에 백업 될 때마다 iTunes와 동기화되며 장비의 고유 ID가있는 특정 폴더가 있습니다. 최신 백업 형식에서 파일은 파일 이름의 처음 두 16 진수 문자를 포함하는 하위 폴더에 저장됩니다. 이러한 백업 파일에는 Manifest.db라는 데이터베이스와 함께 유용한 info.plist와 같은 파일이 있습니다. 다음 표는 iTunes 백업의 운영 체제에 따라 다른 백업 위치를 보여줍니다.
OS | 백업 위치 |
---|---|
Win7 | C : \ Users \ [사용자 이름] \ AppData \ Roaming \ AppleComputer \ MobileSync \ Backup \ |
맥 OS X | ~ / Library / Application Suport / MobileSync / Backup / |
Python으로 iTunes 백업을 처리하려면 먼저 운영 체제에 따라 백업 위치의 모든 백업을 식별해야합니다. 그런 다음 각 백업을 반복하고 Manifest.db 데이터베이스를 읽습니다.
이제 다음 Python 코드의 도움으로 동일한 작업을 수행 할 수 있습니다.
먼저 다음과 같이 필요한 라이브러리를 가져옵니다.
from __future__ import print_function
import argparse
import logging
import os
from shutil import copyfile
import sqlite3
import sys
logger = logging.getLogger(__name__)
이제 iTunes 백업 및 원하는 출력 폴더를 나타내는 INPUT_DIR 및 OUTPUT_DIR의 두 위치 인수를 제공하십시오.
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
이제 백업의 디렉토리 경로를 사용할 함수를 정의하겠습니다.
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 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)
이제 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 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"))
위의 Python 스크립트를 사용하여 출력 폴더에서 업데이트 된 백업 파일 구조를 가져올 수 있습니다. 우리는 사용할 수 있습니다pycrypto python 라이브러리를 사용하여 백업을 해독합니다.
와이파이
모바일 장치는 어디에서나 사용할 수있는 Wi-Fi 네트워크를 통해 연결하여 외부 세계에 연결할 수 있습니다. 때로는 장치가 이러한 개방형 네트워크에 자동으로 연결됩니다.
iPhone의 경우 기기가 연결된 열린 Wi-Fi 연결 목록이 PLIST 파일에 저장됩니다. com.apple.wifi.plist. 이 파일에는 Wi-Fi SSID, BSSID 및 연결 시간이 포함됩니다.
Python을 사용하여 표준 Cellebrite XML 보고서에서 Wi-Fi 세부 정보를 추출해야합니다. 이를 위해서는 Wi-Fi 네트워크의 이름을 사용하여 장치의 위치를 찾는 데 사용할 수있는 널리 사용되는 플랫폼 인 WIGLE (Wireless Geographic Logging Engine)의 API를 사용해야합니다.
파이썬 라이브러리를 사용할 수 있습니다. requestsWIGLE에서 API에 액세스합니다. 다음과 같이 설치할 수 있습니다.
pip install requests
WIGLE의 API
WIGLE 웹 사이트에 등록해야합니다. https://wigle.net/accountWIGLE에서 무료 API를 얻으려면. WIGEL의 API를 통해 사용자 장치 및 연결에 대한 정보를 가져 오는 Python 스크립트는 아래에서 설명합니다.
먼저, 다른 작업을 처리하기 위해 다음 라이브러리를 가져옵니다.
from __future__ import print_function
import argparse
import csv
import os
import sys
import xml.etree.ElementTree as ET
import 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 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()
이제 다음 코드 줄은 입력 파일이 존재하고 파일인지 확인합니다. 그렇지 않은 경우 스크립트를 종료합니다.
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(":")
이제 다음과 같이 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:]
이제 다음과 같이 BSSID, SSID 및 타임 스탬프를 wifi 사전에 추가해야합니다.
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_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)
이제 우리는 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"])
이제이 장의 이전 스크립트에서 수행 한 것처럼 CSV 파일로 출력을 작성할 수 있습니다. write_csv() 함수.