Maximize
Bookmark

VX Heaven

Library Collection Sources Engines Constructors Simulators Utilities Links Forum

ГСЧ в вирусах

Z0mbie
Март 2001

[Вернуться к списку] [Комментарии]

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

В чем разница между действием всегда или же с вероятностью? Разница в том, что возникает неопределенность, то есть непредсказуемость работы вируса. А отсюда следует, что изучать, детектировать и лечить такой вирус становится сложнее.

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есмотря на всю бредовость идеи с монеткой, в ней есть смысл: чем больше "случайных" выборов производит вирус, тем больше он непредсказуем и свободен. Больше случайности -- больше свободы, но не доводя до абсурда, конечно.

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

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

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

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

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

т.е. раньше были декрипторы:

а стали:

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

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

Вопрос: как сгенерить такую хитрую маску?

Ответ:

                ...
__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);
 

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

[Вернуться к списку] [Комментарии]
By accessing, viewing, downloading or otherwise using this content you agree to be bound by the Terms of Use! vxheaven.org aka vx.netlux.org
deenesitfrplruua