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

Тема в разделе "Крипторы и исследование защиты", создана пользователем X-Shar, 8 май 2016.

↑ ↓
  1. X-Shar :)
    X-Shar
    Ответить в чате

    Администрация

    Регистрация:
    03.06.2012
    Сообщения:
    5.800
    Симпатии:
    425
    Пол:
    Мужской
    Репа:
    +957 / 152 / -29
    Jabber:
    Skype:
    ICQ:

    638294628

    DSC_0167.JPG

    Всем привет !

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

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


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


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

    Почему Python?

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

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

    Установка Python:

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

    [​IMG]
    Код:
     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 2
    • Информативный пост Информативный пост x 1
  2. X-Shar :)
    X-Shar
    Ответить в чате

    Администрация

    Регистрация:
    03.06.2012
    Сообщения:
    5.800
    Симпатии:
    425
    Пол:
    Мужской
    Репа:
    +957 / 152 / -29
    Jabber:
    Skype:
    ICQ:

    638294628

    Часть 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“, создающий вот такую прелесть:

    [​IMG]

    Каждое поле OptionalHeader и DosHeader заслуживает внимания.
    Код:
    python sf.py head sample.exe

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

    [​IMG]

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

    И последним инструментом является режим сканирования “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)
     
    Последнее редактирование: 8 май 2016
    • Мне нравится Мне нравится x 2
  3. Ворошилов Форумчанин
    Ворошилов
    Ответить в чате

    VIP

    Регистрация:
    21.08.2012
    Сообщения:
    27
    Симпатии:
    30
    Репа:
    +32 / 0 / -0
    • Мне нравится Мне нравится x 1
  4. Indy Уважаемый пользователь
    Indy
    Ответить в чате

    Форумчанин

    Регистрация:
    21.01.2015
    Сообщения:
    251
    Симпатии:
    144
    Пол:
    Мужской
    Репа:
    +170 / 5 / -28
    >
    • выбираем размер криптографического окна (о нём далее)
    • производится замена всех вариантов “окон”
    Сигнатуры имеют сложный формат, который определяется кодом. Ветвления в коде етц, сигнатура это далеко не тупо строка в памяти. Мало что этим можно обнаружить. Теоретически.
     
    • Мне нравится Мне нравится x 2

Поделиться этой страницей