├── .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 | --------------------------------------------------------------------------------