ข้ามไปที่เนื้อหา

การวิเคราะห์ผลลัพธ์ของ Hayabusa ด้วย jq

ผู้เขียน

Zach Mathis (@yamatosecurity) - 2023/03/22

เกี่ยวกับ

ความสามารถในการระบุ ดึงออกมา และสร้างเมตริกจากฟิลด์สำคัญในล็อกเป็นทักษะที่จำเป็นสำหรับนักวิเคราะห์ DFIR และการล่าภัยคุกคาม โดยทั่วไปผลลัพธ์ของ Hayabusa จะถูกบันทึกเป็นไฟล์ .csv เพื่อนำเข้าสู่โปรแกรมอย่าง Excel หรือ Timeline Explorer สำหรับการวิเคราะห์ไทม์ไลน์ อย่างไรก็ตาม เมื่อมีเหตุการณ์เดียวกันหลายร้อยรายการหรือมากกว่านั้น การตรวจสอบด้วยตนเองจึงไม่สะดวกหรือเป็นไปไม่ได้ ในสถานการณ์เหล่านี้ นักวิเคราะห์มักจะเรียงลำดับและนับข้อมูลประเภทที่คล้ายกันเพื่อค้นหาค่าผิดปกติ สิ่งนี้ยังเป็นที่รู้จักในชื่อ long tail analysis, stack ranking, frequency analysis ฯลฯ... สามารถทำได้ด้วย Hayabusa โดยการส่งออกผลลัพธ์เป็นไฟล์ .json หรือ .jsonl แล้ววิเคราะห์ด้วย jq

ตัวอย่างเช่น นักวิเคราะห์สามารถเปรียบเทียบบริการที่ติดตั้งบนเวิร์กสเตชันทั้งหมดในองค์กรได้ แม้ว่าจะเป็นไปได้ที่มัลแวร์บางตัวอาจถูกติดตั้งบนทุกเวิร์กสเตชัน แต่มีความเป็นไปได้สูงกว่าที่มันจะมีอยู่เพียงไม่กี่ระบบเท่านั้น ในกรณีนี้ บริการที่ติดตั้งบนทุกระบบมีแนวโน้มที่จะไม่เป็นอันตราย ในขณะที่บริการที่พบได้น้อยมีแนวโน้มที่จะน่าสงสัยมากกว่าและควรได้รับการตรวจสอบเป็นระยะ

อีกกรณีการใช้งานหนึ่งคือเพื่อช่วยพิจารณาว่าบางสิ่งน่าสงสัยมากเพียงใด ตัวอย่างเช่น นักวิเคราะห์สามารถวิเคราะห์ล็อกการเข้าสู่ระบบล้มเหลว 4625 เพื่อพิจารณาว่าที่อยู่ IP หนึ่งล้มเหลวในการเข้าสู่ระบบกี่ครั้ง หากมีการเข้าสู่ระบบล้มเหลวเพียงไม่กี่ครั้ง ก็มีแนวโน้มว่าผู้ดูแลระบบเพียงแค่พิมพ์รหัสผ่านผิด อย่างไรก็ตาม หากมีการเข้าสู่ระบบล้มเหลวหลายร้อยครั้งหรือมากกว่านั้นในช่วงเวลาสั้นๆ จากที่อยู่ IP หนึ่ง ก็มีแนวโน้มว่าที่อยู่ IP นั้นเป็นอันตราย

การเรียนรู้วิธีใช้ jq จะช่วยให้คุณเชี่ยวชาญไม่เพียงแค่การวิเคราะห์ล็อกเหตุการณ์ของ Windows เท่านั้น แต่รวมถึงล็อกในรูปแบบ JSON ทั้งหมด ในปัจจุบันที่ JSON ได้กลายเป็นรูปแบบล็อกที่ได้รับความนิยมอย่างมากและผู้ให้บริการคลาวด์ส่วนใหญ่ใช้สำหรับล็อกของพวกเขา ความสามารถในการแยกวิเคราะห์ด้วย jq จึงกลายเป็นทักษะที่จำเป็นสำหรับนักวิเคราะห์ความปลอดภัยยุคใหม่

ในคู่มือนี้ ผมจะอธิบายวิธีใช้ jq สำหรับผู้ที่ไม่เคยใช้มาก่อนก่อน แล้วจึงอธิบายการใช้งานที่ซับซ้อนยิ่งขึ้นพร้อมกับตัวอย่างในโลกจริง ผมแนะนำให้ใช้ linux, macOS หรือ linux บน Windows เพื่อให้สามารถรวม jq เข้ากับคำสั่งที่มีประโยชน์อื่นๆ เช่น sort, uniq, grep, sed ฯลฯ...

การติดตั้ง jq

กรุณาดูที่ https://stedolan.github.io/jq/ และติดตั้งคำสั่ง jq

เกี่ยวกับรูปแบบ JSON

ล็อก JSON เป็นรายการของอ็อบเจกต์ที่บรรจุอยู่ในวงเล็บปีกกา { } ภายในอ็อบเจกต์เหล่านี้คือคู่ key-value ที่คั่นด้วยเครื่องหมายโคลอน key ต้องเป็นสตริง แต่ value อาจเป็นหนึ่งในประเภทต่อไปนี้: * สตริง (ตัวอย่าง: "string") * ตัวเลข (ตัวอย่าง: 10) * อ็อบเจกต์อื่น (ตัวอย่าง: { xxxx }) * อาเรย์ (ตัวอย่าง: ["string", 10]) * บูลีน (ตัวอย่าง: true, false) * null

คุณสามารถซ้อนอ็อบเจกต์ได้มากเท่าที่ต้องการภายในอ็อบเจกต์

ในตัวอย่างนี้ Details เป็นอ็อบเจกต์ที่ซ้อนอยู่ภายในอ็อบเจกต์ราก:

{
    "Timestamp": "2016-08-19 08:06:57.658 +09:00",
    "Computer": "IE10Win7",
    "Channel": "Sec",
    "EventID": 4688,
    "Level": "info",
    "RecordID": 6845,
    "RuleTitle": "Proc Exec",
    "Details": {
        "CmdLine": "C:\\Windows\\system32\\ipconfig /release",
        "Path": "C:\\Windows\\System32\\ipconfig.exe",
        "PID": "0xcf4",
        "User": "IE10WIN7$",
        "LID": "0x3e7"
    }
}

เกี่ยวกับรูปแบบ JSON และ JSONL กับ Hayabusa

ในเวอร์ชันก่อนหน้านี้ Hayabusa จะใช้รูปแบบ JSON แบบดั้งเดิมโดยการนำอ็อบเจกต์ล็อก { xxx } ทั้งหมดมาใส่ในอาเรย์ขนาดใหญ่หนึ่งอัน

ตัวอย่าง:

[
    {
        "Timestamp": "2016-08-19 08:06:57.658 +09:00",
        "Computer": "IE10Win7",
        "Channel": "Sec",
        "EventID": 4688,
        "Level": "info",
        "RecordID": 6845,
        "RuleTitle": "Proc Exec",
        "Details": {
            "CmdLine": "C:\\Windows\\system32\\ipconfig /release",
            "Path": "C:\\Windows\\System32\\ipconfig.exe",
            "PID": "0xcf4",
            "User": "IE10WIN7$",
            "LID": "0x3e7"
        }
    },
    {
        "Timestamp": "2016-08-19 11:07:47.489 +09:00",
        "Computer": "IE10Win7",
        "Channel": "Sec",
        "EventID": 4688,
        "Level": "info",
        "RecordID": 6847,
        "RuleTitle": "Proc Exec",
        "Details": {
            "CmdLine": "taskhost.exe $(Arg0)",
            "Path": "C:\\Windows\\System32\\taskhost.exe",
            "PID": "0x228",
            "User": "IE10WIN7$",
            "LID": "0x3e7"
        }
    }
]

มีปัญหาสองประการกับสิ่งนี้ ปัญหาแรกคือคิวรี jq จะยุ่งยากมากขึ้นเนื่องจากทุกอย่างต้องเริ่มต้นด้วย .[] เพิ่มเติมเพื่อบอกให้มันมองเข้าไปในอาเรย์นั้น ปัญหาที่ใหญ่กว่ามากคือเพื่อให้สิ่งใดสามารถแยกวิเคราะห์ล็อกดังกล่าวได้ จำเป็นต้องโหลดข้อมูลทั้งหมดในอาเรย์ก่อน สิ่งนี้กลายเป็นปัญหาหากคุณมีไฟล์ JSON ขนาดใหญ่มากและมีหน่วยความจำไม่มากนัก เพื่อลดการใช้ CPU และหน่วยความจำที่จำเป็น รูปแบบ JSONL (JSON Lines) ซึ่งไม่นำทุกอย่างมาใส่ในอาเรย์ขนาดใหญ่ จึงได้รับความนิยมมากขึ้น Hayabusa ส่งออกในรูปแบบ JSON และ JSONL อย่างไรก็ตามรูปแบบ JSON จะไม่ถูกบันทึกภายในอาเรย์อีกต่อไป ความแตกต่างเพียงอย่างเดียวคือรูปแบบ JSON อ่านได้ง่ายกว่าในโปรแกรมแก้ไขข้อความหรือบนคอนโซล ในขณะที่รูปแบบ JSONL จัดเก็บทุกอ็อบเจกต์ JSON ไว้บนบรรทัดเดียว รูปแบบ JSONL จะเร็วกว่าเล็กน้อยและมีขนาดเล็กกว่า จึงเหมาะอย่างยิ่งหากคุณจะนำเข้าล็อกไปยัง SIEM ฯลฯ... แต่ไม่ดูพวกมัน รูปแบบ JSON เหมาะอย่างยิ่งหากคุณจะทำการตรวจสอบด้วยตนเองด้วย

การสร้างไฟล์ผลลัพธ์ JSON

ใน Hayabusa เวอร์ชัน 2.x ปัจจุบัน คุณสามารถบันทึกผลลัพธ์เป็น JSON ด้วย hayabusa json-timeline -d <directory> -o results.json หรือ hayabusa json-timeline -d <directory> -J -o results.jsonl สำหรับรูปแบบ JSONL

Hayabusa จะใช้โปรไฟล์ standard เป็นค่าเริ่มต้น และบันทึกเฉพาะข้อมูลขั้นต่ำสำหรับการวิเคราะห์ในอ็อบเจกต์ Details หากคุณต้องการบันทึกข้อมูลฟิลด์ดั้งเดิมทั้งหมดในล็อก .evtx คุณสามารถใช้โปรไฟล์ all-field-info ด้วยตัวเลือก --profile all-field-info สิ่งนี้จะบันทึกข้อมูลฟิลด์ทั้งหมดไปยังอ็อบเจกต์ AllFieldInfo หากคุณต้องการบันทึกทั้งอ็อบเจกต์ Details และ AllFieldInfo ไว้เผื่อกรณีฉุกเฉิน คุณสามารถใช้โปรไฟล์ super-verbose

ประโยชน์ของการใช้ Details แทน AllFieldInfo

ประโยชน์แรกของการใช้ Details แทน AllFieldInfo คือมีการบันทึกเฉพาะฟิลด์สำคัญเท่านั้น และชื่อฟิลด์ถูกย่อให้สั้นลงเพื่อประหยัดพื้นที่ไฟล์ ข้อเสียคือมีความเป็นไปได้ที่จะพลาดข้อมูลที่คุณสนใจจริงๆ แต่ถูกมองข้ามไป ประโยชน์ที่สองคือ Hayabusa จะบันทึกฟิลด์ในลักษณะที่สม่ำเสมอมากขึ้นโดยการทำให้ชื่อฟิลด์เป็นมาตรฐาน ตัวอย่างเช่น ในล็อก Windows ดั้งเดิม ชื่อผู้ใช้มักจะอยู่ในฟิลด์ SubjectUserName หรือ TargetUserName อย่างไรก็ตาม บางครั้งชื่อผู้ใช้จะอยู่ในฟิลด์ AccountName บางครั้งผู้ใช้เป้าหมายจะอยู่ในฟิลด์ SubjectUserName จริงๆ ฯลฯ... น่าเสียดายที่มีชื่อฟิลด์ที่ไม่สอดคล้องกันมากมายในล็อกเหตุการณ์ของ Windows Hayabusa พยายามทำให้ฟิลด์เหล่านี้เป็นมาตรฐาน ดังนั้นนักวิเคราะห์จึงต้องแยกวิเคราะห์เพียงชื่อทั่วไปแทนที่จะต้องเข้าใจความแปลกประหลาดและความไม่สอดคล้องกันที่มีอยู่อย่างไม่สิ้นสุดระหว่าง event ID ใน Windows

นี่คือตัวอย่างของฟิลด์ผู้ใช้ Hayabusa จะทำให้ SubjectUserName, TargetUserName, AccountName ฯลฯ... เป็นมาตรฐานในลักษณะต่อไปนี้: * SrcUser (Source User): เมื่อการกระทำเกิดขึ้น จาก ผู้ใช้ (โดยปกติคือผู้ใช้ระยะไกล) * TgtUser (Target User): เมื่อการกระทำเกิดขึ้น ต่อ ผู้ใช้ (ตัวอย่างเช่น การเข้าสู่ระบบ ไปยัง ผู้ใช้) * User: เมื่อการกระทำเกิดขึ้นโดยผู้ใช้ที่เข้าสู่ระบบอยู่ในปัจจุบัน (ไม่มีทิศทางเฉพาะในการกระทำ)

อีกตัวอย่างหนึ่งคือโพรเซส ในล็อกเหตุการณ์ Windows ดั้งเดิม ฟิลด์โพรเซสถูกอ้างถึงด้วยรูปแบบการตั้งชื่อหลายแบบ: ProcessName, Image, processPath, Application, WindowsDefenderProcessName ฯลฯ... หากไม่มีการทำให้ฟิลด์เป็นมาตรฐาน นักวิเคราะห์จะต้องมีความรู้เกี่ยวกับชื่อฟิลด์ต่างๆ ทั้งหมดก่อน แล้วจึงดึงล็อกทั้งหมดที่มีชื่อฟิลด์เหล่านี้ออกมา แล้วจึงรวมเข้าด้วยกัน

นักวิเคราะห์สามารถประหยัดเวลาและความยุ่งยากได้มากเพียงแค่ใช้ฟิลด์ Proc เดี่ยวที่ถูกทำให้เป็นมาตรฐานซึ่ง Hayabusa จัดเตรียมไว้ในอ็อบเจกต์ Details

บทเรียน/สูตรการใช้ jq

ตอนนี้ผมจะแสดงรายการบทเรียน/สูตรของตัวอย่างเชิงปฏิบัติหลายอย่างที่อาจช่วยคุณในการทำงาน

1. การตรวจสอบด้วยตนเองด้วย jq และ Less แบบสี

นี่คือสิ่งแรกๆ ที่ควรทำเพื่อทำความเข้าใจว่ามีฟิลด์ใดบ้างในล็อก คุณสามารถทำเพียงแค่ less results.json แต่วิธีที่ดีกว่าคือดังต่อไปนี้: cat results.json | jq -C | less -R

ด้วยการส่งผ่านไปยัง jq มันจะจัดรูปแบบฟิลด์ทั้งหมดให้คุณอย่างเรียบร้อยหากในตอนแรกยังไม่ได้จัดรูปแบบอย่างเรียบร้อย ด้วยการใช้ตัวเลือก -C (สี) กับ jq และตัวเลือก -R (raw output) กับ less คุณสามารถเลื่อนขึ้นและลงเป็นสีได้

2. เมตริก

Hayabusa มีฟังก์ชันในการพิมพ์จำนวนและเปอร์เซ็นต์ของเหตุการณ์ตาม event ID อยู่แล้ว อย่างไรก็ตาม การรู้วิธีทำสิ่งนี้ด้วย jq ก็เป็นเรื่องดี สิ่งนี้จะช่วยให้คุณปรับแต่งข้อมูลที่คุณต้องการสร้างเมตริกได้

ก่อนอื่นมาดึงรายการ Event ID ด้วยคำสั่งต่อไปนี้:

cat results.json | jq '.EventID'

สิ่งนี้จะดึงเฉพาะหมายเลข Event ID จากแต่ละล็อก หลังจาก jq ในเครื่องหมายอัญประกาศเดี่ยว เพียงแค่พิมพ์ . และชื่อฟิลด์ที่คุณต้องการดึงออกมา คุณจะเห็นรายการยาวๆ แบบนี้:

4624
4688
4688
4634
1337
1
1
1
1
10
27
11
11

ตอนนี้ ส่งผลลัพธ์ผ่านไปยังคำสั่ง sort และ uniq -c เพื่อนับว่า event ID เกิดขึ้นกี่ครั้ง:

cat results.json | jq '.EventID' | sort | uniq -c

ตัวเลือก -c สำหรับ uniq จะนับว่า event ID ที่ไม่ซ้ำกันเกิดขึ้นกี่ครั้ง

คุณจะเห็นบางอย่างแบบนี้:

 168 59
  23 6
  38 6005
  37 6006
   3 6416
 129 7
   1 7040
1382 7045
   2 770
 391 8

ด้านซ้ายคือจำนวนนับ และด้านขวาคือ Event ID อย่างที่คุณเห็นมันไม่ได้ถูกเรียงลำดับ ดังนั้นจึงยากที่จะบอกว่า event ID ใดเกิดขึ้นมากที่สุด

คุณสามารถเพิ่ม sort -n ต่อท้ายเพื่อแก้ไขสิ่งนี้:

cat results.json | jq '.EventID' | sort | uniq -c | sort -n

ตัวเลือก -n บอกให้ sort เรียงลำดับตามตัวเลข

คุณจะเห็นบางอย่างแบบนี้:

 400 4624
 433 5140
 682 4103
1131 4104
1382 7045
2322 1
2584 5145
7135 4625
12277 4688

เราจะเห็นได้ว่าเหตุการณ์ 4688 (Process creation) ถูกบันทึกมากที่สุด เหตุการณ์ที่ถูกบันทึกมากที่สุดเป็นอันดับสองคือ 4625 (Failed Logon)

หากคุณต้องการพิมพ์เหตุการณ์ที่ถูกบันทึกมากที่สุดไว้ด้านบน คุณสามารถกลับลำดับการเรียงด้วย sort -n -r หรือ sort -nr คุณยังสามารถพิมพ์เพียงแค่ 10 เหตุการณ์ที่ถูกบันทึกมากที่สุดได้โดยส่งผลลัพธ์ผ่านไปยัง head -n 10

cat results.json | jq '.EventID' | sort | uniq -c | sort -nr | head -n 10

สิ่งนี้จะให้:

12277 4688
7135 4625
2584 5145
2322 1
1382 7045
1131 4104
 682 4103
 433 5140
 400 4624
 391 8

สิ่งสำคัญที่ต้องพิจารณาคือ EID (Event ID) ไม่ใช่ค่าที่ไม่ซ้ำกัน ดังนั้นคุณอาจมีเหตุการณ์ที่แตกต่างกันโดยสิ้นเชิงด้วย Event ID เดียวกัน ดังนั้น จึงเป็นเรื่องสำคัญที่จะตรวจสอบ Channel ด้วย

เราสามารถเพิ่มข้อมูลฟิลด์นี้ได้แบบนี้:

cat results.json | jq -j ' .Channel , " " , .EventID , "\n" ' | sort | uniq -c | sort -nr | head -n 10

เราเพิ่มตัวเลือก -j (join) ให้กับ jq เพื่อรวมฟิลด์ทั้งหมดเข้าด้วยกันโดยคั่นด้วยเครื่องหมายจุลภาคและจบด้วยอักขระขึ้นบรรทัดใหม่ \n

สิ่งนี้จะให้เรา:

12277 Sec 4688
7135 Sec 4625
2584 Sec 5145
2321 Sysmon 1
1382 Sys 7045
1131 PwSh 4104
 682 PwSh 4103
 433 Sec 5140
 400 Sec 4624
 391 Sysmon 8

หมายเหตุ: Security ถูกย่อเป็น Sec, System เป็น Sys, และ PowerShell เป็น PwSh

เราสามารถเพิ่ม rule title ได้ดังนี้:

cat results.json | jq -j ' .Channel , " " , .EventID , " " , .RuleTitle , "\n" ' | sort | uniq -c | sort -nr | head -n 10

สิ่งนี้จะให้เรา:

9714 Sec 4688 Proc Exec
3564 Sec 4625 Logon Failure (Wrong Password)
3561 Sec 4625 Metasploit SMB Authentication
2564 Sec 5145 NetShare File Access
1459 Sysmon 1 Proc Exec
1418 Sec 4688 Susp CmdLine (Possible LOLBIN)
 789 PwSh 4104 PwSh Scriptblock
 680 PwSh 4103 PwSh Pipeline Exec
 433 Sec 5140 NetShare Access
 342 Sec 4648 Explicit Logon

ตอนนี้คุณสามารถดึงข้อมูลใดๆ จากล็อกได้อย่างอิสระและนับจำนวนการเกิดขึ้น

3. การกรองข้อมูลบางอย่าง

หลายครั้งคุณจะต้องการกรองเฉพาะ Event ID, ผู้ใช้, โพรเซส, LID (Logon ID) บางอย่าง ฯลฯ... คุณสามารถทำได้ด้วย select ภายในคิวรี jq

ตัวอย่างเช่น มาดึงเหตุการณ์การเข้าสู่ระบบสำเร็จ 4624 ทั้งหมด:

cat results.json | jq 'select ( .EventID == 4624 ) '

สิ่งนี้จะคืนค่าอ็อบเจกต์ JSON ทั้งหมดสำหรับ EID 4624:

{
  "Timestamp": "2021-12-12 16:16:04.237 +09:00",
  "Computer": "fs03vuln.offsec.lan",
  "Channel": "Sec",
  "Provider": "Microsoft-Windows-Security-Auditing",
  "EventID": 4624,
  "Level": "info",
  "RecordID": 1160369,
  "RuleTitle": "Logon (Network)",
  "RuleAuthor": "Zach Mathis",
  "RuleCreationDate": "2020/11/08",
  "RuleModifiedDate": "2022/12/16",
  "Status": "stable",
  "Details": {
    "Type": 3,
    "TgtUser": "admmig",
    "SrcComp": "",
    "SrcIP": "10.23.123.11",
    "LID": "0x87249a8"
  },
  "RuleFile": "Sec_4624_Info_Logon-Type-3-Network.yml",
  "EvtxFile": "../hayabusa-sample-evtx/EVTX-to-MITRE-Attack/TA0007-Discovery/T1046-Network Service Scanning/ID4624-Anonymous login with domain specified (DonPapi).evtx",
  "AllFieldInfo": {
    "AuthenticationPackageName": "NTLM",
    "ImpersonationLevel": "%%1833",
    "IpAddress": "10.23.123.11",
    "IpPort": 60174,
    "KeyLength": 0,
    "LmPackageName": "NTLM V2",
    "LogonGuid": "00000000-0000-0000-0000-000000000000",
    "LogonProcessName": "NtLmSsp",
    "LogonType": 3,
    "ProcessId": "0x0",
    "ProcessName": "-",
    "SubjectDomainName": "-",
    "SubjectLogonId": "0x0",
    "SubjectUserName": "-",
    "SubjectUserSid": "S-1-0-0",
    "TargetDomainName": "OFFSEC",
    "TargetLogonId": "0x87249a8",
    "TargetUserName": "admmig",
    "TargetUserSid": "S-1-5-21-4230534742-2542757381-3142984815-1111",
    "TransmittedServices": "-",
    "WorkstationName": ""
  }

หากคุณต้องการกรองด้วยเงื่อนไขหลายข้อ คุณสามารถใช้คีย์เวิร์ดอย่าง and, or และ not

ตัวอย่างเช่น มาค้นหาเหตุการณ์ 4624 ที่ type เป็น 3 (Network logon)

cat results.json | jq 'select ( ( .EventID == 4624 ) and ( .Details.Type == 3 ) ) '

สิ่งนี้จะคืนค่าอ็อบเจกต์ทั้งหมดที่ EventID เป็น 4624 และฟิลด์ที่ซ้อนอยู่ "Details": { "Type" } เป็น 3

อย่างไรก็ตามมีปัญหาอยู่ คุณอาจสังเกตเห็นข้อผิดพลาดที่บอกว่า jq: error (at <stdin>:10636): Cannot index string with string "Type" ทุกครั้งที่คุณเห็นข้อผิดพลาด Cannot index string with string หมายความว่าคุณกำลังบอกให้ jq ส่งออกฟิลด์ที่ไม่มีอยู่หรือเป็นประเภทที่ผิด คุณสามารถกำจัดข้อผิดพลาดเหล่านี้ได้โดยการเพิ่ม ? ต่อท้ายฟิลด์ สิ่งนี้บอกให้ jq เพิกเฉยต่อข้อผิดพลาด

ตัวอย่าง: cat results.json | jq 'select ( ( .EventID == 4624 ) and ( .Details.Type? == 3 ) ) '

ตอนนี้ หลังจากกรองด้วยเกณฑ์บางอย่างแล้ว เราสามารถใช้ | ภายในคิวรี jq เพื่อเลือกฟิลด์ที่สนใจบางฟิลด์ได้

ตัวอย่างเช่น มาดึงชื่อผู้ใช้เป้าหมาย TgtUser และที่อยู่ IP ต้นทาง SrcIP:

cat results.json | jq -j 'select ( ( .EventID == 4624 ) and ( .Details.Type? == 3 ) ) | .Details.TgtUser , " " , .Details.SrcIP , "\n" '

อีกครั้ง เราเพิ่มตัวเลือก -j (join) ให้กับ jq เพื่อเลือกหลายฟิลด์เพื่อส่งออก จากนั้นคุณสามารถรัน sort, uniq -c ฯลฯ... เหมือนในตัวอย่างก่อนหน้าเพื่อหาว่าที่อยู่ IP หนึ่งเข้าสู่ระบบไปยังผู้ใช้ผ่านการเข้าสู่ระบบเครือข่าย type 3 กี่ครั้ง

4. การบันทึกผลลัพธ์เป็นรูปแบบ CSV

น่าเสียดายที่ฟิลด์ในล็อกเหตุการณ์ Windows จะแตกต่างกันโดยสิ้นเชิงตามประเภทของเหตุการณ์ ดังนั้นจึงไม่สามารถสร้างไทม์ไลน์ที่คั่นด้วยจุลภาคตามฟิลด์ได้ง่ายๆ โดยไม่มีคอลัมน์หลายร้อยคอลัมน์ อย่างไรก็ตาม มันเป็นไปได้ที่จะสร้างไทม์ไลน์ที่คั่นด้วยฟิลด์สำหรับเหตุการณ์ประเภทเดียว ตัวอย่างทั่วไปสองอย่างคือ Security 4624 (Successful Logons) และ 4625 (Failed Logons) เพื่อตรวจสอบการเคลื่อนที่ด้านข้างและการเดารหัสผ่าน/การพ่นรหัสผ่าน

ในตัวอย่างนี้ เรากำลังดึงเฉพาะล็อก Security 4624 และส่งออก timestamp, ชื่อคอมพิวเตอร์ และข้อมูล Details ทั้งหมด เราบันทึกเป็นไฟล์ CSV โดยใช้ | @csv อย่างไรก็ตาม เราจำเป็นต้องส่งข้อมูลเป็นอาเรย์ เราสามารถทำได้โดยการเลือกฟิลด์ที่เราต้องการส่งออกเหมือนที่เราทำก่อนหน้านี้ และล้อมรอบด้วยวงเล็บเหลี่ยม [ ] เพื่อเปลี่ยนให้เป็นอาเรย์

ตัวอย่าง: cat results.json | jq 'select ( (.Channel == "Sec" ) and ( .EventID == 4624 ) ) | [ .Timestamp , .Computer , .Details[]? ] | @csv ' -r

หมายเหตุ: * ในการเลือกฟิลด์ทั้งหมดในอ็อบเจกต์ Details เราเพิ่ม [] * มีกรณีที่ Details เป็นสตริงและไม่ใช่อาเรย์ และจะให้ข้อผิดพลาด Cannot iterate over string ดังนั้นคุณจึงต้องเพิ่ม ? * เราเพิ่มตัวเลือก -r (Raw output) ให้กับ jq เพื่อไม่ให้ใส่แบ็กสแลชหน้าเครื่องหมายอัญประกาศคู่

ผลลัพธ์:

"2019-03-19 08:23:52.491 +09:00","WIN-77LTAPHIQ1R.example.corp",3,"user01","","10.0.2.17","0x15e1a7"
"2019-03-19 08:23:57.397 +09:00","WIN-77LTAPHIQ1R.example.corp",3,"WIN-77LTAPHIQ1R$","","fe80::79bf:8ee2:433c:2567","0x15e25f"
"2019-03-19 09:02:04.179 +09:00","WIN-77LTAPHIQ1R.example.corp",3,"ANONYMOUS LOGON","NULL","10.0.2.17","0x17e29a"
"2019-03-19 09:02:04.210 +09:00","WIN-77LTAPHIQ1R.example.corp",3,"Administrator","","10.0.2.17","0x17e2aa"
"2019-03-19 09:02:04.226 +09:00","WIN-77LTAPHIQ1R.example.corp",3,"Administrator","","10.0.2.17","0x17e2c0"
"2019-03-19 09:02:21.929 +09:00","WIN-77LTAPHIQ1R.example.corp",3,"WIN-77LTAPHIQ1R$","","fe80::79bf:8ee2:433c:2567","0x18423d"
"2019-05-12 02:10:10.889 +09:00","IEWIN7",9,"IEUser","","::1","0x1bbdce"

หากเราเพียงแค่ตรวจสอบว่าใครเข้าสู่ระบบสำเร็จ เราอาจไม่ต้องการฟิลด์ LID (Logon ID) สุดท้าย คุณสามารถลบคอลัมน์ที่ไม่ต้องการใดๆ ด้วยฟังก์ชัน del

ตัวอย่าง: cat results.json | jq 'select ( ( .Channel == "Sec" ) and ( .EventID == 4624 ) ) | [ .Timestamp , .Computer , .Details[]? ] | del( .[6] ) | @csv ' -r

อาเรย์นับจาก 0 ดังนั้นในการลบฟิลด์ที่ 7 เราใช้ 6

ตอนนี้คุณสามารถบันทึกไฟล์ CSV ได้โดยการเพิ่ม > 4624-logs.csv แล้วนำเข้าสู่ Excel หรือ Timeline Explorer เพื่อการวิเคราะห์เพิ่มเติม

โปรดทราบว่าคุณจะต้องเพิ่มส่วนหัวเพื่อทำการกรอง แม้ว่าจะเป็นไปได้ที่จะเพิ่มส่วนหัวภายในคิวรี jq แต่โดยปกติแล้วจะง่ายที่สุดที่จะเพิ่มแถวบนสุดด้วยตนเองหลังจากบันทึกไฟล์

5. การค้นหาวันที่ที่มีการแจ้งเตือนมากที่สุด

โดยค่าเริ่มต้น Hayabusa จะบอกคุณถึงวันที่ที่มีการแจ้งเตือนมากที่สุดตามระดับความรุนแรง อย่างไรก็ตาม คุณอาจต้องการหาวันที่ที่มีการแจ้งเตือนมากที่สุดเป็นอันดับสอง สาม ฯลฯ... ด้วย เราสามารถทำได้ด้วยการตัดสตริงของ timestamp เพื่อจัดกลุ่มตามปี เดือน หรือวันที่ตามความต้องการของคุณ

ตัวอย่าง: cat results.json | jq ' .Timestamp | .[:10] ' -r | sort | uniq -c | sort

.[:10] บอกให้ jq ดึงเฉพาะ 10 ไบต์แรกจาก Timestamp

สิ่งนี้จะให้เราวันที่ที่มีเหตุการณ์มากที่สุด:

1066 2021-12-12
1093 2016-09-02
1571 2021-04-22
1750 2016-09-03
2271 2016-08-19
2932 2021-11-03
8095 2016-09-20

หากคุณต้องการทราบเดือนที่มีเหตุการณ์มากที่สุด คุณสามารถเปลี่ยน .[:10] เป็น .[:7] เพื่อดึง 7 ไบต์แรก

หากคุณต้องการรายการวันที่ที่มีการแจ้งเตือน high มากที่สุด คุณสามารถทำได้แบบนี้:

cat results.json | jq 'select ( .Level == "high" ) | .Timestamp | .[:10] ' -r | sort | uniq -c | sort

คุณสามารถเพิ่มเงื่อนไขการกรองในฟังก์ชัน select ได้เรื่อยๆ ตามชื่อคอมพิวเตอร์ event ID ฯลฯ... ตามความต้องการของคุณ

6. การสร้างล็อก PowerShell ขึ้นใหม่

สิ่งที่น่าเสียดายเกี่ยวกับล็อก PowerShell คือล็อกมักจะถูกแบ่งออกเป็นหลายล็อกทำให้อ่านได้ยาก เราสามารถทำให้ล็อกอ่านได้ง่ายขึ้นมากโดยการดึงเฉพาะคำสั่งที่ผู้โจมตีรัน

ตัวอย่างเช่น หากคุณมีล็อก ScriptBlock EID 4104 คุณสามารถดึงเฉพาะฟิลด์นั้นเพื่อสร้างไทม์ไลน์ที่อ่านได้ง่าย

cat results.json | jq 'select ( .EventID == 4104) | .Timestamp[:16] , " " , .Details.ScriptBlock , "\n" ' -jr

สิ่งนี้จะให้ผลลัพธ์เป็นไทม์ไลน์ดังต่อไปนี้:

2022-12-24 10:56 ipconfig
2022-12-24 10:56 prompt
2022-12-24 10:56 pwd
2022-12-24 10:56 prompt
2022-12-24 10:56 whoami
2022-12-24 10:56 prompt
2022-12-24 10:57 cd..
2022-12-24 10:57 prompt
2022-12-24 10:57 ls

7. การค้นหาการเชื่อมต่อเครือข่ายที่น่าสงสัย

ก่อนอื่นคุณสามารถได้รายการของที่อยู่ IP เป้าหมายทั้งหมดด้วยคำสั่งต่อไปนี้:

cat results.json | jq 'select ( .Details.TgtIP? ) | .Details.TgtIP ' -r | sort | uniq

หากคุณมีข่าวกรองภัยคุกคาม คุณสามารถตรวจสอบได้ว่ามีที่อยู่ IP ใดที่ทราบว่าเป็นอันตรายหรือไม่

คุณสามารถนับจำนวนครั้งที่มีการเชื่อมต่อไปยังที่อยู่ IP เป้าหมายหนึ่งด้วยคำสั่งต่อไปนี้:

cat results.json | jq 'select ( .Details.TgtIP? ) | .Details.TgtIP ' -r | sort | uniq -c | sort -n

ด้วยการเปลี่ยน TgtIP เป็น SrcIP คุณสามารถทำการตรวจสอบข่าวกรองภัยคุกคามเดียวกันสำหรับที่อยู่ IP ที่เป็นอันตรายโดยอิงตามที่อยู่ IP ต้นทาง

สมมติว่าคุณพบว่ามีการเชื่อมต่อไปยังที่อยู่ IP ที่เป็นอันตราย 93.184.220.29 จากสภาพแวดล้อมของคุณ คุณสามารถได้รายละเอียดเกี่ยวกับเหตุการณ์เหล่านั้นด้วยคิวรีต่อไปนี้:

cat results.json | jq 'select ( .Details.TgtIP? == "93.184.220.29" ) '

สิ่งนี้จะให้ผลลัพธ์ JSON เช่นนี้:

{
  "Timestamp": "2019-07-30 06:33:20.711 +09:00",
  "Computer": "MSEDGEWIN10",
  "Channel": "Sysmon",
  "EventID": 3,
  "Level": "med",
  "RecordID": 4908,
  "RuleTitle": "Net Conn (Sysmon Alert)",
  "Details": {
    "Proto": "tcp",
    "SrcIP": "10.0.2.15",
    "SrcPort": 49827,
    "SrcHost": "MSEDGEWIN10.home",
    "TgtIP": "93.184.220.29",
    "TgtPort": 80,
    "TgtHost": "",
    "User": "MSEDGEWIN10\\IEUser",
    "Proc": "C:\\Windows\\System32\\mshta.exe",
    "PID": 3164,
    "PGUID": "747F3D96-661E-5D3F-0000-00107F248700"
  }
}

หากคุณต้องการรายการโดเมนที่ถูกติดต่อ คุณสามารถใช้คำสั่งต่อไปนี้:

cat results.json | jq 'select ( .Details.TgtHost ) ? | .Details.TgtHost ' -r | sort | uniq | grep "\."

หมายเหตุ: ผมเพิ่มตัวกรอง grep สำหรับ . เพื่อลบชื่อโฮสต์ NETBIOS

8. การดึงแฮชของไบนารีที่เรียกใช้งานได้

ในล็อก Sysmon EID 1 Process Creation นั้น sysmon สามารถถูกกำหนดค่าให้คำนวณแฮชของไบนารีได้ นักวิเคราะห์ความปลอดภัยสามารถเปรียบเทียบแฮชเหล่านี้กับแฮชที่เป็นอันตรายที่ทราบด้วยข่าวกรองภัยคุกคาม คุณสามารถดึงฟิลด์ Hashes ด้วยคำสั่งต่อไปนี้:

cat results.json | jq 'select ( .Details.Hashes? ) | .Details.Hashes ' -r

สิ่งนี้จะให้คุณรายการของแฮชแบบนี้:

MD5=E112A827FAB9F8378C76040187A6F336,SHA256=ED369187681A62247E38D930320F1CD771756D0B7B67072D8EC655EF99E14AEB,IMPHASH=8EEAA9499666119D13B3F44ECD77A729
MD5=E112A827FAB9F8378C76040187A6F336,SHA256=ED369187681A62247E38D930320F1CD771756D0B7B67072D8EC655EF99E14AEB,IMPHASH=8EEAA9499666119D13B3F44ECD77A729
MD5=E112A827FAB9F8378C76040187A6F336,SHA256=ED369187681A62247E38D930320F1CD771756D0B7B67072D8EC655EF99E14AEB,IMPHASH=8EEAA9499666119D13B3F44ECD77A729
MD5=E112A827FAB9F8378C76040187A6F336,SHA256=ED369187681A62247E38D930320F1CD771756D0B7B67072D8EC655EF99E14AEB,IMPHASH=8EEAA9499666119D13B3F44ECD77A729

โดยปกติ Sysmon จะคำนวณแฮชหลายตัวเช่น MD5, SHA1 และ IMPHASH คุณสามารถดึงแฮชเหล่านี้ออกมาด้วย regular expressions ใน jq หรือเพียงแค่ใช้การตัดสตริงเพื่อประสิทธิภาพที่ดีกว่า

ตัวอย่างเช่น คุณสามารถดึงแฮช MD5 และลบรายการที่ซ้ำกันด้วยคำสั่งต่อไปนี้:

cat results.json | jq 'select ( .Details.Hashes? ) | .Details.Hashes | .[4:36] ' -r | sort | uniq

9. ดึงล็อก PowerShell

ล็อก PowerShell Scriptblock (EID: 4104) มักจะถูกแบ่งออกเป็นหลายล็อก และเมื่อส่งออกเป็นรูปแบบ CSV นั้น Hayabusa จะลบแท็บและอักขระขึ้นบรรทัดเพื่อทำให้ผลลัพธ์กระชับขึ้น อย่างไรก็ตาม จะง่ายที่สุดในการวิเคราะห์ล็อก powershell ด้วยการจัดรูปแบบแท็บและอักขระขึ้นบรรทัดดั้งเดิมและการรวมล็อกเข้าด้วยกัน นี่คือตัวอย่างของการดึงล็อก PowerShell EID 4104 จาก COMPUTER-A และบันทึกเป็นไฟล์ .ps1 เพื่อเปิดและวิเคราะห์ใน VSCode ฯลฯ... หลังจากดึงฟิลด์ ScriptBlock เราใช้ awk เพื่อแทนที่ \r\n และ \n ด้วยอักขระขึ้นบรรทัด และ \t ด้วยแท็บ

cat results.json | jq 'select ( .EventID == 4104 and .Details.ScriptBlock? != "n/a"  and .Computer == "COMPUTER-A.domain.local" ) | .Details.ScriptBlock , "\r\n"' -j | awk '{ gsub(/\\r\\n/,"\r\n"); print; }' | awk '{ gsub(/\\t/, "\t"); print; }' | awk '{ gsub(/\\n/, "\r\n"); print; }' > 4104-PowerShell-Logs.ps1

หลังจากนักวิเคราะห์วิเคราะห์ล็อกเพื่อหาคำสั่ง PowerShell ที่เป็นอันตรายแล้ว โดยปกติพวกเขาจะต้องค้นหาว่าคำสั่งเหล่านั้นถูกรันเมื่อใด นี่คือตัวอย่างของการส่งออก Timestamp และล็อก PowerShell ลงในไฟล์ CSV เพื่อค้นหาเวลาที่คำสั่งถูกรัน:

cat results.json | jq ' select (.EventID == 4104 and .Details.ScriptBlock? != "n/a" and .Computer == "COMPUTER-A.domain.local") | .Timestamp, ",¦", .Details.ScriptBlock?, "¦\r\n" ' -j | awk '{ gsub(/\\r\\n/,"\r\n"); print; }' | awk '{ gsub(/\\t/,"\t"); print; }' | awk '{ gsub(/\\n/,"\r\n"); print; }' > 4104-PowerShell-Logs.csv

หมายเหตุ: ตัวคั่นสตริงที่ใช้คือ ¦ เพราะเครื่องหมายอัญประกาศเดี่ยวและคู่มักพบในล็อก PowerShell และจะทำให้ผลลัพธ์ CSV เสียหาย เมื่อคุณนำเข้าไฟล์ CSV คุณจะต้องระบุตัวคั่นสตริง ¦ ให้กับแอปพลิเคชัน