This commit is contained in:
DustInDark
2021-12-19 19:14:16 +09:00
39 changed files with 835 additions and 1201 deletions

View File

@@ -1,13 +1,57 @@
、__⌒ ̄}
______/
〃〓/ ̄ > <  ̄\〓〃
ミ☆/: :: :: >☆彡
★≡〃\/〉:: ::〈\/ ≡〃★
●※○ ^^^^^^^^ ○※●
〃≡★ Merry Christmas ★≡〃
☆〓 〓☆
〃≡★ ;) ★≡〃
●※○- ,_】【_, ,-○※●
★〃≡〓 〓≡〃★
ミ☆-★※★-☆彡
,,
,,,,
.,,,,,,,,,,,..
.,,,,,,,,,,,,,,,,
,,,,,,,,,,,
,,,,,,,,,,,
,,,,(((,,,,.
. ,((((((
(((((((((
(((((((((((((
(((((((,,,,((((*
((((((((,,,,,,,((((
((((((((((,,,,,,(((((((
((*,(((((((((((((((((((((/
((* ((. .((((((((((((((((
((((( Merry (((((((((
(((((((, Christmas (((((((((((
((((((((/(((((( (((((((((((((((((((((
(((((((*******(((((((((((((((((((((((((((((((
(((((((((*******(((((((((((((((((((((((((((((((((
.//////(((((((((((((((((((((((((((,,,,,(((((((((//////*
(((((((((((((((((((((((((((,,,,,,,(((((((((
(( ((((((((((((((((((((((,,,,,((((((((((((
,((. .((((((((((((((((((((((((((((((((((
(((((((( from ((((((((((((((((((((((((((((((*
(((((((((((( , (((((((((((((((((( *.(*
.((((((((((((((((((((((((((((((((((((((((( (((
(((((((((((((((((((((((((((((((((((((((/ Yamato ,(((((((
(((((((((((((((((((((((((((((((((((((((( ((((((((((((
((((((((((((((((((((((((,,,((((( (, ,((((((((((
((((((((((((((((((((((((,,,,,,,((((((((((((((((((((((((((
*(((( ((((((((((((((((/,,,,,,((((((((((((((((******((((((
(((( ((((((((((((((((((((((((((((((((********((((((.
(((((( Security! ((((((((((((((((((((((((((((((****((((((((((
((((((((( (((((((((((((((((((((((((((((((((((((((((((
,(((((((((((((((( .(( ((((((((((((((((((( ( (((((((((((((
((((((((((((((((,,,,,((((((((( (((((((((((( Ho ho ho!!! ,(((((((((((((.
(((((((((((((((((,,,,,,,(((((((((((((((((((((( ((((((((((((((((((((
((((((((((((((((((((,,,,,((((((((((((((((((((((((((( ,(((((((/,,,,,((((((((((((((((
(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((,,,,,,,(((((((((((((((((
.(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((,,,,*(((((((((((((((((((/
(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((
///////////
///////////
///////////
///////////
///////////
///////////
*****************************
*****************************
%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%#%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%
#%%%%%%%%%%%%%%%#%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%

View File

@@ -1,10 +1,26 @@
_〆
(∴)
( ̄ ̄ ̄)
( ̄ ̄ ̄ ̄)
[二◆二二◆二]
|◇ ● ◇|
|◆ ◆|
|____|
A Happy New Year!!
@@
@@@@@ @@ @@@@,
@@& @@@@@@@@@@@@@@@@@. @@@@@@@@@@@@@@@@@@
@@@@@@@@@@@( @@@@@@ @@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@.
.@@@@@@@@@@@ @@@@@ #@@@ @@@@@@ @@@@@@@@@@@@@
@@@ @@@@ @@@@@@@@@@@@@@( @@@@@@@
,@@@ (@@@ @@ .@@@@@
@@@@ @@@@@@@ %% @@@@@
@@@@ @@@@ @@@@@@@%*.,@@@@@@ @@@@,
@@@@ @@@@@@ @@@@@ @@@@. @@@@@@@@@
@@@@ @@@ @@@@@@@@ @@@@@ @@@@@@@ @@@@@@@@@@@@@@@@@
@@@, @@@@& @@@@ @@@@
@@@ %@@@@@@ %@@@@, @@@@ @@@@
@@@@@@@@# @@@@@ @@@@ @@@@
@@@ (@@@@@@@@@@ @@@@ @@@@
@@@@@@@@@@@@@@@@@@@@@ @@@@ @@@@ %@@@
@@@@@@( @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@ @@@@@@@ ,@@@@@@@@@@@@@@@@@@@@@@/ &@@@@@@@@@@@@@@&
@@@@@ @@@@@@@ @@@@@@@@@@ @@@@#
@@@@@ @@@@@@@ *@
@@@@@ #@@@@
Happy New Year from Yamato Security!!!
Akemashite Omedetou Gozaimasu!
Honnen mo yoroshiku onegai shimasu!

View File

@@ -1,7 +1,8 @@
██╗ ██╗ █████╗ ██╗ ██╗ █████╗ ██████╗ ██╗ ██╗███████╗ █████╗
██║ ██║██╔══██╗╚██╗ ██╔╝██╔══██╗██╔══██╗██║ ██║██╔════╝██╔══██╗
███████║███████║ ╚████╔╝ ███████║██████╔╝██║ ██║███████╗███████║
██╔══██║██╔══██║ ╚██╔╝ ██╔══██║██╔══██╗██║ ██║╚════██║██╔══██║
██║ ██║██║ ██║ ██║ ██║ ██║██████╔╝╚██████╔╝███████║██║ ██║
╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═════╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝
by Yamato Security
by Yamato Security

View File

@@ -1,5 +1,3 @@
Today is Ninja Day (2/22)!
.`,I>>+<;"'
.,}u#zcccccz*#W&jI.
@@ -35,3 +33,7 @@
[$#ccccccccccccB$%WMcnnnnnnnnz$$$B&cc#@8nnnnnnu#@$$&*cccccccMB*ccccc#$$$,
@%ccccccccccccz$#cxcnnnnnnnnM$$$@zcccc*$8nnnnnnnnW8$$%MMMM*#&zccccccc@$$|
"$*cccccccccccc#$cnx@WnnnnnnW$$$$Wccccc#@$8unnnn*@Wu&@$$$$$$@#cccccccc&$$W
Happy Ninja Day! Nin Nin! (2/22)!
from Yamato Security

View File

@@ -1,43 +1,38 @@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@#------@@@@@@@@--------@@@@@@@@--------@@@@@@@@--------@@@@@@@@--------@@@@@@@@------#@@
@@* @@@@@@@% @@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@@ *@@
@@* @@@@@@@% @@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@@ *@@
@@#------********--------********--------********--------********--------********=-----#@@
@@@@@@@@@ @@@@@@@# @@@@@@@@ @@@@@@@@ @@@@@@@% @@@@@@@@@
@@@@@@@@@ @@@@@@@# @@@@@@@@ @@@@@@@@ @@@@@@@% @@@@@@@@@
@@@@@@@@@ @@@@@@@# @@@@@@@@ @@@@@@@@ @@@@@@@% @@@@@@@@@
@@@@@@@@@-------=@@@@@@@%-------=@@@@@@@@-------=@@@@@@@@-------=@@@@@@@@-------=@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@=:@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*.+@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@= *@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%:-@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*.*@@@@@@@@@@@@
@@@@@@@@@@@%*=-:::-=*@@@@@@@@@%*=-:::-+#@@@@@@@@@#+=-::-=+#@@@@@@@@@#+--::: =%@@@@@@@@@@@@
@@@@@@@@@*: = :#@@@@+. -%@@@@= =@@@@%- .+@@@@@@@@@@
@@@@@@@@: :: #* -@%. - *= =@* -. =#. *@+ :: -#: .#@@@@@@@@
@@@@@@@. : -. . : .= : : = . %@@@@@@@
@@@@@@+ =*. -+=. .++ =+= :*= =+- -*- .++- . -@@@@@@@
@@@@@@= *@@@@=-#@@@@+:+@% .*@@@%==#@@@@=:*@# :#@@@#==%@@@%--#@+ -%@@@*-=@@@@#--%@-:@@@@@@@
@@@@-== #@@@@@@@@@@@@@@@+ %@@@@@@@@@@@@@@@- .@@@@@@@@@@@@@@@@. :@@@@@@@@@@@@@@@@ :=:@@@@@
@@@@#. =*#@@@@@@@@@@@* *-.%@@@@@@@@@@@@@= #::@@@@@@@@@@@@@@:.# -@@@@@@@@@@@%*=. .*@@@@@
@@@@@@#- .:-=+*#%*::%@@*.-#@@@@@@@@@+.-%@@=.=%@@@@@@@@%=.=@@@=.+%#*+=-:. :*@@@@@@@
@@@@@@@@@*- ..::- :====-. .=++++++: .-====-. :::.. :*@@@@@@@@@@
@@@@@@@@@@@@%+-. .-+#@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@%#*+-::. ..:-=*#%@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@%##***++++=============++++***##%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@======@@#--%@@#=+@@+=#@#=--=#@#==@@==#@@+-=@@@+=*@@==%==#@@@*====+%@@@#--%@@*=*@%==%==@@
@@*+ ##@@. .@@* .@= :@- .+*: -# %% +@# -@@- =@: =@ *@@@- -#+ #@@. .@@= .@* % %@
@@@# @@@: =- -@* .@@ #@@% @# *@% *. *@- =@@ #@@@= =@@= -@- -= -@@+ .#@:.@@
@@@# @@+ :: ** .@: :@+ -- +@@# #@@: .:: %- =%. =@ *@@@- :+- .%* :: +@@+ @@@=-@@
@@@%++@@*+%@@#+*%+*@@*+#@@*++*@@@@@**@@@++@@@*+##+#@%++%++%@@@#+++*#@@*+%@@%+*@@%**@@@*+@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@:.......@@@@@@@@:.......@@@@@@@@:.......@@@@@@@@:.......%@@@@@@@:.......@@@@@@@@@
@@@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@@ %@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@@ %@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@@ %@@@@@@@ @@@@@@@@@
@@#:::::-########::::::::########::::::::########::::::::########::::::::########::::::#@@
@@* .@@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@@ *@@
@@* .@@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@@ *@@
@@%+++++*@@@@@@@@++++++++@@@@@@@@++++++++@@@@@@@@++++++++@@@@@@@@++++++++@@@@@@@@++++++%@@
####
##.##
##.(#
.#.,#
#..#
##.#*
##.##
*#.##
#.##
#(/#
##.#
(#.#,
#.#(
#*##
####
.,####* ##(#
##.##../##((####.((####,#
####(.(....(((((##,.#(((######
###.(##(((#######..##......#((((##/
########((((####.(##########(*(((#,##..#
#################..####*..(((######*,##..#
/###############......*####((((###.*##..####
#######(,#####################*#####.((((####
#(###,,,,#############(/######...##..#(######
#,,,,,,,,*#######,,,,,,,,######(.######/*/###
#,,,,,,,,,,,,,,,,,,,,,,,#######/#############
#,,,,,,,,,,,,,,,,,,,,,,##########,,,(#######
#,,,,,,,,,,,,,,,,,,,,,#######,,,,,,#######
#,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,######
##,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*##
##,,,,,,,,,,,,,,,,,,,,,,,,,,,,,##
###,,,,,,,,,,,,,,,,,,,,,,##.
.#################
HAPPY TAKOYAKI DAY!!! (8/8)
from Yamato Security

View File

@@ -177,4 +177,5 @@ WorkstationName,Event.EventData.WorkstationName
param1,Event.EventData.param1
param2,Event.EventData.param2
provider_Name,Event.EventData.Provider_Name
sha1,Event.EventData.Hashes_sha1
service,Event.EventData.Service
sha1,Event.EventData.Hashes_sha1

View File

@@ -0,0 +1,8 @@
4fe151c2-ecf9-4fae-95ae-b88ec9c2fca6 # ./rules/sigma/other/msexchange/win_exchange_transportagent.yml
c92f1896-d1d2-43c3-92d5-7a5b35c217bb # ./rules/sigma/other/msexchange/win_exchange_cve_2021_42321.yml
9f7aa113-9da6-4a8d-907c-5f1a4b908299 # ./rules/sigma/deprecated/powershell_syncappvpublishingserver_exe.yml
# Replaced by hayabusa rules
c265cf08-3f99-46c1-8d59-328247057d57 # ./rules/sigma/builtin/security/win_user_added_to_local_administrators.yml
66b6be3d-55d0-4f47-9855-d69df21740ea # ./rules/sigma/builtin/security/win_user_creation.yml
7b449a5e-1db5-4dd0-a2dc-4e3a67282538 # ./rules/sigma/builtin/security/win_hidden_user_creation.yml

View File

@@ -2,4 +2,5 @@
c92f1896-d1d2-43c3-92d5-7a5b35c217bb
7b449a5e-1db5-4dd0-a2dc-4e3a67282538
c265cf08-3f99-46c1-8d59-328247057d57
66b6be3d-55d0-4f47-9855-d69df21740ea
66b6be3d-55d0-4f47-9855-d69df21740ea
9f7aa113-9da6-4a8d-907c-5f1a4b908299

View File

@@ -0,0 +1,9 @@
0f06a3a5-6a09-413f-8743-e6cf35561297 # ./rules/sigma/wmi_event/sysmon_wmi_event_subscription.yml
b0d77106-7bb0-41fe-bd94-d1752164d066 # ./rules/sigma/builtin/security/win_rare_schtasks_creations.yml
66bfef30-22a5-4fcd-ad44-8d81e60922ae # ./rules/sigma/builtin/system/win_rare_service_installs.yml
e98374a6-e2d9-4076-9b5c-11bdb2569995 # ./rules/sigma/builtin/security/win_susp_failed_logons_single_source.yml
6309ffc4-8fa2-47cf-96b8-a2f72e58e538 # ./rules/sigma/builtin/security/win_susp_failed_logons_single_source2.yml
61ab5496-748e-4818-a92f-de78e20fe7f1 # ./rules/sigma/process_creation/win_multiple_suspicious_cli.yml
add2ef8d-dc91-4002-9e7e-f2702369f53a # ./rules/sigma/builtin/security/win_susp_failed_remote_logons_single_source.yml
196a29c2-e378-48d8-ba07-8a9e61f7fab9 # ./rules/sigma/builtin/security/win_susp_failed_logons_explicit_credentials.yml
72124974-a68b-4366-b990-d30e0b2a190d # ./rules/sigma/builtin/security/win_metasploit_authentication.yml

View File

@@ -3,4 +3,8 @@ b0d77106-7bb0-41fe-bd94-d1752164d066
66bfef30-22a5-4fcd-ad44-8d81e60922ae
e98374a6-e2d9-4076-9b5c-11bdb2569995
6309ffc4-8fa2-47cf-96b8-a2f72e58e538
61ab5496-748e-4818-a92f-de78e20fe7f1
add2ef8d-dc91-4002-9e7e-f2702369f53a
196a29c2-e378-48d8-ba07-8a9e61f7fab9
72124974-a68b-4366-b990-d30e0b2a190d
b20f6158-9438-41be-83da-a5a16ac90c2b

View File

@@ -1,20 +0,0 @@
title: PowerShell Execution Remote Command
title_jp: Powershellのリモートコマンドの実行
description: Powershell command executed remotely.
description_jp: Powershell command executed remotely.
author: Eric Conrad, Zach Mathis
mitre_attack: T1059
level: medium
detection:
selection:
Channel: Microsoft-Windows-PowerShell/Operational
EventID: 4104
Path: null
ScriptBlockText|re: '.+'
# condition: selection
falsepositives:
- normal system usage
output: 'Command: %ScriptBlockText%'
output: 'コマンド: %ScriptBlockText%'
creation_date: 2020/11/08
updated_date: 2021/11/06

View File

@@ -1,14 +0,0 @@
title: Command Line Logging
description: Command line logging.
author: Eric Conrad, Zach Mathis
detection:
selection:
Channel: Security
EventID: 4688
CommandLine|re: '.+'
# condition: selection
falsepositives:
- unknown
output: 'CommandLine:%CommandLine% : ParentProcessName:%ParentProcessName%'
creation_date: 2020/11/8
updated_date: 2021/11/8

View File

@@ -1,30 +0,0 @@
title: Hidden Local User Creation
author: Christian Burkard
date: 2021/05/03
description: Detects the creation of a local hidden user account which should not
happen for event ID 4720.
detection:
SELECTION_1:
EventID: 4720
SELECTION_2:
TargetUserName: '*$'
condition: (SELECTION_1 and SELECTION_2)
falsepositives:
- unknown
fields:
- EventCode
- AccountName
id: 7b449a5e-1db5-4dd0-a2dc-4e3a67282538
level: high
logsource:
product: windows
service: security
references:
- https://twitter.com/SBousseaden/status/1387743867663958021
status: experimental
tags:
- attack.persistence
- attack.t1136.001
yml_filename: win_hidden_user_creation.yml
yml_path: /Users/user/Documents/YamatoSecurity/sigma/rules/windows/builtin

View File

@@ -1,32 +0,0 @@
title: User Added to Local Administrators
author: Florian Roth
date: 2017/03/14
description: This rule triggers on user accounts that are added to the local Administrators
group, which could be legitimate activity or a sign of privilege escalation activity
detection:
SELECTION_1:
EventID: 4732
SELECTION_2:
TargetUserName: Administr*
SELECTION_3:
TargetSid: S-1-5-32-544
SELECTION_4:
SubjectUserName: '*$'
condition: ((SELECTION_1 and (SELECTION_2 or SELECTION_3)) and not (SELECTION_4))
falsepositives:
- Legitimate administrative activity
id: c265cf08-3f99-46c1-8d59-328247057d57
level: medium
logsource:
product: windows
service: security
modified: 2021/07/07
status: stable
tags:
- attack.privilege_escalation
- attack.t1078
- attack.persistence
- attack.t1098
yml_filename: win_user_added_to_local_administrators.yml
yml_path: /Users/user/Documents/YamatoSecurity/sigma/rules/windows/builtin

View File

@@ -1,33 +0,0 @@
title: Local User Creation
author: Patrick Bareiss
date: 2019/04/18
description: Detects local user creation on windows servers, which shouldn't happen
in an Active Directory environment. Apply this Sigma Use Case on your windows
server logs and not on your DC logs.
detection:
SELECTION_1:
EventID: 4720
condition: SELECTION_1
falsepositives:
- Domain Controller Logs
- Local accounts managed by privileged account management tools
fields:
- EventCode
- AccountName
- AccountDomain
id: 66b6be3d-55d0-4f47-9855-d69df21740ea
level: low
logsource:
product: windows
service: security
modified: 2020/08/23
references:
- https://patrick-bareiss.com/detecting-local-user-creation-in-ad-with-sigma/
status: experimental
tags:
- attack.persistence
- attack.t1136
- attack.t1136.001
yml_filename: win_user_creation.yml
yml_path: /Users/user/Documents/YamatoSecurity/sigma/rules/windows/builtin

View File

@@ -1,27 +0,0 @@
title: WMI Event Subscription
author: Tom Ueltschi (@c_APT_ure)
date: 2019/01/12
description: Detects creation of WMI event subscription persistence method
detection:
SELECTION_1:
EventID: 19
SELECTION_2:
EventID: 20
SELECTION_3:
EventID: 21
condition: (SELECTION_1 or SELECTION_2 or SELECTION_3)
falsepositives:
- exclude legitimate (vetted) use of WMI event subscription in your network
id: 0f06a3a5-6a09-413f-8743-e6cf35561297
level: high
logsource:
category: wmi_event
product: windows
status: experimental
tags:
- attack.t1084
- attack.persistence
- attack.t1546.003
yml_filename: sysmon_wmi_event_subscription.yml
yml_path: /Users/user/Documents/YamatoSecurity/sigma/rules/windows/wmi_event

View File

@@ -1,40 +0,0 @@
title: Metasploit SMB Authentication
author: Chakib Gzenayi (@Chak092), Hosni Mribah
date: 2020/05/06
description: Alerts on Metasploit host's authentications on the domain.
detection:
SELECTION_1:
EventID: 4625
SELECTION_2:
EventID: 4624
SELECTION_3:
LogonType: 3
SELECTION_4:
AuthenticationPackageName: NTLM
SELECTION_5:
WorkstationName|re: ^[A-Za-z0-9]{16}$
SELECTION_6:
ProcessName|re: ^$
SELECTION_7:
EventID: 4776
SELECTION_8:
Workstation|re: ^[A-Za-z0-9]{16}$
condition: (((SELECTION_1 or SELECTION_2) and SELECTION_3 and SELECTION_4 and
SELECTION_5) or (SELECTION_6 and SELECTION_7 and SELECTION_8))
falsepositives:
- Linux hostnames composed of 16 characters.
id: 72124974-a68b-4366-b990-d30e0b2a190d
level: high
logsource:
product: windows
service: security
modified: 2021/07/07
references:
- https://github.com/rapid7/metasploit-framework/blob/master/lib/rex/proto/smb/client.rb
tags:
- attack.lateral_movement
- attack.t1077
- attack.t1021.002
yml_filename: win_metasploit_authentication.yml
yml_path: /Users/user/Documents/YamatoSecurity/sigma/rules/windows/builtin

View File

@@ -1,109 +0,0 @@
title: Quick Execution of a Series of Suspicious Commands
author: juju4
date: 2019/01/16
description: Detects multiple suspicious process in a limited timeframe
detection:
SELECTION_1:
EventID: 1
SELECTION_10:
CommandLine: '*nbtstat.exe*'
SELECTION_11:
CommandLine: '*net.exe*'
SELECTION_12:
CommandLine: '*netsh.exe*'
SELECTION_13:
CommandLine: '*nslookup.exe*'
SELECTION_14:
CommandLine: '*ping.exe*'
SELECTION_15:
CommandLine: '*quser.exe*'
SELECTION_16:
CommandLine: '*qwinsta.exe*'
SELECTION_17:
CommandLine: '*reg.exe*'
SELECTION_18:
CommandLine: '*runas.exe*'
SELECTION_19:
CommandLine: '*sc.exe*'
SELECTION_2:
CommandLine: '*arp.exe*'
SELECTION_20:
CommandLine: '*schtasks.exe*'
SELECTION_21:
CommandLine: '*ssh.exe*'
SELECTION_22:
CommandLine: '*systeminfo.exe*'
SELECTION_23:
CommandLine: '*taskkill.exe*'
SELECTION_24:
CommandLine: '*telnet.exe*'
SELECTION_25:
CommandLine: '*tracert.exe*'
SELECTION_26:
CommandLine: '*wscript.exe*'
SELECTION_27:
CommandLine: '*xcopy.exe*'
SELECTION_28:
CommandLine: '*pscp.exe*'
SELECTION_29:
CommandLine: '*copy.exe*'
SELECTION_3:
CommandLine: '*at.exe*'
SELECTION_30:
CommandLine: '*robocopy.exe*'
SELECTION_31:
CommandLine: '*certutil.exe*'
SELECTION_32:
CommandLine: '*vssadmin.exe*'
SELECTION_33:
CommandLine: '*powershell.exe*'
SELECTION_34:
CommandLine: '*wevtutil.exe*'
SELECTION_35:
CommandLine: '*psexec.exe*'
SELECTION_36:
CommandLine: '*bcedit.exe*'
SELECTION_37:
CommandLine: '*wbadmin.exe*'
SELECTION_38:
CommandLine: '*icacls.exe*'
SELECTION_39:
CommandLine: '*diskpart.exe*'
SELECTION_4:
CommandLine: '*attrib.exe*'
SELECTION_5:
CommandLine: '*cscript.exe*'
SELECTION_6:
CommandLine: '*dsquery.exe*'
SELECTION_7:
CommandLine: '*hostname.exe*'
SELECTION_8:
CommandLine: '*ipconfig.exe*'
SELECTION_9:
CommandLine: '*mimikatz.exe*'
condition: (SELECTION_1 and (SELECTION_2 or SELECTION_3 or SELECTION_4 or SELECTION_5
or SELECTION_6 or SELECTION_7 or SELECTION_8 or SELECTION_9 or SELECTION_10
or SELECTION_11 or SELECTION_12 or SELECTION_13 or SELECTION_14 or SELECTION_15
or SELECTION_16 or SELECTION_17 or SELECTION_18 or SELECTION_19 or SELECTION_20
or SELECTION_21 or SELECTION_22 or SELECTION_23 or SELECTION_24 or SELECTION_25
or SELECTION_26 or SELECTION_27 or SELECTION_28 or SELECTION_29 or SELECTION_30
or SELECTION_31 or SELECTION_32 or SELECTION_33 or SELECTION_34 or SELECTION_35
or SELECTION_36 or SELECTION_37 or SELECTION_38 or SELECTION_39))| count()
by MachineName > 5
falsepositives:
- False positives depend on scripts and administrative tools used in the monitored
environment
id: 61ab5496-748e-4818-a92f-de78e20fe7f1
level: low
logsource:
category: process_creation
product: windows
modified: 2021/06/13
references:
- https://car.mitre.org/wiki/CAR-2013-04-002
status: experimental
tags:
- car.2013-04-002
yml_filename: win_multiple_suspicious_cli.yml
yml_path: /Users/user/Documents/YamatoSecurity/sigma/rules/windows/process_creation

View File

@@ -1,29 +0,0 @@
title: PowerShell Scripts Installed as Services
author: oscd.community, Natalia Shornikova
date: 2020/10/06
description: Detects powershell script installed as a Service
detection:
SELECTION_1:
EventID: 7045
SELECTION_2:
ImagePath: '*powershell*'
SELECTION_3:
ImagePath: '*pwsh*'
condition: (SELECTION_1 and (SELECTION_2 or SELECTION_3))
falsepositives:
- Unknown
id: a2e5019d-a658-4c6a-92bf-7197b54e2cae
level: high
logsource:
product: windows
service: system
modified: 2021/09/21
references:
- https://speakerdeck.com/heirhabarov/hunting-for-powershell-abuse
status: experimental
tags:
- attack.execution
- attack.t1569.002
yml_filename: win_powershell_script_installed_as_service.yml
yml_path: /Users/user/Documents/YamatoSecurity/sigma/rules/windows/builtin

View File

@@ -1,33 +0,0 @@
title: Rare Schtasks Creations
author: Florian Roth
date: 2017/03/23
description: Detects rare scheduled tasks creations that only appear a few times per
time frame and could reveal password dumpers, backdoor installs or other types
of malicious code
detection:
SELECTION_1:
EventID: 4698
condition: SELECTION_1| count() by TaskName < 5
falsepositives:
- Software installation
- Software updates
id: b0d77106-7bb0-41fe-bd94-d1752164d066
level: low
logsource:
definition: The Advanced Audit Policy setting Object Access > Audit Other Object
Access Events has to be configured to allow this detection (not in the baseline
recommendations by Microsoft). We also recommend extracting the Command field
from the embedded XML in the event data.
product: windows
service: security
status: experimental
tags:
- attack.execution
- attack.privilege_escalation
- attack.persistence
- attack.t1053
- car.2013-08-001
- attack.t1053.005
yml_filename: win_rare_schtasks_creations.yml
yml_path: /Users/user/Documents/YamatoSecurity/sigma/rules/windows/builtin

View File

@@ -1,28 +0,0 @@
title: Rare Service Installs
author: Florian Roth
date: 2017/03/08
description: Detects rare service installs that only appear a few times per time frame
and could reveal password dumpers, backdoor installs or other types of malicious
services
detection:
SELECTION_1:
EventID: 7045
condition: SELECTION_1| count() by ServiceFileName < 5
falsepositives:
- Software installation
- Software updates
id: 66bfef30-22a5-4fcd-ad44-8d81e60922ae
level: low
logsource:
product: windows
service: system
status: experimental
tags:
- attack.persistence
- attack.privilege_escalation
- attack.t1050
- car.2013-09-005
- attack.t1543.003
yml_filename: win_rare_service_installs.yml
yml_path: /Users/user/Documents/YamatoSecurity/sigma/rules/windows/builtin

View File

@@ -1,34 +0,0 @@
title: Failed Logins with Different Accounts from Single Source System
author: Florian Roth
date: 2017/01/10
description: Detects suspicious failed logins with different user accounts from a
single source system
detection:
SELECTION_1:
EventID: 529
SELECTION_2:
EventID: 4625
SELECTION_3:
TargetUserName: '*'
SELECTION_4:
WorkstationName: '*'
condition: ((SELECTION_1 or SELECTION_2) and SELECTION_3 and SELECTION_4)| count(TargetUserName)
by WorkstationName > 3
falsepositives:
- Terminal servers
- Jump servers
- Other multiuser systems like Citrix server farms
- Workstations with frequently changing users
id: e98374a6-e2d9-4076-9b5c-11bdb2569995
level: medium
logsource:
product: windows
service: security
modified: 2021/09/21
tags:
- attack.persistence
- attack.privilege_escalation
- attack.t1078
yml_filename: win_susp_failed_logons_single_source.yml
yml_path: /Users/user/Documents/YamatoSecurity/sigma/rules/windows/builtin

View File

@@ -1,35 +0,0 @@
title: Failed Logins with Different Accounts from Single Source System
author: Florian Roth
date: 2017/01/10
description: Detects suspicious failed logins with different user accounts from a
single source system
detection:
SELECTION_1:
EventID: 4776
SELECTION_2:
TargetUserName: '*'
SELECTION_3:
Workstation: '*'
condition: (SELECTION_1 and SELECTION_2 and SELECTION_3)| count(TargetUserName)
by Workstation > 3
falsepositives:
- Terminal servers
- Jump servers
- Other multiuser systems like Citrix server farms
- Workstations with frequently changing users
id: 6309ffc4-8fa2-47cf-96b8-a2f72e58e538
level: medium
logsource:
product: windows
service: security
modified: 2021/09/21
related:
- id: e98374a6-e2d9-4076-9b5c-11bdb2569995
type: derived
tags:
- attack.persistence
- attack.privilege_escalation
- attack.t1078
yml_filename: win_susp_failed_logons_single_source2.yml
yml_path: /Users/user/Documents/YamatoSecurity/sigma/rules/windows/builtin

View File

@@ -1,15 +0,0 @@
title: The ... service entered the stopped|running state
description: hogehoge
author: DeepblueCLI, Zach Mathis
detection:
selection:
Channel: System
EventID: 7036
param1:
regexes: ./config/regex/regexes_suspicous_service.txt
condition: selection
falsepositives:
- unknown
output: 'Suspicious Service Name¥nService name: %ServiceName%'
creation_date: 2020/11/8
uodated_date: 2020/11/8

View File

@@ -6,8 +6,8 @@ title: Malicious service installed
title_jp: 悪意のあるサービスがインストールされた
output: 'Service: %ServiceName% : Image path: %ImagePath'
output_jp: 'サービス名: %ServiceName% : Imageパス: %ImagePath'
description: Malicious service was installed based on suspicious entries in ./config/regex/regexes_suspicous_service.txt
description_jp: Malicious service was installed based on suspicious entries in ./config/regex/regexes_suspicous_service.txt
description: Malicious service was installed based on suspicious entries in ./config/regex/detectlist_suspicous_services.txt
description_jp: Malicious service was installed based on suspicious entries in ./config/regex/detectlist_suspicous_services.txt
id: dbbfd9f3-9508-478b-887e-03ddb9236909
level: high
@@ -17,10 +17,10 @@ detection:
Channel: System
EventID: 7045
ServiceName:
regexes: ./config/regex/regexes_suspicous_service.txt
regexes: ./config/regex/detectlist_suspicous_services.txt
ImagePath:
min_length: 1000
allowlist: ./config/regex/allowlist_legimate_serviceimage.txt
allowlist: ./config/regex/allowlist_legitimate_services.txt
condition: selection
falsepositives:
- normal system usage

View File

@@ -6,6 +6,7 @@ use serde::Serialize;
use std::error::Error;
use std::fs::File;
use std::io;
use std::io::BufWriter;
use std::process;
#[derive(Debug, Serialize)]
@@ -50,7 +51,7 @@ pub fn after_fact() {
{
// ファイル出力する場合
match File::create(csv_path) {
Ok(file) => Box::new(file),
Ok(file) => Box::new(BufWriter::new(file)),
Err(err) => {
AlertMessage::alert(
&mut std::io::stderr().lock(),
@@ -63,7 +64,7 @@ pub fn after_fact() {
} else {
displayflag = true;
// 標準出力に出力する場合
Box::new(io::stdout())
Box::new(BufWriter::new(io::stdout()))
};
if let Err(err) = emit_csv(&mut target, displayflag) {

View File

@@ -1,4 +1,6 @@
use crate::detections::print::AlertMessage;
use crate::detections::utils;
use chrono::{DateTime, Utc};
use clap::{App, AppSettings, ArgMatches};
use hashbrown::HashMap;
use hashbrown::HashSet;
@@ -55,6 +57,8 @@ fn build_app<'a>() -> ArgMatches<'a> {
--rfc-2822 'Output date and time in RFC 2822 format. Example: Mon, 07 Aug 2006 12:34:56 -0600'
--rfc-3339 'Output date and time in RFC 3339 format. Example: 2006-08-07T12:34:56.485214 -06:00'
--verbose 'Output verbose information to target event file path and rule file'
--starttimeline=[STARTTIMELINE] 'Start time of the event to load from event file. Example: '2018/11/28 12:00:00 +09:00''
--endtimeline=[ENDTIMELINE]'End time of the event to load from event file. Example: '2018/11/28 12:00:00 +09:00''
-q 'Quiet mode. Do not display the launch banner'
-r --rules=[RULEDIRECTORY] 'Rule file directory (default: ./rules)'
-m --min-level=[LEVEL] 'Minimum level for rules (default: informational)'
@@ -119,6 +123,80 @@ fn load_target_ids(path: &str) -> TargetEventIds {
return ret;
}
#[derive(Debug, Clone)]
pub struct TargetEventTime {
start_time: Option<DateTime<Utc>>,
end_time: Option<DateTime<Utc>>,
}
impl TargetEventTime {
pub fn new() -> Self {
let start_time = if let Some(s_time) = CONFIG.read().unwrap().args.value_of("starttimeline")
{
match DateTime::parse_from_str(s_time, "%Y-%m-%d %H:%M:%S %z") // 2014-11-28 21:00:09 +09:00
.or_else(|_| DateTime::parse_from_str(s_time, "%Y/%m/%d %H:%M:%S %z")) // 2014/11/28 21:00:09 +09:00
{
Ok(dt) => Some(dt.with_timezone(&Utc)),
Err(err) => {
AlertMessage::alert(
&mut std::io::stderr().lock(),
format!("starttimeline field: {}", err),
)
.ok();
None
}
}
} else {
None
};
let end_time = if let Some(e_time) = CONFIG.read().unwrap().args.value_of("endtimeline") {
match DateTime::parse_from_str(e_time, "%Y-%m-%d %H:%M:%S %z") // 2014-11-28 21:00:09 +09:00
.or_else(|_| DateTime::parse_from_str(e_time, "%Y/%m/%d %H:%M:%S %z")) // 2014/11/28 21:00:09 +09:00
{
Ok(dt) => Some(dt.with_timezone(&Utc)),
Err(err) => {
AlertMessage::alert(
&mut std::io::stderr().lock(),
format!("endtimeline field: {}", err),
)
.ok();
None
}
}
} else {
None
};
return Self::set(start_time, end_time);
}
pub fn set(
start_time: Option<chrono::DateTime<chrono::Utc>>,
end_time: Option<chrono::DateTime<chrono::Utc>>,
) -> Self {
return Self {
start_time: start_time,
end_time: end_time,
};
}
pub fn is_target(&self, eventtime: &Option<DateTime<Utc>>) -> bool {
if eventtime.is_none() {
return true;
}
if let Some(starttime) = self.start_time {
if eventtime.unwrap() < starttime {
return false;
}
}
if let Some(endtime) = self.end_time {
if eventtime.unwrap() > endtime {
return false;
}
}
return true;
}
}
#[derive(Debug, Clone)]
pub struct EventKeyAliasConfig {
key_to_eventkey: HashMap<String, String>,
@@ -233,26 +311,51 @@ fn load_eventcode_info(path: &str) -> EventInfoConfig {
return config;
}
// #[cfg(test)]
// mod tests {
#[cfg(test)]
mod tests {
use crate::detections::configs;
use chrono::{DateTime, Utc};
// use crate::detections::configs;
// #[test]
// #[ignore]
// fn singleton_read_and_write() {
// let message =
// "EventKeyAliasConfig { key_to_eventkey: {\"EventID\": \"Event.System.EventID\"} }";
// configs::EVENT_KEY_ALIAS_CONFIG =
// configs::load_eventkey_alias("test_files/config/eventkey_alias.txt");
// let display = format!(
// "{}",
// format_args!(
// "{:?}",
// configs::CONFIG.write().unwrap().event_key_alias_config
// )
// );
// assert_eq!(message, display);
// }
// }
// #[test]
// #[ignore]
// fn singleton_read_and_write() {
// let message =
// "EventKeyAliasConfig { key_to_eventkey: {\"EventID\": \"Event.System.EventID\"} }";
// configs::EVENT_KEY_ALIAS_CONFIG =
// configs::load_eventkey_alias("test_files/config/eventkey_alias.txt");
#[test]
fn target_event_time_filter() {
let start_time = Some("2018-02-20T12:00:09Z".parse::<DateTime<Utc>>().unwrap());
let end_time = Some("2020-03-30T12:00:09Z".parse::<DateTime<Utc>>().unwrap());
let time_filter = configs::TargetEventTime::set(start_time, end_time);
// let display = format!(
// "{}",
// format_args!(
// "{:?}",
// configs::CONFIG.write().unwrap().event_key_alias_config
// )
// );
// assert_eq!(message, display);
// }
// }
let out_of_range1 = Some("1999-01-01T12:00:09Z".parse::<DateTime<Utc>>().unwrap());
let within_range = Some("2019-02-27T01:05:01Z".parse::<DateTime<Utc>>().unwrap());
let out_of_range2 = Some("2021-02-27T01:05:01Z".parse::<DateTime<Utc>>().unwrap());
assert_eq!(time_filter.is_target(&out_of_range1), false);
assert_eq!(time_filter.is_target(&within_range), true);
assert_eq!(time_filter.is_target(&out_of_range2), false);
}
#[test]
fn target_event_time_filter_containes_on_time() {
let start_time = Some("2018-02-20T12:00:09Z".parse::<DateTime<Utc>>().unwrap());
let end_time = Some("2020-03-30T12:00:09Z".parse::<DateTime<Utc>>().unwrap());
let time_filter = configs::TargetEventTime::set(start_time, end_time);
assert_eq!(time_filter.is_target(&start_time), true);
assert_eq!(time_filter.is_target(&end_time), true);
}
}

View File

@@ -1,18 +1,18 @@
extern crate csv;
use crate::detections::rule::AggResult;
use serde_json::Value;
use std::collections::HashMap;
use tokio::{runtime::Runtime, spawn, task::JoinHandle};
use crate::detections::configs;
use crate::detections::print::AlertMessage;
use crate::detections::print::MESSAGES;
use crate::detections::rule;
use crate::detections::rule::AggResult;
use crate::detections::rule::RuleNode;
use crate::detections::utils::get_serde_number_to_string;
use crate::filter;
use crate::yaml::ParseYaml;
use hashbrown;
use serde_json::Value;
use std::collections::HashMap;
use tokio::{runtime::Runtime, spawn, task::JoinHandle};
use std::sync::Arc;
@@ -24,15 +24,12 @@ pub struct EvtxRecordInfo {
pub evtx_filepath: String, // イベントファイルのファイルパス ログで出力するときに使う
pub record: Value, // 1レコード分のデータをJSON形式にシリアライズしたもの
pub data_string: String,
pub key_2_value: hashbrown::HashMap<String, String>,
}
impl EvtxRecordInfo {
pub fn new(evtx_filepath: String, record: Value, data_string: String) -> EvtxRecordInfo {
return EvtxRecordInfo {
evtx_filepath: evtx_filepath,
record: record,
data_string: data_string,
};
pub fn get_value(&self, key: &String) -> Option<&String> {
return self.key_2_value.get(key);
}
}
@@ -185,9 +182,8 @@ impl Detection {
// 複数のイベントレコードに対して、ルールを1個実行します。
fn execute_rule(mut rule: RuleNode, records: Arc<Vec<EvtxRecordInfo>>) -> RuleNode {
let records = &*records;
let agg_condition = rule.has_agg_condition();
for record_info in records {
for record_info in records.as_ref() {
let result = rule.select(&record_info.evtx_filepath, &record_info);
if !result {
continue;

View File

@@ -1,5 +1,5 @@
pub mod configs;
pub mod detection;
pub mod print;
mod rule;
pub mod rule;
pub mod utils;

View File

@@ -1,5 +1,6 @@
extern crate lazy_static;
use crate::detections::configs;
use crate::detections::utils;
use crate::detections::utils::get_serde_number_to_string;
use chrono::{DateTime, TimeZone, Utc};
use lazy_static::lazy_static;
@@ -169,23 +170,7 @@ impl Message {
pub fn get_event_time(event_record: &Value) -> Option<DateTime<Utc>> {
let system_time = &event_record["Event"]["System"]["TimeCreated_attributes"]["SystemTime"];
let system_time_str = system_time.as_str().unwrap_or("");
if system_time_str.is_empty() {
return Option::None;
}
let rfc3339_time = DateTime::parse_from_rfc3339(system_time_str);
if rfc3339_time.is_err() {
return Option::None;
}
let datetime = Utc
.from_local_datetime(&rfc3339_time.unwrap().naive_utc())
.single();
if datetime.is_none() {
return Option::None;
} else {
return Option::Some(datetime.unwrap());
}
return utils::str_time_to_datetime(system_time.as_str().unwrap_or(""));
}
/// message内のマップをクリアする。テストする際の冪等性の担保のため作成。

View File

@@ -503,11 +503,10 @@ impl ConditionCompiler {
#[cfg(test)]
mod tests {
use yaml_rust::YamlLoader;
use crate::detections::detection::EvtxRecordInfo;
use crate::detections::rule::create_rule;
use crate::detections::rule::tests::parse_rule_from_str;
use crate::detections::{self, utils};
use yaml_rust::YamlLoader;
const SIMPLE_RECORD_STR: &str = r#"
{
@@ -537,11 +536,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(
rule_node.select(&"testpath".to_owned(), &recinfo),
expect_select
@@ -586,11 +582,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_rec) => {
@@ -633,11 +626,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_rec) => {

View File

@@ -316,10 +316,11 @@ pub fn judge_timeframe(
#[cfg(test)]
mod tests {
use crate::detections::detection::EvtxRecordInfo;
use crate::detections;
use crate::detections::rule::create_rule;
use crate::detections::rule::AggResult;
use std::collections::HashMap;
use crate::detections::utils;
use hashbrown::HashMap;
use chrono::{TimeZone, Utc};
use yaml_rust::YamlLoader;
@@ -642,11 +643,8 @@ mod tests {
for record in target {
match serde_json::from_str(record) {
Ok(rec) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: rec,
data_string: record.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(rec, "testpath".to_owned(), &keys);
let _result = rule_node.select(&"testpath".to_string(), &recinfo);
}
Err(_rec) => {
@@ -735,11 +733,8 @@ mod tests {
for record_str in records_str {
match serde_json::from_str(record_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
let result = &rule_node.select(&"testpath".to_owned(), &recinfo);
assert_eq!(result, &true);
}

View File

@@ -1,11 +1,15 @@
use regex::Regex;
use serde_json::Value;
use std::collections::VecDeque;
use yaml_rust::Yaml;
use crate::detections::{detection::EvtxRecordInfo, utils};
use mopa::mopafy;
use lazy_static::lazy_static;
lazy_static! {
pub static ref STR_DEFAULT: String = String::default();
}
// 末端ードがEventLogの値を比較するロジックを表す。
// 正規条件のマッチや文字数制限など、比較ロジック毎にこのtraitを実装したクラスが存在する。
//
@@ -18,7 +22,7 @@ pub trait LeafMatcher: mopa::Any {
/// 引数に指定されたJSON形式のデータがマッチするかどうか判定する。
/// main.rsでWindows Event LogをJSON形式に変換していて、そのJSON形式のWindowsのイベントログデータがここには来る
/// 例えば正規表現でマッチするロジックなら、ここに正規表現でマッチさせる処理を書く。
fn is_match(&self, event_value: Option<&Value>, recinfo: &EvtxRecordInfo) -> bool;
fn is_match(&self, event_value: Option<&String>, recinfo: &EvtxRecordInfo) -> bool;
/// 初期化ロジックをここに記載します。
/// ルールファイルの書き方が間違っている等の原因により、正しくルールファイルからパースできない場合、戻り値のResult型でエラーを返してください。
@@ -60,11 +64,10 @@ impl LeafMatcher for MinlengthMatcher {
return Result::Ok(());
}
fn is_match(&self, event_value: Option<&Value>, _recinfo: &EvtxRecordInfo) -> bool {
return match event_value.unwrap_or(&Value::Null) {
Value::String(s) => s.len() as i64 >= self.min_len,
Value::Number(n) => n.to_string().len() as i64 >= self.min_len,
_ => false,
fn is_match(&self, event_value: Option<&String>, _recinfo: &EvtxRecordInfo) -> bool {
return match event_value {
Some(s) => s.len() as i64 >= self.min_len,
None => false,
};
}
}
@@ -118,12 +121,10 @@ impl LeafMatcher for RegexesFileMatcher {
return Result::Ok(());
}
fn is_match(&self, event_value: Option<&Value>, _recinfo: &EvtxRecordInfo) -> bool {
//TODO Wildcardの場合、CaseInsensitiveなので、ToLowerする。
return match event_value.unwrap_or(&Value::Null) {
Value::String(s) => !utils::check_regex(s, &self.regexes),
Value::Number(n) => !utils::check_regex(&n.to_string(), &self.regexes),
_ => false,
fn is_match(&self, event_value: Option<&String>, _recinfo: &EvtxRecordInfo) -> bool {
return match event_value {
Some(s) => utils::check_regex(s, &self.regexes),
None => false,
};
}
}
@@ -177,12 +178,10 @@ impl LeafMatcher for AllowlistFileMatcher {
return Result::Ok(());
}
fn is_match(&self, event_value: Option<&Value>, _recinfo: &EvtxRecordInfo) -> bool {
return match event_value.unwrap_or(&Value::Null) {
Value::String(s) => !utils::check_allowlist(s, &self.regexes),
Value::Number(n) => !utils::check_allowlist(&n.to_string(), &self.regexes),
Value::Bool(b) => !utils::check_allowlist(&b.to_string(), &self.regexes),
_ => true,
fn is_match(&self, event_value: Option<&String>, _recinfo: &EvtxRecordInfo) -> bool {
return match event_value {
Some(s) => !utils::check_allowlist(s, &self.regexes),
None => true,
};
}
}
@@ -320,50 +319,18 @@ impl LeafMatcher for DefaultMatcher {
return Result::Ok(());
}
fn is_match(&self, event_value: Option<&Value>, recinfo: &EvtxRecordInfo) -> bool {
// unwrap_orの引数に""ではなく" "を指定しているのは、
// event_valueが文字列じゃない場合にis_event_value_nullの値がfalseになるように、len() == 0とならない値を指定している。
let is_event_value_null = event_value.is_none()
|| event_value.unwrap().is_null()
|| event_value.unwrap().as_str().unwrap_or(" ").len() == 0;
fn is_match(&self, event_value: Option<&String>, _recinfo: &EvtxRecordInfo) -> bool {
// yamlにnullが設定されていた場合
// keylistが空(==JSONのgrep検索)の場合、無視する。
if !self.key_list.is_empty() && self.re.is_none() {
return is_event_value_null;
}
// JSON形式のEventLogデータをstringに変換するための前処理
// 以前のコードはstringに変換に変換する必ずto_string()がするような処理になっていた。
// そうすると、凄く遅くなるので、そうならないように回避
let mut b_str = String::default();
let mut n_str = String::default();
match event_value.unwrap_or(&Value::Null) {
Value::Bool(b) => b_str = b.to_string(),
Value::Number(n) => {
n_str = n.to_string();
}
_ => (),
};
// JSON形式のEventLogデータをstringに変換
let event_value_str: Option<&String> = if self.key_list.is_empty() {
Option::Some(&recinfo.data_string)
} else {
let value = match event_value.unwrap_or(&Value::Null) {
Value::Bool(_) => Option::Some(&b_str),
Value::String(s) => Option::Some(s),
Value::Number(_) => Option::Some(&n_str),
_ => Option::None,
};
value
};
if event_value_str.is_none() {
if self.key_list.is_empty() && self.re.is_none() {
return false;
}
// 変換したデータに対してパイプ処理を実行する。
let event_value_str = event_value_str.unwrap();
if event_value.is_none() {
return false;
}
let event_value_str = event_value.unwrap();
if self.key_list.is_empty() {
// この場合ただのgrep検索なので、ただ正規表現に一致するかどうか調べればよいだけ
return self.re.as_ref().unwrap().is_match(&event_value_str);
@@ -515,9 +482,8 @@ mod tests {
use super::super::selectionnodes::{
AndSelectionNode, LeafSelectionNode, OrSelectionNode, SelectionNode,
};
use crate::detections::detection::EvtxRecordInfo;
use crate::detections::rule::tests::parse_rule_from_str;
use serde_json::Value;
use crate::detections::{self, utils};
#[test]
fn test_rule_parse() {
@@ -538,8 +504,8 @@ mod tests {
- ホスト アプリケーション
ImagePath:
min_length: 1234321
regexes: ./config/regex/regexes_suspicous_service.txt
allowlist: ./config/regex/allowlist_legimate_serviceimage.txt
regexes: ./config/regex/detectlist_suspicous_services.txt
allowlist: ./config/regex/allowlist_legitimate_services.txt
falsepositives:
- unknown
level: medium
@@ -739,11 +705,8 @@ mod tests {
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {
@@ -772,11 +735,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {
@@ -805,11 +765,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_) => {
@@ -839,11 +796,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {
@@ -873,11 +827,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {
@@ -906,11 +857,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_) => {
@@ -939,11 +887,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {
@@ -973,11 +918,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {
@@ -1007,11 +949,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_) => {
@@ -1041,11 +980,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_) => {
@@ -1075,11 +1011,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_) => {
@@ -1109,11 +1042,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {
@@ -1142,11 +1072,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_) => {
@@ -1165,7 +1092,7 @@ mod tests {
selection:
EventID: 4103
Channel:
- allowlist: ./config/regex/allowlist_legimate_serviceimage.txt
- allowlist: ./config/regex/allowlist_legitimate_services.txt
output: 'command=%CommandLine%'
"#;
@@ -1179,11 +1106,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {
@@ -1202,7 +1126,7 @@ mod tests {
selection:
EventID: 4103
Channel:
- allowlist: ./config/regex/allowlist_legimate_serviceimage.txt
- allowlist: ./config/regex/allowlist_legitimate_services.txt
output: 'command=%CommandLine%'
"#;
@@ -1216,11 +1140,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {
@@ -1239,7 +1160,7 @@ mod tests {
selection:
EventID: 4103
Channel:
- allowlist: ./config/regex/allowlist_legimate_serviceimage.txt
- allowlist: ./config/regex/allowlist_legitimate_services.txt
output: 'command=%CommandLine%'
"#;
@@ -1252,11 +1173,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {
@@ -1297,11 +1215,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_rec) => {
@@ -1342,11 +1257,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_rec) => {
@@ -1387,11 +1299,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_rec) => {
@@ -1432,11 +1341,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_rec) => {
@@ -1477,11 +1383,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_rec) => {
@@ -1522,11 +1425,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_rec) => {
@@ -1555,11 +1455,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_) => {
@@ -1588,11 +1485,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {
@@ -1621,11 +1515,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_) => {
@@ -1711,13 +1602,9 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(rec) => {
let rec: Value = rec;
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: rec,
data_string: record_json_str.to_string(),
};
Ok(record) => {
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_) => {
@@ -1746,12 +1633,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let rec: Value = record;
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: rec,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {
@@ -1782,11 +1665,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_) => {
@@ -1817,11 +1697,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {

View File

@@ -9,7 +9,7 @@ use yaml_rust::Yaml;
mod matchers;
mod selectionnodes;
use self::selectionnodes::SelectionNode;
use self::selectionnodes::{LeafSelectionNode, SelectionNode};
mod aggregation_parser;
use self::aggregation_parser::AggregationParseInfo;
@@ -93,6 +93,31 @@ impl RuleNode {
}
}
// RuleNodeのdetectionに定義されているキーの一覧を取得する。
pub fn get_detection_keys(node: &RuleNode) -> Vec<String> {
let mut ret = vec![];
let detection = &node.detection;
for key in detection.name_to_selection.keys() {
let selection = &detection.name_to_selection[key];
let desc = selection.get_descendants();
let keys = desc.iter().filter_map(|node| {
if !node.is::<LeafSelectionNode>() {
return Option::None;
}
let node = node.downcast_ref::<LeafSelectionNode>().unwrap();
let key = node.get_key();
if key.is_empty() {
return Option::None;
}
return Option::Some(key.to_string());
});
ret.extend(keys);
}
return ret;
}
/// Ruleファイルのdetectionを表すード
struct DetectionNode {
pub name_to_selection: HashMap<String, Arc<Box<dyn SelectionNode>>>,
@@ -300,10 +325,9 @@ impl AggResult {
#[cfg(test)]
mod tests {
use crate::detections::{detection::EvtxRecordInfo, rule::create_rule};
use yaml_rust::YamlLoader;
use super::RuleNode;
use crate::detections::{self, rule::create_rule, utils};
use yaml_rust::YamlLoader;
pub fn parse_rule_from_str(rule_str: &str) -> RuleNode {
let rule_yaml = YamlLoader::load_from_str(rule_str);
@@ -335,11 +359,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_) => {
@@ -368,11 +389,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {
@@ -401,11 +419,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {
@@ -487,11 +502,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_) => {
@@ -549,11 +561,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {
@@ -618,11 +627,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_) => {
@@ -665,11 +671,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_) => {
@@ -713,11 +716,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {
@@ -780,11 +780,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_) => {
@@ -847,11 +844,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {
@@ -896,11 +890,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_rec) => {
@@ -957,11 +948,8 @@ mod tests {
let _init = rule_node.init();
match serde_json::from_str(record_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
let result = rule_node.select(&"testpath".to_string(), &recinfo);
assert_eq!(rule_node.detection.aggregation_condition.is_some(), true);
assert_eq!(result, true);

View File

@@ -1,6 +1,5 @@
use crate::detections::{detection::EvtxRecordInfo, utils};
use mopa::mopafy;
use serde_json::Value;
use std::{sync::Arc, vec};
use yaml_rust::Yaml;
@@ -268,13 +267,13 @@ impl LeafSelectionNode {
}
/// JSON形式のEventJSONから値を取得する関数 aliasも考慮されている。
fn get_event_value<'a>(&self, event_value: &'a Value) -> Option<&'a Value> {
fn get_event_value<'a>(&self, record: &'a EvtxRecordInfo) -> Option<&'a String> {
// keyが指定されたいない場合は
if self.key_list.is_empty() {
return Option::Some(event_value);
return Option::Some(&record.data_string);
}
return utils::get_event_value(&self.get_key(), event_value);
return record.get_value(self.get_key());
}
/// matchers::LeafMatcherの一覧を取得する。
@@ -334,7 +333,7 @@ impl SelectionNode for LeafSelectionNode {
.matcher
.as_ref()
.unwrap()
.is_match(Option::Some(eventdata_data), event_record);
.is_match(event_record.get_value(self.get_key()), event_record);
}
// 配列の場合は配列の要素のどれか一つでもルールに合致すれば条件に一致したことにする。
if eventdata_data.is_array() {
@@ -343,11 +342,12 @@ impl SelectionNode for LeafSelectionNode {
.unwrap()
.iter()
.any(|ary_element| {
let aryelement_val = utils::value_to_string(ary_element);
return self
.matcher
.as_ref()
.unwrap()
.is_match(Option::Some(ary_element), event_record);
.is_match(aryelement_val.as_ref(), event_record);
});
} else {
return self
@@ -358,7 +358,7 @@ impl SelectionNode for LeafSelectionNode {
}
}
let event_value = self.get_event_value(&event_record.record);
let event_value = self.get_event_value(&event_record);
return self
.matcher
.as_ref()
@@ -407,7 +407,7 @@ impl SelectionNode for LeafSelectionNode {
#[cfg(test)]
mod tests {
use crate::detections::{detection::EvtxRecordInfo, rule::tests::parse_rule_from_str};
use crate::detections::{self, rule::tests::parse_rule_from_str, utils};
#[test]
fn test_detect_mutiple_regex_and() {
@@ -430,11 +430,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_) => {
@@ -466,11 +463,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {
@@ -501,11 +495,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_) => {
@@ -536,11 +527,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), true);
}
Err(_) => {
@@ -571,11 +559,8 @@ mod tests {
let mut rule_node = parse_rule_from_str(rule_str);
match serde_json::from_str(record_json_str) {
Ok(record) => {
let recinfo = EvtxRecordInfo {
evtx_filepath: "testpath".to_owned(),
record: record,
data_string: record_json_str.to_string(),
};
let keys = detections::rule::get_detection_keys(&rule_node);
let recinfo = utils::create_rec_info(record, "testpath".to_owned(), &keys);
assert_eq!(rule_node.select(&"testpath".to_owned(), &recinfo), false);
}
Err(_) => {

View File

@@ -7,6 +7,7 @@ use crate::detections::configs;
use tokio::runtime::Builder;
use tokio::runtime::Runtime;
use chrono::{DateTime, TimeZone, Utc};
use regex::Regex;
use serde_json::Value;
use std::fs::File;
@@ -15,6 +16,8 @@ use std::io::{BufRead, BufReader};
use std::str;
use std::string::String;
use super::detection::EvtxRecordInfo;
pub fn concat_selection_key(key_list: &Vec<String>) -> String {
return key_list
.iter()
@@ -46,6 +49,17 @@ pub fn check_allowlist(target: &str, regexes: &Vec<Regex>) -> bool {
return false;
}
pub fn value_to_string(value: &Value) -> Option<String> {
return match value {
Value::Null => Option::None,
Value::Bool(b) => Option::Some(b.to_string()),
Value::Number(n) => Option::Some(n.to_string()),
Value::String(s) => Option::Some(s.to_string()),
Value::Array(_) => Option::None,
Value::Object(_) => Option::None,
};
}
pub fn read_txt(filename: &str) -> Result<Vec<String>, String> {
let f = File::open(filename);
if f.is_err() {
@@ -93,6 +107,29 @@ pub fn get_event_id_key() -> String {
return "Event.System.EventID".to_string();
}
pub fn get_event_time() -> String {
return "Event.System.TimeCreated_attributes.SystemTime".to_string();
}
pub fn str_time_to_datetime(system_time_str: &str) -> Option<DateTime<Utc>> {
if system_time_str.is_empty() {
return Option::None;
}
let rfc3339_time = DateTime::parse_from_rfc3339(system_time_str);
if rfc3339_time.is_err() {
return Option::None;
}
let datetime = Utc
.from_local_datetime(&rfc3339_time.unwrap().naive_utc())
.single();
if datetime.is_none() {
return Option::None;
} else {
return Option::Some(datetime.unwrap());
}
}
/// serde:Valueの型を確認し、文字列を返します。
pub fn get_serde_number_to_string(value: &serde_json::Value) -> Option<String> {
if value.is_string() {
@@ -160,6 +197,41 @@ pub fn create_tokio_runtime() -> Runtime {
.unwrap();
}
// EvtxRecordInfoを作成します。
pub fn create_rec_info(data: Value, path: String, keys: &Vec<String>) -> EvtxRecordInfo {
// EvtxRecordInfoを作る
let data_str = data.to_string();
let mut rec = EvtxRecordInfo {
evtx_filepath: path,
record: data,
data_string: data_str,
key_2_value: hashbrown::HashMap::new(),
};
// 高速化のための処理
// 例えば、Value型から"Event.System.EventID"の値を取得しようとすると、value["Event"]["System"]["EventID"]のように3回アクセスする必要がある。
// この処理を高速化するため、rec.key_2_valueというhashmapに"Event.System.EventID"というキーで値を設定しておく。
// これなら、"Event.System.EventID"というキーを1回指定するだけで値を取得できるようになるので、高速化されるはず。
// あと、serde_jsonのValueからvalue["Event"]みたいな感じで値を取得する処理がなんか遅いので、そういう意味でも早くなるかも
// それと、serde_jsonでは内部的に標準ライブラリのhashmapを使用しているが、hashbrownを使った方が早くなるらしい。
for key in keys {
let val = get_event_value(key, &rec.record);
if val.is_none() {
continue;
}
let val = value_to_string(val.unwrap());
if val.is_none() {
continue;
}
rec.key_2_value.insert(key.to_string(), val.unwrap());
}
return rec;
}
#[cfg(test)]
mod tests {
use crate::detections::utils;
@@ -168,7 +240,7 @@ mod tests {
#[test]
fn test_check_regex() {
let regexes = utils::read_txt("./config/regex/regexes_suspicous_service.txt")
let regexes = utils::read_txt("./config/regex/detectlist_suspicous_services.txt")
.unwrap()
.into_iter()
.map(|regex_str| Regex::new(&regex_str).unwrap())
@@ -183,7 +255,7 @@ mod tests {
#[test]
fn test_check_allowlist() {
let commandline = "\"C:\\Program Files\\Google\\Update\\GoogleUpdate.exe\"";
let allowlist = utils::read_txt("./config/regex/allowlist_legimate_serviceimage.txt")
let allowlist = utils::read_txt("./config/regex/allowlist_legitimate_services.txt")
.unwrap()
.into_iter()
.map(|allow_str| Regex::new(&allow_str).unwrap())

View File

@@ -4,9 +4,9 @@ extern crate serde_derive;
use chrono::Datelike;
use chrono::{DateTime, Local};
use evtx::{EvtxParser, ParserSettings};
use hayabusa::detections::detection;
use hayabusa::detections::detection::EvtxRecordInfo;
use hayabusa::detections::detection::{self, EvtxRecordInfo};
use hayabusa::detections::print::AlertMessage;
use hayabusa::detections::rule::{get_detection_keys, RuleNode};
use hayabusa::filter;
use hayabusa::omikuji::Omikuji;
use hayabusa::{afterfact::after_fact, detections::utils};
@@ -14,305 +14,345 @@ use hayabusa::{detections::configs, timeline::timeline::Timeline};
use hhmmss::Hhmmss;
use pbr::ProgressBar;
use serde_json::Value;
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::fmt::Display;
use std::sync::Arc;
use std::{
fs::{self, File},
path::PathBuf,
vec,
};
use tokio::runtime::Runtime;
use tokio::spawn;
use tokio::task::JoinHandle;
// 一度にtimelineやdetectionを実行する行数
const MAX_DETECT_RECORDS: usize = 5000;
fn main() {
let analysis_start_time: DateTime<Local> = Local::now();
if !configs::CONFIG.read().unwrap().args.is_present("q") {
output_logo();
println!("");
output_eggs(&format!(
"{:02}/{:02}",
&analysis_start_time.month().to_owned(),
&analysis_start_time.day().to_owned()
));
}
if configs::CONFIG.read().unwrap().args.args.len() == 0 {
println!(
"{}",
configs::CONFIG.read().unwrap().args.usage().to_string()
);
return;
}
if let Some(filepath) = configs::CONFIG.read().unwrap().args.value_of("filepath") {
if !filepath.ends_with(".evtx") {
AlertMessage::alert(
&mut std::io::stderr().lock(),
"--filepath only accepts .evtx files.".to_owned(),
)
.ok();
return;
}
analysis_files(vec![PathBuf::from(filepath)]);
} else if let Some(directory) = configs::CONFIG.read().unwrap().args.value_of("directory") {
let evtx_files = collect_evtxfiles(&directory);
if evtx_files.len() == 0 {
AlertMessage::alert(
&mut std::io::stderr().lock(),
"No .evtx files were found.".to_owned(),
)
.ok();
return;
}
analysis_files(evtx_files);
} else if configs::CONFIG
.read()
.unwrap()
.args
.is_present("contributors")
{
print_contributors();
return;
}
let analysis_end_time: DateTime<Local> = Local::now();
let analysis_duration = analysis_end_time.signed_duration_since(analysis_start_time);
println!("Elapsed Time: {}", &analysis_duration.hhmmssxxx());
println!("");
let mut app = App::new();
app.exec();
app.rt.shutdown_background();
}
fn collect_evtxfiles(dirpath: &str) -> Vec<PathBuf> {
let entries = fs::read_dir(dirpath);
if entries.is_err() {
let stderr = std::io::stderr();
let mut stderr = stderr.lock();
AlertMessage::alert(&mut stderr, format!("{}", entries.unwrap_err())).ok();
return vec![];
pub struct App {
rt: Runtime,
rule_keys: Vec<String>,
}
impl App {
pub fn new() -> App {
return App {
rt: utils::create_tokio_runtime(),
rule_keys: Vec::new(),
};
}
let mut ret = vec![];
for e in entries.unwrap() {
if e.is_err() {
continue;
fn exec(&mut self) {
let analysis_start_time: DateTime<Local> = Local::now();
if !configs::CONFIG.read().unwrap().args.is_present("q") {
self.output_logo();
println!("");
self.output_eggs(&format!(
"{:02}/{:02}",
&analysis_start_time.month().to_owned(),
&analysis_start_time.day().to_owned()
));
}
if configs::CONFIG.read().unwrap().args.args.len() == 0 {
println!(
"{}",
configs::CONFIG.read().unwrap().args.usage().to_string()
);
return;
}
if let Some(filepath) = configs::CONFIG.read().unwrap().args.value_of("filepath") {
if !filepath.ends_with(".evtx") {
AlertMessage::alert(
&mut std::io::stderr().lock(),
"--filepath only accepts .evtx files.".to_owned(),
)
.ok();
return;
}
self.analysis_files(vec![PathBuf::from(filepath)]);
} else if let Some(directory) = configs::CONFIG.read().unwrap().args.value_of("directory") {
let evtx_files = self.collect_evtxfiles(&directory);
if evtx_files.len() == 0 {
AlertMessage::alert(
&mut std::io::stderr().lock(),
"No .evtx files were found.".to_owned(),
)
.ok();
return;
}
self.analysis_files(evtx_files);
} else if configs::CONFIG
.read()
.unwrap()
.args
.is_present("contributors")
{
self.print_contributors();
return;
}
let analysis_end_time: DateTime<Local> = Local::now();
let analysis_duration = analysis_end_time.signed_duration_since(analysis_start_time);
println!("Elapsed Time: {}", &analysis_duration.hhmmssxxx());
println!("");
}
fn collect_evtxfiles(&self, dirpath: &str) -> Vec<PathBuf> {
let entries = fs::read_dir(dirpath);
if entries.is_err() {
let stderr = std::io::stderr();
let mut stderr = stderr.lock();
AlertMessage::alert(&mut stderr, format!("{}", entries.unwrap_err())).ok();
return vec![];
}
let path = e.unwrap().path();
if path.is_dir() {
path.to_str().and_then(|path_str| {
let subdir_ret = collect_evtxfiles(path_str);
ret.extend(subdir_ret);
return Option::Some(());
});
} else {
let path_str = path.to_str().unwrap_or("");
if path_str.ends_with(".evtx") {
ret.push(path);
let mut ret = vec![];
for e in entries.unwrap() {
if e.is_err() {
continue;
}
let path = e.unwrap().path();
if path.is_dir() {
path.to_str().and_then(|path_str| {
let subdir_ret = self.collect_evtxfiles(path_str);
ret.extend(subdir_ret);
return Option::Some(());
});
} else {
let path_str = path.to_str().unwrap_or("");
if path_str.ends_with(".evtx") {
ret.push(path);
}
}
}
return ret;
}
fn print_contributors(&self) {
match fs::read_to_string("./contributors.txt") {
Ok(contents) => println!("{}", contents),
Err(err) => {
AlertMessage::alert(&mut std::io::stderr().lock(), format!("{}", err)).ok();
}
}
}
return ret;
}
fn analysis_files(&mut self, evtx_files: Vec<PathBuf>) {
let level = configs::CONFIG
.read()
.unwrap()
.args
.value_of("min-level")
.unwrap_or("informational")
.to_uppercase();
println!("Analyzing event files: {:?}", evtx_files.len());
fn print_contributors() {
match fs::read_to_string("./contributors.txt") {
Ok(contents) => println!("{}", contents),
Err(err) => {
AlertMessage::alert(&mut std::io::stderr().lock(), format!("{}", err)).ok();
let rule_files = detection::Detection::parse_rule_files(
level,
configs::CONFIG.read().unwrap().args.value_of("rules"),
&filter::exclude_ids(),
);
let mut pb = ProgressBar::new(evtx_files.len() as u64);
pb.show_speed = false;
self.rule_keys = self.get_all_keys(&rule_files);
let mut detection = detection::Detection::new(rule_files);
for evtx_file in evtx_files {
if configs::CONFIG.read().unwrap().args.is_present("verbose") {
println!("Checking target evtx FilePath: {:?}", &evtx_file);
}
detection = self.analysis_file(evtx_file, detection);
pb.inc();
}
after_fact();
detection.print_unique_results();
}
}
fn analysis_files(evtx_files: Vec<PathBuf>) {
let level = configs::CONFIG
.read()
.unwrap()
.args
.value_of("min-level")
.unwrap_or("informational")
.to_uppercase();
println!("Analyzing event files: {:?}", evtx_files.len());
let rule_files = detection::Detection::parse_rule_files(
level,
configs::CONFIG.read().unwrap().args.value_of("rules"),
&filter::exclude_ids(),
);
let mut pb = ProgressBar::new(evtx_files.len() as u64);
let mut detection = detection::Detection::new(rule_files);
for evtx_file in evtx_files {
if configs::CONFIG.read().unwrap().args.is_present("verbose") {
println!("Checking target evtx FilePath: {:?}", &evtx_file);
// Windowsイベントログファイルを1ファイル分解析する。
fn analysis_file(
&self,
evtx_filepath: PathBuf,
mut detection: detection::Detection,
) -> detection::Detection {
let path = evtx_filepath.display();
let parser = self.evtx_to_jsons(evtx_filepath.clone());
if parser.is_none() {
return detection;
}
detection = analysis_file(evtx_file, detection);
pb.inc();
}
after_fact();
detection.print_unique_results();
}
// Windowsイベントログファイルを1ファイル分解析する。
fn analysis_file(
evtx_filepath: PathBuf,
mut detection: detection::Detection,
) -> detection::Detection {
let filepath_disp = evtx_filepath.display();
let parser = evtx_to_jsons(evtx_filepath.clone());
if parser.is_none() {
return detection;
}
let mut tl = Timeline::new();
let mut parser = parser.unwrap();
let mut records = parser.records_json_value();
let mut tl = Timeline::new();
let mut parser = parser.unwrap();
let mut records = parser.records_json_value();
let tokio_rt = utils::create_tokio_runtime();
loop {
let mut records_per_detect = vec![];
while records_per_detect.len() < MAX_DETECT_RECORDS {
// パースに失敗している場合、エラーメッセージを出力
let next_rec = records.next();
if next_rec.is_none() {
break;
}
loop {
let mut records_per_detect = vec![];
while records_per_detect.len() < MAX_DETECT_RECORDS {
// パースに失敗している場合、エラーメッセージを出力
let next_rec = records.next();
if next_rec.is_none() {
let record_result = next_rec.unwrap();
if record_result.is_err() {
let evtx_filepath = &path;
let errmsg = format!(
"Failed to parse event file. EventFile:{} Error:{}",
evtx_filepath,
record_result.unwrap_err()
);
AlertMessage::alert(&mut std::io::stderr().lock(), errmsg).ok();
continue;
}
// target_eventids.txtでフィルタする。
let data = record_result.unwrap().data;
if self._is_target_event_id(&data) == false {
continue;
}
// EvtxRecordInfo構造体に変更
records_per_detect.push(data);
}
if records_per_detect.len() == 0 {
break;
}
let record_result = next_rec.unwrap();
if record_result.is_err() {
let evtx_filepath = &filepath_disp;
let errmsg = format!(
"Failed to parse event file. EventFile:{} Error:{}",
evtx_filepath,
record_result.unwrap_err()
);
AlertMessage::alert(&mut std::io::stderr().lock(), errmsg).ok();
continue;
let records_per_detect = self.rt.block_on(App::create_rec_infos(
records_per_detect,
&path,
self.rule_keys.clone(),
));
// // timeline機能の実行
tl.start(&records_per_detect);
// // ruleファイルの検知
detection = detection.start(&self.rt, records_per_detect);
}
detection.add_aggcondtion_msg();
tl.tm_stats_dsp_msg();
return detection;
}
async fn create_rec_infos(
records_per_detect: Vec<Value>,
path: &dyn Display,
rule_keys: Vec<String>,
) -> Vec<EvtxRecordInfo> {
let path = Arc::new(path.to_string());
let rule_keys = Arc::new(rule_keys);
let threads: Vec<JoinHandle<EvtxRecordInfo>> = records_per_detect
.into_iter()
.map(|rec| {
let arc_rule_keys = Arc::clone(&rule_keys);
let arc_path = Arc::clone(&path);
return spawn(async move {
let rec_info =
utils::create_rec_info(rec, arc_path.to_string(), &arc_rule_keys);
return rec_info;
});
})
.collect();
let mut ret = vec![];
for thread in threads.into_iter() {
ret.push(thread.await.unwrap());
}
return ret;
}
fn get_all_keys(&self, rules: &Vec<RuleNode>) -> Vec<String> {
let mut key_set = HashSet::new();
for rule in rules {
let keys = get_detection_keys(rule);
key_set.extend(keys);
}
let ret: Vec<String> = key_set.into_iter().collect();
return ret;
}
// target_eventids.txtの設定を元にフィルタする。
fn _is_target_event_id(&self, data: &Value) -> bool {
let eventid = utils::get_event_value(&utils::get_event_id_key(), data);
if eventid.is_none() {
return true;
}
return match eventid.unwrap() {
Value::String(s) => utils::is_target_event_id(s),
Value::Number(n) => utils::is_target_event_id(&n.to_string()),
_ => true, // レコードからEventIdが取得できない場合は、特にフィルタしない
};
}
fn evtx_to_jsons(&self, evtx_filepath: PathBuf) -> Option<EvtxParser<File>> {
match EvtxParser::from_path(evtx_filepath) {
Ok(evtx_parser) => {
// parserのデフォルト設定を変更
let mut parse_config = ParserSettings::default();
parse_config = parse_config.separate_json_attributes(true); // XMLのattributeをJSONに変換する時のルールを設定
parse_config = parse_config.num_threads(0); // 設定しないと遅かったので、設定しておく。
let evtx_parser = evtx_parser.with_configuration(parse_config);
return Option::Some(evtx_parser);
}
// target_eventids.txtでフィルタする。
let data = record_result.unwrap().data;
if _is_target_event_id(&data) == false {
continue;
Err(e) => {
eprintln!("{}", e);
return Option::None;
}
// EvtxRecordInfo構造体に変更
records_per_detect.push(_create_rec_info(data, &filepath_disp));
}
if records_per_detect.len() == 0 {
break;
}
// timeline機能の実行
tl.start(&records_per_detect);
// ruleファイルの検知
detection = detection.start(&tokio_rt, records_per_detect);
}
tokio_rt.shutdown_background();
detection.add_aggcondtion_msg();
tl.tm_stats_dsp_msg();
return detection;
}
// target_eventids.txtの設定を元にフィルタする。
fn _is_target_event_id(data: &Value) -> bool {
let eventid = utils::get_event_value(&utils::get_event_id_key(), data);
if eventid.is_none() {
return true;
}
return match eventid.unwrap() {
Value::String(s) => utils::is_target_event_id(s),
Value::Number(n) => utils::is_target_event_id(&n.to_string()),
_ => true, // レコードからEventIdが取得できない場合は、特にフィルタしない
};
}
// EvtxRecordInfoを作成します。
fn _create_rec_info(mut data: Value, path: &dyn Display) -> EvtxRecordInfo {
// 高速化のための処理
// RuleNodeでワイルドカードや正規表現のマッチング処理をする際には、
// Value(JSON)がstring型以外の場合はstringに変換して比較している。
// RuleNodeでマッチングする毎にstring変換していると、
// 1回の処理はそこまででもないが相当回数呼び出されれるとボトルネックになりうる。
// なので、よく使われるstring型ではない値を事前に変換しておくことで、
// string変換する回数を減らせる。
// 本当はやりたくないが...
match &data["Event"]["System"]["EventID"] {
Value::Number(n) => data["Event"]["System"]["EventID"] = Value::String(n.to_string()),
_ => (),
};
match &data["Event"]["EventData"]["LogonType"] {
Value::Number(n) => data["Event"]["EventData"]["LogonType"] = Value::String(n.to_string()),
_ => (),
}
match &data["Event"]["EventData"]["DestinationPort"] {
Value::Number(n) => {
data["Event"]["EventData"]["DestinationPort"] = Value::String(n.to_string())
}
_ => (),
}
// EvtxRecordInfoを作る
let data_str = data.to_string();
return EvtxRecordInfo::new(path.to_string(), data, data_str);
}
fn evtx_to_jsons(evtx_filepath: PathBuf) -> Option<EvtxParser<File>> {
match EvtxParser::from_path(evtx_filepath) {
Ok(evtx_parser) => {
// parserのデフォルト設定を変更
let mut parse_config = ParserSettings::default();
parse_config = parse_config.separate_json_attributes(true); // XMLのattributeをJSONに変換する時のルールを設定
parse_config = parse_config.num_threads(utils::get_thread_num()); // 設定しないと遅かったので、設定しておく。
let evtx_parser = evtx_parser.with_configuration(parse_config);
return Option::Some(evtx_parser);
}
Err(e) => {
eprintln!("{}", e);
return Option::None;
}
}
}
fn _output_with_omikuji(omikuji: Omikuji) {
let fp = &format!("art/omikuji/{}", omikuji);
let content = fs::read_to_string(fp).unwrap();
println!("{}", content);
}
fn _output_with_omikuji(&self, omikuji: Omikuji) {
let fp = &format!("art/omikuji/{}", omikuji);
let content = fs::read_to_string(fp).unwrap();
println!("{}", content);
}
/// output logo
fn output_logo() {
let fp = &format!("art/logo.txt");
let content = fs::read_to_string(fp).unwrap_or("".to_owned());
println!("{}", content);
}
/// output logo
fn output_logo(&self) {
let fp = &format!("art/logo.txt");
let content = fs::read_to_string(fp).unwrap_or("".to_owned());
println!("{}", content);
}
/// output easter egg arts
fn output_eggs(exec_datestr: &str) {
let mut eggs: HashMap<&str, &str> = HashMap::new();
eggs.insert("01/01", "art/happynewyear.txt");
eggs.insert("02/22", "art/ninja.txt");
eggs.insert("08/08", "art/takoyaki.txt");
eggs.insert("12/25", "art/christmas.txt");
/// output easter egg arts
fn output_eggs(&self, exec_datestr: &str) {
let mut eggs: HashMap<&str, &str> = HashMap::new();
eggs.insert("01/01", "art/happynewyear.txt");
eggs.insert("02/22", "art/ninja.txt");
eggs.insert("08/08", "art/takoyaki.txt");
eggs.insert("12/25", "art/christmas.txt");
match eggs.get(exec_datestr) {
None => {}
Some(path) => {
let content = fs::read_to_string(path).unwrap_or("".to_owned());
println!("{}", content);
match eggs.get(exec_datestr) {
None => {}
Some(path) => {
let content = fs::read_to_string(path).unwrap_or("".to_owned());
println!("{}", content);
}
}
}
}
#[cfg(test)]
mod tests {
use crate::collect_evtxfiles;
use crate::App;
#[test]
fn test_collect_evtxfiles() {
let files = collect_evtxfiles("test_files/evtx");
let app = App::new();
let files = app.collect_evtxfiles("test_files/evtx");
assert_eq!(3, files.len());
files.iter().for_each(|file| {