Я читаю книги о написании качественного кода, но то, что хорошо применимо для высокоуровневых языков типа C++, не всегда применимо для асма. Мне интересно, как писать чистый, читаемый код на асме. Пробовал отрефакторить простой код и получил следующее: Code (ASM): GetKernel32 proc .while byte ptr [ebp + Kernel32Limit] != 00h assume esi: ptr IMAGE_DOS_HEADER .if [esi].e_magic == "ZM" invoke CheckPE .if eax != 0 ret .endif .endif sub esi, 10000h dec byte ptr[ebp + Kernel32Limit] .endw ret GetKernel32 endp CheckPE proc assume esi: ptr IMAGE_DOS_HEADER mov edi, [esi].e_lfanew add edi, esi assume edi: ptr IMAGE_NT_HEADERS xor eax, eax .if [edi].Signature == IMAGE_NT_SIGNATURE xchg eax, esi .endif ret CheckPE endp Не могу решить: код с макросами читается лучше, чем код с метками, или это ужасно? Стоит ли карать своей елдой за такую мешанину из if'ов как в GetKernel32? Посмотрел получившийся код в IDA Pro: код с макросами имеет более сложную структуру, чем код с метками.
УжоснахЪ... Начнем с того, что в коде непонятно что со содержит EBP в GetKernel32 и что есть Kernel32Limit на момент выполнения GetKernel32. Ну да ладно. Логика процедуры примерно понятна и заключается в получении ImageBase образа Kernel32.dll. Но алгоритм в корне неверен. .while byte ptr [ebp + Kernel32Limit] != 00h тут совсем все запущено. (А)Я бы все-таки использовал .repeat вместо .while по соображениям оптимизации итогового кода. (Б)Адрес byte ptr [ebp + Kernel32Limit] лучше представлять как двумерный массив (для читаемости) т.е. так byte ptr [ebp][Kernel32Limit] (на самом деле по вкусу, результат все равно останется неизменным, но второй вариант мне кажется более читаемым). (В) И почему по условию цикла нулевой байт будет означать конец? byte ptr [ebp + Kernel32Limit] - это счетчик чего ? assume esi: ptr IMAGE_DOS_HEADER: Эта строка, конечно имеет право на существование, но в твоем случае она только загромождает код и вместо 1 строки ты получаешь 2 (3+4 строки). Их можно заменить одной .if [esi].IMAGE_DOS_HEADER.e_magic == "ZM" invoke CheckPE: Этот инвок ессно в том виде в котором представлен выдаст ошибку компиляции, т.к. не указан прототип CheckPE. В случае, если бы CheckPE располагалась ДО GetKernel32, прототип можно было бы не объявлять и ошибки компиляции бы не возникало. 6-8 строки кода, конечно нужны, но дублирование ret'a это +3 байта к коду. Можно было бы заюзать директиву .break sub esi, 10000h: Вычитание из адреса по 10000h байт - не слишком ли расточительно ? Выравнивание Kernel, если мне не изменяет память 1000h байт, а значит ты таким макаром свободно можешь проскочить искомый ImageBase и креш в подарок. dec byte ptr[ebp + Kernel32Limit]: Смысла этой строки мне совсем не понять... CheckPE: см. п.3 Если ты в процедурах используешь регистры ebx, esi, edi - сохраняй их на входе и восстанавливай на выходе, т.к. эти регистры не модифицируются другими функциями и если ты где-то с этим накосячишь можешь в будущем получить такую ошибку, что неделю будешь чесать репу откуда она приплыла(из прошлого опыта). Не совсем понятна необходимость разбивать этот код на 2 функции В итоге код можно представить в таком виде. Code (Text): ;=============== GetKernel32ImageBase Function =============== ; ; Description: ; ; Функция, получающая адрес образа kernel32.dll по любому адресу его пространства ; ; Arguments: ; ; 1. dwKernelAddress: адрес, принадлежащий библиотеке kernel32 ; ; Return value: ; ; В случае успеха, возвращаемое значение - адрес образа библиотеки kernel32.dll, иначе - 0. ; ; Note: ; ; В качестве адреса, принадлежащего kernel32, можно использовать адрес, находящийся на вершине стека в момент выполнения точки входа в программу (dword ptr ss:[esp]) в случае EXE-файла. В случае dll - dword ptr ss:[esp] будет содержать адрес ntdll.dll. ; GetKernel32ImageBase proc uses esi dwKernelAddress:dword local RetVal:dword mov RetVal, 0 ; Инициализируем возвращаемое значение mov esi, dwKernelAddress ; esi = dwKernelAddress and esi, 0FFFFF000h ; Обнуляем младшие 12 бит, т.к. адреса меньшие 1000h система по-умолчанию считает неверными, а так же в связи с тем, что выравнивание секций библиотеки kernel32 = 1000h .if esi != 0 ; если после проверки esi != 0, значит считаем, что в функцию был передан валидный адрес ; в esi на начало выполнения цикла содержится адрес kernel32.dll .repeat ; начинаем искать начало образа .if [esi].IMAGE_DOS_HEADER.e_magic == IMAGE_DOS_SIGNATURE ; если найдена искомая сигнатура mov eax, [esi].IMAGE_DOS_HEADER.e_lfanew ; извлекаем из структуры IMAGE_DOS_HEADER поле e_lfanew add eax, esi ; получаем указатель на структуру IMAGE_NT_HEADERS .if [eax].IMAGE_NT_HEADERS.Signature == IMAGE_NT_SIGNATURE ; Если формат PE валиден mov RetVal, esi ; Присваиваем возвращаемому значению значение ImageBase (esi) .endif .endif sub esi, 1000h ; уменьшаем адрес на выравнивание kernel32.dll .until zero? || RetVal != 0 ; продолжаем цикл до тех пор, пока не переберем все возможные или не найдем искомый .endif mov eax, RetVal ; вернем значение ret GetKernel32ImageBase endp Я не хочу сказать, что этот код идеален, однако с позиции читаемости значительно лучше, чем представленный. P.S. Этот алгоритм, конечно работает, но есть и альтернативные методы нахождения адресов kernel и ntdll - поиск через PEB. P.S.S. Что касается kernel32.dll надо помнить что на системах выше XP речь будет идти о kernelbase.dll И встречный вопрос: что за литературку читаешь ?
Хороший ответ. Комментарии по алгоритму хорошие, но меня интересовал именно стиль кода, потому что для профессиональных разработчиков на высокоуровневых языках есть книги типа "Чистого кода" Роберта Мартина, в котором излагается многолетний опыт написания высококачественного кода, но оказалось, что напрямую применять советы оттуда к ассемблеру иногда проблематично и только ухудшает читаемость. В ebp у меня дельта-оффсет. >И встречный вопрос: что за литературку читаешь Роберт Мартин "Чистый код" Брюс Эккель "Философия C++" "Экстремальное программирование" Кента Бека "Рефакторинг" Фаулера И книга про test-driven development на C++. Я крестами деньги зарабатываю, ассемблер для меня просто развлечение.
Вряд ли получится по пунктам, потому что я не владею всеми средствами masm. Интересует, как писать короткий, легко читаемый, эффективный и компактный код на асме, который легко изменять и повторно использовать даже спустя время. Вот про синтаксис типа mov edi, [esi].IMAGE_DOS_HEADER.e_lfanew без assume я даже не знал.
Aoizora, > .if eax != 0 Эквивалентно (.if Eax), иначе (!Eax); не нужно лишние выражение вводить. > код с макросами читается лучше, чем код с метками, или это ужасно? Минимально используйте макро, не используйте никогда сложные макросы.
Объять необъятное ? Авторы этих книг шли к этому годами. А ты же хочешь это все сразу освоить. Стиль - это как почерк, он у каждого свой, и понятие красоты у каждого свое. По сему открывай прописи и тренируйся на кошках.. Формируй почерк
http://cellperformance.beyond3d.com/articles/2008/03/three-big-lies.html Выработайте свой стиль написания и больше такой фигнёй не занимайтесь.
Как можно избавиться от директивы assume fs:nothing? Без нее код с mov eax, fs:[30h] не компилируется. Code (ASM): GetKernel32 proc assume fs:nothing mov esi, fs:[30h] ; Get the address of PEB mov esi, [esi + 0Ch] ; Get PEB->Ldr mov esi, [esi+ 1Ch] ; Get PEB->Ldr.InInitializationOrderModuleList.Flink (1st module) mov esi, [esi] ; Get the 2nd module mov esi, [esi] ; Get the third module mov eax, [esi + 08h] ; Get the 3rd entries base address (kernel32.dll) ret GetKernel32 endp Можно ли заменить mov esi, [esi] на lodsd?
В случае с fs - никак. Можно, но тогда следующие инструкции должны использовать в качестве базы уже не [esi], a [eax]
Проблема в том, что на асме не изобрели юнит-тесты, которые при рефакторинге и модификации кода контролируют, что ничего не сломалось.
Не надо надеяться на кого-то(что-то). Надейся в первую очередь на себя, не плоди говнокод, его и так хватает ! На реализованные кем-то системы, как правило, надеются говнокодеры, которые напишут дерьма, а потом удивляются, почему все такое тормозное и косячное.
Мало того, в реализованных кем-то системах зачастую ещё over 1000 багов, которые вы вряд ли сможете пофиксить самостоятельно по разным причинам (закрытый код, тухлое коммьюнити и т.д.).
Ты просто никогда не писал средние и большие системы для бизнеса, в которых производительность должна быть не высокой, а приемлемой, но должна быть возможность расширять функционал по требованию заказчика, который за это некисло платит. Говнокодеры это как раз те, кто сдувает пылинки с полированных яиц кота, а на практике пишет лапшу с высокой связностью и проводит сервисы через четыре архитектурных слоя. Из-за таких людей кодовая база становится неподдерживаемой и проект переписывают с нуля. Правильная архитектура приложения, подобранные алгоритмы и структуры данных принесут больше пользы, чем полирование яиц. Вот статьи по вирусописательству середины нулевых писали как раз говнокодеры-дауны, не имеющие понятия о чистом коде, принципе разделения обязанностей, layered архитектуре приложения и прочем. Ыыыы, ассемблер процедурки лапша ыыы и давай говнокод писать. Наконец, ничто (кроме лени и ретроградства) не мешает писать код эффективно и чисто, но для этого надо читать много книг, изучать инструменты и думать.
Aoizora, покажите хотя бы одну статью с открытым доступом по вирусописательству, где имеются все вышеперечисленные вами фичи . Если не сможете, то забирайте свои слова назад. Статьи эти пишутся для новичков и тем, кто хочет что-то понять, но сам не смог. Там затрагиваются механизмы заражения. Все остальное там не важно - оно не относится к вирмейкингу напрямую.
TermoSINteZ, Это не так, виксы не преследуют цель написания копирующегося кода, так же и нет там задач по инфектам/инжектам. Это было на этапе зарождения викс, когда системы были никак не защищены. Постепенно это всё изменилось и сейчас виксы не относятся к малвари. В малваре остались примитивные технологии, методы и подходы к задачам. Но вы правы, масштаб необходимых знаний невероятен, что бы туда лезть. Aoizora, > Ты просто никогда не писал средние и большие системы для бизнеса Что есчо за системы ? Имхо это самый примитивный кодинг, никаких требований к реализации нет, а все нужные детали заложены в IDE. Это реализуют школьники, используя визуальные средства разработки.
Это к каким же таким системам по ТЗ предъявляются требования, заведомо ограничивающие производительность ? Мне всегда казалось, что в бизнесе время - деньги. Мб к майнинг фермам тоже, я просто об этом не знаю? ))) Это только подчеркивает твой возраст... P.S. А вот так было раньше