ล่ามาโคร Office ด้วย Sysmon และ Pandas

Dec 02 2022
ใช้ Pandas และ Jupyter ได้ทุกที่ เป็นเรื่องตลกเมื่อคุณพูดถึงมาโครในสำนักงาน พวกเราในสาขาความปลอดภัยรู้สึกหนาวสั่นไปถึงสันหลัง เราทุกคนทราบดีว่ามีหลายวิธีในการรักษาความปลอดภัยให้กับบริษัทจากพวกเขา แต่สิ่งหนึ่งที่ฉันแน่ใจก็คือ หากขึ้นอยู่กับเรา เราทุกคนคงต้องการให้มาโครถูกปิดใช้งานในองค์กรของเรา

ใช้ 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

ภาพถ่ายของ Erik Mclean: https://www.pexels.com/es-es/foto/ciudad-carretera-hombre-arte-4065797/

ใครก็ตามที่เคยเล่นกับ 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>

มากาเมะเมะ https://makeameme.org/meme/PANDAS-PANDAS-EVERYWHERE

ในการแยกวิเคราะห์ไฟล์ 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)

เปรียบเทียบเมนู

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

เจอกันคราวหน้า!