- A+
连载介绍信息:http://zone.wooyun.org/content/23138
原作者:Chris Katsaropoulos
第一译者:@草帽小子-DJ
第二译者:crown丶prince
第三章:用python进行调查取证
本章内容:
1.通过Windows注册表定位
2.回收站调查
3.审查PDF和DOC文件的元数据
4.从Exif元数据中提取GPS坐标
5.探究Skype结构
6.从火狐的数据库中枚举浏览器结构
7.审查移动设备结构
最终,你必须忘记技术。你越是进步,教导的也就越少,伟大的路是没有真正的道路的。
—Ueshiba Morihei, Kaiso, Founder, Aikido
引文:如何解决BTK谋杀案
2005年2月,Wichita警方取证调查Randy Stone先生,揭开了一件尘封了30年的案件的神秘面纱。几天前,
KSAS电视台提交给警方了一个他们从臭名昭著的BTK(绑定,酷刑,杀戮)的杀手那里接收到的软盘。1974年至1991年的至少十起谋杀案中,BTK的杀手故意留下蛛丝马迹,来嘲弄警察和受害者。2005年2月16日,BTK的杀手给电视台邮寄了一个包含指令的软盘,在这些指令中,磁盘包含了一个叫做Test.A.rtf(Regan, 2006)。然而这份文件包含了BTK杀手指令的同时,也包含了一些别的东西:元数据。在这份微软的RTF格式文件中,包含了杀手的名字和地理位置。这帮助了案件的破获,最终警察确认Denis Rader 就是BTK的杀手,案件最终告破。
计算机取证调查只需要好的调查员和他的好工具。调查员往往有很多挑剔的问题,但没有工具能解决他们的问题。进入Python。在前几章我们看到,解决复杂的问题只用极少的代码证明了Python编程语言的实力。正如我们将在下面章节中看到的,我们能用极少数的Python代码解决复杂的问题。让我们开始用一些独特的Windows注册表来物理跟踪用户吧。
你去哪里了?—在注册表中分析无线接入点
Windows注册表包含了一个存储操作系统配置设置的层次化数据库。随着无线网的出现,Windows注册表存储了与无线连接相关的信息。了解注册表键值的位置和意义可以为我们提供详细的笔记本到过的地理位置。从Windows Vista之后,注册表存储每一个网络信息在HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsNT\CurrentVersion\NetworkList\Signatures\Unmanaged子键值下。从Windows命令提示符,我们可以列出每一个网络显示描述GUID,网络描述,网络名称和网关MAC地址。
C:\Windows\system32>reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\
Windows NT\CurrentVersion\NetworkList\Signatures\Unmanaged" /s
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows
NT\CurrentVersion\NetworkList\Sign
atures\Unmanaged\010103000F0000F0080000000F0000F04BCC2360E4B8F7DC8BDAF
AB8AE4DAD8
62E3960B979A7AD52FA5F70188E103148
ProfileGuid REG_SZ {3B24CE70-AA79-4C9A-B9CC-83F90C2C9C0D}
Description REG_SZ Hooters_San_Pedro
Source REG_DWORD 0x8
DnsSuffix REG_SZ <none>
FirstNetwork REG_SZ Public_Library
DefaultGatewayMac REG_BINARY 00115024687F0000
使用WinReg读取Windows注册表
注册表存储的网关MAC地址作为REG_BINARY类型。在前面的例子中,16进制\x00\x11\x50\x24\x68\x7F\x00\x00表示的实际地址为00:11:50:24:68:7F。我们将写一个快速的函数将REG_BINARY的值转换为实际的MAC地址。在后面我们将会看到无线网络的MAC地址是有用的。
def val2addr(val):
addr = ""
for ch in val:
addr += ("%02x "% ord(ch))
addr = addr.strip(" ").replace(" ",":")[0:17]
return addr
现在,让我们来编写一个函数从Windows注册表键值中获取每一个列出来的网络的网络名称和MAC地址。为此,我们将利用_winreg模块,Windows版的Python默认安装的模块。连接到注册表后,我们可以使用OpenKey()函数打开键,并循环获取键下面的网络描述。对于每一个描述,包含下面子键:ProfileGuid, Description, Source, DnsSuffix, FirstNetwork, DefaultGatewayMac。网络名称和网关MAC地址在注册表键列表中的第四个和第五个。现在我们可以枚举每一个键,并在屏幕上面打印出来。
把所有的组合在一起,现在我们有一个脚本将打印存储在注册表中的先前连接的无线网络的信息。
# coding=UTF-8
import _winreg
def val2addr(val):
addr = ""
for ch in val:
addr += ("%02x "% ord(ch))
addr = addr.strip(" ").replace(" ",":")[0:17]
return addr
def printNets():
net = "SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList\Signatures\Unmanaged"
key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, net)
print '\n[*] Networks You have Joined.'
for i in range(100):
try:
guid = _winreg.EnumKey(key, i)
netKey = _winreg.OpenKey(key, str(guid))
(n, addr, t) = _winreg.EnumValue(netKey, 5)
(n, name, t) = _winreg.EnumValue(netKey, 4)
macAddr = val2addr(addr)
netName = str(name)
print '[+] ' + netName + ' ' + macAddr
_winreg.CloseKey(netKey)
except:
break
def main():
printNets()
if __name__ == "__main__":
main()
在我们的目标笔记本上运行我们的脚本,我们可以看到先前连接过的无线网络及其MAC地址。当测试脚本时,确保使用管理员权限运行的脚本,否则将无法读取注册表键值。
C:\Users\investigator\Desktop\python discoverNetworks.py
[*] Networks You have Joined.
[+] Hooters_San_Pedro, 00:11:50:24:68:7F
[+] LAX Airport, 00:30:65:03:e8:c6
[+] Senate_public_wifi, 00:0b:85:23:23:3e
使用Mechanize 将MAC地址提交到Wigle
然而,脚本不会在次结束。随着获得无线接入点的MAC地址,我们现在开可以打印出无线接入点的物理位置。有相当多的数据库,包括开源的和专有的,包含了大量与无线接入点物理位置相关的信息。专利产品,如手机就是使用这样的地理位置的数据库而没有使用GPS。
SkyHook数据库,可以在http://www.skyhookwireless.com/找到。提供了一个基于WIFI接入点的软件开发工具包。Ian McCracken开发的一个开源项目提供了提供了对这个数据库的访问能力在http://code.google.com/p/maclocate/网站。然而,最近,SkyHook改变了SDK而是使用API密钥来使用数据库。Google也维护这同样大的数据库用于关联无线接入点的MAC地址到物理位置。然而,不久后,不久后,Gorjan Petrovski开发了一个NMAP NSE脚本来和Google的数据库进行交互。Google反对开源代码和他的数据库进行交互。不久之后,由于隐私问题,微软也关闭了类似的WIFI物理位置数据库。
剩下的数据库和开源项目WiGLE.net继续允许用户通过无线接入点的搜索物理位置。注册一个账号之后,用户就能通过一个小的Python脚本和wigle.net进行交互。让我们快速检查如何建立一个脚本与WiGLE.net交互。
使用WiGLE. Net,用户很快就会意识到为了得到WiGLE他必须与第三方的页面进行交互。首先,用户必须打开WiGLE.net的初始化页面在https://wigle.net/网页;然后用户必须登陆到WiGLE在https://wigle.net/页面。最后,用户可以查询特定的无线SSID的MAC地址在https://wigle.net/页面。捕获MAC地址查询请求,我们可以看到在请求无线接入点的GPS地址的HTTP POST请求中tnetid(网络标识符)包含了MAC地址。
POST /gps/gps/main/confirmquery/ HTTP/1.1
Accept-Encoding: identity
Content-Length: 33
Host: wigle.net
User-Agent: AppleWebKit/531.21.10
Connection: close
Content-Type: application/x-www-form-urlencoded
netid=0A%3A2C%3AEF%3A3D%3A25%3A1B
<..SNIPPED..>
此外,我们看到从页面响应的数据中包含了GPS坐标。字符串maplat=47.25264359&maplon=-87.25624084包含了接入点的经度和纬度。
<tr class="search"><td>
<a href="/gps/gps/Map/onlinemap2/?maplat=47.25264359&maplon=-
87.25624084&mapzoom=17&ssid=McDonald's FREE Wifi&netid=0A:2C:EF:3D:
25:1B">Get Map</a></td>
<td>0A:2C:EF:3D:25:1B</td><td>McDonald's FREE Wifi</td><
有了这些信息,我们现在足够建立建立一个简单的函数用来返回WiGLE数据库中记录的无线接入点的的经度和纬度。注意,要使用mechanize模块。可以从http://wwwsearch.sourceforge.net/mechanize/网站获得该模块。mechanize允许通过Python进行WEB状态编程,类似于urllib2模块的功能。这就意味着,一旦我们正常的登陆到WiGLE服务,它就会存储和重用我们的验证cookie。
import mechanize, urllib, re, urlparse
def wiglePrint(username, password, netid):
browser = mechanize.Browser()
browser.open('http://wigle.net')
reqData = urllib.urlencode({'credential_0': username, 'credential_1': password})
browser.open('https://wigle.net/gps/gps/main/login', reqData)
params = {}
params['netid'] = netid
reqParams = urllib.urlencode(params)
respURL = 'http://wigle.net/gps/gps/main/confirmquery/'
resp = browser.open(respURL, reqParams).read()
mapLat = 'N/A'
mapLon = 'N/A'
rLat = re.findall(r'maplat=.*\&', resp)
if rLat:
mapLat = rLat[0].split('&')[0].split('=')[1]
rLon = re.findall(r'maplon=.*\&', resp)
if rLon:
mapLon = rLon[0].split
print('[-] Lat: ' + mapLat + ', Lon: ' + mapLon)
添加WiGLE MAC地址查询功能到我们原来的脚本。我们现在有能力检查注册表中以前连接过的无线接入点并查询他们的物理位置。
# coding=UTF-8
import optparse
import mechanize
import urllib
import re
import _winreg
def val2addr(val):
addr = ""
for ch in val:
addr += ("%02x " % ord(ch))
addr = addr.strip(" ").replace(" ", ":")[0:17]
return addr
def wiglePrint(username, password, netid):
browser = mechanize.Browser()
browser.open('http://wigle.net')
reqData = urllib.urlencode({'credential_0': username, 'credential_1': password})
browser.open('https://wigle.net/gps/gps/main/login', reqData)
params = {}
params['netid'] = netid
reqParams = urllib.urlencode(params)
respURL = 'http://wigle.net/gps/gps/main/confirmquery/'
resp = browser.open(respURL, reqParams).read()
mapLat = 'N/A'
mapLon = 'N/A'
rLat = re.findall(r'maplat=.*\&', resp)
if rLat:
mapLat = rLat[0].split('&')[0].split('=')[1]
rLon = re.findall(r'maplon=.*\&', resp)
if rLon:
mapLon = rLon[0].split
print('[-] Lat: ' + mapLat + ', Lon: ' + mapLon)
def printNets(username, password):
net = "SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList\Signatures\Unmanaged"
key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, net)
print '\n[*] Networks You have Joined.'
for i in range(100):
try:
guid = _winreg.EnumKey(key, i)
netKey = _winreg.OpenKey(key, str(guid))
(n, addr, t) = _winreg.EnumValue(netKey, 5)
(n, name, t) = _winreg.EnumValue(netKey, 4)
macAddr = val2addr(addr)
netName = str(name)
print('[+] ' + netName + ' ' + macAddr)
wiglePrint(username, password, macAddr)
_winreg.CloseKey(netKey)
except:
break
def main():
parser = optparse.OptionParser("usage%prog -u <wigle username> -p <wigle password>")
parser.add_option('-u', dest='username', type='string', help='specify wigle password')
parser.add_option('-p', dest='password', type='string', help='specify wigle username')
(options, args) = parser.parse_args()
username = options.username
password = options.password
if username == None or password == None:
print(parser.usage)
exit(0)
else:
printNets(username, password)
if __name__ == '__main__':
main()
运行我们的新脚本,我们可以看到先前连接过的无线网络和他们的物理位置。知道了计算机在哪,让我们在下一节检查一下回收站。
C:\Users\investigator\Desktop\python discoverNetworks.py
[*] Networks You have Joined.
[+] Hooters_San_Pedro, 00:11:50:24:68:7F
[-] Lat: 29.55995369, Lon: -98.48358154
[+] LAX Airport, 00:30:65:03:e8:c6
[-] Lat: 28.04605293, Lon: -82.60256195
[+] Senate_public_wifi, 00:0b:85:23:23:3e
[-] Lat: 44.95574570, Lon: -93.10277557
用Python来恢复回收站中删除的项目
在微软的操作系统中,回收站作为一个特殊的文件夹包含了已经删除的文件。当用户通过Windows Explorer删除文件时,操作系统会将这个文件移动到这个特殊的文件夹中并标记这文件已删除,但是并不是实际上的删除它们。在Windows 98和更早的系统中用的是FAT文件系统。C:\Recycled\目录保存着回收站目录。支持NTFS的操作系统有Windows NT,2000,和XP存储回收站在C:\Recycler\目录下。Windows Vista和Windows 7系统存储在C:\$Recycle.Bin目录下。
使用OS模块查找已删除的项目
为了让我们的脚本不依赖与特定的Windows操作系统,让我们编写一个函数来测试每一个可能的候选目录并返回系统上存在的第一个目录。
import os
def returnDir():
dirs = ['C:\\Recycler\\', 'C:\\Recycled\\', 'C:\\$Recycle.Bin\\']
for recycleDir in dirs:
if os.path.isdir(recycleDir):
return recycleDir
return None
在发现回收站目录之后,我们需要检查里面的内容。
注意两个子目录,它们都包含字符串S-1-5-21-1275210071-1715567821-725345543-并终止与1005或者500。这个字符串用户的SID,与机器上的用户的账户一一相对应。
C:\RECYCLER>dir /a
Volume in drive C has no label.
Volume Serial Number is 882A-6E93
Directory of C:\RECYCLER
04/12/2011 09:24 AM <DIR> .
04/12/2011 09:24 AM <DIR> ..
04/12/2011 09:56 AM
<DIR> S-1-5-21-1275210071-1715567821-
725345543-
1005
04/12/2011 09:20 AM <DIR> S-1-5-21-1275210071-1715567821-
725345543-
500
0 File(s) 0 bytes
4 Dir(s) 30,700,670,976 bytes free
用Python将用户的SID关联起来
我们将使用Windows注册表将SID转化为一个准确的用户名。通过检查Windows注册表键值HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\<SID>\ProfileImagePath,我们可以看到它返回一个是%SystemDrive%\Documents and Settings\<USERID>。在下图中,我们看到这允许我们将SID为S-1-5-21-1275210071-1715567821-725345543-1005转化为用户名“alex”。
C:\RECYCLER>reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsNT\CurrentVersion\ProfileList\S-1-5-21-1275210071-1715567821-725345543-1005" /v
ProfileImagePath
! REG.EXE VERSION 3.0
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows
NT\CurrentVersion\ProfileList \S-1-5-21-1275210071-1715567821-
725345543-1005 ProfileImagePath
REG_EXPAND_SZ %SystemDrive%\Documents and Settings\alex
我们想知道回收站里谁删除了什么文件。让我们编写一个小的函数来将每一个SID转化为用户名。当我们恢复回收站中被删除的项目时这将使我们打印更多有用的输出。这个函数将打开注册便检查ProfileImagePath键值,找到其值并从中找到用户名。
import _winreg
def sid2user(sid):
try:
key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, "SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\\" + sid)
(value, type) = _winreg.QueryValueEx(key, 'ProfileImagePath')
user = value.split('\\')[-1]
return user
except:
return sid
最后,我们将所有的代码放在一起生成一个脚本,它将打印已删除但还在回收站中的项目。
# coding=UTF-8
import os
import _winreg
def returnDir():
dirs = ['C:\\Recycler\\', 'C:\\Recycled\\', 'C:\\$Recycle.Bin\\']
for recycleDir in dirs:
if os.path.isdir(recycleDir):
return recycleDir
return None
def sid2user(sid):
try:
key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, "SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\\" + sid)
(value, type) = _winreg.QueryValueEx(key, 'ProfileImagePath')
user = value.split('\\')[-1]
return user
except:
return sid
def findRecycled(recycleDir):
dirList = os.listdir(recycleDir)
for sid in dirList:
files = os.listdir(recycleDir + sid)
user = sid2user(sid)
print('\n[*] Listing Files For User: ' + str(user))
for file in files:
print('[+] Found File: ' + str(file))
def main():
recycledDir = returnDir()
findRecycled(recycledDir)
if __name__ == '__main__':
main()
在目标机器上运行我们的脚本,我们看到脚本发现了两个用户:Administrator和alex。它列出了回收站中每个用户的文件。在下一节中,我们将审查一个方法,用于检查那些包含在调查中可能有用的文件的内部内容。
Microsoft Windows XP [Version 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.
C:\>python dumpRecycleBin.py
[*] Listing Files For User: alex
[+] Found File: Notes_on_removing_MetaData.pdf
[+] Found File: ANONOPS_The_Press_Release.pdf
[*] Listing Files For User: Administrator
[+] Found File: 192.168.13.1-router-config.txt
[+] Found File: Room_Combinations.xls
C:\Documents and Settings\john\Desktop>
元数据
在本节中,我们将编写一个脚本用来从文件中提取元数据。文件不是清晰可见的对象,元数据可以存在于文档,电子表格,图像,音频和视频等文件类型中。创作应用程序可能会存储一些细节如文件的作者,创建和修改时间,潜在的修订和注释。例如,拍照手机可以标记本地的GPS在照片中或者微软的Word应用程序可以存储文档的作者。检查每一个文件是个艰难的任务,我们可以使用Python自动处理。
Anonymous的元数据失败
2010年12月10日,黑客组织Anonymous发布了一份声明稿,描述了最近一次命名为Operation Payback攻击的背后的动机。因为对公司不支持维基解密而感到愤怒,从而对有关公司进行分布式拒绝服务攻击(DDOS)报复。黑客发布的声明稿没有签名,没有来源。是一个PDF文件,但是发行时包含元数据。被创建文档的程序添加进的元数据包含作者的名字Mr. Alex Tapanaris。几天内,警方逮捕了他。
使用PyPDF解析PDF元数据
让我们快速创建一个脚本对被逮捕的黑客组织Anonymous的成员用过的文档进行法庭调查取证。Wired.com还保留着ANONOPS_The_Press_Release.pdf那份文档。我们可以从使用wget下载这份文档开始。
forensic:∼# wget
http://www.wired.com/images_blogs/threatlevel/2010/12/ANONOPS_The_
Press_Release.pdf
--2012-01-19 11:43:36--
http://www.wired.com/images_blogs/threatlevel/2010/12/ANONOPS_The_
Press_Release.pdf
Resolving www.wired.com... 64.145.92.35, 64.145.92.34
Connecting to www.wired.com|64.145.92.35|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 70214 (69K) [application/pdf]
Saving to: 'ANONOPS_The_Press_Release.pdf.1'
100%[==================================================
================================>] 70,214 364K/s in 0.2s
2012-01-19 11:43:39 (364 KB/s) - 'ANONOPS_The_Press_Release.pdf' saved
[70214/70214]
PyPDF是一个优秀的第三方管理PDF文件很实用的库,可以从网站http://pybrary.net/pyPdf/获得。它提供了文档的信息提取,分割,合并,加密和解密的能力。为了提取元数据,我们使用函数getDocumentInfo()。这个方法返回一个元组数组,每一个元组包含一个元数据元素和它的值。遍历这个数组并打印PDF文件的全部元数据。
import pyPdf
from pyPdf import PdfFileReader
def printMeta(fileName):
pdfFile = PdfFileReader(file(fileName, 'rb'))
docInfo = pdfFile.getDocumentInfo()
print('[*] PDF MetaData For: ' + str(fileName))
for metaItem in docInfo:
print('[+] ' + metaItem + ':' + docInfo[metaItem])
添加一个选项分析器来识别特定的文件,我们有一个工具可以识别嵌入到PDF中的元数据。同样,我们可以修改我们的脚本来测试特定的元数据,例如特定的用户。当然,这有可能帮助执法管来搜索文件来列出作者名字。
# coding=UTF-8
import pyPdf
from pyPdf import PdfFileReader
import optparse
def printMeta(fileName):
pdfFile = PdfFileReader(file(fileName, 'rb'))
docInfo = pdfFile.getDocumentInfo()
print('[*] PDF MetaData For: ' + str(fileName))
for metaItem in docInfo:
print('[+] ' + metaItem + ':' + docInfo[metaItem])
def main():
parser = optparse.OptionParser('usage %prog -F <PDF file name>')
parser.add_option('-F', dest='fileName', type='string', help='specify PDF file name')
(options, args) = parser.parse_args()
fileName = options.fileName
if fileName == None:
print(parser.usage)
exit(0)
else:
printMeta(fileName)
if __name__ == '__main__':
main()
指定Anonymous发布的声明高运行我们的脚本,我们可以看到同样的元数据导致警方逮捕了Mr. Tapanaris。
forensic:∼# python pdfRead.py -F ANONOPS_The_Press_Release.pdf
[*] PDF MetaData For: ANONOPS_The_Press_Release.pdf
[+] /Author:Alex Tapanaris
[+] /Producer:OpenOffice.org 3.2
[+] /Creator:Writer
[+] /CreationDate:D:20101210031827+02'00'
理解Exif元数据
(Exif是一种图象文件格式,它的数据存储与JPEG格式是完全相同的。实际上Exif格式就是在JPEG格式头部插入了数码照片的信息,包括拍摄时的光圈、快门、白平衡、ISO、焦距、日期时间等各种和拍摄条件以及相机品牌、型号、色彩编码、拍摄时录制的声音以及全球定位系统(GPS)、缩略图等。简单地说,Exif=JPEG+拍摄参数。因此,你可以利用任何可以查看JPEG文件的看图软件浏览Exif格式的照片,但并不是所有的图形程序都能处理Exif信息。)
交换图像文件格式(Exif)标准的定义了如何存储图像和视频文件的规范。如数码相机,扫描仪和智能手机使用这个标准来保存图像和视频文件。Exif标准文件包含了几个对法庭调查取证有用的信息。Phil Harvey编写了一个实用的工具名叫exiftool(从http://www.sno.phy.queensu.ca/~phil/exiftool/可获得)能解析这些参数。检查所有的Exif参数可能会返回几页的信息,所以我们只检查部分需要的参数信息。注意Exif参数包含相机型号名称iPhone 4S以及图像实际的GPS经纬度坐标。这些信息在组织图像是很有帮助的。比如说,Mac OS X应用程序iPhoto使用位置信息来整齐的排列世界地图上的照片。然而,这些信息也被大量的恶意的使用。想象一个士兵将Exif照片放到博客或网站上,敌人可以下载所有的照片几秒钟之类便可以知道士兵的调动信息。在下面的章节中,我们将建立一个脚本来连接WEB网站,下载图像,并检查他们的Exif元数据。
investigator$ exiftool photo.JPG
ExifTool Version Number : 8.76
File Name : photo.JPG
Directory : /home/investigator/photo.JPG
File Size : 1626 kB
File Modification Date/Time : 2012:02:01 08:25:37-07:00
File Permissions : rw-r--r--
File Type : JPEG
MIME Type : image/jpeg
Exif Byte Order : Big-endian (Motorola, MM)
Make
: Apple
Camera Model Name : iPhone 4S
Orientation : Rotate 90 CW
<..SNIPPED..>
GPS Altitude : 10 m Above Sea Level
GPS Latitude : 89 deg 59' 59.97" N
GPS Longitude : 36 deg 26' 58.57" W
<..SNIPPED..>