├── .gitignore
├── IOCs
├── generic
│ ├── 10d8f887-b625-426f-b134-8147a780c369_UAC_sdb.ioc
│ ├── 26f643d6-6af9-4691-bfc3-f1823d4e9047_code_injection_hook.ioc
│ ├── 2823537b-8c9a-454a-8bf4-3aa5ef76ec54_information-stealing_malware.ioc
│ ├── 2b5527f3-e5c4-4f0b-b9fc-bcd2221c313c_PIC_PEB.ioc
│ ├── 4219a887-d10f-499f-a028-5c459b9c83d5_code_injection_API.ioc
│ ├── 710ec573-0b07-40a0-94b6-912af3272b08_LateralMovement_process.ioc
│ ├── 7382c170-7e66-4d72-808e-5f703f39a38d_unusual_path.ioc
│ ├── 7cf5ca41-5e20-4ff0-8fa4-23510b04485a_PIC.ioc
│ ├── 840ae4e7-41eb-4132-a5fe-48c910d99b96_ntfsEA_driver.ioc
│ ├── a50223b5-b213-43e9-beac-dfe9c1ca240c_rogue_svchost.ioc
│ ├── b28d0314-ca44-45da-97e6-be540a92d929_hollowing.ioc
│ ├── b61f88d5-9453-469b-94cd-c5ef59c972db_ntfsEA_proc.ioc
│ ├── b78501b8-9aca-4eda-857f-cc409e269259_LateralMovement_file_reg.ioc
│ ├── c02075e0-c6a4-4f4b-9ad1-0a8ca9232db3_inline_api_hooks_uknown.ioc
│ ├── c7121f8f-8401-4f92-bb02-2be6bb48c3b4_code_injection_pattern.ioc
│ ├── cdcd5fdb-fcd3-4947-8c76-d2fbdc1b5f82_UAC_COM.ioc
│ ├── e2bd07db-dbfd-45f8-a81d-24314516d0c6_equation_driver_generic.ioc
│ ├── e5f73cf8-55ed-463f-81ec-70ffaf81ade9_lsass_checks.ioc
│ └── e747cd9d-2ed5-41fe-9e6a-64b49680eeca_unusual_path_shimcache.ioc
└── specific
│ ├── ec7eed9a-d266-4443-9333-0234cca0f682_equation_proc.ioc
│ └── fb4064f7-8fcd-4a81-9584-cd874c365d12_equation_driver.ioc
├── LICENSE
├── PyIOCe_templates
├── indicator_terms.volatility
└── parameters.volatility
├── README.md
└── openioc_scan.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 |
3 |
--------------------------------------------------------------------------------
/IOCs/generic/10d8f887-b625-426f-b134-8147a780c369_UAC_sdb.ioc:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | UAC pop-up bypass (COM)
5 | push 10840014h ; (FOF_NOCONFIRMATION|FOF_SILENT|FOFX_SHOWELEVATIONPROMPT|FOFX_NOCOPYHOOKS|FOFX_REQUIREELEVATION|FOF_NOERRORUI)
6 | Takahiro Haruyama
7 | 2015-02-26T06:10:23
8 |
9 |
10 |
11 |
12 |
13 | \x68\x14\x00\x84\x10
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/IOCs/generic/26f643d6-6af9-4691-bfc3-f1823d4e9047_code_injection_hook.ioc:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | process code injection (based on unknown hook)
5 |
6 | Takahiro Haruyama
7 | 2015-02-26T03:23:58
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | true
16 |
17 |
18 |
19 | unknown
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/IOCs/generic/2823537b-8c9a-454a-8bf4-3aa5ef76ec54_information-stealing_malware.ioc:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Information-Stealing Malware
5 | Information-Stealing Malware (e.g., ZeuS, SpyEye, Citadel, Andromeda).
6 | Takahiro Haruyama
7 | 2014-09-01T08:21:19
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | HttpSendRequestA
16 |
17 |
18 |
19 | HttpSendRequestW
20 |
21 |
22 |
23 | HttpSendRequestExA
24 |
25 |
26 |
27 | HttpSendRequestExW
28 |
29 | unknown
30 |
31 |
32 | 4040404020
33 |
34 |
--------------------------------------------------------------------------------
/IOCs/generic/2b5527f3-e5c4-4f0b-b9fc-bcd2221c313c_PIC_PEB.ioc:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | position independent code (PEB)
5 | This indicator focuses on 32-bit malware only
6 | Takahiro Haruyama
7 | 2014-11-21T10:12:37
8 |
9 |
10 |
11 |
12 |
13 | \x64\xA1\x30\x00\x00\x00\x8B\x40\x0C
14 |
15 |
16 |
17 | \x64\x8B.\x30\x8B.\x0C\x8B
18 |
19 |
20 |
21 | getting PEB #1PEB#2PEB#1getPCror13AddHash32rol13AddHash32poisonIvyHashrol7AddHash32rol5AddHash32rol3XorEaxrol3XorEax2ror7AddHash32ror9AddHash32ror11AddHash32ror13AddHash32Sub1shl7shr19Hash32sll1AddHash32msfHash32
22 |
23 |
--------------------------------------------------------------------------------
/IOCs/generic/4219a887-d10f-499f-a028-5c459b9c83d5_code_injection_API.ioc:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | process code injection (based on API)
5 |
6 | Takahiro Haruyama
7 | 2014-11-18T10:05:21
8 |
9 |
10 |
11 |
12 |
13 | trueSeDebugPrivilege
14 |
15 |
16 | VirtualAllocEx
17 |
18 |
19 |
20 | AllocateVirtualMemory
21 |
22 |
23 |
24 | VirtualProtectEx
25 |
26 |
27 |
28 | ProtectVirtualMemory
29 |
30 |
31 |
32 | CreateProcess
33 |
34 |
35 |
36 | LoadLibrary
37 |
38 |
39 |
40 | LdrLoadDll
41 |
42 |
43 |
44 |
45 | CreateToolhelp32Snapshot
46 |
47 |
48 |
49 | QuerySystemInformation
50 |
51 |
52 |
53 | EnumProcesses
54 |
55 |
56 |
57 | WriteProcessMemory
58 |
59 |
60 |
61 | WriteVirtualMemory
62 |
63 |
64 |
65 | CreateRemoteThread
66 |
67 |
68 |
69 | ResumeThread
70 |
71 |
72 |
73 | SetThreadContext
74 |
75 |
76 |
77 | SetContextThread
78 |
79 |
80 |
81 | QueueUserAPC
82 |
83 |
84 |
85 | QueueApcThread
86 |
87 |
88 |
89 |
90 | 2525252525101025101025251010
91 |
92 |
--------------------------------------------------------------------------------
/IOCs/generic/710ec573-0b07-40a0-94b6-912af3272b08_LateralMovement_process.ioc:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | lateral movement (process)
5 | Thanks to Junichi Hatta and Tim Slaybaugh
6 |
7 |
8 |
9 | Takahiro Haruyama
10 | 2014-02-17T09:41:39
11 |
12 |
13 |
14 |
15 | psexesvc
16 |
17 | software\sysinternals\psexec
18 |
19 |
20 |
21 | software\sysinternals\sdelete
22 |
23 |
24 |
25 | \mimilib.dll
26 |
27 |
28 |
29 | \sekurlsa.dll
30 |
31 |
32 |
33 | \wceaux.dll
34 |
35 | WCEServicePipe
36 |
37 | \iamdll.dll
38 |
39 | SamLookupDomainInSamServerNlpGetPrimaryCredentialLsaEnumerateLogonSessionsSamOpenDomainSamOpenUserSamGetPrivateDataSamQueryInformationUserSamConnectSamRidToSidPowerCreateRequestSeDebugPrivilege^..?\.exe
40 |
41 | onononon202020202020202020202020
42 |
43 |
--------------------------------------------------------------------------------
/IOCs/generic/7382c170-7e66-4d72-808e-5f703f39a38d_unusual_path.ioc:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | suspicious paths (running)
5 | based on Iron man method: http://journeyintoir.blogspot.jp/2013/07/finding-malware-like-iron-man-slide.html
6 | 2014-07-21T14:11:00Z
7 |
8 | Takahiro Haruyama
9 |
10 |
11 |
12 |
13 |
14 | \ProgramData
15 |
16 |
17 |
18 | \$Recycle.Bin
19 |
20 |
21 |
22 | \Windows\Temp
23 |
24 |
25 |
26 | \Users\All Users
27 |
28 |
29 |
30 | \Users\Default
31 |
32 |
33 |
34 | \Users\Public
35 |
36 |
37 |
38 | \\Users\\.*\\AppData
39 |
40 |
41 |
42 |
43 |
44 | \ProgramData
45 |
46 |
47 |
48 | \$Recycle.Bin
49 |
50 |
51 |
52 | \Windows\Temp
53 |
54 |
55 |
56 | \Users\All Users
57 |
58 |
59 |
60 | \Users\Default
61 |
62 |
63 |
64 | \Users\Public
65 |
66 |
67 |
68 | \\Users\\.*\\AppData
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | onononononononononononononon
78 |
79 |
--------------------------------------------------------------------------------
/IOCs/generic/7cf5ca41-5e20-4ff0-8fa4-23510b04485a_PIC.ioc:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | position independent code (PEB, get PC, API hash)
5 | This indicator focuses on 32-bit malware only
6 | Takahiro Haruyama
7 | 2014-11-20T07:29:01
8 |
9 |
10 |
11 | true
12 |
13 | \xE8\x00\x00\x00\x00[\x5D\x5E\x58]
14 |
15 |
16 |
17 | \xA5\x17\x00\x7C
18 |
19 |
20 |
21 | \x9B\x3B\xCA\xCA
22 |
23 |
24 |
25 | \xDF\x2D\x89\x8C
26 |
27 |
28 |
29 | \xE6\x16\xFF\xD2
30 |
31 |
32 |
33 | \x06\xE5\xB0\xCF
34 |
35 |
36 |
37 | \xA2\x52\x48\x44
38 |
39 |
40 |
41 | \x7A\x2A\xC6\x38
42 |
43 |
44 |
45 | \x93\x32\xE4\x94
46 |
47 |
48 |
49 | \xE7\xEE\xFD\xFA
50 |
51 |
52 |
53 | \xA0\xDF\x16\xD5
54 |
55 |
56 |
57 | \xA4\x17\x00\x7C
58 |
59 |
60 |
61 | \x35\x61\xAE\xA3
62 |
63 |
64 |
65 | \x46\x77\x06\x00
66 |
67 |
68 |
69 | \xDA\xF6\xDA\x4F
70 |
71 |
72 |
73 | \x64\xA1\x30\x00\x00\x00\x8B\x40\x0C
74 |
75 |
76 |
77 | \x64\x8B.\x30\x8B.\x0C\x8B
78 |
79 |
80 |
81 | getting PEB #1PEB#2PEB#1getPCror13AddHash32rol13AddHash32poisonIvyHashrol7AddHash32rol5AddHash32rol3XorEaxrol3XorEax2ror7AddHash32ror9AddHash32ror11AddHash32ror13AddHash32Sub1shl7shr19Hash32sll1AddHash32msfHash32
82 |
83 |
--------------------------------------------------------------------------------
/IOCs/generic/840ae4e7-41eb-4132-a5fe-48c910d99b96_ntfsEA_driver.ioc:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | using NTFS $EA (driver)
5 |
6 | Takahiro Haruyama
7 | 2014-11-28T06:00:36
8 |
9 |
10 |
11 |
12 |
13 |
14 | QueryEaFile
15 |
16 |
17 |
18 | SetEaFile
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/IOCs/generic/a50223b5-b213-43e9-beac-dfe9c1ca240c_rogue_svchost.ioc:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | rogue svchost
5 | 2014-08-06T02:11:39Z
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | services.exe
14 |
15 |
16 |
17 | svchost.exe
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/IOCs/generic/b28d0314-ca44-45da-97e6-be540a92d929_hollowing.ioc:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Process/module hollowing
5 | Check with ldrmodules for empty paths.
6 | Francesco "dfirfpi" Picasso
7 | 2015-02-26T14:04:20
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/IOCs/generic/b61f88d5-9453-469b-94cd-c5ef59c972db_ntfsEA_proc.ioc:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | using NTFS $EA (process)
5 |
6 | Takahiro Haruyama
7 | 2014-11-28T05:58:45
8 |
9 |
10 |
11 |
12 |
13 |
14 | QueryEaFile
15 |
16 |
17 |
18 | SetEaFile
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/IOCs/generic/b78501b8-9aca-4eda-857f-cc409e269259_LateralMovement_file_reg.ioc:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | lateral movement (file/registry)
5 | Thanks to Junichi Hatta and Tim Slaybaugh
6 |
7 |
8 |
9 | Takahiro Haruyama
10 | 2014-11-14T10:39:21
11 |
12 |
13 |
14 |
15 |
16 |
17 | Windows\\Tasks\\At\d\.job
18 |
19 | psexesvc.exe^..?\.exe\-.*\.pf$
20 |
21 | \\..?\.exe$
22 |
23 |
24 |
25 | \mimilib.dll
26 |
27 | mimilib.dllmimikatz.sys\sekurlsa.dllsekurlsa.dll
28 |
29 | \wceaux.dll
30 |
31 | wceaux.dllcredentials.txtwce_krbtkts
32 |
33 | \iamdll.dll
34 |
35 | iamdll.dll
36 |
37 | onononon2020202020202020202020
38 |
39 |
--------------------------------------------------------------------------------
/IOCs/generic/c02075e0-c6a4-4f4b-9ad1-0a8ca9232db3_inline_api_hooks_uknown.ioc:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Inline API hook uknown module
5 | Inline API hooking with hooks inside <unknown> modules
6 | Francesco "dfirfpi" Picasso
7 | 2015-02-13T13:45:09
8 |
9 |
10 |
11 | <unknown>
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/IOCs/generic/c7121f8f-8401-4f92-bb02-2be6bb48c3b4_code_injection_pattern.ioc:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | process code injection (based on hex pattern)
5 |
6 | Takahiro Haruyama
7 | 2015-02-26T03:46:35
8 |
9 |
10 |
11 |
12 |
13 |
14 | \x64\xA1\x30\x00\x00\x00\x8B\x40\x0C
15 |
16 |
17 |
18 | \x64\x8B.\x30\x8B.\x0C\x8B
19 |
20 |
21 |
22 | \xE8\x00\x00\x00\x00[\x5D\x5E\x58]
23 |
24 |
25 |
26 | \x4d\x5a
27 |
28 |
29 |
30 | \x50\x45
31 |
32 |
33 |
34 | \xff[\x56\x96]
35 |
36 |
37 |
38 |
39 |
40 | MZ signature
41 |
42 |
43 | PIC code (call [ESI+?])
44 |
45 | PIC code(PEB#1)PIC code(PEB#2)PIC code(getPC)PE signature
46 |
47 |
--------------------------------------------------------------------------------
/IOCs/generic/cdcd5fdb-fcd3-4947-8c76-d2fbdc1b5f82_UAC_COM.ioc:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | UAC pop-up bypass (sdb)
5 | http://blog.jpcert.or.jp/2015/02/a-new-uac-bypass-method-that-dridex-uses.html
6 |
7 |
8 | Takahiro Haruyama
9 | 2014-06-10T06:32:32
10 |
11 |
12 |
13 |
14 | AllocateAndInitializeSidEqualSidRtlQueryElevationFlagsGetTokenInformationGetSidSubAuthorityGetSidSubAuthorityCountsdbinst.exeRedirectEXEsmss.exe
15 |
16 | 151515151515202015on
17 |
18 |
--------------------------------------------------------------------------------
/IOCs/generic/e2bd07db-dbfd-45f8-a81d-24314516d0c6_equation_driver_generic.ioc:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | EquationDrug HDD/SSD firmware operation (kernel,generic)
5 | nls_933w.dll/win32m.sys
6 | https://securelist.com/blog/research/69203/inside-the-equationdrug-espionage-platform/
7 | Takahiro Haruyama
8 | 2015-05-07T10:20:58
9 |
10 |
11 |
12 |
13 | WRITE_PORT_ULONGWRITE_PORT_USHORTWRITE_PORT_BUFFER_USHORTWRITE_PORT_UCHARWRITE_REGISTER_UCHARWRITE_REGISTER_BUFFER_USHORTWRITE_REGISTER_ULONGWRITE_REGISTER_USHORTPsCreateSystemThreadKeInsertQueueDpcKeRaiseIrqlToDpcLeveltrue
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/IOCs/generic/e5f73cf8-55ed-463f-81ec-70ffaf81ade9_lsass_checks.ioc:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | LSASS check
5 | [v] ImagePath: %SystemRoot%\System32\lsass.exe (defaulting %SystemRoot% to "C:\Windows")
6 | [v] Parent Process: wininit.exe or winlogon.exe (xp)
7 | [~] Check for similar names (just a try...)
8 |
9 | Francesco "dfirfpi" Picasso
10 | 2015-02-12T15:57:37
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | lsass.exe
19 |
20 |
21 |
22 |
23 |
24 | winlogon.exe
25 |
26 |
27 |
28 | wininit.exe
29 |
30 |
31 |
32 |
33 | c:\windows\system32\lsass.exe
34 |
35 |
36 |
37 |
38 |
39 |
40 | lsa
41 |
42 |
43 |
44 | lsass.exe
45 |
46 |
47 |
48 |
49 |
50 |
51 | 100
52 |
53 |
54 | 50
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/IOCs/generic/e747cd9d-2ed5-41fe-9e6a-64b49680eeca_unusual_path_shimcache.ioc:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | suspicious paths (executed)
5 | suspicious executable path extracted from ShimCache
6 | Takahiro Haruyama
7 | 2014-09-26T10:10:08Z
8 |
9 |
10 |
11 |
12 |
13 |
14 | \ProgramData
15 |
16 |
17 |
18 | \$Recycle.Bin
19 |
20 |
21 |
22 | \Windows\Temp
23 |
24 |
25 |
26 | \Users\All Users
27 |
28 |
29 |
30 | \Users\Default
31 |
32 |
33 |
34 | \Users\Public
35 |
36 |
37 |
38 | \\Users\\.*\\AppData
39 |
40 |
41 |
42 |
43 |
44 | on
45 |
46 |
47 | on
48 |
49 | ononononon
50 |
51 |
--------------------------------------------------------------------------------
/IOCs/specific/ec7eed9a-d266-4443-9333-0234cca0f682_equation_proc.ioc:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | EquationDrug HDD/SSD firmware operation (process)
5 | nls_933w.dll/win32m.sys
6 | https://securelist.com/blog/research/69203/inside-the-equationdrug-espionage-platform/
7 | Takahiro Haruyama
8 | 2015-04-27T11:01:32
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | \xc0\x21\x00\x87
17 |
18 |
19 |
20 | \xc4\x21\x00\x87
21 |
22 |
23 |
24 | \xc8\x21\x00\x87
25 |
26 |
27 |
28 | \xcc\x21\x00\x87
29 |
30 |
31 |
32 | \xd0\x21\x00\x87
33 |
34 |
35 |
36 | \xd4\x21\x00\x87
37 |
38 |
39 |
40 |
41 |
42 | \x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x07\xec
43 |
44 |
45 |
46 | \x00\x00\x00\x00\x01\x57\x00\x00\x00\x00\x02\x44\x00\x00\x00\x00\x03\x43\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x06\xa0\x00\x00\x00\x00\x07\x8a
47 |
48 |
49 |
50 | \x00\x00\x00\x00\x01\x57\x00\x00\x00\x00\x02\x44\x00\x00\x00\x00\x03\x43\x00\x00\x00\x00\x04\x0e\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x06\xa0\x00\x00\x00\x00\x07\x91
51 |
52 |
53 |
54 |
55 | IoControlCodeATA data sequence
56 |
57 |
--------------------------------------------------------------------------------
/IOCs/specific/fb4064f7-8fcd-4a81-9584-cd874c365d12_equation_driver.ioc:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | EquationDrug HDD/SSD firmware operation (kernel)
5 | nls_933w.dll/win32m.sys
6 | https://securelist.com/blog/research/69203/inside-the-equationdrug-espionage-platform/
7 | Takahiro Haruyama
8 | 2015-04-28T06:33:48
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | \xc0\x21\x00\x87
17 |
18 |
19 |
20 | \xc4\x21\x00\x87
21 |
22 |
23 |
24 | \xc8\x21\x00\x87
25 |
26 |
27 |
28 | \xcc\x21\x00\x87
29 |
30 |
31 |
32 | \xd0\x21\x00\x87
33 |
34 |
35 |
36 | \xd4\x21\x00\x87
37 |
38 |
39 |
40 |
41 | \x8A\x53\xFF\x80\xFA\xFF\x75\x1D\x8D\x43\xFB\x39\x08\x75\x32\xF6\x03\x04
42 |
43 |
44 |
45 | IoControlCodecode parsing ATA data seq
46 |
47 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 | {description}
294 | Copyright (C) {year} {fullname}
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License along
307 | with this program; if not, write to the Free Software Foundation, Inc.,
308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309 |
310 | Also add information on how to contact you by electronic and paper mail.
311 |
312 | If the program is interactive, make it output a short notice like this
313 | when it starts in an interactive mode:
314 |
315 | Gnomovision version 69, Copyright (C) year name of author
316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 | This is free software, and you are welcome to redistribute it
318 | under certain conditions; type `show c' for details.
319 |
320 | The hypothetical commands `show w' and `show c' should show the appropriate
321 | parts of the General Public License. Of course, the commands you use may
322 | be called something other than `show w' and `show c'; they could even be
323 | mouse-clicks or menu items--whatever suits your program.
324 |
325 | You should also get your employer (if you work as a programmer) or your
326 | school, if any, to sign a "copyright disclaimer" for the program, if
327 | necessary. Here is a sample; alter the names:
328 |
329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
331 |
332 | {signature of Ty Coon}, 1 April 1989
333 | Ty Coon, President of Vice
334 |
335 | This General Public License does not permit incorporating your program into
336 | proprietary programs. If your program is a subroutine library, you may
337 | consider it more useful to permit linking proprietary applications with the
338 | library. If this is what you want to do, use the GNU Lesser General
339 | Public License instead of this License.
340 |
341 |
--------------------------------------------------------------------------------
/PyIOCe_templates/indicator_terms.volatility:
--------------------------------------------------------------------------------
1 | {"volatility": {"FileItem/FileExtension": {"last_modified": "2014-11-14T03:05:26", "context_doc": "FileItem", "content_type": "string"}, "DriverItem/TimerRoutineIncluded": {"last_modified": "2014-09-30T08:30:45", "context_doc": "DriverItem", "content_type": "string"}, "ServiceItem/cmdLine": {"last_modified": "2014-09-30T08:34:17", "context_doc": "ServiceItem", "content_type": "string"}, "ProcessItem/cmdLine": {"last_modified": "2015-02-18T06:53:21", "context_doc": "ProcessItem", "content_type": "string"}, "ProcessItem/Hooked/API/HookingModuleName": {"last_modified": "2015-02-24T09:40:49", "context_doc": "ProcessItem", "content_type": "string"}, "RegistryItem/ShimCache/ExecutablePath": {"last_modified": "2014-09-30T08:33:39", "context_doc": "RegistryItem", "content_type": "string"}, "DriverItem/StringList/string": {"last_modified": "2014-09-30T08:30:41", "context_doc": "DriverItem", "content_type": "string"}, "FileItem/FullPath": {"last_modified": "2014-11-14T03:05:50", "context_doc": "FileItem", "content_type": "string"}, "ProcessItem/PortList/PortItem/localIP": {"last_modified": "2014-09-30T08:31:44", "context_doc": "ProcessItem", "content_type": "string"}, "FileItem/SizeInBytes": {"last_modified": "2014-11-14T03:07:09", "context_doc": "FileItem", "content_type": "int"}, "ProcessItem/ParentProcessName": {"last_modified": "2014-09-30T08:31:38", "context_doc": "ProcessItem", "content_type": "string"}, "ProcessItem/hidden": {"last_modified": "2014-09-30T08:32:36", "context_doc": "ProcessItem", "content_type": "string"}, "HookItem/SSDT/HookedFunctionName": {"last_modified": "2014-09-30T08:30:55", "context_doc": "HookItem", "content_type": "string"}, "FileItem/FileName": {"last_modified": "2014-11-14T03:05:42", "context_doc": "FileItem", "content_type": "string"}, "ProcessItem/PortList/PortItem/localPort": {"last_modified": "2014-09-30T08:31:51", "context_doc": "ProcessItem", "content_type": "int"}, "RegistryItem/Path": {"last_modified": "2014-09-30T08:33:08", "context_doc": "RegistryItem", "content_type": "string"}, "ProcessItem/SectionList/MemorySection/PEInfo/ImportedModules/Module/ImportedFunctions/string": {"last_modified": "2014-09-30T08:32:15", "context_doc": "ProcessItem", "content_type": "string"}, "FileItem/INode": {"last_modified": "2014-11-14T03:05:58", "context_doc": "FileItem", "content_type": "int"}, "ProcessItem/StringList/string": {"last_modified": "2014-09-30T08:32:22", "context_doc": "ProcessItem", "content_type": "string"}, "ProcessItem/PortList/PortItem/remoteIP": {"last_modified": "2014-09-30T08:31:56", "context_doc": "ProcessItem", "content_type": "string"}, "DriverItem/PEInfo/ImportedModules/Module/ImportedFunctions/string": {"last_modified": "2014-09-30T08:30:38", "context_doc": "DriverItem", "content_type": "string"}, "ProcessItem/EnabledPrivilege/Name": {"last_modified": "2014-09-30T08:31:10", "context_doc": "ProcessItem", "content_type": "string"}, "ProcessItem/Hooked/API/FunctionName": {"last_modified": "2014-09-30T08:31:33", "context_doc": "ProcessItem", "content_type": "string"}, "ProcessItem/HandleList/Handle/Type": {"last_modified": "2014-09-30T08:31:22", "context_doc": "ProcessItem", "content_type": "string"}, "ServiceItem/descriptiveName": {"last_modified": "2014-09-30T08:34:23", "context_doc": "ServiceItem", "content_type": "string"}, "ProcessItem/DllHidden": {"last_modified": "2015-02-24T07:46:59", "context_doc": "ProcessItem", "content_type": "string"}, "ProcessItem/PortList/PortItem/remotePort": {"last_modified": "2014-09-30T08:32:02", "context_doc": "ProcessItem", "content_type": "int"}, "ProcessItem/SectionList/MemorySection/InjectedHexPattern": {"last_modified": "2015-02-26T03:51:12", "context_doc": "ProcessItem", "content_type": "string"}, "ServiceItem/name": {"last_modified": "2014-09-30T08:34:29", "context_doc": "ServiceItem", "content_type": "string"}, "DriverItem/IRP/HookingModuleName": {"last_modified": "2014-09-30T08:30:35", "context_doc": "DriverItem", "content_type": "string"}, "DriverItem/DriverName": {"last_modified": "2014-09-30T08:30:32", "context_doc": "DriverItem", "content_type": "string"}, "ProcessItem/HandleList/Handle/Name": {"last_modified": "2014-09-30T08:31:15", "context_doc": "ProcessItem", "content_type": "string"}, "ProcessItem/DllPath": {"last_modified": "2014-09-30T08:31:06", "context_doc": "ProcessItem", "content_type": "string"}, "ProcessItem/SectionList/MemorySection/Injected": {"last_modified": "2014-09-30T08:32:07", "context_doc": "ProcessItem", "content_type": "string"}, "ProcessItem/name": {"last_modified": "2014-09-30T08:32:50", "context_doc": "ProcessItem", "content_type": "string"}, "DriverItem/CallbackRoutine/Type": {"last_modified": "2014-09-30T08:30:29", "context_doc": "DriverItem", "content_type": "string"}}}
--------------------------------------------------------------------------------
/PyIOCe_templates/parameters.volatility:
--------------------------------------------------------------------------------
1 | {"volatility": {"note": {"last_modified": "2014-11-14T09:08:21", "value_type": "string"}, "score": {"last_modified": "2014-11-14T09:08:25", "value_type": "string"}, "detail": {"last_modified": "2014-10-23T10:04:27", "value_type": "string"}}}
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # openioc_scan
2 | openioc_scan Volatility Framework plugin
3 |
4 | ## Usage
5 | Check [my blog](http://takahiroharuyama.github.io/blog/categories/openioc-scan/).
6 |
7 | ## Publication
8 | ["Fast and Generic Malware Triage Using openioc_scan Volatility Plugin"](http://www.dfrws.org/2015eu/proceedings/DFRWS-EU-2015-short-presentation-2.pdf) at Digital Forensics Research Conference Europe 2015
9 |
10 | ["openioc_scan - IOC scanner for memory forensics"](http://www.slideshare.net/takahiroharuyama5/openiocscan-ioc-scanner-for-memory-forensics) at SECURE 2015
11 |
12 | ## License
13 | GNU GPLv2
14 |
--------------------------------------------------------------------------------
/openioc_scan.py:
--------------------------------------------------------------------------------
1 | # openioc_scan Volatility plugin
2 | # based on ioc_writer (https://github.com/mandiant/ioc_writer) and pyioc (https://github.com/jeffbryner/pyioc)
3 | # Copyright (c) 2014 Takahiro Haruyama (@cci_forensics)
4 | # http://takahiroharuyama.github.io/
5 |
6 | import volatility.utils as utils
7 | import volatility.obj as obj
8 | import volatility.debug as debug
9 | import volatility.constants as constants
10 | import volatility.commands as commands
11 |
12 | import volatility.plugins.common as common
13 | import volatility.plugins.netscan as netscan
14 | import volatility.plugins.overlays.windows.tcpip_vtypes as tcpip_vtypes
15 | import volatility.plugins.registry.hivelist as hivelist
16 | import volatility.plugins.registry.shimcache as shimcache
17 | import volatility.plugins.taskmods as taskmods
18 | import volatility.plugins.modules as modules
19 | import volatility.plugins.modscan as modscan
20 | import volatility.plugins.filescan as filescan
21 | import volatility.plugins.privileges as privileges
22 | import volatility.plugins.ssdt as ssdt
23 | import volatility.plugins.mftparser as mftparser
24 | import volatility.plugins.malware.malfind as malfind
25 | import volatility.plugins.malware.impscan as impscan
26 | import volatility.plugins.malware.psxview as psxview
27 | import volatility.plugins.malware.svcscan as svcscan
28 | import volatility.plugins.malware.apihooks as apihooks
29 | import volatility.plugins.malware.devicetree as devicetree
30 | import volatility.plugins.malware.callbacks as callbacks
31 | import volatility.plugins.malware.timers as timers
32 |
33 | import volatility.win32 as win32
34 | import volatility.win32.hive as hivemod
35 | import volatility.win32.rawreg as rawreg
36 | import volatility.win32.tasks as tasks
37 |
38 | import glob, os, re, sqlite3, urllib, socket, time
39 | from lxml import etree as et
40 | from ioc_writer import ioc_api
41 | import colorama
42 | colorama.init()
43 |
44 | g_version = '2015/02/24'
45 | g_cache_path = ''
46 | g_detail_on = False
47 | g_color_term = colorama.Fore.MAGENTA
48 | g_color_detail = colorama.Fore.CYAN
49 | g_sus_path_p = re.compile(r'\\ProgramData|\\\$Recycle\.Bin|\\Windows\\Temp|\\Users\\All Users|\\Users\\Default|\\Users\\Public|\\Users\\.*\\AppData', re.IGNORECASE)
50 | READ_BLOCKSIZE = 1024 * 1024 * 10
51 | SCORE_THRESHOLD = 100
52 |
53 | # copied from netscan
54 | AF_INET = 2
55 | AF_INET6 = 0x17
56 | inaddr_any = utils.inet_ntop(socket.AF_INET, '\0' * 4)
57 | inaddr6_any = utils.inet_ntop(socket.AF_INET6, '\0' * 16)
58 |
59 | if constants.VERSION < 2.4:
60 | # copied from malfind
61 | class MalwareObjectClases(obj.ProfileModification):
62 | before = ['WindowsObjectClasses']
63 | conditions = {'os': lambda x: x == 'windows'}
64 | def modification(self, profile):
65 | profile.object_classes.update({
66 | '_EPROCESS': malfind.MalwareEPROCESS,
67 | })
68 |
69 | # copied from apihooks
70 | # hook modes
71 | HOOK_MODE_USER = 1
72 | HOOK_MODE_KERNEL = 2
73 | # hook types
74 | HOOKTYPE_IAT = 4
75 | HOOKTYPE_EAT = 8
76 | HOOKTYPE_INLINE = 16
77 | HOOKTYPE_NT_SYSCALL = 32
78 | HOOKTYPE_CODEPAGE_KERNEL = 64
79 | HOOKTYPE_IDT = 128
80 | HOOKTYPE_IRP = 256
81 | HOOKTYPE_WINSOCK = 512
82 | # names for hook types
83 | hook_type_strings = apihooks.hook_type_strings
84 | WINSOCK_TABLE = apihooks.WINSOCK_TABLE
85 |
86 | # copied from devicetree
87 | MAJOR_FUNCTIONS = devicetree.MAJOR_FUNCTIONS
88 |
89 | # copied from privileges
90 | PRIVILEGE_INFO = privileges.PRIVILEGE_INFO
91 |
92 | class Timer(object):
93 | def __init__(self, verbose=False):
94 | self.verbose = verbose
95 |
96 | def __enter__(self):
97 | self.start = time.time()
98 | return self
99 |
100 | def __exit__(self, *args):
101 | self.end = time.time()
102 | self.secs = self.end - self.start
103 | self.msecs = self.secs * 1000 # millisecs
104 | if self.verbose:
105 | print 'elapsed time: %f ms' % self.msecs
106 |
107 | class ItemUtil:
108 | def is_condition_bool(self, condition):
109 | supported_conditions = ['is', 'contains']
110 | if condition in supported_conditions:
111 | return True
112 | else:
113 | return False
114 |
115 | def is_condition_string(self, condition):
116 | supported_conditions = ['is', 'contains', 'matches', 'starts-with', 'ends-with']
117 | if condition in supported_conditions:
118 | return True
119 | else:
120 | return False
121 |
122 | def is_condition_integer(self, condition):
123 | supported_conditions = ['is', 'greater-than', 'less-than']
124 | if condition in supported_conditions:
125 | return True
126 | else:
127 | return False
128 |
129 | def make_regex(self, content, preserve_case):
130 | if preserve_case == 'true':
131 | pattern = re.compile(content, re.DOTALL)
132 | else:
133 | pattern = re.compile(content, re.DOTALL | re.IGNORECASE)
134 | return pattern
135 |
136 | def check_string(self, target, content, condition, preserve_case):
137 | #out = colorama.Style.BRIGHT + g_color_detail + target + colorama.Fore.RESET + colorama.Style.RESET_ALL
138 | out = g_color_detail + target + colorama.Fore.RESET
139 | if condition == 'matches':
140 | pattern = self.make_regex(content, preserve_case)
141 | if pattern.search(target) is not None:
142 | if g_detail_on:
143 | print('matched IOC term detail: {0}'.format(out))
144 | return True
145 | else:
146 | if preserve_case == 'false':
147 | target = target.lower()
148 | content = content.lower()
149 | if condition == 'is':
150 | if target == content:
151 | if g_detail_on:
152 | print('matched IOC term detail: {0}'.format(out))
153 | return True
154 | elif condition == 'contains':
155 | if target.find(content) != -1:
156 | if g_detail_on:
157 | print('matched IOC term detail: {0}'.format(out))
158 | return True
159 | elif condition == 'starts-with':
160 | if target.startswith(content):
161 | if g_detail_on:
162 | print('matched IOC term detail: {0}'.format(out))
163 | return True
164 | elif condition == 'ends-with':
165 | if target.endswith(content):
166 | if g_detail_on:
167 | print('matched IOC term detail: {0}'.format(out))
168 | return True
169 | return False
170 |
171 | def check_strings(self, target_list, content, condition, preserve_case):
172 | result = False
173 | for target in target_list:
174 | if self.check_string(target, content, condition, preserve_case):
175 | #return True
176 | result = True
177 | #return False
178 | return result
179 |
180 | def extract_unicode(self, data):
181 | pat = re.compile(ur'(?:[\x20-\x7E][\x00]){4,}')
182 | return list(set([w.decode('utf-16le') for w in pat.findall(data)]))
183 |
184 | def extract_ascii(self, data):
185 | pat = re.compile(r'(?:[\x20-\x7E]){4,}')
186 | return list(set([w.decode('ascii') for w in pat.findall(data)]))
187 |
188 | def check_integer(self, target, content, condition, preserve_case):
189 | if condition == 'is':
190 | if target == content:
191 | return True
192 | elif condition == 'greater-than':
193 | if target > content:
194 | return True
195 | elif condition == 'less-than':
196 | if target < content:
197 | return True
198 | return False
199 |
200 | def check_integers(self, target_list, content, condition, preserve_case):
201 | for target in target_list:
202 | if self.check_integer(target, content, condition, preserve_case):
203 | return True
204 | return False
205 |
206 | def fetchall_from_db(self, cur, table, column):
207 | debug.debug("{0} already done. Results reused".format(table))
208 | sql = "select {0} from {1}".format(column, table)
209 | cur.execute(sql)
210 | return [record[0] for record in cur.fetchall()]
211 |
212 | def fetchone_from_db(self, cur, table, column):
213 | debug.debug("{0} already done. Results reused".format(table))
214 | sql = "select {0} from {1}".format(column, table)
215 | cur.execute(sql)
216 | return cur.fetchone()[0]
217 |
218 | class ProcessItem(impscan.ImpScan, netscan.Netscan, malfind.Malfind, apihooks.ApiHooks):
219 | def __init__(self, process, cur, _config):
220 | self.process = process
221 | self.cur = cur
222 | self._config = _config
223 | self.kernel_space = utils.load_as(self._config)
224 | self.flat_space = utils.load_as(self._config, astype = 'physical')
225 | self.forwarded_imports = { # copied from impscan
226 | "RtlGetLastWin32Error" : "kernel32.dll!GetLastError",
227 | "RtlSetLastWin32Error" : "kernel32.dll!SetLastError",
228 | "RtlRestoreLastWin32Error" : "kernel32.dll!SetLastError",
229 | "RtlAllocateHeap" : "kernel32.dll!HeapAlloc",
230 | "RtlReAllocateHeap" : "kernel32.dll!HeapReAlloc",
231 | "RtlFreeHeap" : "kernel32.dll!HeapFree",
232 | "RtlEnterCriticalSection" : "kernel32.dll!EnterCriticalSection",
233 | "RtlLeaveCriticalSection" : "kernel32.dll!LeaveCriticalSection",
234 | "RtlDeleteCriticalSection" : "kernel32.dll!DeleteCriticalSection",
235 | "RtlZeroMemory" : "kernel32.dll!ZeroMemory",
236 | "RtlSizeHeap" : "kernel32.dll!HeapSize",
237 | "RtlUnwind" : "kernel32.dll!RtlUnwind",
238 | }
239 | self.util = ItemUtil()
240 | self.compiled_rules = self.compile()
241 |
242 | def read_without_zero_page(self, vad, address_space):
243 | PAGE_SIZE = 0x1000
244 | all_zero_page = "\x00" * PAGE_SIZE
245 |
246 | offset = 0
247 | data = ''
248 | while offset < vad.Length:
249 | next_addr = vad.Start + offset
250 | if address_space.is_valid_address(next_addr):
251 | page = address_space.read(next_addr, PAGE_SIZE)
252 | if page != all_zero_page:
253 | data += page
254 | offset += PAGE_SIZE
255 | return data
256 |
257 | def check_done(self, item):
258 | sql = "select {0} from done where pid = ?".format(item)
259 | self.cur.execute(sql, (self.process.UniqueProcessId.v(),))
260 | return self.cur.fetchone()
261 |
262 | def update_done(self, item):
263 | sql = "update done set {0} = ? where pid = ?".format(item)
264 | self.cur.execute(sql, (True, self.process.UniqueProcessId.v()))
265 |
266 | def update_all_done(self, item):
267 | sql = "update done set {0} = ?".format(item)
268 | self.cur.execute(sql, (True, ))
269 |
270 | def fetchall_from_db_by_pid(self, table, column):
271 | debug.debug("{0} already done. Results reused (pid={1})".format(table, self.process.UniqueProcessId))
272 | sql = "select {0} from {1} where pid = ?".format(column, table)
273 | self.cur.execute(sql, (self.process.UniqueProcessId.v(),))
274 | records = self.cur.fetchall()
275 | if records is None:
276 | return []
277 | return [record[0] for record in records]
278 |
279 | def fetchone_from_db_by_pid(self, table, column):
280 | debug.debug("{0} already done. Results reused (pid={1})".format(table, self.process.UniqueProcessId))
281 | sql = "select {0} from {1} where pid = ?".format(column, table)
282 | self.cur.execute(sql, (self.process.UniqueProcessId.v(),))
283 | record = self.cur.fetchone()
284 | if record is None: # for cmdLine
285 | return ''
286 | return record[0]
287 |
288 | def detect_code_injections(self):
289 | injected = []
290 | debug.info("[time-consuming task] detecting code injections...(pid={0})".format(self.process.UniqueProcessId))
291 | for vad, address_space in self.process.get_vads(vad_filter = self.process._injection_filter):
292 | if self._is_vad_empty(vad, address_space):
293 | continue
294 | self.cur.execute("insert into injected values (?, ?, ?)", (self.process.UniqueProcessId.v(), vad.Start, vad.Length))
295 | injected.append([vad.Start, vad.Length])
296 | self.update_done('injected')
297 | return injected
298 |
299 | def SectionList_MemorySection_Injected(self, content, condition, preserve_case):
300 | if not self.util.is_condition_bool(condition):
301 | debug.error('{0} condition is not supported in ProcessItem/SectionList/MemorySection/Injected'.format(condition))
302 | return False
303 |
304 | (done,) = self.check_done('injected')
305 | if int(done):
306 | counts = self.fetchone_from_db_by_pid('injected', 'count(*)')
307 | else:
308 | counts = len(self.detect_code_injections())
309 |
310 | if (counts > 0 and content.lower() == 'true') or (counts == 0 and content.lower() == 'false'):
311 | return True
312 | else:
313 | return False
314 |
315 | def SectionList_MemorySection_InjectedHexPattern(self, content, condition, preserve_case):
316 | if condition != 'matches':
317 | debug.error('{0} condition is not supported in ProcessItem/SectionList/MemorySection/InjectedHexPattern'.format(condition))
318 | return False
319 |
320 | (done,) = self.check_done('injected')
321 | if int(done):
322 | starts = self.fetchall_from_db_by_pid('injected', 'start')
323 | else:
324 | starts = [start for (start, size) in self.detect_code_injections()]
325 |
326 | pattern = self.util.make_regex(content, preserve_case)
327 | addr_space = self.process.get_process_address_space()
328 | for start in starts:
329 | content = addr_space.zread(start, 256)
330 | if pattern.search(content) is not None:
331 | return True
332 | return False
333 |
334 | def extract_strings(self):
335 | debug.info("[time-consuming task] extracting strings from VADs (pid={0})".format(self.process.UniqueProcessId))
336 | strings = []
337 |
338 | for vad, address_space in self.process.get_vads(skip_max_commit = True):
339 | data = self.read_without_zero_page(vad, address_space)
340 | if len(data) == 0:
341 | continue
342 | elif len(data) > READ_BLOCKSIZE:
343 | debug.debug('data size in VAD is more than READ_BLOCKSIZE (pid{0})'.format(self.process.UniqueProcessId))
344 | extracted = list(set(self.util.extract_unicode(data) + self.util.extract_ascii(data)))
345 | strings.extend(extracted)
346 |
347 | records = ((self.process.UniqueProcessId.v(), string) for string in strings)
348 | self.cur.executemany("insert or ignore into strings values (?, ?)", records)
349 | self.update_done('strings')
350 | return strings
351 |
352 | def check_and_extract_strings(self, content, condition, preserve_case):
353 | (done,) = self.check_done('strings')
354 | if int(done):
355 | strings = self.fetchall_from_db_by_pid('strings', 'string')
356 | else:
357 | strings = self.extract_strings()
358 | return self.util.check_strings(strings, content, condition, preserve_case)
359 |
360 | def StringList_string(self, content, condition, preserve_case):
361 | '''
362 | condition: is/contains/matches(regex)/starts-with/ends-with
363 | preserve_case: true/false
364 | '''
365 | result = False
366 |
367 | if not self.util.is_condition_string(condition):
368 | debug.error('{0} condition is not supported in ProcessItem/StringList/string'.format(condition))
369 | return False
370 |
371 | result = self.check_and_extract_strings(content, condition, preserve_case)
372 | if result == False and condition == 'matches': # for searching binary sequences
373 | pattern = self.util.make_regex(content, preserve_case)
374 | (done,) = self.check_done('vaddump')
375 | if int(done):
376 | debug.debug("vaddump already done. Results reused (pid={0})".format(self.process.UniqueProcessId))
377 | f = open(os.path.join(g_cache_path, 'vaddump_pid' + str(self.process.UniqueProcessId)) + '.bin', 'rb')
378 |
379 | i = 0
380 | overlap = 1024
381 | self.cur.execute("select size from vaddump where pid = ?", (self.process.UniqueProcessId.v(),))
382 | maxlen = self.cur.fetchone()[0]
383 | while i < maxlen:
384 | to_read = min(READ_BLOCKSIZE + overlap, maxlen - i)
385 | f.seek(i)
386 | data = f.read(to_read)
387 | if data:
388 | if pattern.search(data) is not None:
389 | return True
390 | i += READ_BLOCKSIZE
391 | return False
392 |
393 | debug.info("[time-consuming task] dumping VADs for regex search... (pid={0})".format(self.process.UniqueProcessId))
394 | f = open(os.path.join(g_cache_path, 'vaddump_pid' + str(self.process.UniqueProcessId)) + '.bin', 'wb')
395 | size = 0
396 | for vad, address_space in self.process.get_vads(skip_max_commit = True):
397 | data = self.read_without_zero_page(vad, address_space)
398 | if len(data) == 0:
399 | continue
400 | elif len(data) > READ_BLOCKSIZE:
401 | debug.debug('data size in VAD is more than READ_BLOCKSIZE (pid{0})'.format(self.process.UniqueProcessId))
402 | if pattern.search(data) is not None:
403 | result = True
404 | f.write(data)
405 | size += len(data)
406 | f.flush()
407 | f.close()
408 | self.cur.execute("insert into vaddump values (?, ?)", (self.process.UniqueProcessId.v(), size))
409 | self.update_done('vaddump')
410 |
411 | return result
412 |
413 | # based on impscan (overrided)
414 | def _vicinity_scan(self, addr_space, calls_imported, apis, base_address, data_len, forward, injected):
415 |
416 | sortedlist = calls_imported.keys()
417 | sortedlist.sort()
418 |
419 | debug.debug('_vicinity_scan: base={0:x}'.format(base_address))
420 | if not sortedlist:
421 | debug.debug('sortedlist:None')
422 | return
423 |
424 | size_of_address = addr_space.profile.get_obj_size("address")
425 |
426 | if forward:
427 | start_addr = sortedlist[0]
428 | else:
429 | start_addr = sortedlist[len(sortedlist) - 1]
430 |
431 | if injected:
432 | # searching dynamically generated IAT
433 | threshold = 0x400
434 | if not forward:
435 | start_addr += 0x1000
436 | else:
437 | threshold = 5
438 | i = 0
439 |
440 | while threshold and i < 0x2000:
441 | if forward:
442 | next_addr = start_addr + (i * size_of_address)
443 | else:
444 | next_addr = start_addr - (i * size_of_address)
445 |
446 | debug.debug('next_addr {0:x} (threshold={1})'.format(next_addr, threshold))
447 | call_dest = obj.Object("address", offset = next_addr,
448 | vm = addr_space).v()
449 |
450 | #if (not call_dest or call_dest < base_address or call_dest > base_address + data_len): <- original code miss the entries
451 | if (not call_dest or (call_dest > base_address and call_dest < base_address + data_len)):
452 | debug.debug('continued {0:x}:{1:x}'.format(next_addr, call_dest))
453 | threshold -= 1
454 | i += 1
455 | continue
456 |
457 | if call_dest in apis and call_dest not in calls_imported:
458 | debug.debug('found {0:x}:{1:x}'.format(next_addr, call_dest))
459 | calls_imported[next_addr] = call_dest
460 | if injected:
461 | threshold = 0x400
462 | else:
463 | threshold = 5
464 | else:
465 | threshold -= 1
466 |
467 | i += 1
468 |
469 | # based on impscan
470 | def SectionList_MemorySection_PEInfo_ImportedModules_Module_ImportedFunctions_string(self, content, condition, preserve_case):
471 | result = False
472 |
473 | if not self.util.is_condition_string(condition):
474 | debug.error('{0} condition is not supported in ProcessItem/SectionList/MemorySection/PEInfo/ImportedModules/Module/ImportedFunctions/string'.format(condition))
475 | return False
476 |
477 | (done,) = self.check_done('impfunc')
478 | if int(done):
479 | imp_funcs = self.fetchall_from_db_by_pid('impfunc', 'func_name')
480 | return self.util.check_strings(imp_funcs, content, condition, preserve_case)
481 |
482 | debug.info("[time-consuming task] extracting imported functions...(pid={0})".format(self.process.UniqueProcessId))
483 | if self.process._vol_vm == self.flat_space:
484 | debug.warning('This process (pid={0}) seems to be dead. Skipping extraction of imported functions..'.format(self.process.UniqueProcessId))
485 | self.update_done('impfunc')
486 | return False
487 |
488 | scan_list = []
489 | all_mods = list(self.process.get_load_modules())
490 | if all_mods is not None and len(all_mods) > 0:
491 | # add the process image region
492 | scan_list.append((all_mods[0].DllBase, all_mods[0].SizeOfImage, False)) # start, size, injected
493 |
494 | # add suspicious DLL regions based on ldrmodules
495 | p = re.compile(r'')
496 | for vad, address_space in self.process.get_vads(vad_filter = self.process._mapped_file_filter):
497 | if obj.Object("_IMAGE_DOS_HEADER", offset = vad.Start, vm = address_space).e_magic != 0x5A4D:
498 | continue
499 | path = str(vad.FileObject.FileName or 'none').lower()
500 | if g_sus_path_p.search(path) is not None:
501 | entry = (vad.Start, vad.Length, False)
502 | if entry not in scan_list: # exclude exe
503 | debug.info('add suspicious dll to scan_list: {0}'.format(path))
504 | scan_list.append(entry)
505 |
506 | # add injected memory regions
507 | (done,) = self.check_done('injected')
508 | if int(done):
509 | self.cur.execute("select start, size from injected where pid = ?", (self.process.UniqueProcessId.v(),))
510 | records = self.cur.fetchall()
511 | if records is not None:
512 | scan_list.extend([(start, size, True) for start, size in records])
513 | else:
514 | scan_list.extend([(start, size, True) for start, size in self.detect_code_injections()])
515 |
516 | debug.debug(scan_list)
517 | for base_address, size_to_read, injected in scan_list:
518 | addr_space = self.process.get_process_address_space()
519 | if not addr_space:
520 | debug.warning("SectionList_MemorySection_PEInfo_ImportedModules_Module_ImportedFunctions_string: Cannot acquire process AS")
521 | return False
522 | data = addr_space.zread(base_address, size_to_read)
523 | apis = self.enum_apis(all_mods)
524 | calls_imported = dict(
525 | (iat, call)
526 | for (_, iat, call) in self.call_scan(addr_space, base_address, data)
527 | if call in apis
528 | )
529 | if injected:
530 | self._vicinity_scan(addr_space, calls_imported, apis, base_address, len(data), True, True)
531 | self._vicinity_scan(addr_space, calls_imported, apis, base_address, len(data), False, True)
532 | else:
533 | self._vicinity_scan(addr_space, calls_imported, apis, base_address, len(data), True, False)
534 | self._vicinity_scan(addr_space, calls_imported, apis, base_address, len(data), False, False)
535 | for iat, call in sorted(calls_imported.items()):
536 | mod_name, func_name = self._original_import(str(apis[call][0].BaseDllName or ''), apis[call][1])
537 | self.cur.execute("insert into impfunc values (?, ?, ?, ?, ?)", (self.process.UniqueProcessId.v(), iat, call, mod_name, func_name))
538 | if func_name == '':
539 | continue
540 | if self.util.check_string(func_name, content, condition, preserve_case):
541 | result = True
542 |
543 | self.update_done('impfunc')
544 | return result
545 |
546 | def name(self, content, condition, preserve_case):
547 | if not self.util.is_condition_string(condition):
548 | debug.error('{0} condition is not supported in ProcessItem/name'.format(condition))
549 | return False
550 | return self.util.check_string(str(self.process.ImageFileName), content, condition, preserve_case)
551 |
552 | def ParentProcessName(self, content, condition, preserve_case):
553 | if not self.util.is_condition_string(condition):
554 | debug.error('{0} condition is not supported in ProcessItem/name'.format(condition))
555 | return False
556 | if str(self.process.ImageFileName) == "System":
557 | return self.util.check_string('none', content, condition, preserve_case)
558 | self.cur.execute("select offset from hidden where pid = ?", (self.process.InheritedFromUniqueProcessId.v(),))
559 | res = self.cur.fetchone()
560 | if res is None:
561 | return False
562 | pprocess = obj.Object("_EPROCESS", offset = res[0], vm = self.flat_space)
563 | return self.util.check_string(str(pprocess.ImageFileName), content, condition, preserve_case)
564 |
565 | def cmdLine(self, content, condition, preserve_case):
566 | if not self.util.is_condition_string(condition):
567 | debug.error('{0} condition is not supported in ProcessItem/cmdLine'.format(condition))
568 | return False
569 | path = self.fetchone_from_db_by_pid('hidden', 'cmdLine')
570 | return self.util.check_string(path, content, condition, preserve_case)
571 |
572 | # based on malfind
573 | def extract_dllpaths(self, is_path=False, is_hidden=False):
574 | debug.info("[time-consuming task] extracting dllpaths from VADs... (pid={0})".format(self.process.UniqueProcessId))
575 |
576 | inloadorder = dict((mod.DllBase.v(), mod)
577 | for mod in self.process.get_load_modules())
578 | ininitorder = dict((mod.DllBase.v(), mod)
579 | for mod in self.process.get_init_modules())
580 | inmemorder = dict((mod.DllBase.v(), mod)
581 | for mod in self.process.get_mem_modules())
582 |
583 | mapped_files = {}
584 | for vad, address_space in self.process.get_vads(vad_filter = self.process._mapped_file_filter):
585 | if obj.Object("_IMAGE_DOS_HEADER", offset = vad.Start, vm = address_space).e_magic != 0x5A4D:
586 | continue
587 | mapped_files[int(vad.Start)] = str(vad.FileObject.FileName or '')
588 |
589 | records = []
590 | for base in mapped_files.keys():
591 | load_mod = inloadorder.get(base, None)
592 | init_mod = ininitorder.get(base, None)
593 | mem_mod = inmemorder.get(base, None)
594 | result = (load_mod == None) and (init_mod == None) and (mem_mod == None)
595 | records.append((self.process.UniqueProcessId.v(), mapped_files[base], result))
596 |
597 | self.cur.executemany("insert or ignore into dllpath values (?, ?, ?)", records)
598 | self.update_done('dllpath')
599 | if is_path:
600 | return [record[1] for record in records]
601 | elif is_hidden:
602 | return [record[2] for record in records]
603 |
604 | def DllPath(self, content, condition, preserve_case):
605 | if not self.util.is_condition_string(condition):
606 | debug.error('{0} condition is not supported in ProcessItem/DllPath'.format(condition))
607 | return False
608 |
609 | (done,) = self.check_done('dllpath')
610 | if int(done):
611 | dllpaths = self.fetchall_from_db_by_pid('dllpath', 'path')
612 | else:
613 | dllpaths = self.extract_dllpaths(is_path=True)
614 | return self.util.check_strings(dllpaths, content, condition, preserve_case)
615 |
616 | def DllHidden(self, content, condition, preserve_case):
617 | if not self.util.is_condition_bool(condition):
618 | debug.error('{0} condition is not supported in ProcessItem/DllHidden'.format(condition))
619 | return False
620 |
621 | (done,) = self.check_done('dllpath')
622 | if int(done):
623 | hiddens = self.fetchall_from_db_by_pid('dllpath', 'hidden')
624 | else:
625 | hiddens = self.extract_dllpaths(is_hidden=True)
626 |
627 | if (True in hiddens and content.lower() == 'true') or (False not in hiddens and content.lower() == 'false'):
628 | if g_detail_on:
629 | sql = "select path from dllpath where pid = ? and hidden = ?"
630 | self.cur.execute(sql, (self.process.UniqueProcessId.v(), content.lower() == 'true'))
631 | records = self.cur.fetchall()
632 | for (path,) in records:
633 | out = g_color_detail + str(path) + colorama.Fore.RESET
634 | print('matched IOC term detail: {0}'.format(out))
635 | return True
636 | else:
637 | return False
638 |
639 | # based on handles
640 | def extract_handles(self, is_name=False, is_type=False):
641 | debug.info("[time-consuming task] extracting handle information... (pid={0})".format(self.process.UniqueProcessId))
642 |
643 | pid = self.process.UniqueProcessId
644 | handle_list = []
645 | if self.process.ObjectTable.HandleTableList:
646 | for handle in self.process.ObjectTable.handles():
647 |
648 | if not handle.is_valid():
649 | continue
650 |
651 | name = ""
652 | object_type = handle.get_object_type()
653 | if object_type == "File":
654 | file_obj = handle.dereference_as("_FILE_OBJECT")
655 | name = str(file_obj.file_name_with_device())
656 | elif object_type == "Key":
657 | key_obj = handle.dereference_as("_CM_KEY_BODY")
658 | name = key_obj.full_key_name()
659 | elif object_type == "Process":
660 | proc_obj = handle.dereference_as("_EPROCESS")
661 | name = "{0}({1})".format(proc_obj.ImageFileName, proc_obj.UniqueProcessId)
662 | elif object_type == "Thread":
663 | thrd_obj = handle.dereference_as("_ETHREAD")
664 | name = "TID {0} PID {1}".format(thrd_obj.Cid.UniqueThread, thrd_obj.Cid.UniqueProcess)
665 | elif handle.NameInfo.Name == None:
666 | name = ''
667 | else:
668 | name = str(handle.NameInfo.Name)
669 |
670 | handle_list.append((int(pid), object_type, name))
671 |
672 | records = list(set(handle_list))
673 | for record in records:
674 | #print record
675 | pid, object_type, name = record
676 | #self.cur.execute("insert or ignore into handles values (?, ?, ?)", (pid, object_type, name.decode('utf8')))
677 | self.cur.execute("insert or ignore into handles values (?, ?, ?)", (pid, object_type, unicode(name))) # all executemany should be explicitly converted to unicode?
678 | #self.cur.executemany("insert or ignore into handles values (?, ?, ?)", records)
679 |
680 | self.update_done('handles')
681 | if is_name:
682 | return [record[2] for record in records]
683 | elif is_type:
684 | return [record[1] for record in records]
685 |
686 | self.update_done('handles')
687 | return None
688 |
689 | def HandleList_Handle_Name(self, content, condition, preserve_case):
690 | if not self.util.is_condition_string(condition):
691 | debug.error('{0} condition is not supported in ProcessItem/HandleList/Handle/Name'.format(condition))
692 | return False
693 |
694 | (done,) = self.check_done('handles')
695 | if int(done):
696 | names = self.fetchall_from_db_by_pid('handles', 'name')
697 | else:
698 | names = self.extract_handles(is_name=True)
699 | if names is None:
700 | debug.warning('cannot get handles (pid = {0})'.format(self.process.UniqueProcessId))
701 | return False
702 | return self.util.check_strings(names, content, condition, preserve_case)
703 |
704 | def HandleList_Handle_Type(self, content, condition, preserve_case):
705 | if not self.util.is_condition_string(condition):
706 | debug.error('{0} condition is not supported in ProcessItem/HandleList/Handle/Type'.format(condition))
707 | return False
708 |
709 | (done,) = self.check_done('handles')
710 | if int(done):
711 | types = self.fetchall_from_db_by_pid('handles', 'type')
712 | else:
713 | types = self.extract_handles(is_type=True)
714 | if types is None:
715 | debug.warning('cannot get handles (pid = {0})'.format(self.process.UniqueProcessId))
716 | return False
717 | return self.util.check_strings(types, content, condition, preserve_case)
718 |
719 | def extract_netinfo(self, is_protocol=False, is_laddr=False, is_lport=False, is_raddr=False, is_rport=False, is_state=False):
720 | debug.info("[time-consuming task] extracting network information...")
721 |
722 | net_list = []
723 | # addef for default values (AbstractScanCommand)
724 | self._config.VIRTUAL = False
725 | self._config.SHOW_UNALLOCATED = False
726 | self._config.START = None
727 | self._config.LENGTH = None
728 |
729 | for net_object, proto, laddr, lport, raddr, rport, state in netscan.Netscan.calculate(self):
730 | if proto.find("UDP") == -1:
731 | net_list.append((net_object.Owner.UniqueProcessId.v(), proto, str(laddr), int(lport), str(raddr), int(rport), str(state)))
732 | else:
733 | net_list.append((net_object.Owner.UniqueProcessId.v(), proto, str(laddr), int(lport), str(raddr), 0, str(state))) # changed rport (from "*" to 0) in UDP entry
734 |
735 | records = list(set(net_list))
736 | self.cur.executemany("insert or ignore into netinfo values (?, ?, ?, ?, ?, ?, ?)", records)
737 |
738 | self.update_all_done('netinfo')
739 | if is_protocol:
740 | return [record[1] for record in records if self.process.UniqueProcessId.v() == record[0]]
741 | elif is_laddr:
742 | return [record[2] for record in records if self.process.UniqueProcessId.v() == record[0]]
743 | elif is_lport:
744 | return [record[3] for record in records if self.process.UniqueProcessId.v() == record[0]]
745 | elif is_raddr:
746 | return [record[4] for record in records if self.process.UniqueProcessId.v() == record[0]]
747 | elif is_rport:
748 | return [record[5] for record in records if self.process.UniqueProcessId.v() == record[0]]
749 | elif is_state:
750 | return [record[6] for record in records if self.process.UniqueProcessId.v() == record[0]]
751 |
752 | return None
753 |
754 | def PortList_PortItem_localPort(self, content, condition, preserve_case):
755 | if not self.util.is_condition_integer(condition):
756 | debug.error('{0} condition is not supported in ProcessItem/PortList/PortItem/localPort'.format(condition))
757 | return False
758 |
759 | (done,) = self.check_done('netinfo')
760 | if int(done):
761 | lports = self.fetchall_from_db_by_pid('netinfo', 'lport')
762 | else:
763 | lports = self.extract_netinfo(is_lport=True)
764 | if lports is None:
765 | debug.warning('cannot get netinfo (pid = {0})'.format(self.process.UniqueProcessId))
766 | return False
767 | return self.util.check_integers(lports, content, condition, preserve_case)
768 |
769 | def PortList_PortItem_remotePort(self, content, condition, preserve_case):
770 | if not self.util.is_condition_integer(condition):
771 | debug.error('{0} condition is not supported in ProcessItem/PortList/PortItem/localPort'.format(condition))
772 | return False
773 |
774 | (done,) = self.check_done('netinfo')
775 | if int(done):
776 | rports = self.fetchall_from_db_by_pid('netinfo', 'rport')
777 | else:
778 | rports = self.extract_netinfo(is_rport=True)
779 | if rports is None:
780 | debug.warning('cannot get netinfo (pid = {0})'.format(self.process.UniqueProcessId))
781 | return False
782 | return self.util.check_integers(rports, content, condition, preserve_case)
783 |
784 | def PortList_PortItem_localIP(self, content, condition, preserve_case):
785 | if not self.util.is_condition_string(condition):
786 | debug.error('{0} condition is not supported in ProcessItem/PortList/PortItem/localIP'.format(condition))
787 | return False
788 |
789 | (done,) = self.check_done('netinfo')
790 | if int(done):
791 | laddrs = self.fetchall_from_db_by_pid('netinfo', 'laddr')
792 | else:
793 | laddrs = self.extract_netinfo(is_laddr=True)
794 | if laddrs is None:
795 | debug.warning('cannot get netinfo (pid = {0})'.format(self.process.UniqueProcessId))
796 | return False
797 | return self.util.check_strings(laddrs, content, condition, preserve_case)
798 |
799 | def PortList_PortItem_remoteIP(self, content, condition, preserve_case):
800 | if not self.util.is_condition_string(condition):
801 | debug.error('{0} condition is not supported in ProcessItem/PortList/PortItem/remoteIP'.format(condition))
802 | return False
803 |
804 | (done,) = self.check_done('netinfo')
805 | if int(done):
806 | raddrs = self.fetchall_from_db_by_pid('netinfo', 'raddr')
807 | else:
808 | raddrs = self.extract_netinfo(is_raddr=True)
809 | if raddrs is None:
810 | debug.warning('cannot get netinfo (pid = {0})'.format(self.process.UniqueProcessId))
811 | return False
812 | return self.util.check_strings(raddrs, content, condition, preserve_case)
813 |
814 | def hidden(self, content, condition, preserve_case):
815 | if not self.util.is_condition_bool(condition):
816 | debug.error('{0} condition is not supported in ProcessItem/hidden'.format(condition))
817 | return False
818 |
819 | result = self.fetchone_from_db_by_pid('hidden', 'result')
820 | if (result and content.lower() == 'true') or ((not result) and content.lower() == 'false'):
821 | return True
822 | else:
823 | return False
824 |
825 | # based on apihooks
826 | def extract_hooked_APIs(self, is_API=False, is_hookingMod=False):
827 | debug.info("[time-consuming task] extracting hooked APIs... (pid={0})".format(self.process.UniqueProcessId))
828 |
829 | process_space = self.process.get_process_address_space()
830 | if not process_space:
831 | return []
832 |
833 | module_group = apihooks.ModuleGroup(self.process.get_load_modules())
834 |
835 | records = []
836 | for dll in module_group.mods:
837 | if not process_space.is_valid_address(dll.DllBase):
838 | continue
839 | for hook in self.get_hooks(HOOK_MODE_USER, process_space, dll, module_group):
840 | if self.whitelist(hook.hook_mode | hook.hook_type, self.process.ImageFileName.v(), hook.VictimModule, hook.HookModule, hook.Function):
841 | continue
842 | records.append((self.process.UniqueProcessId.v(), hook.Mode, hook.Type, str(dll.BaseDllName or ''), hook.Function, hook.HookModule))
843 | self.cur.executemany("insert or ignore into api_hooked values (?, ?, ?, ?, ?, ?)", records)
844 | self.update_done('api_hooked')
845 | if is_API:
846 | return [record[4] for record in records]
847 | elif is_hookingMod:
848 | return [record[5] for record in records]
849 |
850 | def Hooked_API_FunctionName(self, content, condition, preserve_case):
851 | if not self.util.is_condition_string(condition):
852 | debug.error('{0} condition is not supported in ProcessItem/Hooked/API/FunctionName'.format(condition))
853 | return False
854 |
855 | (done,) = self.check_done('api_hooked')
856 | if int(done):
857 | hooked_funcs = self.fetchall_from_db_by_pid('api_hooked', 'hooked_func')
858 | else:
859 | hooked_funcs = self.extract_hooked_APIs(is_API=True)
860 | return self.util.check_strings(hooked_funcs, content, condition, preserve_case)
861 |
862 | def Hooked_API_HookingModuleName(self, content, condition, preserve_case):
863 | if not self.util.is_condition_string(condition):
864 | debug.error('{0} condition is not supported in ProcessItem/Hooked/API/HookingModuleName'.format(condition))
865 | return False
866 |
867 | (done,) = self.check_done('api_hooked')
868 | if int(done):
869 | hooking_mods = self.fetchall_from_db_by_pid('api_hooked', 'hooking_module')
870 | else:
871 | hooking_mods = self.extract_hooked_APIs(is_hookingMod=True)
872 | return self.util.check_strings(hooking_mods, content, condition, preserve_case)
873 |
874 | # based on privileges
875 | def extract_privileges(self):
876 | debug.info("[time-consuming task] extracting enabled privilege information... (pid={0})".format(self.process.UniqueProcessId))
877 | records = []
878 |
879 | for value, present, enabled, default in self.process.get_token().privileges():
880 | try:
881 | name, desc = PRIVILEGE_INFO[int(value)]
882 | except KeyError:
883 | continue
884 |
885 | if enabled:
886 | records.append((self.process.UniqueProcessId.v(), name))
887 |
888 | self.cur.executemany("insert or ignore into privs values (?, ?)", records)
889 | self.update_done('privs')
890 | return [record[1] for record in records]
891 |
892 | def EnabledPrivilege_Name(self, content, condition, preserve_case):
893 | if not self.util.is_condition_string(condition):
894 | debug.error('{0} condition is not supported in ProcessItem/EnabledPrivilege/Name'.format(condition))
895 | return False
896 |
897 | (done,) = self.check_done('privs')
898 | if int(done):
899 | privs = self.fetchall_from_db_by_pid('privs', 'priv')
900 | else:
901 | privs = self.extract_privileges()
902 | return self.util.check_strings(privs, content, condition, preserve_case)
903 |
904 | class RegistryItem(hivelist.HiveList, shimcache.ShimCache):
905 | def __init__(self, cur, _config):
906 | self.cur = cur
907 | self._config = _config
908 | self.kernel_space = utils.load_as(self._config)
909 | self.flat_space = utils.load_as(self._config, astype = 'physical')
910 | self.util = ItemUtil()
911 | self.reg_path_list = []
912 |
913 | def get_path(self, keypath, key):
914 | if key.Name != None:
915 | self.reg_path_list.append('{0}'.format(keypath + "\\" + key.Name))
916 | for k in rawreg.subkeys(key):
917 | self.get_path(keypath + "\\" + key.Name, k)
918 | for v in rawreg.values(key):
919 | if key.Name != None:
920 | self.reg_path_list.append('{0}'.format(keypath + "\\" + key.Name + "\\" + v.Name))
921 |
922 | def Path(self, content, condition, preserve_case):
923 | debug.error('RegistryItem/Path is currently disabled because it takes toooo long time :-(')
924 | return False
925 | '''
926 | if not self.util.is_condition_string(condition):
927 | debug.error('{0} condition is not supported in RegistryItem/Path'.format(condition))
928 | return False
929 |
930 | paths = []
931 | count = self.util.fetchone_from_db(self.cur, "regpath", "count(*)")
932 | if count > 0:
933 | paths = self.util.fetchall_from_db(self.cur, "regpath", "path")
934 | else:
935 | debug.info("[time-consuming task] extracting registry key/value paths...")
936 | debug.warning('Please redefine using process handle name instead of this term because it will take too long time :-(')
937 | hive_offsets = []
938 | for hive in hivelist.HiveList.calculate(self):
939 | if hive.Hive.Signature == 0xbee0bee0 and hive.obj_offset not in hive_offsets:
940 | hive_offsets.append(hive.obj_offset)
941 | h = hivemod.HiveAddressSpace(self.kernel_space, self._config, hive.obj_offset)
942 | #key = rawreg.open_key(rawreg.get_root(h), 'software\\microsoft\\windows\\currentversion\\run'.split('\\')) # <- for test
943 | #if key:
944 | # self.get_path('', key)
945 | self.get_path('', rawreg.get_root(h))
946 | paths = list(set(self.reg_path_list))
947 | self.cur.executemany("insert or ignore into regpath values (?)", [(path, ) for path in paths])
948 | return self.util.check_strings(paths, content, condition, preserve_case)
949 | '''
950 |
951 | def ShimCache_ExecutablePath(self, content, condition, preserve_case):
952 | if not self.util.is_condition_string(condition):
953 | debug.error('{0} condition is not supported in RegistryItem/ShimCache/ExecutablePath'.format(condition))
954 | return False
955 |
956 | paths = []
957 | records = []
958 | count = self.util.fetchone_from_db(self.cur, "shimcache", "count(*)")
959 | if count > 0:
960 | paths = self.util.fetchall_from_db(self.cur, "shimcache", "path")
961 | else:
962 | debug.info("[time-consuming task] extracting shimcache registry information...")
963 | for path, modified, updated in shimcache.ShimCache.calculate(self):
964 | path_str ='{0}'.format(path)
965 | records.append((path_str, modified.v()))
966 | if len(records) == 0:
967 | records.append(('dummy', 'dummy')) # insert dummy for done
968 | self.cur.executemany("insert or ignore into shimcache values (?, ?)", records)
969 | paths = [record[0] for record in records]
970 | return self.util.check_strings(paths, content, condition, preserve_case)
971 |
972 | class ServiceItem(svcscan.SvcScan):
973 | def __init__(self, cur, _config):
974 | self.cur = cur
975 | self._config = _config
976 | self.kernel_space = utils.load_as(self._config)
977 | self.flat_space = utils.load_as(self._config, astype = 'physical')
978 | self.util = ItemUtil()
979 |
980 | def extract_services(self, is_service_name=False, is_display_name=False, is_bin_path=False):
981 | debug.info("[time-consuming task] extracting service information...")
982 |
983 | records = []
984 | for rec in svcscan.SvcScan.calculate(self):
985 | service_name = '{0}'.format(rec.ServiceName.dereference())
986 | display_name = '{0}'.format(rec.DisplayName.dereference())
987 | bin_path = '{0}'.format(rec.Binary)
988 | records.append((service_name, display_name, bin_path))
989 | self.cur.executemany("insert or ignore into service values (?, ?, ?)", records)
990 |
991 | if is_service_name:
992 | return [record[0] for record in records]
993 | elif is_display_name:
994 | return [record[1] for record in records]
995 | elif is_bin_path:
996 | return [record[2] for record in records]
997 |
998 | def name(self, content, condition, preserve_case):
999 | if not self.util.is_condition_string(condition):
1000 | debug.error('{0} condition is not supported in ServiceItem/name'.format(condition))
1001 | return False
1002 |
1003 | count = self.util.fetchone_from_db(self.cur, "service", "count(*)")
1004 | if count > 0:
1005 | service_names = self.util.fetchall_from_db(self.cur, "service", "service_name")
1006 | else:
1007 | service_names = self.extract_services(is_service_name=True)
1008 | if service_names is None:
1009 | debug.error('cannot get service information')
1010 | return self.util.check_strings(service_names, content, condition, preserve_case)
1011 |
1012 | def descriptiveName(self, content, condition, preserve_case):
1013 | if not self.util.is_condition_string(condition):
1014 | debug.error('{0} condition is not supported in ServiceItem/descriptiveName'.format(condition))
1015 | return False
1016 |
1017 | count = self.util.fetchone_from_db(self.cur, "service", "count(*)")
1018 | if count > 0:
1019 | display_names = self.util.fetchall_from_db(self.cur, "service", "display_name")
1020 | else:
1021 | display_names = self.extract_services(is_display_name=True)
1022 | if display_names is None:
1023 | debug.error('cannot get service information')
1024 | return self.util.check_strings(display_names, content, condition, preserve_case)
1025 |
1026 | def cmdLine(self, content, condition, preserve_case):
1027 | if not self.util.is_condition_string(condition):
1028 | debug.error('{0} condition is not supported in ServiceItem/cmdLine'.format(condition))
1029 | return False
1030 |
1031 | count = self.util.fetchone_from_db(self.cur, "service", "count(*)")
1032 | if count > 0:
1033 | cmdlines = self.util.fetchall_from_db(self.cur, "service", "bin_path")
1034 | else:
1035 | cmdlines = self.extract_services(is_bin_path=True)
1036 | if cmdlines is None:
1037 | debug.error('cannot get service information')
1038 | return self.util.check_strings(cmdlines, content, condition, preserve_case)
1039 |
1040 | #class DriverItem(modules.Modules, modules.UnloadedModules, modscan.ModScan, impscan.ImpScan):
1041 | class DriverItem(impscan.ImpScan, devicetree.DriverIrp, callbacks.Callbacks, timers.Timers):
1042 | def __init__(self, kmod, cur, _config):
1043 | self.kmod = kmod
1044 | self.cur = cur
1045 | self._config = _config
1046 | self.kernel_space = utils.load_as(self._config)
1047 | self.flat_space = utils.load_as(self._config, astype = 'physical')
1048 | self.util = ItemUtil()
1049 | self.forwarded_imports = { # copied from impscan
1050 | "RtlGetLastWin32Error" : "kernel32.dll!GetLastError",
1051 | "RtlSetLastWin32Error" : "kernel32.dll!SetLastError",
1052 | "RtlRestoreLastWin32Error" : "kernel32.dll!SetLastError",
1053 | "RtlAllocateHeap" : "kernel32.dll!HeapAlloc",
1054 | "RtlReAllocateHeap" : "kernel32.dll!HeapReAlloc",
1055 | "RtlFreeHeap" : "kernel32.dll!HeapFree",
1056 | "RtlEnterCriticalSection" : "kernel32.dll!EnterCriticalSection",
1057 | "RtlLeaveCriticalSection" : "kernel32.dll!LeaveCriticalSection",
1058 | "RtlDeleteCriticalSection" : "kernel32.dll!DeleteCriticalSection",
1059 | "RtlZeroMemory" : "kernel32.dll!ZeroMemory",
1060 | "RtlSizeHeap" : "kernel32.dll!HeapSize",
1061 | "RtlUnwind" : "kernel32.dll!RtlUnwind",
1062 | }
1063 |
1064 | def fetchall_from_db_by_base(self, table, column):
1065 | name = str(self.kmod.BaseDllName or '')
1066 | base_addr = self.kmod.DllBase
1067 | debug.debug("fetchall: {0} already done. Results reused (name={1}, base=0x{2:x})".format(table, name, base_addr))
1068 | sql = "select {0} from {1} where base = ?".format(column, table)
1069 | #self.cur.execute(sql, (base_addr.v(),))
1070 | self.cur.execute(sql, (str(base_addr.v()),)) # for unsigned long ("OverflowError: Python int too large to convert to SQLite INTEGER")
1071 | return [record[0] for record in self.cur.fetchall()]
1072 |
1073 | def fetchone_from_db_by_base(self, table, column):
1074 | name = str(self.kmod.BaseDllName or '')
1075 | base_addr = self.kmod.DllBase
1076 | debug.debug("fetchone: {0} from {1} (name={2}, base=0x{3:x})".format(column, table, name, base_addr))
1077 | sql = "select {0} from {1} where base = ?".format(column, table)
1078 | #self.cur.execute(sql, (base_addr.v(),))
1079 | self.cur.execute(sql, (str(base_addr.v()),))
1080 | return self.cur.fetchone()[0]
1081 |
1082 | def DriverName(self, content, condition, preserve_case):
1083 | if not self.util.is_condition_string(condition):
1084 | debug.error('{0} condition is not supported in DriverItem/DriverName'.format(condition))
1085 | return False
1086 | return self.util.check_string(str(self.kmod.BaseDllName or ''), content, condition, preserve_case)
1087 |
1088 | def get_data(self):
1089 | base_address = self.kmod.DllBase
1090 | size_to_read = self.kmod.SizeOfImage
1091 | data = ""
1092 | mod_filepath = os.path.join(g_cache_path, 'kmod_0x{0:x}'.format(self.kmod.DllBase)) + '.sys'
1093 |
1094 | if os.path.exists(mod_filepath):
1095 | with open(mod_filepath, 'rb') as f:
1096 | data = f.read()
1097 | else:
1098 | if not size_to_read:
1099 | pefile = obj.Object("_IMAGE_DOS_HEADER",
1100 | offset = base_address,
1101 | vm = self.kernel_space)
1102 | try:
1103 | nt_header = pefile.get_nt_header()
1104 | size_to_read = nt_header.OptionalHeader.SizeOfImage
1105 | except ValueError:
1106 | pass
1107 | if not size_to_read:
1108 | debug.warning('cannot get size info (kernel module name={0} base=0x{1:x})'.format(str(self.kmod.BaseDllName or ''), self.kmod.DllBase))
1109 |
1110 | procs = list(tasks.pslist(self.kernel_space))
1111 | kernel_space = tasks.find_space(self.kernel_space, procs, base_address) # for some GUI drivers (e.g., win32k.sys)
1112 | if not kernel_space:
1113 | debug.warning('Cannot read supplied address (kernel module name={0} base=0x{1:x})'.format(str(self.kmod.BaseDllName or ''), self.kmod.DllBase))
1114 | else:
1115 | data = kernel_space.zread(base_address, size_to_read)
1116 | with open(mod_filepath, 'wb') as f:
1117 | f.write(data)
1118 |
1119 | return base_address, size_to_read, data
1120 |
1121 | # based on impscan
1122 | def PEInfo_ImportedModules_Module_ImportedFunctions_string(self, content, condition, preserve_case):
1123 | if not self.util.is_condition_string(condition):
1124 | debug.error('{0} condition is not supported in DriverItem/PEInfo/ImportedModules/Module/ImportedFunctions/string'.format(condition))
1125 | return False
1126 |
1127 | imp_funcs = []
1128 | count = self.fetchone_from_db_by_base("kernel_mods_impfunc", "count(*)")
1129 | if count > 0:
1130 | imp_funcs = self.fetchall_from_db_by_base("kernel_mods_impfunc", "func_name")
1131 | else:
1132 | debug.info("[time-consuming task] extracting import functions... (kernel module name={0} base=0x{1:x})".format(str(self.kmod.BaseDllName or ''), self.kmod.DllBase))
1133 | records = []
1134 |
1135 | all_mods = list(win32.modules.lsmod(self.kernel_space))
1136 | base_address, size_to_read, data = self.get_data()
1137 |
1138 | if data != '':
1139 | apis = self.enum_apis(all_mods)
1140 | procs = list(tasks.pslist(self.kernel_space))
1141 | addr_space = tasks.find_space(self.kernel_space, procs, base_address) # for some GUI drivers (e.g., win32k.sys)
1142 |
1143 | calls_imported = dict(
1144 | (iat, call)
1145 | for (_, iat, call) in self.call_scan(addr_space, base_address, data)
1146 | if call in apis
1147 | )
1148 | self._vicinity_scan(addr_space,
1149 | calls_imported, apis, base_address, len(data),
1150 | forward = True)
1151 | self._vicinity_scan(addr_space,
1152 | calls_imported, apis, base_address, len(data),
1153 | forward = False)
1154 |
1155 | for iat, call in sorted(calls_imported.items()):
1156 | mod_name, func_name = self._original_import(str(apis[call][0].BaseDllName or ''), apis[call][1])
1157 | #records.append((self.kmod.DllBase.v(), iat, call, mod_name, func_name))
1158 | records.append((str(self.kmod.DllBase.v()), str(iat), str(call), mod_name, func_name))
1159 | imp_funcs.append(func_name)
1160 |
1161 | if len(records) == 0:
1162 | debug.info('inserting marker "done"... (kernel module name={0} base=0x{1:x})'.format(str(self.kmod.BaseDllName or ''), self.kmod.DllBase))
1163 | records.append((str(self.kmod.DllBase.v()), 0, 0, 'marker_done', 'marker_done'))
1164 | self.cur.executemany("insert or ignore into kernel_mods_impfunc values (?, ?, ?, ?, ?)", records)
1165 |
1166 | return self.util.check_strings(imp_funcs, content, condition, preserve_case)
1167 |
1168 | def StringList_string(self, content, condition, preserve_case):
1169 | if not self.util.is_condition_string(condition):
1170 | debug.error('{0} condition is not supported in DriverItem/StringList/string'.format(condition))
1171 | return False
1172 |
1173 | count = self.fetchone_from_db_by_base("kernel_mods_strings", "count(*)")
1174 | strings = []
1175 | records = []
1176 | if count > 0:
1177 | strings = self.fetchall_from_db_by_base("kernel_mods_strings", "string")
1178 | else:
1179 | debug.info("[time-consuming task] extracting strings... (kernel module name={0} base=0x{1:x})".format(str(self.kmod.BaseDllName or ''), self.kmod.DllBase))
1180 | base_address, size_to_read, data = self.get_data()
1181 | if data != '':
1182 | strings = list(set(self.util.extract_unicode(data) + self.util.extract_ascii(data)))
1183 | records = [(str(self.kmod.DllBase.v()), string) for string in strings]
1184 | if len(records) == 0:
1185 | debug.info('inserting marker "done"... (kernel module name={0} base=0x{1:x})'.format(str(self.kmod.BaseDllName or ''), self.kmod.DllBase))
1186 | records.append((str(self.kmod.DllBase.v()), 'marker_done'))
1187 | self.cur.executemany("insert or ignore into kernel_mods_strings values (?, ?)", records)
1188 |
1189 | result = self.util.check_strings(strings, content, condition, preserve_case)
1190 | if result == False and condition == 'matches': # for searching binary sequences
1191 | pattern = self.util.make_regex(content, preserve_case)
1192 | base_address, size_to_read, data = self.get_data()
1193 | if data != '' and pattern.search(data) is not None:
1194 | result = True
1195 |
1196 | return result
1197 |
1198 | # based on devicetree.py
1199 | def extract_IRP_info(self):
1200 | debug.info("[time-consuming task] extracting hooking module names in IRP array...")
1201 |
1202 | records = []
1203 | # added for default option values (AbstractScanCommand)
1204 | self._config.VIRTUAL = False
1205 | self._config.SHOW_UNALLOCATED = False
1206 | self._config.START = None
1207 | self._config.LENGTH = None
1208 |
1209 | mods = dict((self.kernel_space.address_mask(mod.DllBase), mod) for mod in win32.modules.lsmod(self.kernel_space))
1210 | mod_addrs = sorted(mods.keys())
1211 |
1212 | for driver in devicetree.DriverIrp.calculate(self):
1213 | header = driver.get_object_header()
1214 | driver_name = str(header.NameInfo.Name or '')
1215 |
1216 | for i, function in enumerate(driver.MajorFunction):
1217 | function = driver.MajorFunction[i]
1218 | module = tasks.find_module(mods, mod_addrs, self.kernel_space.address_mask(function))
1219 | if module:
1220 | module_name = str(module.BaseDllName or '')
1221 | else:
1222 | module_name = "Unknown"
1223 | #records.append((driver.DriverStart.v(), MAJOR_FUNCTIONS[i], function.v(), module_name))
1224 | records.append((str(driver.DriverStart.v()), str(MAJOR_FUNCTIONS[i]), str(function.v()), module_name))
1225 |
1226 | self.cur.executemany("insert or ignore into kernel_mods_irp values (?, ?, ?, ?)", records)
1227 | return [record[3] for record in records if self.kmod.DllBase.v() == record[0]]
1228 |
1229 | def IRP_HookingModuleName(self, content, condition, preserve_case):
1230 | if not self.util.is_condition_string(condition):
1231 | debug.error('{0} condition is not supported in DriverItem/IRP/HookingModuleName'.format(condition))
1232 | return False
1233 |
1234 | mod_names = []
1235 | count = self.util.fetchone_from_db(self.cur, "kernel_mods_irp", "count(*)")
1236 | if count > 0:
1237 | mod_names = self.fetchall_from_db_by_base("kernel_mods_irp", "mod_name")
1238 | else:
1239 | mod_names = self.extract_IRP_info()
1240 | return self.util.check_strings(mod_names, content, condition, preserve_case)
1241 |
1242 | # based on callbacks
1243 | def extract_callbacks(self):
1244 | debug.info("[time-consuming task] extracting kernel callbacks...")
1245 |
1246 | records = []
1247 | # added for default option values (filescan)
1248 | self._config.VIRTUAL = False
1249 | self._config.SHOW_UNALLOCATED = False
1250 | self._config.START = None
1251 | self._config.LENGTH = None
1252 |
1253 | for (sym, cb, detail), mods, mod_addrs in callbacks.Callbacks.calculate(self):
1254 | module = tasks.find_module(mods, mod_addrs, mods.values()[0].obj_vm.address_mask(cb))
1255 | type_name = '{0}'.format(sym)
1256 | #records.append((module.DllBase.v(), type_name, cb.v(), str(detail or "-")))
1257 | records.append((str(module.DllBase.v()), type_name, str(cb.v()), str(detail or "-")))
1258 |
1259 | if len(records) == 0:
1260 | records.append(('dummy', 'dummy', 'dummy', 'dummy')) # insert dummy for done
1261 | self.cur.executemany("insert or ignore into kernel_mods_callbacks values (?, ?, ?, ?)", records)
1262 | return [record[1] for record in records if self.kmod.DllBase.v() == record[0]]
1263 |
1264 | def CallbackRoutine_Type(self, content, condition, preserve_case):
1265 | if not self.util.is_condition_string(condition):
1266 | debug.error('{0} condition is not supported in DriverItem/CallbackRoutine/Type'.format(condition))
1267 | return False
1268 |
1269 | types = []
1270 | count = self.util.fetchone_from_db(self.cur, "kernel_mods_callbacks", "count(*)")
1271 | if count > 0:
1272 | types = self.fetchall_from_db_by_base("kernel_mods_callbacks", "type")
1273 | else:
1274 | types = self.extract_callbacks()
1275 | return self.util.check_strings(types, content, condition, preserve_case)
1276 |
1277 | # based on timers
1278 | def extract_timers(self):
1279 | debug.info("[time-consuming task] extracting kernel timers...")
1280 |
1281 | records = []
1282 | # added for default option value
1283 | self._config.LISTHEAD = None
1284 |
1285 | for timer, module in timers.Timers.calculate(self):
1286 | if timer.Header.SignalState.v():
1287 | signaled = "Yes"
1288 | else:
1289 | signaled = "-"
1290 | due_time = "{0:#010x}:{1:#010x}".format(timer.DueTime.HighPart, timer.DueTime.LowPart)
1291 | #records.append((module.DllBase.v(), timer.obj_offset, due_time, timer.Period.v(), signaled, timer.Dpc.DeferredRoutine.v()))
1292 | #Quickfix for module=None
1293 | if module is None:
1294 | records.append(('None', str(timer.obj_offset), due_time, timer.Period.v(), signaled, str(timer.Dpc.DeferredRoutine.v())))
1295 | else:
1296 | records.append((str(module.DllBase.v()), str(timer.obj_offset), due_time, timer.Period.v(), signaled, str(timer.Dpc.DeferredRoutine.v())))
1297 |
1298 | if len(records) == 0:
1299 | records.append(('dummy', 'dummy', 'dummy', 'dummy', 'dummy', 'dummy')) # insert dummy for done
1300 | self.cur.executemany("insert or ignore into kernel_mods_timers values (?, ?, ?, ?, ?, ?)", records)
1301 | timer_routines = [record[5] for record in records if self.kmod.DllBase.v() == record[0]]
1302 | return len(timer_routines)
1303 |
1304 | def TimerRoutineIncluded(self, content, condition, preserve_case):
1305 | if not self.util.is_condition_bool(condition):
1306 | debug.error('{0} condition is not supported in DriverItem/TimerRoutineIncluded'.format(condition))
1307 | return False
1308 |
1309 | included = 0
1310 | count = self.util.fetchone_from_db(self.cur, "kernel_mods_timers", "count(*)") # total
1311 | if count > 0:
1312 | included = self.fetchall_from_db_by_base("kernel_mods_timers", "count(*)")[0] # per kmod
1313 | else:
1314 | included = self.extract_timers()
1315 |
1316 | debug.debug('{0}={1}'.format(str(self.kmod.BaseDllName or ''), included))
1317 | if (included > 0 and content.lower() == 'true') or (included == 0 and content.lower() == 'false'):
1318 | return True
1319 | else:
1320 | return False
1321 |
1322 | class HookItem(ssdt.SSDT):
1323 | def __init__(self, cur, _config):
1324 | self.cur = cur
1325 | self._config = _config
1326 | self.kernel_space = utils.load_as(self._config)
1327 | self.flat_space = utils.load_as(self._config, astype = 'physical')
1328 | self.util = ItemUtil()
1329 |
1330 | # based on ssdt
1331 | def extract_SSDT_hooked_functions(self):
1332 | debug.info("[time-consuming task] extracting hooked entries in SSDT...")
1333 | records = []
1334 |
1335 | # need to be modified in the future
1336 | hooked_wl = ['win32k.sys', 'ntkrnlpa.exe', 'ntoskrnl.exe', 'ntkrnlmp.exe', 'ntkrpamp.exe']
1337 | hooked_wl_inline = ['win32k.sys', 'ntkrnlpa.exe', 'ntoskrnl.exe', 'ntkrnlmp.exe', 'ntkrpamp.exe', 'hal.dll']
1338 |
1339 | addr_space = utils.load_as(self._config)
1340 | syscalls = addr_space.profile.syscalls
1341 | bits32 = addr_space.profile.metadata.get('memory_model', '32bit') == '32bit'
1342 |
1343 | for idx, table, n, vm, mods, mod_addrs in ssdt.SSDT.calculate(self):
1344 | for i in range(n):
1345 | if bits32:
1346 | syscall_addr = obj.Object('address', table + (i * 4), vm).v()
1347 | else:
1348 | offset = obj.Object('long', table + (i * 4), vm).v()
1349 | syscall_addr = table + (offset >> 4)
1350 | try:
1351 | syscall_name = syscalls[idx][i]
1352 | except IndexError:
1353 | syscall_name = "UNKNOWN"
1354 |
1355 | syscall_mod = tasks.find_module(mods, mod_addrs, addr_space.address_mask(syscall_addr))
1356 | if syscall_mod:
1357 | syscall_modname = syscall_mod.BaseDllName.v()
1358 | else:
1359 | syscall_modname = "UNKNOWN"
1360 |
1361 | if syscall_modname.lower() not in hooked_wl:
1362 | records.append((idx, idx * 0x1000 + i, syscall_addr, syscall_name, syscall_modname, False))
1363 | continue
1364 |
1365 | hook_name = ''
1366 | # for inline hook
1367 | if (addr_space.profile.metadata.get('memory_model', '32bit') == '32bit' and syscall_mod is not None):
1368 | ret = apihooks.ApiHooks.check_inline(va = syscall_addr, addr_space = vm,
1369 | mem_start = syscall_mod.DllBase,
1370 | mem_end = syscall_mod.DllBase + syscall_mod.SizeOfImage)
1371 | if ret is not None:
1372 | (hooked, data, dest_addr) = ret
1373 | if hooked:
1374 | hook_mod = tasks.find_module(mods, mod_addrs, dest_addr)
1375 | if hook_mod:
1376 | hook_name = hook_mod.BaseDllName.v()
1377 | else:
1378 | hook_name = "UNKNOWN"
1379 |
1380 | if hook_name.lower() not in hooked_wl_inline:
1381 | records.append((idx, idx * 0x1000 + i, dest_addr, syscall_name, hook_name, True))
1382 |
1383 | if len(records) == 0:
1384 | records.append(('dummy', 'dummy', 'dummy', 'dummy', 'dummy', 'dummy')) # insert dummy for done
1385 | self.cur.executemany("insert or ignore into ssdt_hooked values (?, ?, ?, ?, ?, ?)", records)
1386 | return [record[3] for record in records]
1387 |
1388 | def SSDT_HookedFunctionName(self, content, condition, preserve_case):
1389 | if not self.util.is_condition_string(condition):
1390 | debug.error('{0} condition is not supported in HookItem/SSDT/HookedFunctionName'.format(condition))
1391 | return False
1392 |
1393 | syscall_names = []
1394 | count = self.util.fetchone_from_db(self.cur, "ssdt_hooked", "count(*)")
1395 | if count > 0:
1396 | syscall_names = self.util.fetchall_from_db(self.cur, "ssdt_hooked", "syscall_name")
1397 | else:
1398 | syscall_names = self.extract_SSDT_hooked_functions()
1399 | return self.util.check_strings(syscall_names, content, condition, preserve_case)
1400 |
1401 | class FileItem(mftparser.MFTParser):
1402 | def __init__(self, cur, _config):
1403 | self.cur = cur
1404 | self._config = _config
1405 | self.kernel_space = utils.load_as(self._config)
1406 | self.flat_space = utils.load_as(self._config, astype = 'physical')
1407 | self.util = ItemUtil()
1408 |
1409 | # based on mftparser
1410 | def extract_MFT_entries(self, is_inode=False, is_name=False, is_extension=False, is_path=False, is_size=False):
1411 | debug.info("[time-consuming task] extracting NTFS MFT entries...")
1412 | records = []
1413 |
1414 | # added for default option values (mftparser)
1415 | self._config.MACHINE = ""
1416 | self._config.OFFSET = None
1417 | self._config.ENTRYSIZE = 1024
1418 | self._config.DEBUGOUT = False
1419 | self._config.NOCHECK = False
1420 |
1421 | for offset, mft_entry, attributes in mftparser.MFTParser.calculate(self):
1422 | full = ""
1423 | for a, i in attributes:
1424 | size = -1
1425 | if a.startswith("FILE_NAME"):
1426 | debug.debug(a)
1427 | if hasattr(i, "ParentDirectory"):
1428 | name = mft_entry.remove_unprintable(i.get_name()) or "(Null)"
1429 | if len(name.split('.')) > 1:
1430 | ext = name.split('.')[-1]
1431 | else:
1432 | ext = ''
1433 | full = mft_entry.get_full_path(i)
1434 | #size = int(i.RealFileSize)
1435 | size = str(i.RealFileSize.v())
1436 | debug.debug('NTFS file info from MFT entry $FN: name={0}, ext={1}, full={2}'.format(name, ext, full))
1437 | #records.append((offset, mft_entry.RecordNumber.v(), name, ext, full, size))
1438 | records.append((offset, mft_entry.RecordNumber.v(), unicode(name), unicode(ext), unicode(full), unicode(size)))
1439 |
1440 | if len(records) == 0:
1441 | records.append((0, 0, 'dummy', 'dummy', 'dummy', 0)) # insert dummy for done
1442 | self.cur.executemany("insert or ignore into files values (?, ?, ?, ?, ?, ?)", records)
1443 |
1444 | if is_inode:
1445 | return [record[1] for record in records]
1446 | elif is_name:
1447 | return [record[2] for record in records]
1448 | elif is_extension:
1449 | return [record[3] for record in records]
1450 | elif is_path:
1451 | return [record[4] for record in records]
1452 | elif is_size:
1453 | #return [record[5] for record in records]
1454 | return [long(record[5]) for record in records]
1455 |
1456 | def INode(self, content, condition, preserve_case):
1457 | if not self.util.is_condition_integer(condition):
1458 | debug.error('{0} condition is not supported in FileItem/INode'.format(condition))
1459 | return False
1460 |
1461 | ent_nums = []
1462 | count = self.util.fetchone_from_db(self.cur, "files", "count(*)")
1463 | if count > 0:
1464 | ent_nums = self.util.fetchall_from_db(self.cur, "files", "inode")
1465 | else:
1466 | ent_nums = self.extract_MFT_entries(is_inode=True)
1467 | return self.util.check_integers(ent_nums, content, condition, preserve_case)
1468 |
1469 | def FileName(self, content, condition, preserve_case):
1470 | if not self.util.is_condition_string(condition):
1471 | debug.error('{0} condition is not supported in FileItem/FileName'.format(condition))
1472 | return False
1473 |
1474 | names = []
1475 | count = self.util.fetchone_from_db(self.cur, "files", "count(*)")
1476 | if count > 0:
1477 | names = self.util.fetchall_from_db(self.cur, "files", "name")
1478 | else:
1479 | names = self.extract_MFT_entries(is_name=True)
1480 | return self.util.check_strings(names, content, condition, preserve_case)
1481 |
1482 | def FileExtension(self, content, condition, preserve_case):
1483 | if not self.util.is_condition_string(condition):
1484 | debug.error('{0} condition is not supported in FileItem/FileExtension'.format(condition))
1485 | return False
1486 |
1487 | exts = []
1488 | count = self.util.fetchone_from_db(self.cur, "files", "count(*)")
1489 | if count > 0:
1490 | exts = self.util.fetchall_from_db(self.cur, "files", "extension")
1491 | else:
1492 | exts = self.extract_MFT_entries(is_extension=True)
1493 | return self.util.check_strings(exts, content, condition, preserve_case)
1494 |
1495 | def FullPath(self, content, condition, preserve_case):
1496 | if not self.util.is_condition_string(condition):
1497 | debug.error('{0} condition is not supported in FileItem/FullPath'.format(condition))
1498 | return False
1499 |
1500 | paths = []
1501 | count = self.util.fetchone_from_db(self.cur, "files", "count(*)")
1502 | if count > 0:
1503 | paths = self.util.fetchall_from_db(self.cur, "files", "path")
1504 | else:
1505 | paths = self.extract_MFT_entries(is_path=True)
1506 | return self.util.check_strings(paths, content, condition, preserve_case)
1507 |
1508 | def SizeInBytes(self, content, condition, preserve_case):
1509 | if not self.util.is_condition_integer(condition):
1510 | debug.error('{0} condition is not supported in FileItem/SizeInBytes'.format(condition))
1511 | return False
1512 |
1513 | sizes = []
1514 | count = self.util.fetchone_from_db(self.cur, "files", "count(*)")
1515 | if count > 0:
1516 | sizes = self.util.fetchall_from_db(self.cur, "files", "size")
1517 | else:
1518 | sizes = self.extract_MFT_entries(is_size=True)
1519 | return self.util.check_integers(sizes, content, condition, preserve_case)
1520 |
1521 | class IOCParseError(Exception):
1522 | pass
1523 |
1524 | class IOC_Scanner:
1525 | def __init__(self):
1526 | self.iocs = {} # elementTree representing the IOC
1527 | self.ioc_name = {} # guid -> name mapping
1528 | self.level = 1 # xml hierarchical level in the IOC
1529 | self.iocEvalString = '' # AND/OR logic of the IOC evaluation
1530 | self.iocLogicString = '' # AND/OR logic result for display
1531 | self.item_obj = None
1532 | self.display_mode = False
1533 | self.cur = None
1534 | self._config = None
1535 | self.items = {'Process':None, 'Registry':None, 'Service':None, 'Driver':None, 'Hook':None, 'File':None}
1536 | self.checked_results = {} # for repeatedly checked Items except ProcessItem and DriverItem
1537 | self.total_score = 0
1538 |
1539 | def __len__(self):
1540 | return len(self.iocs)
1541 |
1542 | def insert(self, filename):
1543 | errors = []
1544 | if os.path.isfile(filename):
1545 | debug.info('loading IOC from: {0}'.format(filename))
1546 | try:
1547 | self.parse(ioc_api.IOC(filename))
1548 | except ioc_api.IOCParseError,e:
1549 | debug.error('Parse Error [{0}]'.format(e))
1550 | elif os.path.isdir(filename):
1551 | debug.info('loading IOCs from: {0}'.format(filename))
1552 | for fn in glob.glob(filename+os.path.sep+'*.ioc'):
1553 | if not os.path.isfile(fn):
1554 | continue
1555 | else:
1556 | try:
1557 | self.parse(ioc_api.IOC(fn))
1558 | except ioc_api.IOCParseError,e:
1559 | debug.error('Parse Error [{0}]'.format(str(e)))
1560 | else:
1561 | pass
1562 | debug.info('Parsed [{0}] IOCs'.format(str(len(self))))
1563 | return errors
1564 |
1565 | def parse(self, ioc_obj):
1566 | if ioc_obj is None:
1567 | return
1568 | iocid = ioc_obj.root.get('id')
1569 | if iocid in self.iocs:
1570 | debug.error('duplicate IOCs (UUID={0})'.format(iocid))
1571 |
1572 | # check items
1573 | try:
1574 | ioc_logic = ioc_obj.root.xpath('.//criteria')[0]
1575 | except IndexError, e:
1576 | debug.warning('Could not find criteria nodes for IOC [{0}]. '.format(str(iocid)))
1577 | return
1578 | for document in ioc_logic.xpath('//Context/@document'):
1579 | item_name = document[:-4]
1580 | if not item_name in self.items.keys():
1581 | debug.error('Not supported item = {0} in IOC [{1}]. '.format(document, str(iocid)))
1582 | return
1583 |
1584 | self.iocs[iocid] = ioc_obj
1585 | return True
1586 |
1587 | def prepare(self, cur, _config):
1588 | self.cur = cur
1589 | self._config = _config
1590 |
1591 | def with_item(self, iocid, name):
1592 | ioc_obj = self.iocs[iocid]
1593 | ioc_logic = ioc_obj.root.xpath('.//criteria')[0]
1594 | if len(ioc_logic.xpath('//Context[@document="{0}Item"]'.format(name))) > 0:
1595 | return True
1596 | else:
1597 | return False
1598 |
1599 | def with_item_all(self, name):
1600 | for iocid in self.iocs:
1601 | if self.with_item(iocid, name):
1602 | return True
1603 | return False
1604 |
1605 | def check_indicator_item(self, node, params, is_last_item):
1606 | iocResult = False
1607 | global g_detail_on
1608 | score = 0
1609 |
1610 | condition = node.get('condition')
1611 | preserve_case = node.get('preserve-case')
1612 | negate = node.get('negate')
1613 |
1614 | document = node.xpath('Context/@document')[0]
1615 | search = node.xpath('Context/@search')[0]
1616 | content = node.findtext('Content')
1617 | logicOperator = str(node.getparent().get("operator")).lower()
1618 |
1619 | theid = node.get('id')
1620 | param_desc = ''
1621 | param_cnt = 0
1622 | note = ''
1623 | for refid, name, value in params:
1624 | if theid == refid:
1625 | if name == 'detail' and value == 'on':
1626 | g_detail_on = True
1627 | param_cnt += 1
1628 | elif name == 'score':
1629 | score += int(value)
1630 | param_cnt += 1
1631 | elif name == 'note':
1632 | param_cnt += 1
1633 | note = value
1634 | if param_cnt > 0:
1635 | param_desc = ' ('
1636 | if g_detail_on:
1637 | param_desc += 'detail=on;'
1638 | if score > 0:
1639 | param_desc += 'score={0};'.format(score)
1640 | if note != '':
1641 | param_desc += 'note="{0}";'.format(note)
1642 | param_desc += ')'
1643 |
1644 | if negate == 'true':
1645 | item_desc = 'Not ' + search + ' ' + condition + ' ' + content + param_desc
1646 | else:
1647 | item_desc = search + ' ' + condition + ' ' + content + param_desc
1648 |
1649 | if self.display_mode:
1650 | if is_last_item:
1651 | self.iocLogicString += ' '*self.level + item_desc + '\n'
1652 | else:
1653 | self.iocLogicString += ' '*self.level + item_desc + '\n' + ' '*self.level + str(logicOperator) + '\n'
1654 | return
1655 |
1656 | method = '_'.join(search.split('/')[1:])
1657 | item_name = document[:-4] # fetch '*' from '*Item'
1658 | if not item_name in self.items.keys():
1659 | debug.error('{0} not supported in this plugin'.format(document))
1660 | if item_name != 'Process' and item_name != 'Driver' and self.items[item_name] is None:
1661 | self.items[item_name] = eval('{0}(self.cur, self._config)'.format(document))
1662 | if not method in dir(self.items[item_name]):
1663 | debug.error('{0} not supported in this plugin'.format(search))
1664 |
1665 | the_term = search + content + condition + preserve_case
1666 | if item_name != 'Process' and item_name != 'Driver' and (the_term) in self.checked_results.keys():
1667 | debug.debug('reusing results about other Items except repeated ProcessItem/DriverItem ("{0}" = {1})'.format(the_term, self.checked_results[the_term]))
1668 | iocResult = self.checked_results[the_term]
1669 | else:
1670 | iocResult = eval('self.items["{0}"].{1}(r"{2}","{3}","{4}")'.format(item_name, method, content, condition, preserve_case))
1671 | #if negate == 'true' and iocResult == True:
1672 | if negate == 'true':
1673 | iocResult = not iocResult
1674 | if item_name != 'Process' and item_name != 'Driver' and (the_term) not in self.checked_results.keys():
1675 | self.checked_results[the_term] = iocResult
1676 |
1677 | if is_last_item:
1678 | self.iocEvalString += ' ' + str(iocResult)
1679 | if iocResult:
1680 | self.iocLogicString += ' '*self.level + colorama.Style.BRIGHT + g_color_term + '>>> ' + item_desc + colorama.Fore.RESET + colorama.Style.RESET_ALL + '\n'
1681 | self.total_score += score
1682 | else:
1683 | self.iocLogicString += ' '*self.level + item_desc + '\n'
1684 | else:
1685 | self.iocEvalString += ' ' + str(iocResult) + ' ' + str(logicOperator)
1686 | if iocResult:
1687 | self.iocLogicString += ' '*self.level + colorama.Style.BRIGHT + g_color_term + '>>> ' + item_desc + colorama.Fore.RESET + colorama.Style.RESET_ALL + '\n' + ' '*self.level + str(logicOperator) + '\n'
1688 | self.total_score += score
1689 | else:
1690 | self.iocLogicString += ' '*self.level + item_desc + '\n' + ' '*self.level + str(logicOperator) + '\n'
1691 |
1692 | g_detail_on = False
1693 |
1694 | def walk_indicator(self, node, params):
1695 | expected_tag = 'Indicator'
1696 | if node.tag != expected_tag:
1697 | raise ValueError('node expected tag is [{0}]'.format(expected_tag))
1698 |
1699 | debug.debug('entering walk_indicator: {0}={1}'.format(node.get('id'), node.get('operator')))
1700 | for chn in node.getchildren():
1701 | chn_id = chn.get('id')
1702 |
1703 | if chn.tag == 'IndicatorItem':
1704 | if chn == node.getchildren()[-1]:
1705 | self.check_indicator_item(chn, params, True)
1706 | else:
1707 | self.check_indicator_item(chn, params, False)
1708 |
1709 | elif chn.tag == 'Indicator':
1710 | debug.debug('parent id=operator: {0}={1}'.format(chn.getparent().get('id'), chn.getparent().get('operator')))
1711 | operator = chn.get('operator').lower()
1712 | if operator not in ['or', 'and']:
1713 | raise IOCParseError('Indicator@operator is not AND/OR. [{0}] has [{1}]'.format(chn_id, operator) )
1714 |
1715 | self.iocEvalString += ' ('
1716 | self.iocLogicString += ' '*self.level + '(\n'
1717 | self.level+=1
1718 |
1719 | self.walk_indicator(chn, params)
1720 |
1721 | self.level-=1
1722 | logicOperator = str(chn.getparent().get("operator")).lower()
1723 | if chn == node.getchildren()[-1]:
1724 | self.iocLogicString += ' '*self.level + ')\n'
1725 | self.iocEvalString += ' )'
1726 | else:
1727 | '''
1728 | theid = chn.getparent().get('id')
1729 | print theid
1730 | for refid, name, value in params:
1731 | if theid == refid:
1732 | if name == 'note':
1733 | logicOperator += '(note="{0}")'.format(value)
1734 | '''
1735 | self.iocLogicString += ' '*self.level + ')\n' + ' '*self.level + str(logicOperator) + '\n'
1736 | self.iocEvalString += ' )' + ' ' + str(logicOperator)
1737 |
1738 | else:
1739 | # should never get here
1740 | raise IOCParseError('node is not a Indicator/IndicatorItem')
1741 |
1742 | def walk_parameter(self, node):
1743 | expected_tag = 'parameters'
1744 | if node.tag != expected_tag:
1745 | raise ValueError('walk_parameter: node expected tag is [{0}]'.format(expected_tag))
1746 |
1747 | params = []
1748 | for chn in node.getchildren():
1749 | if chn.tag != 'param':
1750 | raise ValueError('walk_parameter: chn expected tag is [param]')
1751 | #theid = chn.get('id')
1752 | refid = chn.get('ref-id')
1753 | name = chn.get('name')
1754 | value = chn.findtext('value')
1755 | params.append((refid, name, value))
1756 |
1757 | return params
1758 |
1759 | def scan(self, iocid, process, kmod):
1760 | result = ''
1761 |
1762 | if len(self) < 1:
1763 | debug.error('no iocs available to scan')
1764 | return result
1765 |
1766 | if process is not None:
1767 | self.items['Process'] = ProcessItem(process, self.cur, self._config)
1768 | if kmod is not None:
1769 | self.items['Driver'] = DriverItem(kmod, self.cur, self._config)
1770 |
1771 | ioc_obj = self.iocs[iocid]
1772 |
1773 | try:
1774 | ioc_params = ioc_obj.root.xpath('.//parameters')[0]
1775 | #params = ioc_params.getchildren()[0]
1776 | except IndexError, e:
1777 | debug.debug('Could not find children for the top level parameters/children nodes for IOC [{0}]'.format(str(iocid)))
1778 | else:
1779 | params = self.walk_parameter(ioc_params)
1780 |
1781 | ioc_logic = ioc_obj.root.xpath('.//criteria')[0]
1782 | try:
1783 | tlo = ioc_logic.getchildren()[0]
1784 | except IndexError, e:
1785 | debug.warning('Could not find children for the top level criteria/children nodes for IOC [{0}]'.format(str(iocid)))
1786 | return result
1787 |
1788 | self.walk_indicator(tlo, params)
1789 | debug.debug(self.iocEvalString)
1790 | if eval(self.iocEvalString):
1791 | result += 'IOC matched (by logic)! short_desc="{0}" id={1}\n'.format(ioc_obj.metadata.findtext('.//short_description'), iocid)
1792 | result += 'logic (matched item is magenta-colored):\n{0}'.format(self.iocLogicString)
1793 | elif self.total_score >= SCORE_THRESHOLD:
1794 | result += 'IOC matched (by score)! short_desc="{0}" id={1}\n'.format(ioc_obj.metadata.findtext('.//short_description'), iocid)
1795 | result += 'logic (matched item is magenta-colored):\n{0}'.format(self.iocLogicString)
1796 | elif self._config.test:
1797 | result += '[Test Mode for improving IOC] short_desc="{0}" id={1}\n'.format(ioc_obj.metadata.findtext('.//short_description'), iocid)
1798 | result += 'logic (matched item is magenta-colored):\n{0}'.format(self.iocLogicString)
1799 | self.iocEvalString=""
1800 | self.iocLogicString=""
1801 | self.total_score = 0
1802 |
1803 | self.items['Process'] = None
1804 | self.items['Driver'] = None
1805 | return result
1806 |
1807 | def display(self):
1808 | self.display_mode = True
1809 | result = ''
1810 |
1811 | if len(self) < 1:
1812 | debug.error('no iocs to display')
1813 | return result
1814 |
1815 | for iocid in self.iocs:
1816 | ioc_obj = self.iocs[iocid]
1817 |
1818 | try:
1819 | ioc_params = ioc_obj.root.xpath('.//parameters')[0]
1820 | #params = ioc_params.getchildren()[0]
1821 | except IndexError, e:
1822 | debug.debug('Could not find children for the top level parameters/children nodes for IOC [{0}]'.format(str(iocid)))
1823 | else:
1824 | params = self.walk_parameter(ioc_params)
1825 |
1826 | ioc_logic = ioc_obj.root.xpath('.//criteria')[0]
1827 | try:
1828 | tlo = ioc_logic.getchildren()[0]
1829 | except IndexError, e:
1830 | debug.warning('Could not find children for the top level criteria/children nodes for IOC [{0}]'.format(str(iocid)))
1831 | continue
1832 |
1833 | self.walk_indicator(tlo, params)
1834 | result += '++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n'
1835 | result += 'IOC definition \nshort_desc: {0} \ndesc: {1} \nid: {2}\n'.format(ioc_obj.metadata.findtext('.//short_description'), ioc_obj.metadata.findtext('.//description'), iocid)
1836 | result += 'logic:\n{0}'.format(self.iocLogicString)
1837 | self.iocLogicString=""
1838 |
1839 | return result
1840 |
1841 | class OpenIOC_Scan(psxview.PsXview, taskmods.DllList):
1842 | """Scan OpenIOC 1.1 based indicators"""
1843 | meta_info = commands.Command.meta_info
1844 | meta_info['author'] = 'Takahiro Haruyama'
1845 | meta_info['copyright'] = 'Copyright (c) 2014 Takahiro Haruyama'
1846 | meta_info['license'] = 'GNU General Public License 2.0'
1847 | meta_info['url'] = 'http://takahiroharuyama.github.io/'
1848 |
1849 | def __init__(self, config, *args, **kwargs):
1850 | common.AbstractWindowsCommand.__init__(self, config, *args, **kwargs)
1851 | self._config.add_option('PID', short_option = 'p', default = None,
1852 | help = 'Operate on these Process IDs (comma-separated)',
1853 | action = 'store', type = 'str')
1854 | self._config.add_option('ioc_dir', short_option = 'i', default = None,
1855 | help = 'Location of IOCs directory',
1856 | action = 'store', type = 'str')
1857 | self._config.add_option('show', short_option = 's', default = False,
1858 | help = 'Display IOC definition only',
1859 | action = 'store_true')
1860 | self._config.add_option('cache_path', short_option = 'c', default = None,
1861 | help = 'Specify the cache folder path of analysis result',
1862 | action = 'store', type = 'str')
1863 | self._config.add_option('kmod', short_option = 'm', default = None,
1864 | help = 'Operate on these kernel module names (comma-separated, case-insensitive)',
1865 | action = 'store', type = 'str')
1866 | self._config.add_option('test', short_option = 't', default = False,
1867 | help = 'display all scan results for improving IOC',
1868 | action = 'store_true')
1869 | self._config.add_option('erase', short_option = 'e', default = False,
1870 | help = 'erase cached db then scan',
1871 | action = 'store_true')
1872 | self._config.add_option('not_carve', short_option = 'n', default = False,
1873 | help = 'not carve _EPROCESS object (fetch from linked list)',
1874 | action = 'store_true')
1875 | self.db = None
1876 | self.cur = None
1877 | self.total_secs = 0
1878 |
1879 | def filter_tasks(self, tasks):
1880 | if self._config.PID is None:
1881 | return tasks
1882 |
1883 | try:
1884 | pidlist = [int(p) for p in self._config.PID.split(',')]
1885 | except ValueError:
1886 | debug.error("Invalid PID {0}".format(self._config.PID))
1887 |
1888 | return [t for t in tasks if t.UniqueProcessId in pidlist]
1889 |
1890 | def clear_tables(self):
1891 | debug.info("Clearing the tables... (old DB or erase flag)")
1892 |
1893 | self.cur.execute("drop table if exists version")
1894 |
1895 | self.cur.execute("drop table if exists hidden")
1896 | self.cur.execute("drop table if exists done")
1897 | self.cur.execute("drop table if exists injected")
1898 | self.cur.execute("drop table if exists strings")
1899 | self.cur.execute("drop table if exists vaddump")
1900 | self.cur.execute("drop table if exists impfunc")
1901 | self.cur.execute("drop table if exists handles")
1902 | self.cur.execute("drop table if exists netinfo")
1903 | self.cur.execute("drop table if exists dllpath")
1904 | self.cur.execute("drop table if exists api_hooked")
1905 | self.cur.execute("drop table if exists privs")
1906 |
1907 | self.cur.execute("drop table if exists kernel_mods")
1908 | self.cur.execute("drop table if exists kernel_mods_impfunc")
1909 | self.cur.execute("drop table if exists kernel_mods_strings")
1910 | self.cur.execute("drop table if exists kernel_mods_irp")
1911 | self.cur.execute("drop table if exists kernel_mods_callbacks")
1912 | self.cur.execute("drop table if exists kernel_mods_timers")
1913 |
1914 | self.cur.execute("drop table if exists regpath")
1915 | self.cur.execute("drop table if exists shimcache")
1916 | self.cur.execute("drop table if exists service")
1917 | self.cur.execute("drop table if exists ssdt_hooked")
1918 | self.cur.execute("drop table if exists files")
1919 |
1920 | def make_tables(self):
1921 | debug.info("Making new DB tables...")
1922 |
1923 | self.cur.execute("create table if not exists version(version unique)")
1924 | self.cur.execute("insert into version values(?)", (g_version,))
1925 |
1926 | self.cur.execute("create table if not exists hidden(pid unique, result, offset unique, cmdline)")
1927 | self.cur.execute("create table if not exists done(pid unique, injected, strings, vaddump, impfunc, handles, netinfo, dllpath, api_hooked, privs)")
1928 | self.cur.execute("create table if not exists injected(pid, start, size)")
1929 | self.cur.execute("create table if not exists strings(pid, string)")
1930 | self.cur.execute("create table if not exists vaddump(pid unique, size)")
1931 | self.cur.execute("create table if not exists impfunc(pid, iat, call, mod_name, func_name)")
1932 | self.cur.execute("create table if not exists handles(pid, type, name)")
1933 | self.cur.execute("create table if not exists netinfo(pid, protocol, laddr, lport, raddr, rport, state)")
1934 | self.cur.execute("create table if not exists dllpath(pid, path, hidden)")
1935 | self.cur.execute("create table if not exists api_hooked(pid, mode, type, hooked_module, hooked_func, hooking_module)")
1936 | self.cur.execute("create table if not exists privs(pid, priv)")
1937 |
1938 | self.cur.execute("create table if not exists kernel_mods(offset unique, name, base, size, fullname)")
1939 | self.cur.execute("create table if not exists kernel_mods_impfunc(base, iat, call, mod_name, func_name)")
1940 | self.cur.execute("create table if not exists kernel_mods_strings(base, string)")
1941 | self.cur.execute("create table if not exists kernel_mods_irp(base, mj_func, addr, mod_name)")
1942 | self.cur.execute("create table if not exists kernel_mods_callbacks(base, type, callback, detail)")
1943 | self.cur.execute("create table if not exists kernel_mods_timers(base, offset, duetime, period, signaled, routine)")
1944 |
1945 | self.cur.execute("create table if not exists regpath(path unique)")
1946 | self.cur.execute("create table if not exists shimcache(path, modified)")
1947 | self.cur.execute("create table if not exists service(service_name, display_name, bin_path)")
1948 | self.cur.execute("create table if not exists ssdt_hooked(table_idx, entry_idx, syscall_ptr, syscall_name, hooking_mod_name, inline_hooked)")
1949 | self.cur.execute("create table if not exists files(offset, inode, name, extension, path, size)")
1950 |
1951 | def init_db(self, f_erase):
1952 | global g_cache_path
1953 | image_url = self._config.opts["location"]
1954 | image_path = urllib.url2pathname(image_url.split('///')[1])
1955 |
1956 | if self._config.cache_path is None:
1957 | g_cache_path = os.path.join(os.path.dirname(image_path), os.path.basename(image_path).split('.')[0] + '_cache')
1958 | if not os.path.exists(g_cache_path):
1959 | os.mkdir(g_cache_path)
1960 | else:
1961 | g_cache_path = self._config.cache_path
1962 | self.db = sqlite3.connect(os.path.join(g_cache_path, os.path.basename(image_path).split('.')[0] + '.db'))
1963 | self.cur = self.db.cursor()
1964 |
1965 | # version is null or not matched, make new tables
1966 | self.cur.execute("select * from sqlite_master where type='table'")
1967 | if self.cur.fetchone() == None:
1968 | self.make_tables()
1969 | else:
1970 | self.cur.execute("select * from version")
1971 | db_version = self.cur.fetchone()[0]
1972 | if db_version != g_version or f_erase:
1973 | self.clear_tables()
1974 | self.make_tables()
1975 | else:
1976 | debug.info("Results in existing database loaded")
1977 |
1978 | def parse_cmdline(self, process):
1979 | debug.debug(process.ImageFileName)
1980 | #if (str(process.ImageFileName) != "System") and (not isinstance(process.Peb, obj.NoneObject)):
1981 | if not isinstance(process.Peb.ProcessParameters.CommandLine.v(), obj.NoneObject):
1982 | debug.debug('Hi pid={0}'.format(process.UniqueProcessId))
1983 | cmdline = str(process.Peb.ProcessParameters.CommandLine).lower()
1984 | debug.debug('name="{0}", cmdline="{1}" (pid{2})'.format(process.ImageFileName, cmdline or None, process.UniqueProcessId))
1985 | if cmdline is not None:
1986 | name_idx = cmdline.find(str(process.ImageFileName).lower())
1987 | debug.debug('name_idx={0}'.format(name_idx))
1988 | if name_idx != -1:
1989 | a = re.search(r'\.exe|\.msi|\.ocx|\.dll|\.cab|\.cat|\.js|\.vbs|\.scr', cmdline) # any other?
1990 | if a is not None:
1991 | debug.debug("name='{0}', path='{1}', arg='{2}' (pid{3})".format(process.ImageFileName, cmdline[:a.end()].strip('" '), cmdline[a.end():].strip('" '), process.UniqueProcessId))
1992 | return cmdline[:a.end()].strip('" '), cmdline[a.end():].strip('" ')
1993 | return 'none', 'none'
1994 |
1995 | # based on psxview
1996 | def extract_all_active_procs(self, not_carve):
1997 | kernel_space = utils.load_as(self._config)
1998 | flat_space = utils.load_as(self._config, astype = 'physical')
1999 | self.cur.execute("select count(*) from hidden")
2000 | carved = self.cur.fetchone()[0]
2001 |
2002 | procs = []
2003 | if carved > 0:
2004 | self.cur.execute("select offset from hidden")
2005 | for record in self.cur.fetchall():
2006 | if isinstance(self.virtual_process_from_physical_offset(kernel_space, record[0]), obj.NoneObject):
2007 | procs.append(obj.Object("_EPROCESS", offset = record[0], vm = flat_space))
2008 | else:
2009 | procs.append(self.virtual_process_from_physical_offset(kernel_space, record[0]))
2010 | #return [self.virtual_process_from_physical_offset(kernel_space, record[0]) for record in self.cur.fetchall()]
2011 | #return [obj.Object("_EPROCESS", offset = record[0], vm = flat_space) for record in self.cur.fetchall()]
2012 | #return [obj.Object("_EPROCESS", offset = record[0], vm = kernel_space) for record in self.cur.fetchall()]
2013 | else:
2014 | records = []
2015 | procs = []
2016 | if not_carve:
2017 | debug.info('getting processes from linked list... (-n option enabled)')
2018 | procs = list(tasks.pslist(kernel_space))
2019 | for proc in procs:
2020 | cmdline = proc.Peb.ProcessParameters.CommandLine.v() or ''
2021 | offset = kernel_space.vtop(proc.obj_offset)
2022 | records.append((proc.UniqueProcessId.v(), False, offset, cmdline))
2023 | else:
2024 | debug.info("[time-consuming task] extracting all processes including hidden/dead ones...")
2025 | all_tasks = list(tasks.pslist(kernel_space))
2026 | ps_sources = {}
2027 | ps_sources['pslist'] = self.check_pslist(all_tasks)
2028 | ps_sources['psscan'] = self.check_psscan()
2029 | #ps_sources['thrdproc'] = self.check_thrdproc(kernel_space)
2030 | ps_sources['pspcid'] = self.check_pspcid(kernel_space)
2031 |
2032 | seen_offsets = []
2033 | pids = []
2034 | for source in ps_sources.values():
2035 | for offset in source.keys():
2036 | if offset not in seen_offsets:
2037 | seen_offsets.append(offset)
2038 | #if source[offset].ExitTime != 0: # exclude dead process even if it is included in process list
2039 | #if (source[offset].ExitTime != 0) and (not ps_sources['pslist'].has_key(offset)): # exclude dead process not included in process list <- cannot resolve from ethread!
2040 | # continue
2041 | if isinstance(self.virtual_process_from_physical_offset(kernel_space, offset), obj.NoneObject):
2042 | ep = obj.Object("_EPROCESS", offset = offset, vm = flat_space)
2043 | else:
2044 | ep = self.virtual_process_from_physical_offset(kernel_space, offset)
2045 | if source[offset].UniqueProcessId not in pids: # cross view in crashdump file seems to be buggy (duplicated processes) :-(
2046 | result = not (ps_sources['pslist'].has_key(offset) and ps_sources['psscan'].has_key(offset) and ps_sources['pspcid'].has_key(offset))
2047 | if result == True and source[offset].ExitTime != 0:
2048 | # I checked there were some dead processes without exit time, but I don't know other methods to judge them...
2049 | result = False
2050 | cmdline = ep.Peb.ProcessParameters.CommandLine.v() or ''
2051 | if isinstance(ep.UniqueProcessId.v(), obj.NoneObject):
2052 | debug.warning('skipping NoneObject from flat_space')
2053 | continue
2054 | #pid = 0 if isinstance(ep.UniqueProcessId.v(), obj.NoneObject) else ep.UniqueProcessId.v()
2055 | records.append((ep.UniqueProcessId.v(), bool(result), offset, cmdline))
2056 | procs.append(ep)
2057 | pids.append(ep.UniqueProcessId)
2058 |
2059 | self.cur.executemany("insert or ignore into hidden values (?, ?, ?, ?)", records)
2060 | debug.debug('{0} procs carved'.format(len(procs)))
2061 | return procs
2062 |
2063 | def extract_all_loaded_kernel_mods(self):
2064 | self.cur.execute("select count(*) from kernel_mods")
2065 | cnt = self.cur.fetchone()[0]
2066 | kernel_space = utils.load_as(self._config)
2067 |
2068 | if cnt > 0:
2069 | self.cur.execute("select offset from kernel_mods")
2070 | return [obj.Object("_LDR_DATA_TABLE_ENTRY", offset = record[0], vm = kernel_space) for record in self.cur.fetchall()]
2071 | else:
2072 | # currently get kernel modules from linked list because the result of modscan is noisy. need to improve for hidden malicious modules in the future
2073 | debug.info("[time-consuming task] extracting all loaded kernel modules...")
2074 | mods = list(win32.modules.lsmod(kernel_space))
2075 | #records = [(mod.obj_offset, str(mod.BaseDllName or ''), mod.DllBase.v(), mod.SizeOfImage.v(), str(mod.FullDllName or '')) for mod in mods]
2076 | records = [(str(mod.obj_offset), str(mod.BaseDllName or ''), str(mod.DllBase.v()), mod.SizeOfImage.v(), str(mod.FullDllName or '')) for mod in mods]
2077 | self.cur.executemany("insert or ignore into kernel_mods values (?, ?, ?, ?, ?)", records)
2078 | #for record in records:
2079 | # print record
2080 | # self.cur.execute("insert or ignore into kernel_mods values (?, ?, ?, ?, ?)", record)
2081 | return mods
2082 |
2083 | def filter_mods(self, mods):
2084 | if self._config.kmod is not None:
2085 | try:
2086 | modlist = [m.lower() for m in self._config.kmod.split(',')]
2087 | except ValueError:
2088 | debug.error("Invalid kmod option {0}".format(self._config.kmod))
2089 |
2090 | filtered_mods = [mod for mod in mods if str(mod.BaseDllName or '').lower() in modlist]
2091 | if len(filtered_mods) == 0:
2092 | debug.error("Cannot find kernel module {0}.".format(self._config.kmod))
2093 | return filtered_mods
2094 | return mods
2095 |
2096 | def calculate(self):
2097 | # load IOCs
2098 | scanner = IOC_Scanner()
2099 | if self._config.ioc_dir is None:
2100 | debug.error("You should specify IOCs directory")
2101 | scanner.insert(self._config.ioc_dir)
2102 |
2103 | # display mode
2104 | if self._config.show:
2105 | definitions = scanner.display()
2106 | yield definitions
2107 | else:
2108 | self.init_db(self._config.erase)
2109 | scanner.prepare(self.cur, self._config)
2110 | procs = [None]
2111 | kmods = [None]
2112 | if scanner.with_item_all('Process'):
2113 | with Timer() as t:
2114 | procs = self.extract_all_active_procs(self._config.not_carve)
2115 | debug.debug("=> elapsed scan: {0}s for process carving".format(t.secs))
2116 | self.total_secs += t.secs
2117 | #print procs
2118 | # pre-generated process entries in db for all updated tasks (e.g., netinfo)
2119 | for process in self.filter_tasks(procs):
2120 | #pid = 0 if isinstance(process.UniqueProcessId.v(), obj.NoneObject) else process.UniqueProcessId.v()
2121 | self.cur.execute("insert or ignore into done values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", (process.UniqueProcessId.v(), False, False, False, False, False, False, False, False, False))
2122 | if scanner.with_item_all('Driver'):
2123 | kmods = self.extract_all_loaded_kernel_mods()
2124 |
2125 | if len(procs) > 1:
2126 | debug.info('{0} processes found'.format(len(procs)))
2127 | if len(kmods) > 1:
2128 | debug.info('{0} kernel modules found'.format(len(kmods)))
2129 | self.db.commit() # save procs/mods into db
2130 |
2131 | for iocid in scanner.iocs:
2132 | debug.info('Scanning iocid={0}'.format(iocid))
2133 | if (not scanner.with_item(iocid, 'Process')) and (not scanner.with_item(iocid, 'Driver')):
2134 | debug.debug('Scanning... (Process=None, Driver=None)')
2135 | with Timer() as t:
2136 | result = scanner.scan(iocid, None, None)
2137 | debug.debug("=> elapsed scan: {0}s".format(t.secs))
2138 | self.total_secs += t.secs
2139 | if result != '':
2140 | yield None, None, result
2141 | elif scanner.with_item(iocid, 'Process') and (not scanner.with_item(iocid, 'Driver')):
2142 | debug.debug('Scanning... (Driver=None)')
2143 | for process in self.filter_tasks(procs):
2144 | with Timer() as t:
2145 | result = scanner.scan(iocid, process, None)
2146 | debug.debug("=> elapsed scan: {0}s, pid={1}".format(t.secs, process.UniqueProcessId))
2147 | self.total_secs += t.secs
2148 | if result != '':
2149 | yield process, None, result
2150 | elif (not scanner.with_item(iocid, 'Process')) and scanner.with_item(iocid, 'Driver'):
2151 | debug.debug('Scanning... (Process=None)')
2152 | for kmod in self.filter_mods(kmods):
2153 | with Timer() as t:
2154 | result = scanner.scan(iocid, None, kmod)
2155 | debug.debug("=> elapsed scan: {0}s, kmod={1}, kmod_base=0x{2:x}".format(t.secs, str(kmod.BaseDllName or ''), kmod.DllBase))
2156 | self.total_secs += t.secs
2157 | if result != '':
2158 | yield None, kmod, result
2159 | else:
2160 | debug.warning('Combination of ProcessItem and DriverItem will take very long time. If possible, define separately or specify PID/kmod.')
2161 | debug.debug('Scanning...')
2162 | for kmod in self.filter_mods(kmods):
2163 | for process in self.filter_tasks(procs):
2164 | with Timer() as t:
2165 | result = scanner.scan(iocid, process, kmod)
2166 | pid = ', pid={0}'.format(process.UniqueProcessId) if process is not None else ''
2167 | kmod_name = ', kmod={0}'.format(str(kmod.BaseDllName or '')) if kmod is not None else ''
2168 | debug.debug("=> elapsed scan: {0}s{1}(base=0x{2:x}){3}".format(t.secs, kmod_name, kmod.DllBase, pid))
2169 | self.total_secs += t.secs
2170 | if result != '':
2171 | yield process, kmod, result
2172 | self.db.commit()
2173 |
2174 | def render_text(self, outfd, data):
2175 | if self._config.show:
2176 | for definitions in data:
2177 | outfd.write(definitions)
2178 | outfd.write('++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n')
2179 | else:
2180 | for process, kmod, ioc_result in data:
2181 | outfd.write('***************************************************************\n')
2182 | outfd.write(ioc_result)
2183 | if process is not None:
2184 | outfd.write("Note: ProcessItem was evaluated only in {0} (Pid={1})\n".format(process.ImageFileName, process.UniqueProcessId))
2185 | if kmod is not None:
2186 | outfd.write("Note: DriverItem was evaluated only in {0} (base=0x{1:x})\n".format(str(kmod.BaseDllName or ''), kmod.DllBase))
2187 | outfd.write('***************************************************************\n')
2188 |
2189 | self.db.commit()
2190 | self.cur.close()
2191 | debug.info("=> elapsed scan total: about {0} s".format(self.total_secs))
2192 |
2193 |
--------------------------------------------------------------------------------