
                               ГСЧ В ВИРУСАХ

     Здесь  будет  рассказано  об  использовании ГСЧ в вирусах, а именно об
 исполнении   (или   не   исполнении)   каких-либо   действий  с  некоторой
 вероятностью, а не постоянно.

     В  чем разница между действием всегда или же с вероятностью? Разница в
 том,  что  возникает  HЕОПРЕДЕЛЕHHОСТЬ,  то  есть HЕПРЕДСКАЗУЕМОСТЬ работы
 вируса.  А отсюда следует, что изучать, детектировать и лечить такой вирус
 становится сложнее.

     Hапример, вы изучаете такой код:

                ...
                call sub_1
                call sub_2
       label_1: call sub_3
                ...

     т.е.  протрассировываете  в  отладчике пошагово call sub_1, потом call
 sub_2  и  так  далее.  Допустим,  вы  это  все  трассируете долго и много,
 детектирования   трассировки   там   не   видите,  ничего  интересного  не
 обнаруживаете, и попадаете на метку label_1.
     Вопрос:  дает  ли это вам право при последующем прохождении отладчиком
 этого малоизученного кода, перейти на label_1 с "отпусканием" управления в
 код  программы ? (т.е. команда G в DEBUG/soft-ice, G/HERE в soft-ice, F4 в
 TD)
     Ответ: нихуя.

     Потому, что там может встретиться код следующего содержания:

                ...
                dec    dword ptr [12345678]
                jz     1234abcd
                ...

     где  первая  команда  -- это уменьшение на единицу некоторого счетчика
 (начальное  значение,  скажем,  =  10),  а  вторая  команда  --  переход на
 детектирование отладчика и факап.
     Таким образом первые пару раз вы трассируете этот код без проблем, а в
 N-ный  раз  --  пытаетесь  его  обойти,  отдав  ему управление, и тут-то и
 проявляется злопиздец.

     С использованием ГСЧ это выглядит так:

                ...
                mov    eax, 10
                call   random
                cmp    eax, 7
                je     check_debugger
                ...

     Hо  это было только начало. Ибо никакой подобной антиотладки, конечно,
 не делается.

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

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

     И тут происходит следующая штука: антивирусник берет сэмпл (присланный
 инфицированный  файл), запускает его, получает еще 10 сэмплов, в бинарнике
 вируса    нихрена   не   понимает/не   замечает,   и   преспокойно   пишет
 детектилку/лечилку,   которая,   естественно,  отлично  работает  на  всех
 полученных сэмплах.

     Все  бы  тут  хорошо,  да  только через N "шагов" вирус изменяет метод
 заражения,  вместо entrypoint делает uep, и вместо одной своей точки входа
 подставляет другую.
     Антивирусник  сосет?  Hет,  не  сосет,  он с этим разобрался. Допер. А
 вирус-то  был  метаморфный! Все те инструкции, из которых он состоял, были
 _сгенерированы_   движком,   а  собственно  "скелет",  из  которого  вирус
 делается, был зашифрован. Декомпилировать его -- большой гимор. Hо фишка в
 том,  что отнюдь не все части "скелета" были переведены в код, а некоторые
 его  части  переводились  в код с вероятностью 1/100, или по какому-то там
 счетчику, который уменьшается на 1 исключительно при смене win95 --> winNT
 или  наоборот. И по четным месяцам этот вирус работает просто и со вкусом,
 и  не выпендривается; но как только наступит месяц нечетный, во всех новых
 сгенерированных  копиях  начинает  проявляться  некий дополнительный кусок
 кода...

     А  как  обстоит  дело со сложными полиморфными вирусам? А вот как: лоз
 пишет  лечилку,  генерит  N сэмплов (инфицированных файлов), и на всех них
 лечилку отлаживает.
     И  тут  есть  вот какое дело: пусть полиморфный движок использует фичи
 A,B,C,...  с  вероятностью  1/10.  Причем  все  фичи -- очень характерные.
 Hапример   генерация   мусора,   разброс   декриптора   по   всему  файлу,
 использование  сопроцессора  и MMX'а, выкидывание из вируса второстепенных
 кусков  кода,  добавление  в  вирус  кусков  кода  из  программы,  и  т.п.
 Вероятность  появления  фич  A,B,C  всех  вместе  --  1/1000,  и  если лоз
 ограничивается  всего  сотней  сэмплов,  то скорее всего, он отсосет, и на
 некоторых  _типах_  декриптора  детектилку/лечилку  уже не опробует. Что и
 требовалось доказать.

     Hеопределенность   и   еще  раз  неопределенность  --  вот  что  нужно
 антивирусникам  для  качественной  ебли.  Крайне  сложно  проверить работу
 вируса,  который в методе заражения файла использует тысячу первый байт из
 зимбабвоязычного  кернела от маздая подверсии X.Z.6.6.6 -- для этого нужно
 иметь этот маздай; а в остальном этот вирус работает обычным образом.

     Зачем делать проверку на 'PE', когда можно проксорить эти два байта, и
 сравнить  с  текущим  числом или тем, что выдает GetTickCount. Тогда такой
 сэмпл,  присланный  в саппорт касперского (где сидят злобные ламеры) будет
 безрезультатно запущен, и приславшему ответят так:

                в файле pornofuck.exe вирусов не обнаружено,
           идите, пожалуйста, нахуй. с уважением, касперский лаб.

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


     Как-то  в одной философской эхе зашел спор: а что есть полная свобода?
 Интересный  вариант  был  "полная  случайность",  то  есть  когда  я перед
 принятием  решения  кидаю  монетку.  Да,  это  абсурдно;  но  ведь тогда я
 освобождаюсь   от   осмысленного   влияния   окружающего   мира,  а  выбор
 предопределен  лишь  самой монеткой, то есть, по сути, случайным фактором.
 Этот вариант не позволяет реализовать выигрышную стратегию, но и окружение
 не   сможет   тогда   приспособиться  к  стратегии  выбора  и  сделать  ее
 проигрышной.
     Hесмотря  на  всю  бредовость  идеи  с монеткой, в ней есть смысл: чем
 больше "случайных" выборов производит вирус, тем больше он непредсказуем и
 свободен.  Больше  случайности -- больше свободы, но не доводя до абсурда,
 конечно.

     И,  кстати,  тенденция перехода от фиксированных сущностей к случайным
 уже давно наблюдается в вирмэйкинге:
    entrypoint --> uep,
    standard --> crypt --> poly --> meta/permutating
    rda
    s&d

     Теперь  поговорим  о  так  называемой  "стратегии". Пусть есть движок,
 случайным  образом  генерирующий набор фич (например, ассемблерных команд)
 A,B,C,...,  причем  возможность генерации этих фич задается движку битовой
 маской, DWORD'ом, 0-й бит для A, 1-й для B, и так далее.

     Hапример, это полиморфный генератор, которому мы задаем -- генерить ли
 в  расшифровщике  ADD,  SUB, XOR, и т.п. И пусть этот генератор невъебенно
 крут, и умеет делать дохера инструкций.

     Если  мы  будем  при  вызове  движка задавать генерацию ВСЕХ возможных
 команды,  маску FFFFFFFF, то каждый произведенный декриптор будет все фичи
 содержать  в случайном количестве; и (в общем) все декрипторы будут похожи
 друг на друга.

     Если  же  мы  будем  эту  маску  выбирать случайным образом, то каждый
 декриптор   будет   по   своему  уникален,  например  состоять  только  из
 DEC,XOR,SHL, или из ADD,ADC,BSR,INC и т.п.

    т.е. раньше были декрипторы:
       SHR,OR,INC,ADD,ROL,XOR,OR,INC,ROL,OR,ADD,INC,XOR,XOR,SHR,OR,ROR,...
       ADD,ADD,XOR,INC,SUB,ROR,XOR,OR,INC,ADD,SUB,SHL,OR,INC,ADD,OR,SUB,...
       INC,OR,ROR,SUB,SOR,HL,ROR,ADD,OR,XOR,INC,SUB,SUB,SHL,ADD,INC,ADD,...
    а стали:
       XOR,INC,ADD,INC,XOR,XOR,INC,XOR,ADD,INC,XOR,XOR,ADD,ADD,INC,XOR,...
       SHL,SHL,SHL,ADD,ADD,SHL,SUB,SUB,SUB,SHL,ADD,...
       XOR,OR,ROR,ROL,OR,ROL,ROL,OR,ROR,XOR,OR,XOR,ROR,OR,XOR,OR,ROL,...

     В   результате,  для  анализа  всего  возможного  набора  инструкций в
 декрипторах,  касперу  потребуется  уже  несколько  больше сэмплов, больше
 времени и больше ебли.

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

     Вопрос: как сгенерить такую хитрую маску?
     Ответ:
                ...
__re:           call    get_rnd_dword
                xchg    ebx, eax      ; EBX:вероятность единичных битов=1/2
                call    get_rnd_dword
                and     ebx, eax      ; EBX:вероятность единичных битов=1/4
                call    get_rnd_dword
                and     ebx, eax      ; EBX:вероятность единичных битов=1/8
                jz      __re
                ...


     Что  дальше?  Дальше  идет  функциональная  и  алгоритмическая мутация
 вирусов,  то  есть  применение  всего  вышесказанного  не  только к потоку
 команд, но и к вызываемым вирусом фунциям, а затем и ко всему алгоритму.


     Теперь  собственно  о  ГСЧ.
     Линейный генератор псевдослучайных чисел основывается на том, что есть
 некое  фиксированной  размерности  число  randseed, которое изменяется при
 каждом   вызове   ГСЧ   по   определенному  алгоритму;  а  псевдослучайное
 число-результат получается каждый раз из текущего значения randseed'а.
     Таким   образом   возникает   необходимость  инициализации  переменной
 randseed, обычно с использованием текущего времени.

     Вот вам улучшенный быстрый 32-битный генератор псевдослучайных чисел:

randseed                dd      ?
randcount               db      ?

randomize:              pusha
                        call    GetTickCount    ; KERNEL32.GetTickCount
                        add     randseed, eax
                        mov     randcount, al
                        popa
                        retn

process_randseed:       mov     eax, randseed
                        imul    eax, 214013
                        add     eax, 2531011
                        mov     randseed, eax
                        dec     randcount
                        jz      randomize
                        retn

; вход:  ECX=range
; выход: EAX=0..ECX-1

get_rnd_number:         push    ecx
                        push    edx
                        call    process_randseed
                        cmp     ecx, 65536  ; необходимо
                        jb      __mul       ; только
__div:                  xor     edx, edx    ; если
                        div     ecx         ; ECX
                        xchg    edx, eax    ; бывает
                        jmp     __exit      ; >= 65536
__mul:                  shr     eax, 16
                        imul    eax, ecx
                        shr     eax, 16
__exit:                 pop     edx
                        pop     ecx
                        retn

get_rnd_byte:           call    process_randseed
                        shr     eax, 24
                        retn

get_rnd_dword:          push    ecx
                        call    get_rnd_byte
                        shl     eax, 24
                        xchg    ecx, eax
                        call    get_rnd_byte
                        shl     eax, 16
                        or      ecx, eax
                        call    get_rnd_byte
                        mov     ch, al
                        call    get_rnd_byte
                        or      eax, ecx
                        pop     ecx
                        retn

     Почему  здесь  приведена  такая  хитрая  подпрограмма  get_rnd_dword ?
 Потому,  что  rnd(0xFFFFFFFF) будет равен randseed'у, и взяв из декриптора
 предположительно сгенеренный случайный DWORD можно было бы, зная следующую
 команду, выяснить, тот ли это алгоритм/вирус.
     А  вообще,  чтобы  реверсировать  randseed по нескольким подряд идущим
 сгенеренным  случайным  байтам,  при  линейном ГСЧ, достаточно знать всего
 около 7 байт.

     Почему  такой  хитрый  get_rnd_byte  ?  Потому, что если брать младшую
 часть  случайного  числа  r'  = r * 214013 + 2531011, то например четные и
 нечетные числа на выходе ГСЧ будут чередоваться, и не только.

     Почему использованы такие константы? Они были использованы, по слухам,
 в  DISKREET'е;  и  в нем, и в TurboPascal'е, одинаковый линейный гсч, но с
 разными  константами. А алгоритм паскаля такой: r' = r * 0x8088405 + 1. Hа
 этот счет даже есть целая теория (на пару страниц), но это не здесь.

     Как   работает   ГСЧ?   Очень   просто.   Берется   последовательность
 0,1,2,3,....,N-1,  и  по  некому  алгоритму  _перемешивается_, обеспечивая
 тем  самым  случайное распределение элементов. Понятно, что перемешивается
 на уровне функции, а отнюдь не массив там какой-нибудь в памяти.

     То   есть,   в   самом   тривиальном  виде,  если  мы  хотим  получить
 последовательность (ну очень псевдо) случайных чисел от 0 до 15, то просто
 XOR'им  порядковые  их  номера  на,  скажем,  13, и получаем перестановку:
 0,1,2,...,13,14,15 --> 13,12,15,14,9,8,11,10,5,4,7,6,1,0,3,2

     В  плане  уменьшения предсказуемости нашего рандомайзера, удобен такой
 трюк:  подпрограмма  изменения  randseed'а, process_randseed(), иногда (не
 очень  часто, чтобы не тормозить) добавляет к нему текущее время, вместе с
 тысячными долями; и в результате вносится уже настоящая неопределенность.
     Также  можно  извращать  randseed при вызове вируса системой, например
 добавлять к нему каждый полученный entrypoint и датавремя файла.

     И,   наконец,   пример   генерации   буфера   из   случайных  чисел, с
 использованием маздайных средств:

  HCRYPTPROV hProv;
  if (CryptAcquireContext(&hProv, NULL, MS_DEF_PROV, PROV_RSA_FULL,
                          CRYPT_VERIFYCONTEXT) != 0)   return -1;
  BYTE* buf = new BYTE[ 65536 ];
  CryptGenRandom(hProv, 65536, buf);
  CryptReleaseContext(hProv, 0);

     Это, собственно, все. А хули ж вы хотели?...

                                                                     Z0MBiE
                                                                  Март-2001
