ВАЖНО Чистка от сигнатурного детекта антивирусов

Кто просматривает этот контент: "Тема" (Всего пользователей: 0; Гостей: 1)

X-Shar

:)
Администрация
Регистрация
03.06.2012
Сообщения
5 578
Репутация
121
#1
DSC_0167.JPG


Всем привет !

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

В общем тема может и не новая, но понравилось как всё понятно изложено, в этой-же теме можно обсуждать и выкладывать другие методики чистки:


Добро пожаловать в пилотный выпуск серии EasyHack! Сегодня мы напишем простую утилиту для чистки pe32 от сигнатур антивируса.


Как было сказано в анонсе, мы будем писать на Python.

Почему Python?

- Кроссплатформенность – один и тот же код будет работать везде, исключая некоторые модули
- Поставка исходного кода – легко проверить наличие бекдоров и внести изменения в чужой код
- Лёгкость чтения кода – существует даже специальный термин Pythonic
- Легкость написания – существуют готовые модули почти подо все задачи
- Большое сообщество – много документации, любой вопрос уже освещён в гугле

Полагаю, что со временем, в силу риска склейки, pe32-утилиты канут в небытие, уступив место python‘у.

Установка Python:

Я использую версию 2.7, скачать её можно здесь.

Код:
 00 00 00 00 00 00
Отмечаем “Add python to %PATH%”

Чистка Exe

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

Принцип его работы прост до безобразия:
  • берется исполняемый файл
  • внутри него выбирается смещение и его размер
  • выбираем размер криптографического окна (о нём далее)
  • производится замена всех вариантов “окон”
  • весь массив полученных файлов сканируется локальным антивирусом
  • в тех файлах, которые остались не тронуты – “окно” повредило сигнатуру (или формат pe32 )
  • если она была на данных, которые безболезнено можно удалить – вам повезло
  • если нет – чистка проводится в исходниках или хотпатчем (рассмотрим в след части)
В нашем случае, “окно” – это участок фиксированного размера внутри файла. Мы проходим по файлу с “шагом” равным размеру “окна”.

Представим что у нас есть файл с такими данными:
Код:
00 00 00 00 00 00
Возьмём размер “окна” – 2 байта
Диапазон – от начала до конца файла
Замена – на FF

Все варианты замены будут выглядеть так:
Код:
FF FF 00 00 00 00
00 00 FF FF 00 00
00 00 00 00 FF FF
Окей, перейдём к кодингу.

SignFinder
Я долго думал как подать код чтобы он был понятен, но не переписывая учебник по python. В итоге было решено дать код утилиты с обильными комментариями.
Код:
#coding=utf8

'''
SignFinder - Скрипт для чистки PE32 от сигнатур антивирусов
'''

__author__   = 'Auth0r'
__site__     = 'vxlab.info'
__twitter__  = 'https://twitter.com/vxlab_info/'
__version__  = '04.05.2016'

import os
import sys


#вывод текста ошибки и выход
def DieWithError(err):
    sys.exit('[!] '+err)

def PrintLogo():
    print '\n[------------------------]'
    print ' SignFinder by ' + __site__
    print ' Version on ' + __version__
    print '[------------------------]\n'

#установим текущей папку в которой лежит файл path
def set_home(path):
    home = os.path.realpath(os.path.dirname(path))
    os.chdir(home)

def ReadFile(path):
    #проверим cуществование файла
    if os.path.isfile(path):
        #открываем в режиме Read и Binary
        f = open(path, 'rb')
        #читаем файл целиком
        data = f.read()
        f.close()
        return data
    else:
        DieWithError('file '+path+' not found!')

def SaveFile(path,data):
    f = open(path, 'wb')
    f.write(data)
    f.close()
    
#создаём папку с именем файла, в которую будем складывать получившиеся семплы
def CreatePe32Folder(path):
    #получим имя файла из пути
    name = os.path.basename(path)
    #получим имя без расширения
    tmp = name.split('.')
    name = tmp[0]
    #выберем имя папки
    dir_name = name+'_SignFinder'
    #проверим существование
    if not os.path.isdir(dir_name):
        #создаём папку
        os.mkdir(dir_name)
    #возвращаем её имя
    return dir_name

#замена байт
def ReplaceByte(data, offset, window_size, window_byte):
    r = window_byte * window_size
    new_data = data[:offset] + r  + data[offset+window_size:]
    return new_data


def CleanOneFile(dir_name, file_data, offset, window_size, window_byte, i, file_num_tmp):
        #заменим "окна"
        new_data = ReplaceByte(file_data, offset, window_size, window_byte)
        #имя файла - смещение окна и его размер
        file_name = '{}-{}'.format(offset,window_size)
        #сформируем путь до файла
        file_path = '{}\\{}.clean'.format(dir_name,file_name)
        #запишем файл
        SaveFile(file_path, new_data)
        #подсчитаем процент выполнения
        i_tmp = i + 1
        percent = round(float(i_tmp)/file_num_tmp*100, 2)
        #выведем лог
        print "[-] Created {} in {} - {}%".format(i_tmp, file_num_tmp, percent)
    

#создаём все варианты файла с заменой "окна"
def CleanAllFile(dir_name, file_data, window_size, window_byte='\x00'):

    #размер файла
    filesize = len(file_data)

    #расчитаем сколько будет файлов
    file_num = filesize / window_size
    file_num_tmp = file_num

    #расчитаем размер последнего неполного "окна"
    last_window_size = filesize % window_size
    #учтем наличие остатка
    if last_window_size > 0:
        file_num_tmp = file_num + 1

    #цикл создания
    for i in range(file_num):
    
        #вычислим позицию "окна"
        offset = i * window_size
        CleanOneFile(dir_name, file_data, offset, window_size, window_byte, i, file_num_tmp)
    
    #последнее "окно" неполного размера
    if last_window_size > 0:
        offset = file_num * window_size
        i = i + 1
        CleanOneFile(dir_name, file_data, offset, last_window_size, window_byte, i, file_num_tmp)


def main():
    #выведем инфо об утилите
    PrintLogo()
    #получим данные из командной строки
    if len(sys.argv)==3:
        file_path = sys.argv[1]
        window_size = int(sys.argv[2])
    
        #установим текущей папкой - место расположения файла
        set_home(file_path)
        #создаем папку для выходных семплов
        dir_name = CreatePe32Folder(file_path)
        #читаем оригинальный файл
        file_data = ReadFile(file_path)
        #создаем семплы
        CleanAllFile(dir_name, file_data, window_size)
    else:
        print "USAGE:   SF.py path_to_exe window_size"
        print "EXAMPLE: SF.py C:\\test\\1.exe 4"
    

#вызов основной функции
main()
Что умеет текущая версия?
Для запуска, откройте командную строку CMD и введите:
Код:
python C:\SignFinder\sf.py C:\test\1.exe 1000
Где параметры:
Код:
SF.py path_to_exe window_size
Скрипт создаёт рядом с целевым exe папку и помещает в неё все варианты замены “окон” под именами в формате:
Код:
offset - windows size .clean
Кстати, не советую делать размер “окна” больше чем 1% от размера файла.

Источник:Чистка PE32 (часть 1)

Вторую версию статьи размещу ниже !
 

X-Shar

:)
Администрация
Регистрация
03.06.2012
Сообщения
5 578
Репутация
121
#2
Часть 2:Чистка PE32 (часть 2)

Сегодня наша утилита из первой части сильно поумнеет! Она научится корректно обрабатывать формат pe32, получит новые режимы сканирования, для нахождения сигнатуры в 4-5 простых шага.

Для понимания процесса необходимо хорошо знать формат pe32-файлов.

Алгоритм чистки:

Чистка сигнатур – кропотливый процесс, но довольно простой для понимания.
Сначала мы, как опытные врачи, отсекаем все возможные диагнозы, после чего находим точное место дислокации сигнатур.

По моему опыту, все сигнатуры можно разделить на несколько категорий:
  • сигнатуры эмулятора
  • сигнатуры на pe32-хидеры
    • на таблицу секций
    • на отдельные поля OptionalHeader и DosHeader
  • сигнатуры на таблицу импорта
  • “жёсткие” сигнатуры внутри секций
Для начала чистки необходимо определить в какой категории находится “заболевание”.

Для этого в нашем сканере есть режим “fast“, создающий пачку семплов:
  • заменяя код на точке входа, прекращая работу эмулятора – файл “EMUL.clean”
  • перезатирая весь импорт – файл “IMPORT.clean”
  • перезатирая секции в следующих комбинациях
    • все секции сразу – файл “ALL_SECTION.clean”
    • по одной секции за раз – файл “SECTION[0].text.clean”
    • все кроме одной за раз – файл “ALL_SECTION_NOT[0].text.clean”.
Код:
python sf.py fast sample.exe
Если сканирование этих файлов показало детект во всех семплах, значит сигнатура расположена в заголовках pe32, для неё предназначен режим “head“, создающий вот такую прелесть:



Каждое поле OptionalHeader и DosHeader заслуживает внимания.
Код:
python sf.py head sample.exe
Чистка хидеров не должна вызывать больших трудностей, чего нельзя сказать про чистку секций. Для неё существует специальный режим “sect”, который делит любую выбранную нами секцию на 100 частей и по очереди их перезатирает.
Код:
 python sf.py sect sample.exe 0


После сканирования этих файлов, антивирус не удалит те, на которых была сигнатура.

И последним инструментом является режим сканирования “man“, которые позволяет вручную выбрать смещение и размер блока, который будет поделен на 10 частей. С помощью неё можно обнаружить точное место расположения сигнатуры.
Код:
python sf.py man sample.exe 0 1000
Код утилиты:

Для начала нужно установить модуль pefile, скачайте его и выполните из командной строки:
Код:
pip install pefile.zip
Я размещу только новый код.
Код:
#-----------------------------------
#v2
import pefile
import struct
#-----------------------------------
Код:
#-----------------------------------
#v2

#преобразуем формат структур pefile в свой формат смещение\размер\имя
def pefile_StructToOffsets(structure):
    offset_list = list()
    tmp_list = list()
    offset = 0
    for s in structure[1]:
        if s is not None:
            tmp = s.split(',')
            name = tmp[1]
            format = str(tmp[0])
            size = struct.calcsize(format)
            #составляем список смещение\размер\имя
            tmp2 = "{},{},{}".format(offset, size, name)
            tmp_list.append(tmp2)
            offset += size
        
    #имя структуры + данные
    offset_list = (structure[0], tmp_list)
    return offset_list
    

#стираем заданый участок и создаём файл
def CleanFileOffset(file_data, dir_name, offset, size, file_name):
        new_data = ReplaceByte(file_data, offset, size, '\x00')
        file_path = '{}\\{}.clean'.format(dir_name, file_name)
        SaveFile(file_path, new_data)

#очищаем каждое поле хидеров по очереди
def CleanHeaderStruct(file_data, file_offset, dir_name, stucture):
    offset_list = pefile_StructToOffsets(stucture)
    struct_name = offset_list[0]
    for i in offset_list[1]:
        tmp = i.split(',')
        offset = int(tmp[0])
        size   = int(tmp[1])
        name   = struct_name+'-'+ tmp[2]
        #запретим чистить поля ломающие pe32 - e_lfanew и e_magic
        if tmp[2]!='e_lfanew' and tmp[2]!='e_magic':
            CleanFileOffset(file_data, dir_name, file_offset + offset, size, name)

    
#коллекционируем интересующую нас информацию из модуля pefile
def GetPeInfo(file_data):
    #обработка ошибок
    try:
        #загрузим файл в парсер pefile
        pe =  pefile.PE(data=file_data, fast_load = True)
    
        #всю информацию будем хранить в словаре
        info = dict()
    
        #а интересуют нас смещения секций
        section_list = list()
        section_num = 0
    
        for section in pe.sections:
            #удалим из имени нули
            name = section.Name.replace('\x00','')
            #добавим номер секции
            name = '[{}]{}'.format(section_num,name)
            #сформируем список
            section_tmp = (name, section.PointerToRawData, section.SizeOfRawData)
            section_list.append(section_tmp)
            section_num += 1
    
        info['sections'] = section_list

        #дирректория импорта - преобразуем вирт адрес в смещение
        import_offset = pe.get_offset_from_rva(pe.OPTIONAL_HEADER.DATA_DIRECTORY[1].VirtualAddress)
        import_dir = (import_offset, pe.OPTIONAL_HEADER.DATA_DIRECTORY[1].Size)
        info['import'] = import_dir
    
        #точка входа
        ep_offset = pe.get_offset_from_rva(pe.OPTIONAL_HEADER.AddressOfEntryPoint)
        info['ep'] = ep_offset
    
        #и смешения структур
        info['optional_offset'] = pe.OPTIONAL_HEADER.get_file_offset()
    
        return info

    except Exception, e:
        #выведем ошибку парсера
        DieWithError(str(e))
    
    
#замена строки байт
def ReplaceByteString(data, offset, new_bytes):
    new_data = data[:offset] + new_bytes  + data[offset+len(new_bytes):]
    return new_data


#режим быстрой чистки
def mode_fast(file_data, dir_name, pe_info):

    #заменим код на точке входа
    new_byte = '\xCC\xC3' # INT3 RET
    new_data = ReplaceByteString(file_data,pe_info['ep'],new_byte)
    file_path = '{}\\EMUL.clean'.format(dir_name)
    SaveFile(file_path, new_data)

    #удаление импорта
    new_byte = pe_info['import'][1] * '\x00'
    new_data = ReplaceByteString(file_data,pe_info['import'][0],new_byte)
    file_path = '{}\\IMPORT.clean'.format(dir_name)
    SaveFile(file_path, new_data)

    #очистим секции одна за другой
    for sect in pe_info['sections']:
        name = sect[0]
        offset = sect[1]
        size = sect[2]
    
        new_byte = size * '\x00'
        new_data = ReplaceByteString(file_data,offset,new_byte)
        file_path = '{}\\SECTION{}.clean'.format(dir_name,name)
        SaveFile(file_path, new_data)
    
    #очистим ВСЕ секции
    new_data = file_data
    for sect in pe_info['sections']:
        offset = sect[1]
        size = sect[2]
        new_byte = size * '\x00'
        new_data = ReplaceByteString(new_data,offset,new_byte)
    file_path = '{}\\ALL_SECTION.clean'.format(dir_name)
    SaveFile(file_path, new_data)
    
    #очистим все секции кроме выбранной

    for sect in pe_info['sections']:
        new_data = file_data
        name = sect[0]
        for sect2 in pe_info['sections']:
            name2 = sect2[0]
            if name2 != name:
                offset = sect2[1]
                size = sect2[2]
                new_byte = size * '\x00'
                new_data = ReplaceByteString(new_data,offset,new_byte)
        file_path = '{}\\ALL_SECTION_NOT{}.clean'.format(dir_name,name)
        SaveFile(file_path, new_data)
    
    print "[-] Fast mode - done"


#режим чистки pe32-заголовков
def mode_header(file_data, dir_name, pe_info):
    #начнем с DosHeader
    CleanHeaderStruct(file_data, 0, dir_name, pefile.PE.__IMAGE_DOS_HEADER_format__)
    #теперь OptionalHeader
    CleanHeaderStruct(file_data, pe_info['optional_offset'], dir_name, pefile.PE.__IMAGE_OPTIONAL_HEADER_format__)
    print "[-] Header mode - done"

    
#режим чистки секций
def mode_section(file_data, dir_name, pe_info, sect_num):
    #делим секцию на 100 отрезков и вырезаем их по очереди
    found = False
    sect_i = 0
    for sect in pe_info['sections']:
        if sect_num == sect_i:
            section_offset = sect[1]
            section_size = sect[2]
            part_num = 100
            part_size = section_size / part_num
        
            #последний кусочек, меньше размера part_size
            last_part_size = section_size % part_num
            offset = section_offset
        
            for i in range(part_num):
                file_name = 'SECTION[{}]_{}_PART-{}-{}'.format(sect_num, i, offset, part_size)
                CleanFileOffset(file_data, dir_name, offset, part_size, file_name)
                offset += part_size
                found = True
        
            #последний кусочек
            if last_part_size > 0:
                file_name = 'SECTION[{}]_{}_PART-{}-{}'.format(sect_num, part_num, offset, last_part_size)
                CleanFileOffset(file_data, dir_name, offset, last_part_size, file_name)
            
    
        sect_i += 1
    
    if found:
        print "[-] Section mode - done"
        return
    else:
        DieWithError('invalid section number')

#режим ручной чистки - делим указанный кусок на 10 частей
def mode_manual(file_data, dir_name, pe_info, file_offset, offset_size):
    part_num = 10
    part_size = offset_size / part_num

    last_part_size = offset_size % part_num

    offset = file_offset
    for i in range(part_num):
        file_name = 'MANUAL_{}_PART-{}-{}'.format( i, offset, part_size)
        CleanFileOffset(file_data, dir_name, offset, part_size, file_name)
        offset += part_size
    
    #последний неполный...
    if last_part_size > 0:
        file_name = 'MANUAL_{}_PART-{}-{}'.format( part_num, offset, last_part_size)
        CleanFileOffset(file_data, dir_name, offset, last_part_size, file_name)
    
    print "[-] Manual mode - done"


def main():
    PrintLogo()
    #распарсим параметры
    if len(sys.argv)>=3:
        mode = sys.argv[1]
        file_path = sys.argv[2]
    
        set_home(file_path)
        dir_name = CreatePe32Folder(file_path)
        file_data = ReadFile(file_path)
        #получим инфо о pe32-хидерах
        pe_info = GetPeInfo(file_data)
    
        #узнаем какой режим работы стоит
        if mode=='fast':
            mode_fast(file_data, dir_name, pe_info)

        elif mode=='head':
            mode_header(file_data, dir_name, pe_info)        

        elif mode=='sect':
            if len(sys.argv)==4:
                sect_num = int(sys.argv[3])
                mode_section(file_data, dir_name, pe_info, sect_num)
            else:
                DieWithError('lost sect param')
        elif mode=='man':
            if len(sys.argv)==5:
                file_offset = int(sys.argv[3])
                offset_size = int(sys.argv[4])
                mode_manual(file_data, dir_name, pe_info, file_offset, offset_size)
            else:
                DieWithError('lost man param')
        else:
            DieWithError('invalid mode')
    else:
        print """
USAGE:    SF.py mode param"
EXAMPLE:
    SF.py fast path_to_exe
    SF.py head path_to_exe
    SF.py sect path_to_exe section_number
    SF.py man path_to_exe window_offset window_size"""

    
#-----------------------------------
Заключение:

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

Автор обещал, что возможно напишит и третью часть, источник здесь:Чистка PE32 (часть 2)
 
Последнее редактирование:

Indy

Уважаемый пользователь
Форумчанин
Регистрация
21.01.2015
Сообщения
300
Репутация
179
#4
>
  • выбираем размер криптографического окна (о нём далее)
  • производится замена всех вариантов “окон”
Сигнатуры имеют сложный формат, который определяется кодом. Ветвления в коде етц, сигнатура это далеко не тупо строка в памяти. Мало что этим можно обнаружить. Теоретически.
 
Вверх