Merge branch 'main' into feature/update_eventkey_alias#274
This commit is contained in:
@@ -1,13 +1,57 @@
|
||||
/⌒\、__/⌒ ̄}
|
||||
\__(__)__/
|
||||
〃〓/ ̄ > <  ̄\〓〃
|
||||
ミ☆/: (:: ::):: >☆彡
|
||||
★≡〃\/〉:: ::〈\/ ≡〃★
|
||||
●※○ ^^^^^^^^ ○※●
|
||||
〃≡★ Merry Christmas ★≡〃
|
||||
☆〓 〓☆
|
||||
〃≡★ (;) ★≡〃
|
||||
●※○- ,_】【_, ,-○※●
|
||||
★〃≡〓 ○ 〓≡〃★
|
||||
ミ☆-★※★-☆彡
|
||||
●
|
||||
|
||||
,,
|
||||
,,,,
|
||||
.,,,,,,,,,,,..
|
||||
.,,,,,,,,,,,,,,,,
|
||||
,,,,,,,,,,,
|
||||
,,,,,,,,,,,
|
||||
,,,,(((,,,,.
|
||||
. ,((((((
|
||||
(((((((((
|
||||
(((((((((((((
|
||||
(((((((,,,,((((*
|
||||
((((((((,,,,,,,((((
|
||||
((((((((((,,,,,,(((((((
|
||||
((*,(((((((((((((((((((((/
|
||||
((* ((. .((((((((((((((((
|
||||
((((( Merry (((((((((
|
||||
(((((((, Christmas (((((((((((
|
||||
((((((((/(((((( (((((((((((((((((((((
|
||||
(((((((*******(((((((((((((((((((((((((((((((
|
||||
(((((((((*******(((((((((((((((((((((((((((((((((
|
||||
.//////(((((((((((((((((((((((((((,,,,,(((((((((//////*
|
||||
(((((((((((((((((((((((((((,,,,,,,(((((((((
|
||||
(( ((((((((((((((((((((((,,,,,((((((((((((
|
||||
,((. .((((((((((((((((((((((((((((((((((
|
||||
(((((((( from ((((((((((((((((((((((((((((((*
|
||||
(((((((((((( , (((((((((((((((((( *.(*
|
||||
.((((((((((((((((((((((((((((((((((((((((( (((
|
||||
(((((((((((((((((((((((((((((((((((((((/ Yamato ,(((((((
|
||||
(((((((((((((((((((((((((((((((((((((((( ((((((((((((
|
||||
((((((((((((((((((((((((,,,((((( (, ,((((((((((
|
||||
((((((((((((((((((((((((,,,,,,,((((((((((((((((((((((((((
|
||||
*(((( ((((((((((((((((/,,,,,,((((((((((((((((******((((((
|
||||
(((( ((((((((((((((((((((((((((((((((********((((((.
|
||||
(((((( Security! ((((((((((((((((((((((((((((((****((((((((((
|
||||
((((((((( (((((((((((((((((((((((((((((((((((((((((((
|
||||
,(((((((((((((((( .(( ((((((((((((((((((( ( (((((((((((((
|
||||
((((((((((((((((,,,,,((((((((( (((((((((((( Ho ho ho!!! ,(((((((((((((.
|
||||
(((((((((((((((((,,,,,,,(((((((((((((((((((((( ((((((((((((((((((((
|
||||
((((((((((((((((((((,,,,,((((((((((((((((((((((((((( ,(((((((/,,,,,((((((((((((((((
|
||||
(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((,,,,,,,(((((((((((((((((
|
||||
.(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((,,,,*(((((((((((((((((((/
|
||||
(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((
|
||||
///////////
|
||||
///////////
|
||||
///////////
|
||||
///////////
|
||||
///////////
|
||||
///////////
|
||||
*****************************
|
||||
*****************************
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%%%%%%%%%%%%%%%#%%%%%%%%%%
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
#%%%%%%%%%%%%%%%#%%%%%%%%%%
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
|
||||
@@ -1,10 +1,26 @@
|
||||
_〆
|
||||
(∴)
|
||||
( ̄ ̄ ̄)
|
||||
<( ̄ ̄ ̄ ̄)>
|
||||
[二◆二二◆二]
|
||||
|◇ ● ◇|
|
||||
|◆ ◆|
|
||||
|____|
|
||||
|
||||
A Happy New Year!!
|
||||
|
||||
@@
|
||||
@@@@@ @@ @@@@,
|
||||
@@& @@@@@@@@@@@@@@@@@. @@@@@@@@@@@@@@@@@@
|
||||
@@@@@@@@@@@( @@@@@@ @@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@.
|
||||
.@@@@@@@@@@@ @@@@@ #@@@ @@@@@@ @@@@@@@@@@@@@
|
||||
@@@ @@@@ @@@@@@@@@@@@@@( @@@@@@@
|
||||
,@@@ (@@@ @@ .@@@@@
|
||||
@@@@ @@@@@@@ %% @@@@@
|
||||
@@@@ @@@@ @@@@@@@%*.,@@@@@@ @@@@,
|
||||
@@@@ @@@@@@ @@@@@ @@@@. @@@@@@@@@
|
||||
@@@@ @@@ @@@@@@@@ @@@@@ @@@@@@@ @@@@@@@@@@@@@@@@@
|
||||
@@@, @@@@& @@@@ @@@@
|
||||
@@@ %@@@@@@ %@@@@, @@@@ @@@@
|
||||
@@@@@@@@# @@@@@ @@@@ @@@@
|
||||
@@@ (@@@@@@@@@@ @@@@ @@@@
|
||||
@@@@@@@@@@@@@@@@@@@@@ @@@@ @@@@ %@@@
|
||||
@@@@@@( @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
@@@@@ @@@@@@@ ,@@@@@@@@@@@@@@@@@@@@@@/ &@@@@@@@@@@@@@@&
|
||||
@@@@@ @@@@@@@ @@@@@@@@@@ @@@@#
|
||||
@@@@@ @@@@@@@ *@
|
||||
@@@@@ #@@@@
|
||||
|
||||
Happy New Year from Yamato Security!!!
|
||||
Akemashite Omedetou Gozaimasu!
|
||||
Honnen mo yoroshiku onegai shimasu!
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
|
||||
██╗ ██╗ █████╗ ██╗ ██╗ █████╗ ██████╗ ██╗ ██╗███████╗ █████╗
|
||||
██║ ██║██╔══██╗╚██╗ ██╔╝██╔══██╗██╔══██╗██║ ██║██╔════╝██╔══██╗
|
||||
███████║███████║ ╚████╔╝ ███████║██████╔╝██║ ██║███████╗███████║
|
||||
██╔══██║██╔══██║ ╚██╔╝ ██╔══██║██╔══██╗██║ ██║╚════██║██╔══██║
|
||||
██║ ██║██║ ██║ ██║ ██║ ██║██████╔╝╚██████╔╝███████║██║ ██║
|
||||
╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═════╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝
|
||||
by Yamato Security
|
||||
by Yamato Security
|
||||
@@ -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
|
||||
|
||||
@@ -1,43 +1,38 @@
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
@@#------@@@@@@@@--------@@@@@@@@--------@@@@@@@@--------@@@@@@@@--------@@@@@@@@------#@@
|
||||
@@* @@@@@@@% @@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@@ *@@
|
||||
@@* @@@@@@@% @@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@@ *@@
|
||||
@@#------********--------********--------********--------********--------********=-----#@@
|
||||
@@@@@@@@@ @@@@@@@# @@@@@@@@ @@@@@@@@ @@@@@@@% @@@@@@@@@
|
||||
@@@@@@@@@ @@@@@@@# @@@@@@@@ @@@@@@@@ @@@@@@@% @@@@@@@@@
|
||||
@@@@@@@@@ @@@@@@@# @@@@@@@@ @@@@@@@@ @@@@@@@% @@@@@@@@@
|
||||
@@@@@@@@@-------=@@@@@@@%-------=@@@@@@@@-------=@@@@@@@@-------=@@@@@@@@-------=@@@@@@@@@
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@=:@@@@@@@
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*.+@@@@@@@@
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@= *@@@@@@@@@
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%:-@@@@@@@@@@@
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*.*@@@@@@@@@@@@
|
||||
@@@@@@@@@@@%*=-:::-=*@@@@@@@@@%*=-:::-+#@@@@@@@@@#+=-::-=+#@@@@@@@@@#+--::: =%@@@@@@@@@@@@
|
||||
@@@@@@@@@*: = :#@@@@+. -%@@@@= =@@@@%- .+@@@@@@@@@@
|
||||
@@@@@@@@: :: #* -@%. - *= =@* -. =#. *@+ :: -#: .#@@@@@@@@
|
||||
@@@@@@@. : -. . : .= : : = . %@@@@@@@
|
||||
@@@@@@+ =*. -+=. .++ =+= :*= =+- -*- .++- . -@@@@@@@
|
||||
@@@@@@= *@@@@=-#@@@@+:+@% .*@@@%==#@@@@=:*@# :#@@@#==%@@@%--#@+ -%@@@*-=@@@@#--%@-:@@@@@@@
|
||||
@@@@-== #@@@@@@@@@@@@@@@+ %@@@@@@@@@@@@@@@- .@@@@@@@@@@@@@@@@. :@@@@@@@@@@@@@@@@ :=:@@@@@
|
||||
@@@@#. =*#@@@@@@@@@@@* *-.%@@@@@@@@@@@@@= #::@@@@@@@@@@@@@@:.# -@@@@@@@@@@@%*=. .*@@@@@
|
||||
@@@@@@#- .:-=+*#%*::%@@*.-#@@@@@@@@@+.-%@@=.=%@@@@@@@@%=.=@@@=.+%#*+=-:. :*@@@@@@@
|
||||
@@@@@@@@@*- ..::- :====-. .=++++++: .-====-. :::.. :*@@@@@@@@@@
|
||||
@@@@@@@@@@@@%+-. .-+#@@@@@@@@@@@@@
|
||||
@@@@@@@@@@@@@@@@@%#*+-::. ..:-=*#%@@@@@@@@@@@@@@@@@@
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@%##***++++=============++++***##%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
@@======@@#--%@@#=+@@+=#@#=--=#@#==@@==#@@+-=@@@+=*@@==%==#@@@*====+%@@@#--%@@*=*@%==%==@@
|
||||
@@*+ ##@@. .@@* .@= :@- .+*: -# %% +@# -@@- =@: =@ *@@@- -#+ #@@. .@@= .@* % %@
|
||||
@@@# @@@: =- -@* .@@ #@@% @# *@% *. *@- =@@ #@@@= =@@= -@- -= -@@+ .#@:.@@
|
||||
@@@# @@+ :: ** .@: :@+ -- +@@# #@@: .:: %- =%. =@ *@@@- :+- .%* :: +@@+ @@@=-@@
|
||||
@@@%++@@*+%@@#+*%+*@@*+#@@*++*@@@@@**@@@++@@@*+##+#@%++%++%@@@#+++*#@@*+%@@%+*@@%**@@@*+@@
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
@@@@@@@@@:.......@@@@@@@@:.......@@@@@@@@:.......@@@@@@@@:.......%@@@@@@@:.......@@@@@@@@@
|
||||
@@@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@@ %@@@@@@@ @@@@@@@@@
|
||||
@@@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@@ %@@@@@@@ @@@@@@@@@
|
||||
@@@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@@ %@@@@@@@ @@@@@@@@@
|
||||
@@#:::::-########::::::::########::::::::########::::::::########::::::::########::::::#@@
|
||||
@@* .@@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@@ *@@
|
||||
@@* .@@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@@ *@@
|
||||
@@%+++++*@@@@@@@@++++++++@@@@@@@@++++++++@@@@@@@@++++++++@@@@@@@@++++++++@@@@@@@@++++++%@@
|
||||
|
||||
####
|
||||
##.##
|
||||
##.(#
|
||||
.#.,#
|
||||
#..#
|
||||
##.#*
|
||||
##.##
|
||||
*#.##
|
||||
#.##
|
||||
#(/#
|
||||
##.#
|
||||
(#.#,
|
||||
#.#(
|
||||
#*##
|
||||
####
|
||||
.,####* ##(#
|
||||
##.##../##((####.((####,#
|
||||
####(.(....(((((##,.#(((######
|
||||
###.(##(((#######..##......#((((##/
|
||||
########((((####.(##########(*(((#,##..#
|
||||
#################..####*..(((######*,##..#
|
||||
/###############......*####((((###.*##..####
|
||||
#######(,#####################*#####.((((####
|
||||
#(###,,,,#############(/######...##..#(######
|
||||
#,,,,,,,,*#######,,,,,,,,######(.######/*/###
|
||||
#,,,,,,,,,,,,,,,,,,,,,,,#######/#############
|
||||
#,,,,,,,,,,,,,,,,,,,,,,##########,,,(#######
|
||||
#,,,,,,,,,,,,,,,,,,,,,#######,,,,,,#######
|
||||
#,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,######
|
||||
##,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*##
|
||||
##,,,,,,,,,,,,,,,,,,,,,,,,,,,,,##
|
||||
###,,,,,,,,,,,,,,,,,,,,,,##.
|
||||
.#################
|
||||
|
||||
HAPPY TAKOYAKI DAY!!! (8/8)
|
||||
from Yamato Security
|
||||
|
||||
@@ -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
|
||||
|
||||
8
config/exclude-rules-full.txt
Normal file
8
config/exclude-rules-full.txt
Normal 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
|
||||
@@ -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
|
||||
9
config/noisy-rules-full.txt
Normal file
9
config/noisy-rules-full.txt
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
pub mod configs;
|
||||
pub mod detection;
|
||||
pub mod print;
|
||||
mod rule;
|
||||
pub mod rule;
|
||||
pub mod utils;
|
||||
|
||||
@@ -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内のマップをクリアする。テストする際の冪等性の担保のため作成。
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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(_) => {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(_) => {
|
||||
|
||||
@@ -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(®ex_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())
|
||||
|
||||
544
src/main.rs
544
src/main.rs
@@ -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| {
|
||||
|
||||
Reference in New Issue
Block a user