forked from Rurik/Noriben
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Noriben.py
executable file
·1574 lines (1367 loc) · 65.3 KB
/
Noriben.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
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
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Noriben Malware Analysis Sandbox
#
# Directions:
# Just copy Noriben.py to a Windows-based VM alongside the SysInternals Procmon.exe
#
# Run Noriben.py, then run your executable.
# When the executable has completed its processing, stop Noriben and view a
# clean text report and timeline
#
# Version 1.0 - 10 Apr 13 - @bbaskin - brian [@] thebaskins.com
# Gracious edits, revisions, and corrections by Daniel Raygoza
# Version 1.1 - 21 Apr 13 -
# Much improved filters and filter parsing
# Version 1.1a - 1 May 13 -
# Revamped regular expression support. Added Python 3.x forward
# compatibility
# Version 1.2 - 28 May 13 -
# Now reads CSV files line-by-line to handle large files, keep
# unsuccessful registry deletes, compartmentalize sections, creates CSV
# timeline, can reparse PMLs, can specify alternative PMC filters,
# changed command line arguments, added global approvelist
# Version 1.3 - 13 Sep 13 -
# Option to generalize file paths in output, option to use a timeout
# instead of Ctrl-C to end monitoring, only writes RegSetValue entries
# if Length > 0
# Version 1.4 - 16 Sep 13 -
# Fixed string generalization on file rename and now supports ()'s in
# environment name (for 64-bit systems), added ability to Ctrl-C from
# a timeout, added specifying malware file from command line, added an
# output directory
# Version 1.5 - 28 Sep 13 -
# Standardized to single quotes, added YARA scanning of resident files,
# reformatted function comments to match appropriate docstring format,
# fixed bug with generalize paths - now generalizes after getting MD5
# Version 1.5b - 1 Oct 13 -
# Ninja edits to fix a few small bug fixes and change path generalization
# to an ordered list instead of an unordered dictionary. This lets you
# prioritize resolutions.
# Version 1.6 - 14 Mar 15 -
# Long delayed and now forked release. This will be the final release for
# Python 2.X except for updated rules. Now requires 3rd party libraries.
# VirusTotal API scanning implemented. Added better filters.
# Added controls for some registry writes that had size but no data.
# Added whitelist for MD5 hashes and --hash option for hash file.
# Renamed 'blacklist' to 'whitelist' because it's supposed to be. LOL
# Change file handling due to 'read entire file' bug in FileInput.
# Version 1.6.1 - 16 Mar 15 -
# Soft fails on Requests import. Lack of module now just disables VirusTotal.
# Added better YARA handling. Instead of failing over a single error, it
# will skip the offending file. You can now hard-set the YARA signature
# folder in the script.
# Version 1.6.2 - 9 Apr 15 -
# Created debug output to file. This now includes full VirusTotal dumps.
# Currently Noriben only displays number of hits, but additional meta is now
# dumped for further analysis by users.
# Version 1.6.3 - 13 Jan 16 -
# Bug fixes to handle path joining. Bug fixes for spaces in all directory
# names. Added support to find default PMC from script working directory.
# Version 1.6.4 - 7 Dec 16 -
# A handful of bug fixes related to bad Internet access. Small variable updates.
# Version 1.7.0 - 4 Feb 17 -
# Default hash method is now SHA256. An argument and global var allow to
# override hash. Numerous filters added. PEP8 cleanup, multiple small fixes to
# code and implementation styles
# Version 1.7.1 - 3 Apr 17 -
# Small updates. Change --filter to not find default if a bad one is specified
# Version 1.7.2 - 21 Apr 17 -
# Fixed Debug output to go to a log file continually, so output is stored if
# unexpected exit. Check for PML and Config file between executions to account
# for destructive malware that erases during runtime. Added headless option for
# automated runs, so that screenshot can be grabbed w/o output on screen
# Version 1.7.3 - 26 Dec 17 -
# Fixed bug where a changed procmon binary was not added to the whitelist, and
# would therefore be included in the output
# Version 1.7.3b - 7 Jan 18 -
# Implemented --troubleshoot option to pause the program upon exit so that the
# error messages can be seen manually
# Version 1.7.4 - 28 Feb 18 -
# More bug fixes related to global use of renamed procmon binary. Added filters
# Version 1.7.5 - 10 Mar 18 -
# Another bug fix related to global use of renamed procmon binary. Edge case fix
# Version 1.7.6 - 12 Apr 18 -
# Some auto PEP-8 formatting. Fixed bug where specific output dir wouldn't add
# to files when specifying a PML or CSV file. Added configuration of new txt
# extension in cases where ransomware was encrypting files. CSV, however, cannot
# be changed due to limitations in ProcMon
# Version 1.8.0 - 9 Jun 18
# Really, truly, dropping Python 2 support now. Added --config file option to load
# global variables from external files. Now uses CSV library. Code cleanup
# Version 1.8.1 - 14 Jun 18
# Added additional config options, such as output_folder. Added value
# global_whitelist_append to allow additional filters
# Version 1.8.2 - 28 Jun 18
# Fixed minor bug that would crash upon writing out CSV
# Version 1.8.3 - 26 Nov 18
# Fixed minor bugs in reading hash files and in sleeping between VirusTotal queries
# Version 1.8.4 - 22 Nov 19
# Minor updates. Added ability to run a non-executable, such as a Word document
# Version 1.8.5 - 02 May 21
# Changed terminology from whitelist to approvelist. Updated filters. Added quick
# fix related to issue #29/#44 for errant ticks in data
# Version 1.8.6 - 26 May 21
# Fixed a long-standing bug that crashed the script when encountering certain
# binary data in a registry key
#
# TODO:
# * Upload files directly to VirusTotal (2.X feature?)
# * extract data directly from registry? (may require python-registry)
# * scan for mutexes, preferably in a way that doesn't require wmi/pywin32
# * Fix CSV issues (see GitHub issue)
# * Place filters into external file so that core script does not need change to update
import argparse
import ast
import codecs
import csv
import datetime
import glob
import hashlib
import os
import re
import subprocess
import string
import sys
import time
import traceback
try:
import yara # pip yara-python
has_yara = True
except ImportError:
yara = None
has_yara = False
try:
import requests
import json
has_internet = True
except ImportError:
requests = None
json = None
has_internet = False
print('[+] Python module "requests" not found. Internet functionality is disabled.')
print('[+] This is acceptable if you do not wish to upload data to VirusTotal.')
try:
import configparser
except ImportError:
print('[!] Python module "configparser" not found. Python 3 required.')
configparser = None
# The below are customizable variables. Change these as you see fit.
config = {
'procmon': 'procmon.exe', # Change this if you have a renamed procmon.exe
'generalize_paths': True, # Generalize paths to their base environment variable
'debug': False,
'headless': False,
'troubleshoot': False, # If True, pause before all exit's
'timeout_seconds': 0, # Set to 0 to manually end monitoring with Ctrl-C
'virustotal_api_key': '', # Set API here
'yara_folder': '',
'hash_type': 'SHA256',
'txt_extension': 'txt',
'output_folder': '',
'global_approvelist_append': ''
}
if os.path.exists('virustotal.api'): # Or put it in here
config['virustotal_api_key'] = open('virustotal.api', 'r').readline().strip()
valid_hash_types = ['MD5', 'SHA1', 'SHA256']
# Rules for creating rules:
# 1. Every rule string must begin with the `r` for regular expressions to work.
# 1.a. This signifies a 'raw' string.
# 2. No backslashes at the end of a filter. Either:
# 2.a. truncate the backslash, or
# 2.b. use '\*' to signify 'zero or more slashes'.
# 3. To find a list of available '%%' variables, type `set` from a command prompt
# These entries are applied to all approvelists
global_approvelist = [
r'VMwareUser.exe', # VMware User Tools
r'CaptureBAT.exe', # CaptureBAT Malware Tool
r'SearchIndexer.exe', # Windows Search Indexer
r'Fakenet.exe', # Practical Malware Analysis FakeNET
r'idaq.exe', # IDA Pro
r'ngen.exe', # Windows Native Image Generator
r'ngentask.exe', # Windows Native Image Generator
r'consent.exe', # Windows UAC prompt
r'taskhost.exe',
r'SearchIndexer.exe',
r'RepUx.exe',
r'RepMgr64.exe',
r'Ecat.exe',
r'WindowsApps\Microsoft.Windows.Photos.*\Microsoft.Photos.exe',
r'Microsoft Visual Studio\Installer\resources\app\ServiceHub\Services\Microsoft.VisualStudio.Setup.Service\BackgroundDownload.exe',
config['procmon'],
config['procmon'].split('.')[0] + '64.exe' # Procmon drops embed as <name>+64
]
cmd_approvelist = [
r'AppData\Local\Microsoft\OneDrive\StandaloneUpdater\OneDriveSetup.exe', # MS is so noisy
r'%SystemRoot%\system32\wbem\wmiprvse.exe',
r'%SystemRoot%\system32\wscntfy.exe',
r'wuauclt.exe',
r'jqs.exe',
r'avgrsa.exe', # AVG AntiVirus
r'avgcsrva.exe', # AVG AntiVirus
r'avgidsagenta.exe', # AVG AntiVirus
r'Program File.*\Microsoft\EdgeUpdate\MicrosoftEdgeUpdate.exe',
r'TCPView.exe',
r'%WinDir%\System32\mobsync.exe',
r'XblGameSaveTask.exe',
r'/Processid:{AB8902B4-09CA-4BB6-B78D-A8F59079A8D5}', # Thumbnail server
r'/Processid:{F9717507-6651-4EDB-BFF7-AE615179BCCF}', # DCOM error
r'\??\%WinDir%\system32\conhost.exe .*-.*-.*-.*' # Experimental
]
file_approvelist = [
r'Desired Access: Execute/Traverse',
r'Desired Access: Synchronize',
r'Desired Access: Generic Read/Execute',
r'Desired Access: Read EA',
r'Desired Access: Read Data/List',
r'Desired Access: Generic Read, ',
r'Desired Access: Read Attributes',
r'desktop.ini$',
r'Google\Chrome\User Data\.*.tmp',
r'Microsoft\Windows\Explorer\iconcache_*',
r'Microsoft\Windows\Explorer\thumbcache_.*.db',
r'Thumbs.db$',
r'wuauclt.exe',
r'wmiprvse.exe',
r'%AllUsersProfile%\Application Data\Microsoft\OFFICE\DATA',
r'%AllUsersProfile%\Microsoft\MapData\*',
r'%AllUsersProfile%\Microsoft\RAC',
r'%AllUsersProfile%\Microsoft\Windows\AppRepository\StateRepository',
r'%AppData%\Microsoft\Proof\*',
r'%AppData%\Microsoft\Templates\*',
r'%AppData%\Microsoft\Windows\Recent\AutomaticDestinations\1b4dd67f29cb1962.automaticDestinations-ms',
r'%LocalAppData%\Google\Drive\sync_config.db*',
r'%LocalAppData%\GDIPFONTCACHEV1.DAT',
r'%LocalAppData%\Microsoft\OneDrive\StandaloneUpdater\*',
r'%LocalAppData%\Microsoft\VSApplicationInsights\vstelAIF',
r'%LocalAppData%\Packages\Microsoft.Windows.Photos_',
r'%ProgramFiles%\Capture\*',
r'%SystemDrive%\Python',
r'%SystemRoot%\assembly',
r'%SystemRoot%\Microsoft.NET\Framework64',
r'%SystemRoot%\Prefetch\*',
r'%SystemRoot%\system32\wbem\Logs\*',
r'%SystemRoot%\System32\LogFiles\Scm',
r'%SystemRoot%\System32\Tasks\Microsoft\Windows', # Some may want to remove this
r'%UserProfile%$',
r'%UserProfile%\Desktop$',
r'%UserProfile%\AppData\LocalLow$',
r'%UserProfile%\Recent\*',
r'%UserProfile%\Local Settings\History\History.IE5\*',
r'%WinDir%\AppCompat\Programs\RecentFileCache.bcf',
r'%WinDir%\ServiceProfiles\LocalService\AppData\Local\FontCache\Fonts\*',
r'%WinDir%\ServiceProfiles\LocalService\AppData\Local\Microsoft\Dlna\*',
r'%WinDir%\SoftwareDistribution\DataStore\DataStore.edb',
r'%WinDir%\SoftwareDistribution\DataStore\Logs\edb....',
r'%WinDir%\SoftwareDistribution\ReportingEvents.log',
r'%WinDir%\System32\catroot2\edb....',
r'%WinDir%\System32\config\systemprofile\AppData\LocalLow\Microsoft\CryptnetUrlCache\MetaData\*',
r'%WinDir%\System32\spool\drivers\*',
r'%WinDir%\Temp\fwtsqmfile00.sqm', # Software Quality Metrics (SQM) from iphlpsvc
r'MAILSLOT\NET\NETLOGON',
r'Windows\Temporary Internet Files\counters.dat',
r'Program Files.*\confer\*'
]
reg_approvelist = [
r'CaptureProcessMonitor',
r'consent.exe',
r'verclsid.exe',
r'wmiprvse.exe',
r'wscntfy.exe',
r'wuauclt.exe',
r'PROCMON',
r'}\DefaultObjectStore\*',
r'}\LocalState\SessionSummaryData\.*\*',
r'HKCR$',
r'HKCR\AllFilesystemObjects\shell',
r'HKCU$',
r'HKCU\Printers\DevModePerUser',
r'HKCU\SessionInformation\ProgramCount',
r'HKCU\Software$',
r'HKCU\Software\Classes\Software\Microsoft\Windows\CurrentVersion\Deployment\SideBySide',
r'HKCU\Software\Classes\Local Settings\MuiCache\*',
r'HKCU\Software\Classes\Local Settings\MrtCache\*',
r'HKCU\Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\SyncMgr\*',
r'HKCU\Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\*',
r'HKCU\Software\Microsoft\Calc$',
r'HKCU\Software\Microsoft\.*\Window_Placement',
r'HKCU\Software\Microsoft\Internet Explorer\TypedURLs',
r'HKCU\Software\Microsoft\Notepad',
r'HKCU\Software\Microsoft\Office',
r'HKCU\Software\Microsoft\Shared Tools',
r'HKCU\Software\Microsoft\SystemCertificates\Root$',
r'HKCU\Software\Microsoft\VisualStudio\*',
r'HKCU\Software\Microsoft\Windows\CurrentVersion\Applets',
r'HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\CIDOpen',
r'HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\CIDSave\Modules\GlobalSettings',
r'HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\.*MRU.*',
r'HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Modules',
r'HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\MountPoints2',
r'HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\RunMRU',
r'HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\SessionInfo',
r'HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\StartPage',
r'HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\StartPage2',
r'HKCU\Software\Microsoft\Windows\Currentversion\Explorer\StreamMRU',
r'HKCU\Software\Microsoft\Windows\Currentversion\Explorer\Streams',
r'HKCU\Software\Microsoft\Windows\CurrentVersion\Group Policy',
r'HKCU\Software\Microsoft\Windows\CurrentVersion\HomeGroup',
r'HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings\Wpad\*',
r'HKCU\Software\Microsoft\Windows\Shell',
r'HKCU\Software\Microsoft\Windows\Shell\BagMRU',
r'HKCU\Software\Microsoft\Windows\Shell\Bags',
r'HKCU\Software\Microsoft\Windows\ShellNoRoam\MUICache',
r'HKCU\Software\Microsoft\Windows\ShellNoRoam\BagMRU',
r'HKCU\Software\Microsoft\Windows\ShellNoRoam\Bags',
r'HKCU\Software\Microsoft\Windows NT\CurrentVersion\Devices',
r'HKCU\Software\Microsoft\Windows NT\CurrentVersion\PrinterPorts\*',
r'HKCU\Software\Microsoft\Windows NT\CurrentVersion\Windows\UserSelectedDefault',
r'HKCU\Software\Policies$',
r'HKCU\Software\Policies\Microsoft$',
r'HKLM$',
r'HKLM\.*\Enum$',
r'HKLM\Software$',
r'HKLM\Software\Microsoft\Cryptography\RNG\Seed', # Some people prefer to leave this in.
r'HKLM\Software\Microsoft$',
r'HKLM\SOFTWARE\Microsoft\Device Association Framework\Store\*',
r'HKLM\Software\MICROSOFT\Dfrg\Statistics',
r'HKLM\Software\Microsoft\Reliability Analysis\RAC',
r'HKLM\Software\MICROSOFT\SystemCertificates$',
r'HKLM\Software\Microsoft\WBEM',
r'HKLM\Software\Microsoft\Windows\CurrentVersion\Group Policy\History\PolicyOverdue',
r'HKLM\Software\Microsoft\Windows\CurrentVersion\Group Policy\State\Machine\Extension-List',
r'HKLM\Software\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products',
r'HKLM\Software\Microsoft\Windows\CurrentVersion\Internet Settings\Cache\Paths\*',
r'HKLM\Software\Microsoft\Windows\CurrentVersion\MMDevices\Audio\Render',
r'HKLM\Software\Microsoft\Windows\CurrentVersion\Shell Extensions',
r'HKLM\Software\Microsoft\Windows Media Player NSS\*',
r'HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Appraiser\*',
r'HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Inventory',
r'HKLM\Software\Microsoft\Windows NT\CurrentVersion\NetworkList\Nla\Cache\*',
r'HKLM\Software\Microsoft\Windows NT\CurrentVersion\Prefetcher\*',
r'HKLM\Software\Microsoft\Windows NT\CurrentVersion\Print\*',
r'HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\.*\RefCount$',
r'HKLM\Software\Microsoft\Windows NT\CurrentVersion\Tracing\*',
r'HKLM\Software\Policies$',
r'HKLM\Software\Policies\Microsoft$',
r'HKLM\Software\Wow6432Node\Google\Update\ClientState\{',
r'HKLM\Software\Wow6432Node\Google\Update\old-uid',
r'HKLM\Software\Wow6432Node\Google\Update\uid',
r'HKLM\System\CurrentControlSet\Control\Class\{.*-E325-11CE-BFC1-08002BE10318}',
r'HKLM\System\CurrentControlSet\Control\Class\{6bdd1fc6-810f-11d0-bec7-08002be2092f}',
r'HKLM\System\CurrentControlSet\Control\DeviceClasses',
r'HKLM\System\CurrentControlSet\Control\DeviceContainers\{.*}\Properties\{.*}\*',
r'HKLM\System\CurrentControlSet\Control\MediaProperties',
r'HKLM\System\CurrentControlSet\Control\Network\{.*-e325-11ce-bfc1-08002be10318}',
r'HKLM\System\CurrentControlSet\Control\Network\NetCfgLockHolder',
r'HKLM\System\CurrentControlSet\Control\NetworkSetup2\Interfaces\{.*}\*',
r'HKLM\System\CurrentControlSet\Control\Nsi\{eb004a03-9b1a-11d4-9123-0050047759bc}',
r'HKLM\System\CurrentControlSet\Control\Print\Environments\*',
r'HKLM\System\CurrentControlSet\Enum\*',
r'HKLM\System\CurrentControlSet\Services\CaptureRegistryMonitor',
r'HKLM\System\CurrentControlSet\Services\Eventlog\*',
r'HKLM\System\CurrentControlSet\Services\iphlpsvc\*',
r'HKLM\System\CurrentControlSet\Services\ksthunk\*',
r'HKLM\System\CurrentControlSet\Services\NetBT\*',
r'HKLM\System\CurrentControlSet\Services\SharedAccess\Epoch2',
r'HKLM\System\CurrentControlSet\Services\Tcpip\Parameters',
r'HKLM\System\CurrentControlSet\Services\Tcpip6\Parameters',
r'HKLM\System\CurrentControlSet\Services\tunnel\*',
r'HKLM\System\CurrentControlSet\Services\W32Time\*',
r'HKLM\System\CurrentControlSet\Services\WinSock2\Parameters',
r'HKLM\System\CurrentControlSet\Services\WSD',
r'HKLM\System\CurrentControlSet\Services\VSS\Diag',
r'HKLM\SYSTEM\Maps\*',
r'HKU\.DEFAULT\Printers\*',
r'HKU\.DEFAULT\SOFTWARE\Classes\Local Settings\MuiCache',
r'LEGACY_CAPTUREREGISTRYMONITOR',
r'Microsoft\Device Association Framework\Store\DAFUPnPProvider',
r'Microsoft\EdgeUpdate\ClientState\{.*}\CurrentState\*',
r'Microsoft\Windows\CurrentVersion\Internet Settings\Wpad\*',
r'Root\InventoryD.*\*',
r'Software\Microsoft\Multimedia\Audio$',
r'Software\Microsoft\Multimedia\Audio Compression Manager',
r'Software\Microsoft\Windows\CurrentVersion\Explorer\MenuOrder',
r'Software\Microsoft\Windows\ShellNoRoam\Bags',
r'Software\Microsoft\Windows\ShellNoRoam\BagMRU',
r'Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.doc',
r'Software\Microsoft\Windows\CurrentVersion\Explorer\RecentDocs',
r'Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders',
r'Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders',
r'UserAssist\{5E6AB780-7743-11CF-A12B-00AA004AE837}',
r'UserAssist\{75048700-EF1F-11D0-9888-006097DEACF9}',
r'UserAssist\{CEBFF5CD-ACE2-4F4F-9178-9926F41749EA}'
]
net_approvelist = [
r'hasplms.exe' # Hasp dongle beacons
# r'192.168.2.', # Example for blocking net ranges
# r'Verizon_router.home'] # Example for blocking local domains
# r' -> .*\..*\..*\..*:1900
]
hash_approvelist = [
r'f8f0d25ca553e39dde485d8fc7fcce89', # WinXP ntdll.dll
r'b60dddd2d63ce41cb8c487fcfbb6419e', # iexplore.exe 8.0
r'6fe42512ab1b89f32a7407f261b1d2d0', # kernel32.dll
r'8b1f3320aebb536e021a5014409862de', # gdi32.dll
r'b26b135ff1b9f60c9388b4a7d16f600b', # user32.dll
r'355edbb4d412b01f1740c17e3f50fa00', # msvcrt.dll
r'd4502f124289a31976130cccb014c9aa', # rpcrt4.dll
r'81faefc42d0b236c62c3401558867faa', # iertutil.dll
r'e40fcf943127ddc8fd60554b722d762b', # msctf.dll
r'0da85218e92526972a821587e6a8bf8f' # imm32.dll
]
# Below are global internal variables. Do not edit these. ################
__VERSION__ = '1.8.6'
path_general_list = []
virustotal_upload = True if config['virustotal_api_key'] else False # TODO
use_virustotal = True if config['virustotal_api_key'] and has_internet else False
use_pmc = False
vt_results = {}
vt_dump = list()
debug_messages = list()
exe_cmdline = ''
time_exec = 0
time_process = 0
script_cwd = ''
debug_file = ''
##########################################################################
noriben_errors = {0: 'Normal exit',
1: 'PML file was not found',
2: 'Unable to find procmon.exe',
3: 'Unable to create output directory',
4: 'Windows is refusing execution based upon permissions',
5: 'Could not create CSV',
6: 'Could not find malware file',
7: 'Error creating CSV',
8: 'Error creating PML',
9: 'Unknown error',
10: 'Invalid arguments given'}
def get_error(code):
"""
Looks up a given code in dictionary set of errors of noriben_errors.
Arguments:
code: Integer that corresponds to a pre-set entry
Results:
string value of a error code
"""
if code in noriben_errors:
return noriben_errors[code]
return 'Unexpected Error'
def read_global_append(append_filename):
"""
Read additional global values from a specific set of filenames.
Arguments:
append_filename: Wildcard-supported file(s) from which to read filters
Result:
none
"""
global global_approvelist
for filename in glob.iglob(append_filename, recursive=True):
with codecs.open(filename, 'r', encoding='utf-8') as f:
for line in f:
if not line[0] == '#':
global_approvelist.append(line.strip())
def read_config(config_filename):
"""
Parse an external configuration file.
Arguments:
config_filename: String of filename, predetermined if exists
Result:
none
"""
global config
global use_virustotal
file_config = configparser.ConfigParser()
with codecs.open(config_filename, 'r', encoding='utf-8') as f:
file_config.read_file(f)
new_config = {}
for key, value in file_config.items('Noriben'):
try:
new_config[key] = ast.literal_eval(value)
except(ValueError, SyntaxError):
new_config[key] = value
config.update(new_config)
if config['virustotal_api_key'] and has_internet:
use_virustotal = True
def terminate_self(error):
"""
Implemented for better troubleshooting.
Arguments:
error: Int of error code to return to system parent
Result:
none
"""
print('[*] Exiting with error code: {}: {}'.format(error, get_error(error)))
if config['troubleshoot']:
errormsg = '[*] Paused for troubleshooting. Press enter to close Noriben.'
input(errormsg)
sys.exit(error)
def log_debug(msg):
"""
Logs a passed message. Results are printed and stored in
a list for later writing to the debug log.
Arguments:
msg: Text string of message.
Results:
none
"""
global debug_messages
if msg and config['debug']:
print(msg)
if debug_file: # File already set, check for message buffer
if debug_messages: # If buffer, write and erase buffer
hdbg = open(debug_file, 'a')
for item in debug_messages:
hdbg.write(item)
hdbg.close()
debug_messages = list()
else:
open(debug_file, 'a').write('{}\n'.format(msg))
else: # Output file hasn't been set yet, append to buffer
debug_messages.append(msg + '\r\n')
def generalize_vars_init():
"""
Initialize a dictionary with the local system's environment variables.
Returns via a global variable, path_general_list
"""
envvar_list = [r'%AllUsersProfile%',
r'%LocalAppData%',
r'%AppData%',
r'%CommonProgramFiles%',
r'%ProgramData%',
r'%ProgramFiles%',
r'%ProgramFiles(x86)%',
r'%Public%',
r'%Temp%',
r'%UserProfile%',
r'%WinDir%']
global path_general_list
log_debug('[*] Enabling Windows string generalization.')
for env in envvar_list:
try:
resolved = os.path.expandvars(env).replace("\\", "\\\\")
# TODO: Resolve this issue with Py3 for x86 folder.
# resolved = resolved.replace(b'(', b'\\(').replace(b')', b'\\)')
# if not resolved == env and not resolved == env.replace(b'(', b'\\(').replace(b')', b'\\)'):
path_general_list.append([env, resolved])
except TypeError:
if resolved in locals():
log_debug('[!] generalize_vars_init(): Unable to parse var: {}'.format(resolved))
continue
def generalize_var(path_string):
"""
Generalize a given string to include its environment variable
Arguments:
path_string: string value to generalize
Results:
string value of a generalized string
"""
if not len(path_general_list):
generalize_vars_init() # For edge cases when this isn't previously called.
for item in path_general_list:
path_string = re.sub(item[1], item[0], path_string)
return path_string
def read_hash_file(hash_filename):
"""
Read a given file of SHA256 hashes and add them to the hash approvelist.
Arguments:
hash_filename: path to a text file containing hashes (either flat or sha256deep)
"""
global hash_approvelist
hash_file_handle = open(hash_filename, newline='', encoding='utf-8')
reader = csv.reader(hash_file_handle)
for hash_line in reader:
hashval = hash_line[0]
try:
if int(hashval, 16) and (len(hashval) == 32 or len(hashval) == 40 or len(hashval) == 64):
hash_approvelist.append(hashval)
except (TypeError, ValueError):
pass
def virustotal_query_hash(hashval):
"""
Submit a given hash to VirusTotal to retrieve number of alerts
Arguments:
hashval: SHA256 hash to a given file
"""
global vt_results
global vt_dump
result = ''
try:
if not (int(hashval, 16) and (len(hashval) == 32 or len(hashval) == 40 or len(hashval) == 64)):
return ''
except (TypeError, ValueError):
pass
try:
previous_result = vt_results[hashval]
log_debug('[*] VT scan already performed for {}. Returning previous: {}'.format(hashval, previous_result))
return previous_result
except KeyError:
pass
vt_query_url = 'https://www.virustotal.com/vtapi/v2/file/report'
post_params = {'apikey': config['virustotal_api_key'],
'resource': hashval}
log_debug('[*] Querying VirusTotal for hash: {}'.format(hashval))
data = ''
try:
http_response = requests.post(vt_query_url, post_params)
except requests.exceptions.RequestException:
return '' # null string to append to output
if http_response.status_code == 204:
print('[!] VirusTotal Rate Limit Exceeded. Sleeping for 60 seconds.')
time.sleep(60)
return virustotal_query_hash(hashval)
else:
try:
data = http_response.json()
except ValueError:
result = 'Error'
try:
if data['response_code'] == -2:
result = ' [VT: Queued]'
elif data['response_code'] == -1:
result = ' [VT: Error 001]'
elif data['response_code'] == 0:
result = ' [VT: Not Scanned]'
elif data['response_code'] == 1:
if data['total']:
vt_dump.append(data)
result = ' [VT: {}/{}]'.format(data['positives'], data['total'])
else:
result = ' [VT: Error 002]'
except TypeError:
result = ' [VT: Error 003]'
vt_results[hashval] = result
log_debug('[*] VirusTotal result for hash {}: {}'.format(hashval, result))
return result
def yara_rule_check(yara_files):
"""
Scan a dictionary of YARA rule files to determine
which are valid for compilation.
Arguments:
yara_files: path to folder containing rules
"""
result = dict()
for yara_id in yara_files:
fname = yara_files[yara_id]
try:
yara.compile(filepath=fname)
result[yara_id] = fname
except yara.SyntaxError:
log_debug('[!] Syntax Error found in YARA file: {}'.format(fname))
log_debug(traceback.format_exc())
return result
def yara_import_rules(yara_path):
"""
Import a folder of YARA rule files
Arguments:
yara_path: path to folder containing rules
Results:
rules: a yara.Rules structure of available YARA rules
"""
yara_files = {}
if not yara_path[-1] == '\\':
yara_path += '\\'
print('[*] Loading YARA rules from folder: {}'.format(yara_path))
files = os.listdir(yara_path)
for file_name in files:
file_extension = os.path.splitext(file_name)[1]
if '.yar' in file_extension:
yara_files[file_name.split(os.sep)[-1]] = os.path.join(yara_path, file_name)
yara_files = yara_rule_check(yara_files)
rules = ''
if yara_files:
try:
rules = yara.compile(filepaths=yara_files)
print('[*] YARA rules loaded. Total files imported: %d' % (len(yara_files)))
except yara.SyntaxError:
print('[!] YARA: Unknown Syntax Errors found.')
print('[!] YARA rules disabled until all Syntax Errors are fixed.')
log_debug('[!] YARA: Unknown Syntax Errors found.')
log_debug('[!] YARA rules disabled until all Syntax Errors are fixed.')
return rules
def yara_filescan(file_path, rules):
"""
Scan a given file to see if it matches a given set of YARA rules
Arguments:
file_path: full path to a file to scan
rules: a yara.Rules structure of available YARA rules
Results:
results: a string value that's either null (no hits)
or formatted with hit results
"""
if not rules:
return ''
if os.path.isdir(file_path):
return ''
try:
matches = rules.match(file_path)
except yara.Error: # If can't open file
log_debug('[!] YARA can\'t open file: {}'.format(file_path))
return ''
if matches:
results = '\t[YARA: {}]'.format(', '.join(str(x) for x in matches))
else:
results = ''
return results
def open_file_with_assoc(fname):
"""
Opens the specified file with its associated application
Arguments:
fname: full path to a file to open
Results:
None
"""
if config['headless']:
# Headless is for automated runs, don't open results on VM
return
if os.name == 'mac':
ret = subprocess.call(('open', fname))
elif os.name == 'nt':
#os.startfile(fname)
ret = subprocess.call(('start', fname), shell=True)
elif os.name == 'posix':
ret = subprocess.call(('open', fname))
return ret
def file_exists(fname):
"""
Determine if a file exists
Arguments:
fname: path to a file
Results:
boolean value if file exists
"""
log_debug('[*] Checking for existence of file: {}'.format(fname))
return os.path.exists(fname) and os.access(fname, os.F_OK) and not os.path.isdir(fname)
def check_procmon():
"""
Finds the local path to Procmon
Results:
folder path to procmon executable
"""
log_debug('[*] Checking for procmon in the following location: {}'.format(config['procmon']))
procmon_exe = config['procmon']
if file_exists(procmon_exe):
return procmon_exe
for path in os.environ['PATH'].split(os.pathsep):
if file_exists(os.path.join(path.strip('"'), procmon_exe)):
return os.path.join(path, procmon_exe)
if file_exists(os.path.join(script_cwd, procmon_exe)):
return os.path.join(script_cwd, procmon_exe)
def hash_file(fname):
"""
Given a filename, returns the hex hash value
Arguments:
fname: path to a file
Results:
hex hash value of file's contents as a string
"""
log_debug('[*] Performing {} hash on file: {}'.format(config['hash_type'], fname))
if config['hash_type'] == 'MD5':
return hashlib.md5(codecs.open(fname, 'rb').read()).hexdigest()
elif config['hash_type'] == 'SHA1':
return hashlib.sha1(codecs.open(fname, 'rb').read()).hexdigest()
elif config['hash_type'] == 'SHA256':
return hashlib.sha256(codecs.open(fname, 'rb').read()).hexdigest()
def get_session_name():
"""
Returns current date and time stamp for file name
Results:
string value of a current timestamp to apply to log file names
"""
return datetime.datetime.now().strftime('%d_%b_%y__%H_%M_%f')
def protocol_replace(text):
"""
Replaces text name resolutions from domain names
Arguments:
text: string of domain with resolved port name
Results:
string value with resolved port name in decimal format
"""
replacements = [(':https', ':443'),
(':http', ':80'),
(':domain', ':53')]
for find, replace in replacements:
text = text.replace(find, replace)
return text
def approvelist_scan(approvelist, data):
"""
Given a approvelist and data string, see if data is in approvelist
Arguments:
approvelist: list of items to ignore
data: string value to compare against approvelist
Results:
boolean value of if item exists in approvelist
"""
for event in data:
for bad in approvelist + global_approvelist:
bad = os.path.expandvars(bad).replace('\\', '\\\\')
try:
if re.search(bad, event, flags=re.IGNORECASE):
return True
except re.error:
log_debug('[!] Error found while processing filters.\r\nFilter:\t{}\r\nEvent:\t{}'.format(bad, event))
log_debug(traceback.format_exc())
return False
return False
def process_pml_to_csv(procmonexe, pml_file, pmc_file, csv_file):
"""
Uses Procmon to convert the PML to a CSV file
Arguments:
procmonexe: path to Procmon executable
pml_file: path to Procmon PML output file
pmc_file: path to PMC filter file
csv_file: path to output CSV file
Results:
None
"""
global time_process
time_convert_start = time.time()
log_debug('[*] Converting session to CSV: {}'.format(csv_file))
if not file_exists(pml_file):
print('[!] Error detected. PML file was not found: {}'.format(pml_file))
terminate_self(1)
cmdline = '"{}" /OpenLog "{}" /SaveApplyFilter /saveas "{}"'.format(procmonexe, pml_file, csv_file)
if use_pmc and file_exists(pmc_file):
cmdline += ' /LoadConfig "{}"'.format(pmc_file)
log_debug('[*] Running cmdline: {}'.format(cmdline))
stdnull = subprocess.Popen(cmdline)
stdnull.wait()
time_convert_end = time.time()
time_process = time_convert_end - time_convert_start
def launch_procmon_capture(procmonexe, pml_file, pmc_file):
"""
Launch Procmon to begin capturing data
Arguments:
procmonexe: path to Procmon executable
pml_file: path to Procmon PML output file
pmc_file: path to PMC filter file
Results:
None
"""
global time_exec
time_exec = time.time()
cmdline = '"{}" /BackingFile "{}" /Quiet /Minimized'.format(procmonexe, pml_file)
if use_pmc and file_exists(pmc_file):
cmdline += ' /LoadConfig "{}"'.format(pmc_file)
log_debug('[*] Running cmdline: {}'.format(cmdline))
subprocess.Popen(cmdline)
time.sleep(3)
def terminate_procmon(procmonexe):
"""
Terminate Procmon cleanly
Arguments:
procmonexe: path to Procmon executable
Results:
None
"""
global time_exec
time_exec = time.time() - time_exec
cmdline = '"{}" /Terminate'.format(procmonexe)
log_debug('[*] Running cmdline: {}'.format(cmdline))
stdnull = subprocess.Popen(cmdline)
stdnull.wait()
def parse_csv(csv_file, report, timeline):
"""
Given the location of CSV and TXT files, parse the CSV for notable items
Arguments:
csv_file: path to csv output to parse
report: OUT string text containing the entirety of the text report
timeline: OUT string text containing the entirety of the CSV report
"""
log_debug('[*] Processing CSV: {}'.format(csv_file))
process_output = list()
file_output = list()
reg_output = list()
net_output = list()
error_output = list()
remote_servers = list()
if config['yara_folder'] and has_yara:
yara_rules = yara_import_rules(config['yara_folder'])