ГСЧ В ВИРУСАХ Здесь будет рассказано об использовании ГСЧ в вирусах, а именно об исполнении (или не исполнении) каких-либо действий с некоторой вероятностью, а не постоянно. В чем разница между действием всегда или же с вероятностью? Разница в том, что возникает 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