MacOS响应取证速查

本文总结MacOS平台下应急响应与入侵取证的基本思路与方法...

0x01 目录/文件

1. 关键目录

1
2
3
4
5
6
7
8
Applications/:应用程序的默认文件夹
Library/:包含 OS X 文件和支持的操作系统项目,用于系统全局功能并适用于所有用户
System/:为 OS X 系统文件保留,包含系统设置和系统功能等项目
Users/:本地用户的主文件夹。还将有一个 "Public" 文件夹,用于在用户之间共享文件
etc 或 private/etc/:配置和其他系统文件
private/sbin/:提供给管理员使用的 Linux-styled的二进制文件
var/ 或 private/var:重要数据文件、日志文件等所在目录
Volumes/:已挂载的设备,例如硬盘、CD、DMG 和 USB 驱动器

2. 系统文件

操作系统版本

1
/System/Library/CoreServices/SystemVersion.plist

时区

1
/Library/Preferences/.GlobalPreferences.plist

语言

1
/Library/Preferences/.GlobalPreferences.plist

MAC地址

1
/private/var/log/daily.out

启动文件夹

1
2
3
4
/Library/LaunchAgents/
/Library/LaunchDaemons/
/System/Library/LaunchAgents/
/System/Library/LaunchDaemons/

系统偏好应用程序

1
/Library/PreferencePanes/

防火墙

1
/Library/Preferences/com.apple.alf.plist

蓝牙

1
/Library/Preferences/com.apple.Bluetooth.plist

键盘

1
/Library/Preferences/com.apple.HIToolbox.plist

最近用户登录

1
/Library/Preferences/com.apple.loginwindow.plist

最近更新信息

1
/Library/Preferences/com.apple.SoftwareUpdate.plist

Time Machine

最后备份,最旧备份,快照编号

1
2
/Library/Preferences/com.apple.TimeMachine.plist
/private/var/db/com.apple.TimeMAchine.SnapshotDates.plist

打印机

1
/Library/Preferences/org.cups.printers.plist

Airport - Remembered Network

1
/Library/Preferences/SystemConfiguration(s)/com.apple.airport.preferences.plist

最后睡眠时间

1
/Library/Preferences/SystemConfiguration(s)/com.apple.PowerManagement.plist

网络接口名称

1
/Library/Preferences/SystemConfiguration(s)/NetworkInterfaces.plist

网络信息

/Library/Preferences/SystemConfiguration(s)/preferences.plist

主机名

1
/Library/Preferences/SystemConfiguration(s)/preferences.plist

VMWare Fusion Network

1
/Library/Preferences/VMWare Fusion/networking

Keychains

1
/Library/Keychains/ /System/Keychains/

主机文件

1
/private/etc/hosts Path /private/etc/paths

DNS

1
/private/etc/resolv.conf

用户信息

1
2
# 可以查找password、name、uid、gid等信息
/private/var/db/dslocal/nodes/[user].plist

组信息

1
2
3
/private/var/db/dslocal/nodes/[group].plist

* admin.plist for admin user * staff.plist for root user

休眠文件

1
/private/var/vm/sleepimage

Swap文件

1
/private/var/vm/swapfile[x]

已安装打印机

1
2
/Library/Printers/
/Library/Printers/InstalledPrinters.plist

3. 用户PROFILE

用户默认文件夹

1
2
3
4
5
6
7
8
删除的文件 (Trash bin): ~/.Trash/
桌面文件: ~/Desktop/
Document文件夹 (default): ~/Documents/
Download文件夹 (default): ~/Downloads/
Library配置和设置: ~/Library/
Movies文件夹 (default): ~/Movies/
Music文件夹 (default): ~/Music/
共享文件夹: ~/Public

Bash 命令历史

1
~/bash_history

SSH连接信息

1
~/.ssh/known_hosts

App访问通讯录的设置

1
~/Library/Application Support/com.apple.TCC/TCC.db

应用程序崩溃时间戳

1
~/Library/Application Support/CrashReporter/[App]_[GUID].plist

崩溃计数

1
~/Library/Application Support/User_Crash_History_[GUID].plist

通知中心

1
~/Library/Application Support/NotificationCenter/[GUID].db

沙盒容器

1
~/Library/Containers/

Keychains (用户)

1
2
3
4
~/Library/Keychains/
~/Library/Keychains/login.keychain
~/Library/Keychains/metadata.keychain
~/Library/Keychains/[XXXX].keychain

LaunchAgents(用户)

1
~/Library/LaunchAgents/[App].plist

Quicktime - 在线多媒体的 URL

1
~/Library/Caches/Quicktime/downloads/TOC.plist

Recent文件夹

1
~/Library/Preferences/com.apple.finder.plist

语言

1
~/Library/Preferences/.GlobalPreferences.plist

AppStore - 可用更新

1
~/Library/Preferences/com.apple.appstore.plist

Recent磁盘映像 (ISO/DMG)

1
~/Library/Preferences/com.apple.DiskUtility.plist

Dock

1
~/Library/Preferences/com.apple.dock.plist

Dashboard - gadget/widget

1
~/Library/Preferences/com.apple.dashboard.plist

Recent信息

1
~/Library/Preferences/com.apple.recentitems.plist

调度程序

1
~/Library/Preferences/com.apple.scheduler.plist

屏幕保护程序

1
~/Library/Preferences/com.apple.screensaver.plist

Finder 侧边栏

1
~/Library/Preferences/com.apple.sidebarlists.plist

Spaces

1
~/Library/Preferences/com.apple.spaces.plist

打印机

1
~/Library/Printers/

Connected iDevices

1
2
3
4
5
6
a) 设备类型
b) 上次连接时间戳
c) 固件版本
d) 序列号和 IMEI

~/Library/Preferences/com.apple.iPod.plist

Connected storage

1
~/Library/Preferences/com.apple.sidebarlists.plist

Recent Documents

1
2
3
4
5
6
7
8
9
10
11
# Preview
~/Library/Preferences/com.apple.Preview.plist

# Quicktime
~/Library/Preferences/com.apple.QuickTimePlayerX.LSSharedFileList.plist

# Console
~/Library/Preferences/com.apple.Console.LSSharedFileList

# Textedit
~/Library/Preferences/com.apple.TextEdit.LSSharedFileList.plist

RSS订阅服务

1
2
3
~/Library/PubSub/Database/Database.sqlite3
~/Library/PubSub/Clients.plist
~/Library/PubSub/Feeds/

Download Quarantine Events

1
~/Library/Preferences/com.apple.LaunchServices.QuarantineEventsV2

4. 日志文件

应用防火墙

1
2
/private/var/log/appfirewall.log
/private/var/log/appfirewall.log.[x].bz2

系统日志

1
2
3
4
5
6
7
8
9
10
11
/private/var/log/asl/YYYY.MM.DD.U[XX].asl
/private/var/log/DiagnosticMessages/YYYY.MM.DD.asl
/private/var/log/install.log
/private/var/log/install.log.[x].bz2
/private/var/log/opendirectoryd.log
/private/var/log/opendirectoryd.log.[x].bz2
/private/var/log/system.log
/private/var/log/system.log.[x].bz2
/private/var/log/vnetlib
/private/var/log/weekly.out
/private/var/log/zzz.log

关机/启动日志

1
2
/private/var/log/com.apple.xpc.launchd/launchd.log
/private/var/log/com.apple.launchd/launchd-shutdown.system.log

系统配置信息

1
2
3
4
5
6
7
/private/var/log/install.log

a. wirelessconnection
b. registered country and city
c. firmware version at logged time
d. created username
e. Install apps

磁盘状态

1
/private/var/log/daily.out MAC address/

网络状态

1
/private/var/log/daily.out

USB设备连接

1
2
# 查找 "USBMSC"
/private/var/log/System.log

开机时间

1
2
# 查找"BOOT_TIME"
/private/var/log/System.log

关机时间

1
2
# 查找 "SHUTDOWN_TIME"
/private/var/log/System.log

用户日志

1
2
3
4
5
6
7
~/Library/Logs/AMRestore.txt
~/Library/Logs/appstore.log
~/Library/Logs/DiagnosticReports/
~Library/Logs/SMSMigrator/SMSMigrator.log
~/Library/Logs/sync/syncservices.log
~/Library/Logs/Ubiquity/[User]/ubiquity-digest.log
~/Library/Logs/Ubiquity/[User]/ubiquity.log

光盘刻录日志

1
~/Library/Logs/DiskRecording.log

磁盘Utility日志

1
~/Library/Logs/DiskUtility.log

文件系统日志

~/Library/Logs/fsck_hfs.log

VMWare

1
2
~/Library/Logs/VMWare
~/Library/Logs/VMWare Fusion/

0x02 速查命令

1. 用户排查

1
2
3
dscl . list /Users UniqueID

dscl . list /Users UniqueID | grep -v ^_

2. 端口排查

1
2
3
4
5
6
7
netstat -na | egrep 'LISTEN|ESTABLISHED'

netstat -A -a -l -n -v # Active网络连接

netstat -n -r -a -l # 路由表信息

lsof -i

3. 网络排查

1
2
3
4
5
# 查看详细网络信息
plutil -p /Library/Preferences/SystemConfiguration/preferences.plist

# 查看代理配置
scutil --proxy

4. 进程排查

1
2
3
ps -axo user,pid,ppid,%cpu,%mem,start,time,command

lsappinfo list

5. 计划任务查看

守护进程服务文件路径

plist按照如下优先级排列(由高到低):

1
2
3
4
5
/System/Library/LaunchDaemons # 由Mac OS X定义的守护进程任务项
/System/Library/LaunchAgents # 由Mac OS X为用户定义的任务项
/Library/LaunchDaemons # 由管理员定义的守护进程任务项
/Library/LaunchAgents # 由管理员为用户定义的任务项
~/Library/LaunchAgents # 由用户自己定义的任务项,接触最多

crontab命令

1
2
3
4
5
6
7
8
9
10
11
12
13
crontab [-u user] file
crontab [-u user] [ -e | -l | -r ]

-u user:用来设定某个用户的crontab服务;
file:file是命令文件的名字,表示将file做为crontab的任务列表文件并载入crontab。
-e:编辑某个用户的crontab文件内容。如果不指定用户,则表示编辑当前用户的crontab文件。
-l:显示某个用户的crontab文件内容,如果不指定用户,则表示显示当前用户的crontab文件内容。
-r:从/var/spool/cron目录中删除某个用户的crontab文件,如果不指定用户,则默认删除当前用户的crontab文件。
-i:在删除用户的crontab文件时给确认提示。

# 查看当前用户定时任务

crontab -l

launchctl命令

launchctl 通过配置文件指定执行周期和任务,不同于 crontab,launchctl 的最小时间间隔是 1s。plist 文件存放路径为 /Library/LaunchAgents/Library/LaunchDaemons

1
2
3
4
5
cd ~/Library/LaunchAgents
launchctl load com.test.launchctl.plist # 加载任务
launchctl unload com.felink.gitmirror.plist # 卸载任务
launchctl start ccom.test.launchctl.plist # 立即执行
launchctl stop ccom.test.launchctl.plist # 停止任务

6. 临时/共享目录

1
2
3
$ ls -al /Users/Shared
$ ls -al /private/tmp
$ ls -al $TMPDIR

7. shells文件

1
2
3
4
5
6
~/.bash_profile  # 如果存在,登录shell时读取一次
~/.bash_login # 如果存在,如果.bash_profile不存在,则读取一次
~/.profile # 如果存在,上面两个不存在则读取一次
/etc/profile # 只有在以上都不存在时才读取
~/.bashrc # 如果存在,每次启动新的 shell 时读取
~/.bash_logout # 如果存在,在登录 shell 退出时读取

8. DB数据查看

1
2
3
4
5
6
7
8
9
10
11
$ sqlite3 /path to db/ .dump

$ sqlite3 /path to db/ .tables

$ sqlite3 /path to db/ 'select * from [tablename]'

$ sqlite3 /path to db/ .schema

# 隐藏数据库查看: Apple 将一些数据库隐藏在 /var/folders/ 文件夹中。当登录为给定用户,可以利用 DARWIN_USER_DIR 环境变量访问

$ cd $(getconf DARWIN_USER_DIR)

9. plist文件查看

1
plutil -p system.plist

0x03 自动化脚本

1. collect.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
#!/bin/bash

#ensure that the script is being executed as root
if [[ $EUID -ne 0 ]]; then
echo 'Incident Response Script needs to be executed as root!'
exit 1
fi

originalUser=`sh -c 'echo $SUDO_USER'`
echo "Collecting data as root escalated from the $originalUser account"

#insert company message here explaining the situation
cat << EOF

-----------------------------------------------------------------------
COLLECTING CRITICAL SYSTEM DATA. PLEASE DO NOT TURN OFF YOUR SYSTEM...
-----------------------------------------------------------------------

EOF

echo "Start time-> `date`"

#Create a pf rule to block all network access except for access to file server over ssh
quarentineRule=/etc/activeIr.conf
echo "Writing quarentine rule to $quarentineRule"
serverIP=192.168.1.111
cat > $quarentineRule << EOF
block in all
block out all
pass in proto tcp from $serverIP to any port 22
EOF

#load the pfconf rule and inform the user there is no internet access
pfctl -f $quarentineRule 2>/dev/null
pfctl -e 2>/dev/null
if [ $? -eq 0 ]; then
echo "Quarentine Enabled. Internet access unavailable"
fi

echo "Running system commands..."

#set up variables
IRfolder=collection
logFile=$IRfolder/collectlog.txt

mkdir $IRfolder
touch $logFile

#redirect errors
exec 2> $logFile

systemCommands=$IRfolder/sysCalls

#create output directory
mkdir $systemCommands

#basic system info
systemInfo=$systemCommands/sysInfo.txt
#create file
touch $systemInfo

#echo ---command name to be used---; use command; append a blank line
echo ---date--- >> $systemInfo; date >> $systemInfo; echo >> $systemInfo
echo ---hostname--- >> $systemInfo; hostname >> $systemInfo; echo >> $systemInfo
echo ---uname -a--- >> $systemInfo; uname -a >> $systemInfo; echo >> $systemInfo
echo ---sw_vers--- >> $systemInfo; sw_vers >> $systemInfo; echo >> $systemInfo
echo ---nvram--- >> $systemInfo; nvram >> $systemInfo; echo >> $systemInfo
echo ---uptime--- >> $systemInfo; uptime >> $systemInfo; echo >> $systemInfo
echo ---spctl --status--- >> $systemInfo; spctl --status >> $systemInfo; echo >> $systemInfo
echo --bash --version--- >> $systemInfo; bash --version >> $systemInfo; echo >> $systemInfo

#collect who-based data
whoInfo=$systemCommands/whoInfo.txt
touch $whoInfo
echo ---ls -la /Users--- >> $whoInfo; ls -la /Users >> $whoInfo; echo >> $whoInfo
echo ---whoami--- >> $whoInfo; whoami >> $whoInfo; echo >> $whoInfo
echo ---who--- >> $whoInfo; who >> $whoInfo; echo >> $whoInfo
echo ---w--- >> $whoInfo; w >> $whoInfo; echo >> $whoInfo
echo ---last--- >> $whoInfo; last >> $whoInfo; echo >> $whoInfo

#collect user info
userInfo=$systemCommands/userInfo.txt
echo ---Users on this system--- >>$userInfo; dscl . -ls /Users >> $userInfo; echo >> $userInfo
#for each user
dscl . -ls /Users | egrep -v ^_ | while read user
do
echo *****$user***** >> $userInfo
echo ---id \($user\)--- >>$userInfo; id $user >> $userInfo; echo >> $userInfo
echo ---groups \($user\)--- >> $userInfo; groups $user >> $userInfo; echo >> $userInfo
echo ---finger \($user\) --- >> $userInfo; finger -m $user >> $userInfo; echo >> $userInfo
echo >> $userInfo
echo >> $userInfo
# find a way to provide printenv
done

#Collect network-based info
networkInfo=$systemCommands/networkInfo.txt
touch $networkInfo
echo ---netstat--- >> $networkInfo; netstat >> $networkInfo; echo >> $networkInfo
echo ---netstat -ru--- >> $networkInfo; netstat -ru >> $networkInfo; echo >> $networkInfo
echo ---networksetup -listallhardwareports--- >> $networkInfo; networksetup -listallhardwareports >> $networkInfo; echo >> $networkInfo
echo ---lsof -i--- >> $networkInfo; lsof -i >> $networkInfo; echo >> $networkInfo
echo ---arp -a--- >> $networkInfo; arp -a >> $networkInfo; echo >> $networkInfo
echo security dump-trust-settings >> $networkInfo; security dump-trust-settings >> $networkInfo; echo >> $networkInfo

#collect process-based info
processInfo=$systemCommands/processInfo.txt
touch $processInfo
echo ---ps aux--- >> $processInfo; ps aux >> $processInfo; echo >> $processInfo
echo ---lsof--- >> $processInfo; lsof >> $processInfo; echo >> $processInfo

#collect startup-based info
startupInfo=$systemCommands/startupInfo.txt
touch $startupInfo
echo ---launchctl list--- >> $startupInfo; launchctl list >> $startupInfo; echo >> $startupInfo
echo ---atq--- >> $startupInfo; atq >> $startupInfo; echo >> $startupInfo
#crontab will be collected later from /usr/lib/cron/<usernames>

#collect driver-based info
driverInfo=$systemCommands/driverInfo.txt
touch $driverInfo
echo ---kextstat--- >> $driverInfo; kextstat >> $driverInfo; echo >>$driverInfo


#collect hard drive info
hardDriveInfo=$systemCommands/hardDriveInfo.txt
touch $hardDriveInfo
echo ---diskutil list--- >> $hardDriveInfo; diskutil list >> $hardDriveInfo; echo >>$hardDriveInfo
echo ---df -h--- >> $hardDriveInfo; df -h >> $hardDriveInfo; echo >> $hardDriveInfo
echo ---du -h--- >> $hardDriveInfo; du -h >> $hardDriveInfo; echo >> $hardDriveInfo

#Collecting file system data
#!/bin/bash

#collect artifiacts
mkdir artifacts

#collect the audit logs
#mkdir artifacts/audit
#ditto /var/audit artifacts/audit

declare -a directories=(
#list dirs to collect here. Don't include a slash at the end of the dir
"/var/audit"
)

declare -a files=(
"/var/log/system.log"
"/var/log/accountpolicy.log"
"/var/log/apache2/access_log"
"/var/log/apache2/error_log"
"/var/log/opendirectoryd.log"
"/var/log/secinitd"
"/var/log/wifi.log"
"/var/log/alf.log"
"/var/log/appstore.log"
"/var/log/authd.log"
"/var/log/commerce.log"
"/var/log/hdiejectd.log"
"/var/log/install.log"
"/Library/Preferences/SystemConfiguration/com.apple.airport.preferences.plist"
"/etc/kcpassword"
"/etc/sudoers"
"/etc/hosts"
"/etc/resolv.conf"
"/private/var/log/fsck_hfs.log"
"/private/var/db/launchd.db/com.apple.launchd/overrides.plist"
"/Library/Logs/AppleFileService/AppleFileServiceError.log"
"/var/log/appfirewall.log"

)

declare -a userFiles=(
#these are user files paths without the ~ at the beginning. The home directories will be concated later
"Library/Preferences/com.apple.finder.plist"
"Library/Preferences/com.apple.recentitems.plist"
"Library/Preferences/com.apple.loginitems.plist"
"Library/Logs/DiskUtility.log"
"Library/Preferences/com.apple.LaunchServices.QuarantineEventsV2"
)

#collect files
for x in "${files[@]}"
do
ditto "$x"* artifacts
done

#collect user files for each user
dscl . -ls /Users | egrep -v ^_ | while read user
do
for x in "${userFiles[@]}"
do
fileLocation="/Users/$user/$x"
echo "Trying to ditto $fileLocation"
if [ -f $fileLocation ]; then
ditto "$fileLocation"* artifacts
fi
done
done

#collect dirs
for x in "${directories[@]}"
do
dirname=`echo "$x" | awk -F "/" '{print $NF}'`
echo created "$dirname" from "$x"
mkdir artifacts/"$dirname"
ditto "$x" artifacts/"$dirname"
done


#ASEP COLLECTION
echo "Collecting system ASEPS"
#the $IRfolder variable was assigned in our original script
ASEPS=$IRfolder/aseps
mkdir $ASEPS

ditto /System/Library/LaunchDaemons $ASEPS/systemLaunchDaemons
ditto /System/Library/LaunchAgents $ASEPS/systemLaunchAgents
ditto /Library/LaunchDaemons $ASEPS/launchDaemons
ditto /Library/LaunchAgents $ASEPS/launchAgents
#ditto <user entry>

#collect crontabs and set permissions so that the analyst can read the results
ditto /usr/lib/cron/tabs/ $ASEPS/crontabs;

#collect at tasks
ditto /private/var/at/jobs/ $ASEPS/atTasks

#collect plist overrides
ditto /var/db/launchd.db $ASEPS/overrides;

#collect StartupItems
ditto /etc/rc* $ASEPS/
ditto /Library/StartupItems/ $ASEPS/
ditto /System/Library/StartupItems/ $ASEPS/systemStartupItems

#collect Login/Logout Hooks
ditto /private/var/root/Library/Preferences/com.apple.loginwindow.plist $ASEPS/loginLogouthooks

#collect launchd configs
#file may or may not exist
ditto /etc/launchd.conf $ASEPS/launchdConfs/

#copy user specific data for each user
dscl . -ls /Users | egrep -v ^_ | while read user
do
ditto /Users/$user/Library/LaunchAgents $ASEPS/$user-launchAgents
ditto /Users/$user/Library/Preferences/com.apple.loginitems.plist $ASEPS/$user-com.apple.loginitems.plist;
ditto /Users/$user/.launchd.conf $ASEPS/launchdConfs/$user-launchd.conf
done

#copy kext files in the extension directories
ditto /System/Library/Extensions $ASEPS/systemExtensions
ditto /Library/Extensions $ASEPS/extensions

#create a function that will scan all files in a directory using codesign
codesignDirScan(){
for filename in $1/*; do
codesign -vv -d $filename &>tmp.txt;
if grep -q "not signed" tmp.txt; then
cat tmp.txt >> $ASEPS/unsignedKexts.txt
fi
done
rm tmp.txt
}

#run a codesign scan on all kext files
codesignDirScan /System/Library/Extensions
codesignDirScan /Library/Extensions

#collect browser history
echo "Copying Web Data..."
dscl . -ls /Users | egrep -v ^_ | while read user
do
#check for and copy Safari data
#Safari is pretty much garenteed to be installed
echo "Looking for /Users/$user/Library/Safari"
if [ -d "/Users/$user/Library/Safari/" ]; then
plutil -convert xml1 /Users/$user/Library/Safari/History.plist -o "$user"_safariHistory.plist
plutil -convert xml1 /Users/$user/Library/Safari/Downloads.plist -o "$user"_safariDownloads.plist
#plutil -p "/Users/$user/Library/Safari/History.plist" > "$user"_safariHistory.plist
#plutil -p "/Users/$user/Library/Safari/Downloads.plist" > "$user"_safariDownloads.plist

#grab the sqlite3 version of the history if you prefer
ditto "/Users/$user/Library/Safari/Downloads.plist" "$user"_safariDownloads.db
fi

#check for and copy Chrome data
if [ -d "/Users/$user/Library/Application Support/Google/Chrome/" ]; then
ditto "/Users/$user/Library/Application Support/Google/Chrome/Default/History" "$user"_chromeHistory.db
fi

#check for and copy firefox data
#there should only be one profile inside the Profiles directory
if [ -d "/Users/$user/Library/Application Support/Firefox/" ]; then
for PROFILE in /Users/$user/Library/Application\ Support/Firefox/Profiles/*; do
ditto "$PROFILE/places.sqlite" "$user"_firefoxHistory.db
done
fi

#check for and copy Opera data
if [ -d "/Users/$user/Library/Application Support/com.operasoftware.Opera/" ]; then
ditto "/Users/$user/Library/Application Support/com.operasoftware.Opera/History" "$user"_operaHistory.db
fi
done

#create a zip file of all the data in the current directory
#this will always be the last thing we do. Do not add code below this section through this book
echo "Archiving Data"
cname=`scutil --get ComputerName | tr ' ' '_'`
now=`date +"_%Y-%m-%d"`
ditto -k --zlibCompressionLevel 5 -c . $cname$now.zip

2. collect_basics.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#!/bin/bash

#create a文件夹 where all collected data will go
IRfolder=collection
#ensure that the script is being executed as root
if [[ $EUID -ne 0 ]]; then
echo "Incident Response Script needs to be executed as root!"
exit 1
fi

sudo -k

#save which user executed the analysis script as we may need it later
originalUser=`sh -c 'echo $SUDO_USER'`
echo "Collecting data as root escalated from the $originalUser account"

#insert company message here explaining the situation
cat << EOF
-----------------------------------------------------------------------
COLLECTING CRITICAL SYSTEM DATA. PLEASE DO NOT TURN OFF YOUR SYSTEM...
-----------------------------------------------------------------------
EOF

echo "Start time-> `date`"

#we will start tracing connections with dtrace below this line#

#we will collect memory below this line#

#we will collect volatile data using shell below this line#

#Create a pf rule to block all network access except for access to file server over ssh#
quarentineRule=/etc/activeIr.conf
echo "Writing quarentine rule to $quarentineRule"
serverIP=192.168.1.111 #IP of the server you want to stay in contact with this system
cat > $quarentineRule << EOF
block in all
block out all
pass in proto tcp from $serverIP to any port 22
EOF

#load the pfconf rule and inform the user there is no internet access#
pfctl -f $quarentineRule 2>/dev/null
pfctl -e 2>/dev/null
if [ $? -eq 0 ]; then
echo "Quarentine Enabled. Internet access unavailable"
fi


#we will collect a file listing here#

#we will collect file artifacts here#

#we will collect system startup artificats and ASEPS here#

#we will collect web browser artifacts here#

#create a zip file of all the data in the current directory#
#this will always be the last thing we do. Do not add code below this section through this book
echo "Archiving Data"
cname=`scutil --get ComputerName | tr ' ' '_' | tr -d \’`
now=`date +"_%Y-%m-%d"`
ditto -k --zlibCompressionLevel 5 -c $IRfolder $cname$now.zip

3. yarafly.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/bin/bash
# Yara on the fly

tmpFile=".yara.tmp"
yaraRule="yarafly.yar"

echo "--add indicators you want to scan for below this line--" > $tmpFile
pico $tmpFile

#build rule with temp file contents
echo -e "rule onTheFly : fly\n{\n\tstrings:" > $yaraRule

counter=0
while read line; do
#if statement skips the first line of the temp file
if [ $counter -gt 0 ]; then
echo -e "\t\t\$$counter = \"$line\"" >> $yaraRule
fi
((counter++))
done <$tmpFile

echo -e "\tcondition:\n\t\tany of them\n}" >> $yaraRule

rm $tmpFile

echo "Rule created - $yaraRule"
echo
cat $yaraRule

4. collect_bash_commands.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#!/bin/bash

#set up variables
IRfolder=collection
systemCommands=$IRfolder/bashCalls

#create output directory
mkdir $IRfolder
mkdir $systemCommands

#start tracing tcp connections in the background
scripts/soconnect_mac.d -o $IRfolder/soconnect.log &
#get pid. avoid using pgrep incase dtrace was already running
dtracePid=`ps aux | grep dtrace.*soconnect_mac.d | grep -v grep | awk '{print $2}'`
echo "Started tracing outbound TCP connections. Dtrace PID is $dtracePid"

#collect volatile bash data
echo "Running system commands..."

#collect bash history
history > $systemCommands/history.txt

#basic system info
systemInfo=$systemCommands/sysInfo.txt
#create file
touch $systemInfo

#echo ---command name to be used---; use command; append a blank line
echo ---date--- >> $systemInfo; date >> $systemInfo; echo >> $systemInfo
echo ---hostname--- >> $systemInfo; hostname >> $systemInfo; echo >> $systemInfo
echo ---uname -a--- >> $systemInfo; uname -a >> $systemInfo; echo >> $systemInfo
echo ---sw_vers--- >> $systemInfo; sw_vers >> $systemInfo; echo >> $systemInfo
echo ---nvram--- >> $systemInfo; nvram >> $systemInfo; echo >> $systemInfo
echo ---uptime--- >> $systemInfo; uptime >> $systemInfo; echo >> $systemInfo
echo ---spctl --status--- >> $systemInfo; spctl --status >> $systemInfo; echo >> $systemInfo
echo --bash --version--- >> $systemInfo; bash --version >> $systemInfo; echo >> $systemInfo

#collect who-based data
ls -la /Users > $systemCommands/ls_la_users.txt
whoami > $systemCommands/whoami.txt
who > $systemCommands/who.txt
w > $systemCommands/w.txt
last > $systemCommands/last.txt



#collect user info
userInfo=$systemCommands/userInfo.txt
echo ---Users on this system--- >>$userInfo; dscl . -ls /Users >> $userInfo; echo >> $userInfo
#for each user
dscl . -ls /Users | egrep -v ^_ | while read user
do
echo *****$user***** >> $userInfo
echo ---id \($user\)--- >>$userInfo; id $user >> $userInfo; echo >> $userInfo
echo ---groups \($user\)--- >> $userInfo; groups $user >> $userInfo; echo >> $userInfo
echo ---finger \($user\) --- >> $userInfo; finger -m $user >> $userInfo; echo >> $userInfo
echo >> $userInfo
echo >> $userInfo
done

#Collect network-based info
netstat > $systemCommands/netstat.txt
netstat -ru > $systemCommands/netstat_ru.txt
networksetup -listallhardwareports > $systemCommands/networksetup_listallhadwarereports.txt
lsof -i > $systemCommands/lsof_i.txt
arp -a > $systemCommands/arp_a.txt
smbutil statshares -a > $systemCommands/smbutil_statshares.txt
security dump-trust-settings > $systemCommands/security_dump_trust_settings.txt
ifconfig > $systemCommands/ifconfig.txt
smbutil statshares -a > $systemCommands/smbutil_statshares.txt

#collect process-based info
ps aux > $systemCommands/ps_aux.txt
ps axo user,pid,ppid,start,command > $systemCommands/ps_axo.txt
lsof > $systemCommands/lsof.txt

#collect driver-based info
kextstat > $systemCommands/kextstat.txt

#collect hard drive info
hardDriveInfo=$systemCommands/hardDriveInfo.txt
touch $hardDriveInfo
echo ---diskutil list--- >> $hardDriveInfo; diskutil list >> $hardDriveInfo; echo >>$hardDriveInfo
echo ---df -h--- >> $hardDriveInfo; df -h >> $hardDriveInfo; echo >> $hardDriveInfo
echo ---du -h--- >> $hardDriveInfo; du -h >> $hardDriveInfo; echo >> $hardDriveInfo
#stop tracing outgoing TCP data
kill -9 $dtracePid

#create a zip file of all the data in the current directory
echo "Archiving Data"
cname=`scutil --get ComputerName | tr ' ' '_' | tr -d \’`
now=`date +"_%Y-%m-%d"`
ditto -k --zlibCompressionLevel 5 -c . $cname$now.zip

5. collect_aseps.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#ASEP COLLECTION
echo "Collecting system ASEPS"
#the $IRfolder variable was assigned in our original script
ASEPS=$IRfolder/aseps
mkdir $ASEPS

ditto /System/Library/LaunchDaemons $ASEPS/systemLaunchDaemons
ditto /System/Library/LaunchAgents $ASEPS/systemLaunchAgents
ditto /Library/LaunchDaemons $ASEPS/launchDaemons
ditto /Library/LaunchAgents $ASEPS/launchAgents

#collect crontabs and set permissions so that the analyst can read the results
ditto /usr/lib/cron/tabs/ $ASEPS/crontabs;

#collect at tasks
ditto /private/var/at/jobs/ $ASEPS/atTasks

#collect plist overrides
ditto /var/db/launchd.db $ASEPS/overrides;

#collect StartupItems
ditto /etc/rc* $ASEPS/
ditto /Library/StartupItems/ $ASEPS/
ditto /System/Library/StartupItems/ $ASEPS/systemStartupItems

#collect Login/Logout Hooks
ditto /private/var/root/Library/Preferences/com.apple.loginwindow.plist $ASEPS/loginLogouthooks

#collect launchd configs
#file may or may not exist
ditto /etc/launchd.conf $ASEPS/launchdConfs/

#copy user specific data for each user
dscl . -ls /Users | egrep -v ^_ | while read user
do
ditto /Users/$user/Library/LaunchAgents $ASEPS/$user-launchAgents
ditto /Users/$user/Library/Preferences/com.apple.loginitems.plist $ASEPS/$user-com.apple.loginitems.plist;
ditto /Users/$user/.launchd.conf $ASEPS/launchdConfs/$user-launchd.conf

touch $user\_launchctl_list.txt
chmod 766 $user\_launchctl_list.txt
su $user -c "launchctl list > $user\_launchctl_list.txt"
done

#copy kext files in the extension directories
ditto /System/Library/Extensions $ASEPS/systemExtensions
ditto /Library/Extensions $ASEPS/extensions

#create a function that will scan all files in a directory using codesign
codesignDirScan(){
for filename in $1/*; do
codesign -vv -d $filename &>tmp.txt;
if grep -q "not signed" tmp.txt; then
cat tmp.txt >> $ASEPS/unsignedKexts.txt
fi
done
rm tmp.txt
}

#run a codesign scan on all kext files
codesignDirScan /System/Library/Extensions
codesignDirScan /Library/Extensions

6. collection_filesystem.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#!/bin/bash

#collect artifiacts
mkdir artifacts


#list of entire directories you wish to collect
declare -a directories=(
#list dirs to collect here. Don't include a slash at the end of the dir
"/var/audit"
)

#list of files you want to collect at the privileged level
declare -a files=(
"/var/log/system.log"
"/var/log/accountpolicy.log"
"/var/log/apache2/access_log"
"/var/log/apache2/error_log"
"/var/log/opendirectoryd.log"
"/var/log/secinitd"
"/var/log/wifi.log"
"/var/log/alf.log"
"/var/log/appstore.log"
"/var/log/authd.log"
"/var/log/commerce.log"
"/var/log/hdiejectd.log"
"/var/log/install.log"
"/Library/Preferences/SystemConfiguration/com.apple.airport.preferences.plist"
"/private/etc/kcpassword"
"/private/etc/sudoers"
"/private/etc/hosts"
"/private/etc/resolv.conf"
"/private/var/log/fsck_hfs.log"
"/private/var/db/launchd.db/com.apple.launchd/overrides.plist"
"/Library/Logs/AppleFileService/AppleFileServiceError.log"
"/var/log/appfirewall.log"
"/etc/profile"
"/etc/bashrc"
)

#list the files at the user level you want to collect here
declare -a userFiles=(
#these are user files paths without the ~ at the beginning. The home directories will be concated later
"Library/Preferences/com.apple.finder.plist"
"Library/Preferences/com.apple.recentitems.plist"
"Library/Preferences/com.apple.loginitems.plist"
"Library/Logs/DiskUtility.log"
"Library/Preferences/com.apple.LaunchServices.QuarantineEventsV2"
".bash_history"
".profile"
".bash_profile"
".bash_login"
".bash_logout"
".bashrc"
)

#Collect parsed Apple System Logs with UTC timestamps
syslog -T UTC > artifacts/appleSystemLogs.txt

#collect dirs
for x in "${directories[@]}"
do
dirname=`echo "$x" | awk -F "/" '{print $NF}'`
echo created "$dirname" from "$x"
mkdir artifacts/"$dirname"
ditto "$x" artifacts/"$dirname"
done

#collect privileged files
for x in "${files[@]}"
do
ditto "$x"* artifacts
done

#collect user files for each user
dscl . -ls /Users | egrep -v ^_ | while read user
do
for x in "${userFiles[@]}"
do
fileLocation="/Users/$user/$x"
echo "Trying to ditto $fileLocation"
if [ -f $fileLocation ]; then
ditto "$fileLocation"* artifacts
fi
done
done

7. file_walker.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import os
import stat
import argparse
import hashlib
import time


class FileWalker(object):

def __init__(self, startDir, dumpdir, md5Bool, whitelist):
if not os.path.isdir(dumpdir):
os.makedirs(dumpdir)
self.fileInfo = "%s/fileinfo.txt" % (dumpdir)
self.fileTimeline = "%s/filetimeline.txt" % (dumpdir)
self.startDir = startDir
self.md5Bool = md5Bool
self.dumpdir = dumpdir
self.errors = []
self.fileTypes = {"010":"file", "014":"socket", "012":"link", "060":"block dev", "004":"dir", "020":"char dev", "001":"FIFO"}
self.specialbits = {"0":"None", "2048":"SETUID", "1024":"SETGID", "512":"STICKYBIT", "3072":"SETUID/SETGID", "2560":"SETUID/STICKYBIT", "1536":"SETGID/STICYKBIT", "3584":"SETUID/SETGIT/STICKYBIT"}
self.whitelist = whitelist or []

def getHash(self, fp):
with open(fp, 'rb') as fh:
m = hashlib.md5()
while True:
data = fh.read(8192)
if not data:
break
m.update(data)
return m.hexdigest()

def formatTime(self, timestamp):
ts = time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime(timestamp))
return ts

def checkTypeSpecial(self, filePath):
special = filePath.st_mode & stat.S_ISUID + filePath.st_mode & stat.S_ISGID + filePath.st_mode & stat.S_ISVTX
return self.specialbits[str(special)]

def statFile(self,filePath):
sr = os.stat(filePath)
return sr, getattr(sr, 'st_birthtime', None)

def collect(self, fileName, fi_fd, time_fd):
try:
(mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime), btime = self.statFile(fileName)

#create a string entry for each timestamp
aString = "%s, accessed, %s\n" % (self.formatTime(atime), fileName)
mString = "%s, modified, %s\n" % (self.formatTime(mtime), fileName)
cString = "%s, changed, %s\n" % (self.formatTime(ctime), fileName)
#add birth time if it exists
if btime != None: bString = "%s, birth, %s\n" % (self.formatTime(btime), fileName)


#check for special file bits - stickybit should not be found since we aren't collecting directories
x = (mode & stat.S_ISUID) + (mode & stat.S_ISGID) + (mode & stat.S_ISVTX)
special = self.specialbits[str(x)]

#create file data for seperate file
filetype = self.fileTypes[str(oct(mode)[:3])]
if self.md5Bool == True and os.path.isfile(fileName):
md5 = self.getHash(fileName)
fileData = "%s, %s, %s, %s, %s, %s, %s, %s\n" % (fileName, oct(mode)[-3:], self.fileTypes[str(oct(mode)[:3])], uid, gid, size, special, md5)
else:
fileData = "%s, %s, %s, %s, %s, %s, %s\n" % (fileName, oct(mode)[-3:], self.fileTypes[str(oct(mode)[:3])],uid, gid, size, special)

#write data to files
fi_fd.write(fileData)
time_fd.write(aString)
time_fd.write(mString)
time_fd.write(cString)
if btime!=None:
time_fd.write(bString)
except OSError:
self.errors.append(fileName)

def run(self):
print("Collecting file listing...")
with open(self.fileInfo, 'w+a') as fi_fd, open(self.fileTimeline, 'w+a') as time_fd:
for dirname, dirnames, filenames in os.walk(self.startDir, topdown=True):
dirnames[:] = [d for d in dirnames if d not in self.whitelist]
for filename in filenames:
fileWithPath = os.path.join(dirname, filename)
self.collect(fileWithPath, fi_fd, time_fd)

if __name__ == '__main__':
#create script arguments
parser = argparse.ArgumentParser()
parser.add_argument("-s", "--start", required=True, help="Specify a starting directory")
parser.add_argument("-d", "--dumpdir", required=True, help="Specify a directory to store the created files")
parser.add_argument("-m", "--md5", required=False, action='store_true', help="Collect MD5 Hashes")
parser.add_argument("-w", "--whitelist", nargs='*', required=False, help="Skip specified directories")
args = parser.parse_args()

#create a run filewalker
fileWalker = FileWalker(args.start, args.dumpdir, args.md5, args.whitelist)
fileWalker.run()

8. storyline.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import os
import time
import datetime
import sqlite3
import glob
import gzip

#fix standard log to write UTC timestamps
#fix year timestamp to reflect the year the file was created

def writeToStory(string):
outputFile = 'storyline.txt'
f = open(outputFile, 'a')
f.write(string)
f.close()

def gunzipFiles(fileLocation):
fileWildcarded = "%s*" % (fileLocation)
for fileName in glob.glob(fileWildcarded):
newFileName = fileName.replace('.gz', '')
outputFile = open(newFileName, 'wb')
try:
inputFile = gzip.open(fileName, 'rb')
file_content = inputFile.read()
outputFile.write(file_content)
outputFile.close()
except IOError:
pass


#many logs on OS X store entries with this format so we can re-use this function
def timelineStandardLog(title, fileLocation):
print fileLocation
now = datetime.datetime.now()
gunzipFiles(fileLocation)

fileWildCarded = "%s*" % (fileLocation)
for fileName in glob.glob(fileWildCarded):
with open(fileName) as f:
for line in f:
#grab the timestamp
ts = line.split()[:3]
ts = ' '.join(ts)
#add the current year to the syslog becase it does not contain it
ts = "%s %s" % (now.year, ts)

#grab the rest of the log
info = line.split()[4:]
info = ' '.join (info)
try:
t = time.strptime(ts, "%Y %b %d %H:%M:%S")
logEntry = "%s, %s, %s\n" % (time.strftime("%Y-%m-%dT%H:%M:%S", t), title, info)
print logEntry
writeToStory(logEntry)

except ValueError:
pass


def timelineQuarantine(fileName):
conn = sqlite3.connect(fileName)
c = conn.cursor()
#EventIdentifier, TimeStamp, AgentBundleIdentifier, Name, DataURLString SenderName SenderAddress TypeNumber OriginTitle OriginURLString LSQuarantineOriginAlias
for row in c.execute('SELECT * FROM LSQuarantineEvent'):
ts = time.gmtime(row[1])

ose = (int(time.mktime(datetime.date(2001,1,1).timetuple())) - time.timezone)
nts = (time.strftime('%Y-%m-%dT%H:%M:%S', time.gmtime(ose+row[1])))

info = row[2:]
info = "".join(str(info))
logEntry = "%s, QUARANTINE, %s\n" % (nts, info)
print logEntry
writeToStory(logEntry)


if __name__ == '__main__':

timelineStandardLog('SYSLOG', 'artifacts/system.log')
timelineStandardLog('APPFirewall', 'artifacts/appfirewall.log')
timelineStandardLog('INSTALL', 'artifacts/install.log')
timelineStandardLog('ACCOUNTPOLICY', 'artifacts/accountpolicy.log')
timelineQuarantine('artifacts/com.apple.LaunchServices.QuarantineEventsV2')

9. browser_collect.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
echo "Copying browser data..."
browserfolder="browserHistory"
mkdir $browserfolder

dscl . -ls /Users | egrep -v ^_ | while read user
do
#check for and copy Safari data
#Safari is pretty much garenteed to be installed
if [ -d "/Users/$user/Library/Safari/" ]; then
plutil -convert xml1 /Users/$user/Library/Safari/History.plist -o "$browserfolder/$user"_safariHistory.plist
plutil -convert xml1 /Users/$user/Library/Safari/Downloads.plist -o "$browserfolder/$user"_safariDownloads.plist

#grab the sqlite3 version of the history if you prefer
ditto "/Users/$user/Library/Safari/Downloads.plist" "$browserfolder/$user"_safariDownloads.plist
fi

#check for and copy Chrome data
if [ -d "/Users/$user/Library/Application Support/Google/Chrome/" ]; then
ditto "/Users/$user/Library/Application Support/Google/Chrome/Default/History" "$browserfolder/$user"_chromeHistory.db
fi

#check for and copy firefox data
#there should only be one profile inside the Profiles directory
if [ -d "/Users/$user/Library/Application Support/Firefox/" ]; then
for PROFILE in /Users/$user/Library/Application\ Support/Firefox/Profiles/*; do
ditto "$PROFILE/places.sqlite" "$browserfolder/$user"_firefoxHistory.db
done
fi

#check for and copy Opera data
if [ -d "/Users/$user/Library/Application Support/com.operasoftware.Opera/" ]; then
ditto "/Users/$user/Library/Application Support/com.operasoftware.Opera/History" "$browserfolder/$user"_operaHistory.db
fi
done

10. browser_parser.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
import sqlite3
import plistlib
import glob
import time
import datetime

dt_lookup = {
0: 'CLEAN',
1: 'DANGEROUS_FILE',
2: 'DANGEROUS_URL',
3: 'DANGEROUS_CONTENT',
4: 'MAYBE_DANGEROUS_CONTENT',
5: 'UNCOMMON_CONTENT',
6: 'USER_VALIDATED',
7: 'DANGEROUS_HOST',
8: 'POTENTIALLY_UNWANTED',
9: 'MAX'
}

def printToFile(string):
output_file = 'browserHistory.txt'
with open(output_file, 'a') as fd:
fd.write(string)

def convertAppleTime(appleFormattedDate):
ose = (int(time.mktime(datetime.date(2001,1,1).timetuple())) - time.timezone)
appleFormattedDate = float(appleFormattedDate)
ts = (time.strftime('%Y-%m-%dT%H:%M:%S', time.gmtime(ose+appleFormattedDate)))
return ts

def parseSafariHistoryplist(histFile):
historyData = plistlib.readPlist(histFile)
for x in historyData['WebHistoryDates']:
string = "%s, safari_history, %s\n" % (convertAppleTime(x['lastVisitedDate']), x[''])
printToFile(string)

def parseSafariHistorydb(histFile):
#need to convert timestamp still.
print("Parsing Safari")
conn = sqlite3.connect(histFile)
c = conn.cursor()
query = """SELECT
h.visit_time,
i.url
FROM
history_visits h
INNER JOIN
history_items i ON h.history_item = i.id"""
for visit_t, url in c.execute(query):
string = "%s, safari_history, %s\n" % (convertAppleTime(visit_t), url)
printToFile(string)

def parseSafariDownloadsplist(histfile):
print("Parsing Safari Downloads")
historyData = plistlib.readPlist(histfile)
for x in historyData['DownloadHistory']:
try:
string = "%s, safari_download, %s, %s\n" % (x['DownloadEntryDateAddedKey'], x['DownloadEntryURL'], x['DownloadEntryPath'])
printToFile(string)
except KeyError:
string = "safari_download, %s, %s\n" % (x['DownloadEntryURL'], x['DownloadEntryPath'])
printToFile(string)


def parseChromeHistory(histFile):
print("Parsing Chrome")
conn = sqlite3.connect(histFile)
c = conn.cursor()
query = """SELECT
strftime('%Y-%m-%dT%H:%M:%S', (v.visit_time/1000000)-11644473600, 'unixepoch'),
u.url
FROM
visits v
INNER JOIN
urls u ON u.id = v.url;"""

for dt, url in c.execute(query):
string = "%s, chrome_history, %s\n" % (dt, url)
printToFile(string)

query = """SELECT
strftime('%Y-%m-%dT%H:%M:%S', (d.start_time/1000000)-11644473600, 'unixepoch'),
dc.url,
d.target_path,
d.danger_type,
d.opened
FROM
downloads d
INNER JOIN
downloads_url_chains dc ON dc.id = d.id;"""

for dt, referrer, target, danger_type, opened in c.execute(query):
string = "%s, chrome_download, %s, %s, danger_type:%s, opened:%s\n" % (dt, referrer, target,
dt_lookup[danger_type], opened)
printToFile(string)

def parseOperaHistory(histFile):
print("Parsing Opera")
conn = sqlite3.connect(histFile)
c = conn.cursor()
query = """SELECT
strftime('%Y-%m-%dT%H:%M:%S', (v.visit_time/1000000)-11644473600, 'unixepoch'),
u.url,
u.title
FROM
visits v
INNER JOIN
urls u ON u.id = v.url;"""

for row in c.execute(query):
string = "%s, opera_history, %s\n" % (row[0], row[1])
printToFile(string)

#parse Opera downloads
query = """SELECT
strftime('%Y-%m-%dT%H:%M:%S', (d.start_time/1000000)-11644473600, 'unixepoch'),
dc.url,
d.target_path,
d.danger_type,
d.opened
FROM
downloads d
INNER JOIN
downloads_url_chains dc ON dc.id = d.id;"""
for row in c.execute(query):
string = "%s, opera_download, %s, %s, danger_type:%s, opened:%s\n" % (row[0], row[1], row[2], dt_lookup[row[3]], row[4])
printToFile(string)

def parseFirefoxHistory(histFile):
print("Parsing Firefox")
conn = sqlite3.connect(histFile)
c = conn.cursor()
query = """SELECT
strftime('%Y-%m-%dT%H:%M:%S',hv.visit_date/1000000, 'unixepoch') as dt,
p.url
FROM
moz_historyvisits hv
INNER JOIN
moz_places p ON hv.place_id = p.id
ORDER by dt ASC;"""
for dt, url in c.execute(query):
string = "%s, firefox_history, %s\n" % (dt, url)
printToFile(string)

#FireFox parse firefox downloads
query = """SELECT
strftime('%Y-%m-%dT%H:%M:%S', a.dateAdded/1000000, 'unixepoch') as dt,
a.content,
p.url
FROM
moz_annos a
INNER JOIN
moz_places p ON p.id = a.place_id
WHERE
a.anno_attribute_id = 6
ORDER BY dt ASC;"""

for dt, content, url in c.execute(query):
string = "%s, firefox_download, %s, %s\n" % (dt, content, url)
printToFile(string)

if __name__ == '__main__':
print("Parsing Browser History...")

safariDBFound = False
for filename in glob.glob('browserHistory/*.db'):
if filename.endswith('chromeHistory.db'):
parseChromeHistory(filename)
elif filename.endswith('firefoxHistory.db'):
parseFirefoxHistory(filename)
elif filename.endswith('operaHistory.db'):
parseOperaHistory(filename)
elif filename.endswith('safariHistory.db'):
parseSafariHistorydb(filename)
safariDBFound = True

for filename in glob.glob('browserHistory/*.plist'):
if filename.endswith('safariHistory.plist') and safariDBFound == False:
parseSafariHistoryplist(filename)
elif filename.endswith('safariDownloads.plist'):
parseSafariDownloadsplist(filename)

11. memory_collection.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#collect memory
#requires osxpmem.zip be inside the tools directory
#requires rekall be inside the tools directory

#scenario 1 -> full memory acquisition
#scenario 2 -> collect memory strings and live memory commands
#scenario 3 -> collect only live memory commands

#scenario 1 set by default
scenario=1
IRfolder=collection
rekallOutputFolder=$IRfolder/rekall
memArtifacts=$IRfolder/memory.aff4
mkdir $IRfolder

#if going with the live memory scenario, set Rekall commands here
function runRekallCommands {
mkdir $rekallOutputFolder
tools/rekall/rekal -f /dev/pmem arp --output $rekallOutputFolder/rekall_arp.txt
tools/rekall/rekal -f /dev/pmem lsmod --output $rekallOutputFolder/rekall_lsmod.txt
tools/rekall/rekal -f /dev/pmem check_syscalls --output $rekallOutputFolder/rekall_check_syscalls.txt
tools/rekall/rekal -f /dev/pmem psxview --output $rekallOutputFolder/rekall_psxview.txt
tools/rekall/rekal -f /dev/pmem pstree --output $rekallOutputFolder/rekall_pstree.txt
tools/rekall/rekal -f /dev/pmem dead_procs --output $rekallOutputFolder/rekall_dead_procs.txt
tools/rekall/rekal -f /dev/pmem psaux --output $rekallOutputFolder/rekall_psaux.txt
tools/rekall/rekal -f /dev/pmem route --output $rekallOutputFolder/rekall_route.txt
tools/rekall/rekal -f /dev/pmem sessions --output $rekallOutputFolder/rekall_sessions.txt
tools/rekall/rekal -f /dev/pmem netstat --output $rekallOutputFolder/rekall_netstat.txt
#add any additional Rekall commands you want to run here
}

function collectSwap {
#Check if swap files are encrpyted and collect if they're not
if ! sysctl vm.swapusage | grep -q encrypted; then
echo "Collecting swap memory..."
osxpmem.app/osxpmem -i /private/var/vm/sleepimage -o $memArtifacts
osxpmem.app/osxpmem -i /private/var/vm/swapfile* -o $memArtifacts
else
echo "Swapfiles encrypted. Skipping..."
fi
}

echo "Starting memory collection..."

#unzip osxpmem app to current directory
unzip tools/osxpmem.zip > /dev/null

#modify permissionbs on kext file so we can load it
chown -R root:wheel osxpmem.app/MacPmem.kext

#try to load kext
if kextload osxpmem.app/MacPmem.kext; then
echo "MacPmem Kext loaded"
else
echo "ERROR: MacPmem Kext failed to load. Can not collect memory."
fi

case $scenario in
1)
#scenario 1 -> full memory acquisition
osxpmem.app/osxpmem -o $memArtifacts > /dev/null
collectSwap
;;
2)
#scenario 2 -> collect memory strings and live memory commands
osxpmem.app/osxpmem -o $memArtifacts > /dev/null
osxpmem.app/osxpmem --export /dev/pmem --output memory.dmp $memArtifacts
echo "Running strings on memory dump..."
strings memory.dmp > $IRfolder/memory.strings

#run Recall commands
runRekallCommands

#clean up since these files may take up a lot of hard drive space
rm $memArtifacts
rm memory.dmp
#test
;;
3)
#scenario 3 -> collect only live memory commands
runRekallCommands
;;
esac

echo "Unloading MacPmem.kext"
kextunload osxpmem.app/MacPmem.kext

12. exfiltrator.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import os
import argparse
import datetime

class Exfiltrator(object):

def __init__(self, outputFile):
self.whiteList = ["zip", "jar", "rar", "egg", "package", "plist", "xlsx", "docx", "pptx", "cdt", "xpi", "ots", "ods", "dotm", "apk" ]
self.enableOutput = False
self.outputFile = outputFile
if self.outputFile != None:
self.enableOutput = True

def getFileHeader(self, fileLocation):
try:
f = open(fileLocation)
header = f.read(4)
header = header.encode('hex')
return header
except IOError:
print "\nError reading %s. Skipping..." % (fileLocation)
return None

def checkWhiteList(self,fileLocation):
found = False
for extension in self.whiteList:
if fileLocation.endswith(extension):
found = True
break
return found

def checkFile(self, fileLocation):
print fileLocation
isFile = os.path.isfile(fileLocation)
isWhiteListed = self.checkWhiteList(fileLocation)
if isFile == False:
header = None
else:
header = self.getFileHeader(fileLocation)

if header is None or isWhiteListed == True:
pass
elif header == "504b0304":
print "\nPKzip file format found"
ts = datetime.datetime.fromtimestamp(os.path.getmtime(fileLocation))
string = "%s %s" % (ts, fileLocation)
if self.enableOutput == True:
print fileLocation
self.writeToFile(string + "\n")
else:
print string


def writeToFile(self, data):
f = open(self.outputFile, 'a')
f.write(data)
f.close()

if __name__ == "__main__":

parser = argparse.ArgumentParser()
parser.add_argument("-a", "--all", required=False, action="store_true", help="Scan all files on system")
parser.add_argument("-s", "--start", required=True, help="Specify a starting directory")
parser.add_argument("-o", "--output", required=False, default=None, help="Output results to specified file")

args = parser.parse_args()

exfiltrator = Exfiltrator(args.output)

#walk each directory
for dirname, dirnames, filenames in os.walk(args.start):
for filename in filenames:
fileWithPath = os.path.join(dirname, filename)
#print "scanning %s" % (fileWithPath)
exfiltrator.checkFile(fileWithPath)

0xFF Reference