ТЕХНИЧЕСКОЕ ЗАДАНИЕ АВТОМАТИЧЕСКИЙ ПОИСК АНТИВИРУСНЫХ ДЕТЕКТОВ В ИСХОДНОМ КОДЕ ЦЕЛЬ Уменьшить время чистки от антивирусных детектов путем автоматизации процесса. ИДЕЯ Обычный алгоритм чистки следующий: Этап 1 1 берем проект, который нужно почистить 2 комментируем вообще все функции и глобальные переменные 3 делаем сборку в боевом профиле 4 получившийся бинарник заливаем на dyncheck и проверяем 5 если число детектов меньше заданного порога, гото этап 2, иначе 6 раскомментируем одну функцию (согласно графу зависимостей) 7 гото 3 Этап 2 для каждой найденной функции с детектами, отсекаем #ifdef'ом части функции по тому же принципу, до тех пор пока не дойдем до одной строчки либо блока кода. Есть оптимизация на шаге 6 - можно раскомментировать сразу несколько функций, оптимистично полагая что детект не здесь) Если запилить программу, которая умеет разобрать исходники, построить граф зависимостей функций, и вставлять #ifdef FUNCTION1_NEVER .. #endif в теле этих функций, то процесс можно автоматизировать. РЕАЛИЗАЦИЯ Программа должна быть оформлена как консольная утилита и написана на C++. По возможности обеспечить кросс-платформенность. Интерфейс пользователя - ключи командной строки (getopt) и вывод на stdout/stderr. Нужно реализовать описанный выше алгоритм со следующими граничными условиями: * поддержка проектов Visual Studio начиная с 2010, включает в себя: - задание профиля сборки (Release/x64) - запуск компиляции на каждой итерации и поиск собранного файла там, где сказано в файле проекта * точка расширения на функции антивирусной проверки (используемой на шаге 5) - принцип плагина - должна быть функция bool avcheck(const char* path), которую легко заменить для разных инструментов проверки - как для локальных антивирусов, так и для интеграции с онлайн-сервисами * анализ кода должен исключать заведомо сложные случаи, такие как - template<> - рекурсию в библиотечные исходники, включая CRT/STL/WinAPI - все что усложняет реализацию, должно быть оговорено и упрощено. Проще привести код к удобному для анализа виду, чем усложнять анализатор. * объяснение на stdout что происходит: Этап 1 итерация 1, закомментировано 1801 функция, число детектов 0 итерация 2, закомментировано 900 функция, число детектов 18 .. итерация 100, закомментировано 50 функций, число детектов 0 Этап 2 Функция foo(), убраны строки 100..200, число детектов 0 Функция foo(), убраны строки 190..200, число детектов 0 Функция foo(), убраны строки 195..200, число детектов 18 Детекты найдены в функциях: foo(): 195..200 bar(): 400..402 dumb(): 151..152 Можно переделать готовые подходящие анализаторы кода - сейчас много статических анализаторов с готовым модулем синтаксического разбора. РЕАЛИЗАЦИЯ, ВАРИАНТ №2, УПРОЩЕННЫЙ Делаем то же самое без разбора исходного кода, путем анализа карты функций компоновщика (параметр /MAP компоновщика): - заказываем у компоновщика карту бинарника ключом /map - парсим выходную карту, находим границы функций - зануляем функции ПРЯМО В БИНАРНИКЕ Так можно избежать парсинга исходников на этапе 1 (на этапе 2 к сожалению не получится) Но и реализация хотя бы в объеме этапа 1 очень сильно облегчила бы жизнь. Т.к. в карте появляется слишком много левых символов, которые вовсе не требуют анализа (функции из CRT, всякая служебная мелочь), то нужны дополнительные инструменты в интерфейсе программы: - маски для исключения символов (черный список) - маски списка проверки объектных файлов (.obj) (белый список) - маски списка проверки символов Также мелкие символы можно отсечь по размеру (см.ниже алгоритм) Есть похожая реализация https://github.com/vxlabinfo/SignFinder основана на статьях https://vxlab.info/%d1%87%d0%b8%d1%81%d1%82%d0%ba%d0%b0-pe32-%d1%87%d0%b0%d1%81%d1%82%d1%8c-1/ https://vxlab.info/%d1%87%d0%b8%d1%81%d1%82%d0%ba%d0%b0-pe32-%d1%87%d0%b0%d1%81%d1%82%d1%8c-2/ сайт уже протух, но остались копии, например здесь https://ru-sfera.org/threads/chistka-ot-signaturnogo-detekta-antivirusov.2870/ Оттуда можно взять эвристики проверок отдельных секций, заголовков и импорта. АЛГОРИТМ ПРОВЕРКИ На входе алгоритма - нераскрашенный список символов (функций и глобальных данных). На выходе алгоритма - раскрашенный список символов, так что каждому символу присвоен цвет - белый или черный. Цель алгоритма - раскрасить все функции за минимальное число шагов. Алгоритм: 1. Первый проход по списку: помечаем как "белые" все символы по списку исключения/не содержащиеся в списках проверки 2. Сортируем список по занимаемому в памяти размеру символа 3. Символы с размером ниже некоторого порога (настройка или эвристически определяемого значение) сразу можно раскрасить "белым" На этом шаге следует занулить все нераскрашенные символы и сделать АВ-проверку. Если проверка даст детект, выводим предупреждение что в бинарном файле есть детекты на самой первой итерации и завершаем работу. Иначе, разнуляем все нераскрашенные символы. Далее в алгоритме необходимо помнить состояние: - список текущих проверяемых символов ("текущий список") - список раскрашенных символов. До конца работы алгоритма: - Белые символы должны быть разнулены - Черные символы должны быть занулены 4. Делаем отбор в текущий список: половина (50%) нераскрашенных символов файла 5. Разнуляем не раскрашенные символы текущего списка 6. АВ проверка - вызываем функцию проверки av_check() 7. Детект есть? - нет: пометили разнуленную часть текущего списка как "белый", продолжаем выполнение (гото 8) - да: в разнуленной части 1 символ? - да: пометили как черный, занулили до конца проверки, гото 6 - нет: занулили половину (50%) нераскрашенной части текущего списка, гото 6 8. Если остались нераскрашенные символы в текущем списке, гото 5 9. Если остались нераскрашенные символы в файле, гото 4 На этот алгоритм можно написать unit-тест, заставив av_check() выдавать детекты только на конкретный список функций. USAGE -i проверяемый файл. Аргумент обязательный. -m карта компоновщика .map. Аргумент обязательный. -d рабочий каталог, куда складываем промежуточные файлы. По умолчанию текущий. -s
список тестируемых секций (.text, .data ...). Можно указать -s несколько раз, например -s .text -t .data. По умолчанию все секции. -z исключить из проверки символы размером меньше чем указанный; размер в байтах. По умолчанию 0 (нет порога). -a defender|dyncheck проверка на указанном антивирусе. Умолчания нет, аргумент обязательный. -f проверять только символы с именем, удовлетворяющим маске. Можно указать -f несколько раз, например -f symb1* -f symb2* -M проверять только символы из указанных модулей. Можно указать -f несколько раз, например -m module1* -m module2* Аргументы -s, -z, -f, -M комбинируются по логическому И (пересечение множеств), т.е. уменьшают диапазон проверки. ИНТЕГРАЦИЯ С АВ-ДВИЖКАМИ На первом этапе обязательны интеграции с Windows Defender (Windows 10) и dyncheck.com через API. Далее код на Powershell * Объект Defender $Defender = @{ #MALWAREPROTECTION_* SCAN_STARTED = 1000 SCAN_COMPLETED = 1001 MALWARE_DETECTED = 1006 BEHAVIOR_DETECTED = 1015 STATE_MALWARE_DETECTED = 1116 STATE_MALWARE_ACTION_TAKEN = 1117 StartTime = $null IsRunning = $false ScanProc = $null ScanId = $null LastScanId = $null } * Запуск Windows Defender $Defender.ScanProc = Start-Process ` -FilePath "$($env:programfiles)\Windows Defender\mpcmdrun.exe" ` -ArgumentList '-Scan', '-ScanType 3', "-File $f" ` -PassThru -NoNewWindow #-Wait $Defender.StartTime = (Get-Date).AddSeconds(-5) $Defender.IsRunning = $true * Получение идентификатора проверки, для последующего чтения журнала: $ScanStarted = Get-WinEvent -LogName "Microsoft-Windows-Windows Defender/Operational" | Where-Object { $_.TimeCreated -ge $Defender.StartTime -and $_.Id -eq $Defender.SCAN_STARTED } if ($ScanStarted) { $Defender.ScanId = $ScanStarted.Properties[$ScanId].Value $Defender.LastScanId = $Defender.ScanId } * Проверка завершения сканирования: $ScanCompleted = Get-WinEvent -LogName "Microsoft-Windows-Windows Defender/Operational" | Where-Object { $_.TimeCreated -ge $Defender.StartTime -and $_.Id -eq $Defender.SCAN_COMPLETED -and $_.Properties[$ScanId].Value -eq $Defender.ScanId } * Наличие детекта в образце: $MalwareDetected = Get-WinEvent -LogName "Microsoft-Windows-Windows Defender/Operational" | Where-Object { $_.TimeCreated -ge $Defender.StartTime -and $_.Id -in ` $Defender.MALWARE_DETECTED, ` $Defender.BEHAVIOR_DETECTED, ` $Defender.STATE_MALWARE_DETECTED ` На втором этапе, нужна интеграция с: - Eset NOD32 - Kaspersky - Norton Antivirus Третий этап: - Avast