ล่ามาโคร Office ด้วย Sysmon และ Pandas
ใช้ Pandas และ Jupyter ได้ทุกที่
เป็นเรื่องตลกเมื่อคุณพูดถึงมาโครในสำนักงาน พวกเราในสาขาความปลอดภัยรู้สึกหนาวสั่นไปถึงสันหลัง เราทุกคนทราบดีว่ามีหลายวิธีในการรักษาความปลอดภัยให้กับบริษัทจากพวกเขา แต่สิ่งหนึ่งที่ฉันแน่ใจก็คือ หากขึ้นอยู่กับเรา เราทุกคนคงต้องการให้มาโครถูกปิดใช้งานในองค์กรของเรา
ไม่ว่าจะดีขึ้นหรือแย่ลง การตัดสินใจเหล่านี้จะต้องมีแรงจูงใจเป็นอย่างดี และเหนือสิ่งอื่นใด ผลกระทบต่อบริษัทจะต้องมีการวัดผลเป็นอย่างดีก่อนที่จะทำการตัดสินใจใดๆ
ในการตรวจสอบการใช้มาโคร มีตัวเลือกไม่มากนักหากเราไม่มีบริการคลาวด์ เช่น O365 แต่เราจะลองดู
เริ่มการผจญภัยกันเถอะ
ลองครั้งแรก ไฟล์นามสกุล.
วิธีง่ายๆ อาจเป็นการค้นหาไฟล์ในองค์กรของเราด้วยมาโครโดยค้นหานามสกุลไฟล์ แต่มันไม่ง่ายอย่างนั้นเลย
สิ่งหนึ่งที่สำคัญที่ต้องรู้และผู้คนจำนวนมากไม่ทราบก็คือ เพื่อให้ไฟล์ Office มีมาโครได้นั้น จะต้องมีนามสกุลไฟล์ที่เฉพาะเจาะจงมาก และนามสกุลอื่นๆ เข้ากันไม่ได้
เป็นเวลาหลายปีที่ Microsoft ต้องการระบุเอกสารที่มีมาโครที่มีตัวอักษร "m" ในนามสกุลเป็น docm, xlsm... และทำให้ไม่สามารถเรียกใช้มาโครในส่วนขยายใหม่ที่มีตัวอักษร "x" ต่อท้ายเป็น docx, xlsx... แต่เช่นเคยกับ Microsoft ความเข้ากันได้แบบย้อนหลังทำสิ่งนี้ที่นี่
ในรูปแบบเอกสาร office 97–2003 เราได้รับอนุญาตให้จัดเก็บและเรียกใช้มาโครในขณะที่รักษานามสกุลง่ายๆ เช่น doc, xls ... ทำให้นักวิเคราะห์ความปลอดภัยไม่สามารถทราบได้ว่าเอกสารสามารถมีมาโครหรือไม่ใช้เฉพาะนามสกุลไฟล์
ลองครั้งที่สอง กฎ Sygma และ Sysmon
หลังจากพูดคุยกับผู้เชี่ยวชาญคนอื่นๆ พวกเขาแนะนำกฎ Sygma ที่เขียนโดย Florian Roth ให้ ฉัน ในกฎนี้ เป็นไปได้ไหมที่จะเห็นว่ามีไลบรารี 3 ไลบรารีที่ office โหลดเมื่อไฟล์มีมาโคร VBA
- '*\VBE7.DLL*'
- '*\VBEUI.DLL*'
- '*\VBE7INTL.DLL*'
สิ่งนี้ดูดีถ้าคุณมี Sysmon หรือคุณสามารถปรับใช้ได้
<RuleGroup name="" groupRelation="or">
<ImageLoad onmatch="include">
<Rule name="Potential Macro file opened" groupRelation="or">
<ImageLoaded condition="end with">vbeintl.dll</ImageLoaded>
<ImageLoaded condition="end with">vbe7.dll</ImageLoaded>
<ImageLoaded condition="end with">vbeui.dll</ImageLoaded>
</Rule>
</ImageLoad>
</RuleGroup>
<RuleGroup name="" groupRelation="or">
<RegistryEvent onmatch="include">
<TargetObject name="T1060,RunKey" condition="contains">Documents\TrustRecords</TargetObject>
</RegistryEvent>
</RuleGroup>
ฉันนึกขึ้นได้ว่าต้องทดสอบกฎ Sygma กับไฟล์ office ต่างๆ และทดสอบสิ่งที่เกิดขึ้นว่าไฟล์เหล่านี้ถูกดาวน์โหลดหรือไม่ และนี่คือผลลัพธ์

ดูเหมือนว่าแนวทางที่สองของเราจะไม่ถูกต้องเช่นกัน แม้ว่าไลบรารี VBE7.dll และ VBEUI.dll จะทำหน้าที่ระบุไฟล์ด้วยมาโครที่สร้างขึ้นในเครื่อง แต่เมื่อดาวน์โหลดไฟล์จากอินเทอร์เน็ต ไลบรารีเหล่านี้จะถูกโหลดด้วย แม้ว่าไฟล์จะไม่มีมาโครก็ตาม
ตัวการของพฤติกรรมนี้คือMark-of-the-Web ( MOTW ) หรือสตรีมข้อมูลสำรองที่เพิ่มลงในไฟล์เมื่อดาวน์โหลดผ่านเว็บเบราว์เซอร์ ซึ่งทำให้ไฟล์ถูกเปิดในมุมมองที่มีการป้องกัน
เนื่องจากความพยายามครั้งแรกไม่ได้ผล ฉันคิดว่าฉันจะลองใช้แนวทางเดียวกันแต่มีขอบเขตที่กว้างขึ้น เช่น ดูไลบรารีทั้งหมดที่โหลดโดย Excel ในแต่ละการดำเนินการเหล่านี้ และสังเกตความแตกต่าง
ความพยายามครั้งที่สามและครั้งสุดท้าย Python และ Pandas

ใครก็ตามที่เคยเล่นกับ Procmon และการโหลดไลบรารีจะรู้ว่าสิ่งนี้อาจน่าเบื่อเล็กน้อย เนื่องจากการทำงานของ Excel แต่ละครั้งจะโหลดไลบรารีหลายร้อยไลบรารี เพื่อช่วยเราในงานนี้ เรามี Python ตัวเอกคนที่สามของเรากับ Jupyter เพื่อนที่ซื่อสัตย์ของเขา
เราจะใช้เหตุการณ์การโหลด Sysmon และ DLL เพื่อค้นหาความแตกต่างระหว่างการดำเนินการของ Office วิธีนี้ทำให้เราสามารถระบุความแตกต่างที่อาจบ่งชี้ว่าเอกสารที่เปิดอยู่มีมาโครหรือไม่ โดยไม่ขึ้นกับส่วนขยาย
อันดับแรก กฎ Sysmon เพื่อตรวจสอบไลบรารีที่โหลดโดยไฟล์ Excel ระหว่างการเปิดเอกสาร
<RuleGroup name="" groupRelation="or">
<ImageLoad onmatch="include">
<Rule name="Image Loaded by Excel" groupRelation="or">
<Image condition="end with">excel.exe</Image>
</Rule>
</ImageLoad>
</RuleGroup>

ในการแยกวิเคราะห์ไฟล์ EVTX ที่ เป็นผลลัพธ์จาก Sysmon เราจะใช้ ไลบรารี PyEvtxParserซึ่งเป็นคนรู้จักเก่าที่มีประโยชน์กับฉันมากในตอนที่ฉันเขียนGrafiki คุณสามารถค้นหาNotebook นี้บน Github ของฉันได้ที่ นี่
def evtx_folder_to_dataframes(directory):
dataframes_list_seven = {}
for filename in os.listdir(directory):
f = os.path.join(directory, filename)
if os.path.isfile(f):
events_one_five = []
events_seven = []
a = open(f, 'rb')
parser = PyEvtxParser(a)
for record in parser.records_json():
event = json.loads(record['data'])
#Image loaded
if event["Event"]["System"]["EventID"] == 7:
event_list = [event["Event"]["EventData"]["UtcTime"],
event["Event"]["System"]["EventID"],
event["Event"]["EventData"]["ImageLoaded"],
event["Event"]["EventData"]["FileVersion"],
event["Event"]["EventData"]["Description"],
event["Event"]["EventData"]["Product"],
event["Event"]["EventData"]["Company"],
event["Event"]["EventData"]["OriginalFileName"],
event["Event"]["EventData"]["Hashes"],
event["Event"]["EventData"]["Signed"],
event["Event"]["EventData"]["Signature"],
event["Event"]["EventData"]["SignatureStatus"]]
events_seven.append(event_list)
name = filename.split("\\")[-1].split(".")[-2]
df_7 = pd.DataFrame.from_records(events_seven,
columns=['UtcTime','EventID', 'ImageLoaded', 'FileVersion', 'Description',
'Product', 'Company', 'OriginalFileName', 'Hashes', 'Signed',
'Signature', 'SignatureStatus'])
dataframes_list_seven[name] = df_7
return dataframes_list_seven
df_count = pd.DataFrame(index=list(dataframes_list_seven.keys()), columns=["Count"])
for e in list(dataframes_list_seven):
df_count["Count"][e] = dataframes_list_seven[e]["ImageLoaded"].nunique()
DLL count results
คำถามต่อไปที่เราถามตัวเองคือ จะมีไลบรารีที่แตกต่างกันระหว่างการดำเนินการเหล่านี้หรือไม่ ซึ่งช่วยให้เราระบุได้ว่าเอกสารมีมาโครหรือไม่
ในการทำเช่นนี้ สิ่งที่เราจะทำคือการเปรียบเทียบระหว่างการวัดทั้งหมด ดังนั้นการสร้างตารางเปรียบเทียบที่จะช่วยให้เราสามารถตรวจจับความผิดปกติได้ ในส่วนด้านซ้ายของตาราง เราจะมีฐานของการเปรียบเทียบของเรา นั่นคือส่วน "ไลบรารีใดที่มีการดำเนินการนี้" และในส่วนบน "ซึ่งไม่มีการดำเนินการนี้"
สำหรับสิ่งนี้ เราจะใช้ฟังก์ชันง่ายๆ ที่ช่วยให้เราทำการเปรียบเทียบ DataFrames และเราจะเก็บเฉพาะอันที่อยู่ในโอเปอเรเตอร์แรกเท่านั้น
def compare_df(df_in, df_not_in):
list = df_in.merge(df_not_in.drop_duplicates(),
on=['ImageLoaded'],
how='left',
indicator=True)
list_min = list[list['_merge'] == 'left_only']["ImageLoaded"].unique()
return(list_min)
df = pd.DataFrame(index=list(dataframes_list_seven.keys()), columns=list(dataframes_list_seven.keys()))
for e in list(dataframes_list_seven.keys()):
for i in list(dataframes_list_seven.keys()):
list_min = compare_df(dataframes_list_seven[e], dataframes_list_seven[i])
df[i][e] = len(list_min)
Comparation result
ณ จุดนี้เราสามารถสรุปได้ว่าวิธีนี้ไม่ถูกต้องสำหรับการตรวจหาเอกสาร Office ด้วยมาโคร การค้นพบที่ยอดเยี่ยมที่ช่วยให้เราไม่ต้องเสียเวลาไปกับการปฏิบัติตามกฎ วิเคราะห์ผลลัพธ์ และสรุปผลจากข้อมูลที่เราเชื่อว่าเชื่อถือได้
โบนัส
เนื่องจากแนวคิดของ Jupyter คือการสร้างเอกสารที่นำกลับมาใช้ใหม่ได้ เรากำลังจะใช้เมนูเล็ก ๆ ที่จะช่วยให้เราดูรายละเอียดการเปรียบเทียบแต่ละรายการ
ในกรณีนี้อาจไม่เกี่ยวข้องมากนัก แต่โน้ตบุ๊กนี้สามารถปรับเปลี่ยนได้ง่ายเพื่อตรวจสอบการดำเนินการประเภทอื่นๆ ที่ตรวจพบ เช่น ความแตกต่างในการเชื่อมต่อเครือข่าย การแก้ไขรีจิสทรีคีย์ การสร้างท่อที่มีชื่อ...
def compare_events():
import ipywidgets as widgets
from IPython.display import display, Markdown, clear_output
pd.set_option('display.max_rows', 500)
output = widgets.Output()
dfs = list(dataframes_list_seven.keys())
columns = dataframes_list_seven[list(dataframes_list_seven.keys())[0]].columns
in_widget = widgets.Dropdown(
options=dfs,
description='Events in:',
disabled=False)
not_in_widget = widgets.Dropdown(
options=dfs,
description='And not in:',
disabled=False)
columns = widgets.Dropdown(
options=columns,
description='Column:',
disabled=False)
button = widgets.Button(description=f'List')
display(in_widget, not_in_widget, columns, button, output)
def _click_function(_):
with output:
clear_output()
list = dataframes_list_seven[in_widget.value].merge(dataframes_list_seven[not_in_widget.value].drop_duplicates(),
on=['ImageLoaded'],
how='left',
indicator=True)
list_min = list[list['_merge'] == 'left_only'][columns.value].unique()
display(len(list_min))
display(pd.DataFrame(list_min).style.set_properties(**{'text-align': 'left'}))
button.on_click(_click_function)

ฉันหวังว่าคุณจะชอบมัน และอย่างที่ฉันทำ คุณไม่เชื่อผลลัพธ์ของฉันและคุณกล้าที่จะลองด้วยตัวเอง หากคุณทำแล้วผลลัพธ์แตกต่างออกไป โปรดแจ้งให้เราทราบ
เจอกันคราวหน้า!