Охота на макросы 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, написанное Флорианом Ротом . В этом правиле можно увидеть, что есть 3 библиотеки, которые офис загружает, когда в файле есть макросы 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 с различными офисными файлами и проверить, что произойдет, если эти файлы будут загружены или нет, и вот результаты.

Полученные результаты.

Кажется, что наш второй подход также недействителен. Хотя библиотеки VBE7.dll и VBEUI.dll служат для идентификации файлов с локально созданными макросами, при загрузке файла из Интернета эти библиотеки также загружаются, даже если файл не содержит макросов.

Причиной такого поведения является Mark-of-the-Web ( MOTW ) или альтернативный поток данных, добавляемый к файлам при загрузке через веб-браузер, что приводит к открытию файлов в защищенном режиме.

Так как эта первая попытка не сработала, я решил попробовать использовать тот же подход, но в более широком масштабе, т.е. посмотреть на все библиотеки, загруженные Excel в каждом из этих исполнений, и понаблюдать за их различиями.

Третья и последняя попытка, Python и Pandas

Фото Эрика Маклина: 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>

Makeameme https://makeameme.org/meme/PANDAS-PANDAS-EVERYWHERE

Для парсинга полученных файлов EVTX от Sysmon мы собираемся использовать библиотеку PyEvtxParser , старую знакомую, которая очень пригодилась мне в прошлом, когда я писал Grafiki . Здесь вы можете найти эту записную книжку на моем 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

Следующий вопрос, который мы задаем себе, заключается в том, будут ли разные библиотеки между этими исполнениями, которые позволят нам определить наличие макросов в документе?

Для этого мы собираемся сравнить все измерения, тем самым создав сравнительную таблицу, которая позволит нам обнаруживать аномалии. В левой части таблицы у нас будет база нашего сравнения, то есть часть «Какие библиотеки содержат это исполнение», а в верхней части «Что не содержит этого исполнения».

Для этого мы будем использовать эту простую функцию, которая позволяет нам сравнивать кадры данных, и мы будем хранить только те, которые находятся в первом операторе.

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)

Сравнить меню

Надеюсь, вам понравилось и что, как и я, вы не доверяете моим результатам и осмелитесь попробовать сами, если вы это сделаете и результаты будут другими, пожалуйста, дайте мне знать

Увидимся в следующий раз!