Чистый код на ассемблере

Тема в разделе "WASM.HEAP", создана пользователем Aoizora, 8 янв 2018.

  1. Aoizora

    Aoizora Active Member

    Публикаций:
    0
    Регистрация:
    29 янв 2017
    Сообщения:
    352
    Я читаю книги о написании качественного кода, но то, что хорошо применимо для высокоуровневых языков типа C++, не всегда применимо для асма. Мне интересно, как писать чистый, читаемый код на асме. Пробовал отрефакторить простой код и получил следующее:

    Код (ASM):
    1. GetKernel32 proc
    2.     .while byte ptr [ebp + Kernel32Limit] != 00h
    3.         assume esi: ptr IMAGE_DOS_HEADER
    4.         .if [esi].e_magic == "ZM"
    5.             invoke CheckPE
    6.             .if eax != 0
    7.                 ret
    8.             .endif
    9.         .endif
    10.         sub esi, 10000h
    11.         dec byte ptr[ebp + Kernel32Limit]
    12.     .endw
    13.     ret
    14. GetKernel32 endp
    15.  
    16. CheckPE proc
    17.     assume esi: ptr IMAGE_DOS_HEADER
    18.     mov edi, [esi].e_lfanew
    19.     add edi, esi
    20.    
    21.     assume edi: ptr IMAGE_NT_HEADERS
    22.     xor eax, eax
    23.     .if [edi].Signature == IMAGE_NT_SIGNATURE
    24.         xchg eax, esi
    25.     .endif
    26.    
    27.     ret
    28. CheckPE endp
    Не могу решить: код с макросами читается лучше, чем код с метками, или это ужасно? Стоит ли карать своей елдой за такую мешанину из if'ов как в GetKernel32?

    Посмотрел получившийся код в IDA Pro: код с макросами имеет более сложную структуру, чем код с метками.
     
  2. HESH

    HESH Active Member

    Публикаций:
    2
    Регистрация:
    20 мар 2008
    Сообщения:
    143
    УжоснахЪ...
    1. Начнем с того, что в коде непонятно что со содержит EBP в GetKernel32 и что есть Kernel32Limit на момент выполнения GetKernel32. Ну да ладно. Логика процедуры примерно понятна и заключается в получении ImageBase образа Kernel32.dll. Но алгоритм в корне неверен.
    2. .while byte ptr [ebp + Kernel32Limit] != 00h тут совсем все запущено. (А)Я бы все-таки использовал .repeat вместо .while по соображениям оптимизации итогового кода. (Б)Адрес byte ptr [ebp + Kernel32Limit] лучше представлять как двумерный массив (для читаемости) т.е. так byte ptr [ebp][Kernel32Limit] (на самом деле по вкусу, результат все равно останется неизменным, но второй вариант мне кажется более читаемым). (В) И почему по условию цикла нулевой байт будет означать конец? byte ptr [ebp + Kernel32Limit] - это счетчик чего ?
    3. assume esi: ptr IMAGE_DOS_HEADER: Эта строка, конечно имеет право на существование, но в твоем случае она только загромождает код и вместо 1 строки ты получаешь 2 (3+4 строки). Их можно заменить одной .if [esi].IMAGE_DOS_HEADER.e_magic == "ZM"
    4. invoke CheckPE: Этот инвок ессно в том виде в котором представлен выдаст ошибку компиляции, т.к. не указан прототип CheckPE. В случае, если бы CheckPE располагалась ДО GetKernel32, прототип можно было бы не объявлять и ошибки компиляции бы не возникало.
    5. 6-8 строки кода, конечно нужны, но дублирование ret'a это +3 байта к коду. Можно было бы заюзать директиву .break
    6. sub esi, 10000h: Вычитание из адреса по 10000h байт - не слишком ли расточительно ? Выравнивание Kernel, если мне не изменяет память 1000h байт, а значит ты таким макаром свободно можешь проскочить искомый ImageBase и креш в подарок.
    7. dec byte ptr[ebp + Kernel32Limit]: Смысла этой строки мне совсем не понять...
    8. CheckPE: см. п.3
    9. Если ты в процедурах используешь регистры ebx, esi, edi - сохраняй их на входе и восстанавливай на выходе, т.к. эти регистры не модифицируются другими функциями и если ты где-то с этим накосячишь можешь в будущем получить такую ошибку, что неделю будешь чесать репу откуда она приплыла(из прошлого опыта).
    10. Не совсем понятна необходимость разбивать этот код на 2 функции
    В итоге код можно представить в таком виде.​

    Код (Text):
    1.  
    2.  
    3. ;=============== GetKernel32ImageBase Function ===============
    4. ;
    5. ;    Description:
    6. ;    
    7. ;        Функция, получающая адрес образа kernel32.dll по любому адресу его пространства
    8. ;
    9. ;    Arguments:
    10. ;    
    11. ;        1. dwKernelAddress: адрес, принадлежащий библиотеке kernel32
    12. ;
    13. ;    Return value:
    14. ;    
    15. ;        В случае успеха, возвращаемое значение - адрес образа библиотеки kernel32.dll, иначе - 0.
    16. ;
    17. ;    Note:
    18. ;    
    19. ;        В качестве адреса, принадлежащего kernel32, можно использовать адрес, находящийся на вершине стека в момент выполнения точки входа в программу (dword ptr ss:[esp]) в случае EXE-файла. В случае dll - dword ptr ss:[esp] будет содержать адрес ntdll.dll.
    20. ;
    21. GetKernel32ImageBase proc uses esi dwKernelAddress:dword
    22. local RetVal:dword
    23.  
    24.     mov RetVal, 0 ; Инициализируем возвращаемое значение
    25.  
    26.     mov esi, dwKernelAddress ; esi = dwKernelAddress
    27.     and esi, 0FFFFF000h ; Обнуляем младшие 12 бит, т.к. адреса меньшие 1000h система по-умолчанию считает неверными, а так же в связи с тем, что выравнивание секций библиотеки kernel32 = 1000h
    28.     .if esi != 0 ; если после проверки esi != 0, значит считаем, что в функцию был передан валидный адрес
    29.    
    30.         ; в esi на начало выполнения цикла содержится адрес kernel32.dll
    31.         .repeat ; начинаем искать начало образа
    32.        
    33.             .if [esi].IMAGE_DOS_HEADER.e_magic == IMAGE_DOS_SIGNATURE ; если найдена искомая сигнатура
    34.        
    35.                 mov eax, [esi].IMAGE_DOS_HEADER.e_lfanew ; извлекаем из структуры IMAGE_DOS_HEADER поле e_lfanew
    36.                 add eax, esi ; получаем указатель на структуру IMAGE_NT_HEADERS
    37.                 .if [eax].IMAGE_NT_HEADERS.Signature == IMAGE_NT_SIGNATURE ; Если формат PE валиден
    38.                     mov RetVal, esi ; Присваиваем возвращаемому значению значение ImageBase (esi)
    39.                 .endif
    40.            
    41.             .endif
    42.        
    43.         sub esi, 1000h ; уменьшаем адрес на выравнивание kernel32.dll
    44.         .until zero? || RetVal != 0 ; продолжаем цикл до тех пор, пока не переберем все возможные или не найдем искомый
    45.    
    46.     .endif
    47.  
    48.  
    49. mov eax, RetVal ; вернем значение
    50. ret
    51. GetKernel32ImageBase endp
    52.  
    53.  
    Я не хочу сказать, что этот код идеален, однако с позиции читаемости значительно лучше, чем представленный.​

    P.S. Этот алгоритм, конечно работает, но есть и альтернативные методы нахождения адресов kernel и ntdll - поиск через PEB.
    P.S.S. Что касается kernel32.dll надо помнить что на системах выше XP речь будет идти о kernelbase.dll


    И встречный вопрос: что за литературку читаешь ?
     
    Aiks нравится это.
  3. Aoizora

    Aoizora Active Member

    Публикаций:
    0
    Регистрация:
    29 янв 2017
    Сообщения:
    352
    Хороший ответ. Комментарии по алгоритму хорошие, но меня интересовал именно стиль кода, потому что для профессиональных разработчиков на высокоуровневых языках есть книги типа "Чистого кода" Роберта Мартина, в котором излагается многолетний опыт написания высококачественного кода, но оказалось, что напрямую применять советы оттуда к ассемблеру иногда проблематично и только ухудшает читаемость.

    В ebp у меня дельта-оффсет.

    >И встречный вопрос: что за литературку читаешь
    Роберт Мартин "Чистый код"
    Брюс Эккель "Философия C++"
    "Экстремальное программирование" Кента Бека
    "Рефакторинг" Фаулера
    И книга про test-driven development на C++. Я крестами деньги зарабатываю, ассемблер для меня просто развлечение.
     
    Последнее редактирование: 8 янв 2018
  4. HESH

    HESH Active Member

    Публикаций:
    2
    Регистрация:
    20 мар 2008
    Сообщения:
    143
    Тогда конкретизируй вопрос. Что конкретно тебя интересует ? По пунктам.
     
  5. Aoizora

    Aoizora Active Member

    Публикаций:
    0
    Регистрация:
    29 янв 2017
    Сообщения:
    352
    Вряд ли получится по пунктам, потому что я не владею всеми средствами masm. Интересует, как писать короткий, легко читаемый, эффективный и компактный код на асме, который легко изменять и повторно использовать даже спустя время.

    Вот про синтаксис типа mov edi, [esi].IMAGE_DOS_HEADER.e_lfanew без assume я даже не знал.
     
  6. Indy_

    Indy_ Well-Known Member

    Публикаций:
    4
    Регистрация:
    29 апр 2011
    Сообщения:
    4.775
    Aoizora,

    > .if eax != 0

    Эквивалентно (.if Eax), иначе (!Eax); не нужно лишние выражение вводить.

    > код с макросами читается лучше, чем код с метками, или это ужасно?

    Минимально используйте макро, не используйте никогда сложные макросы.
     
  7. HESH

    HESH Active Member

    Публикаций:
    2
    Регистрация:
    20 мар 2008
    Сообщения:
    143
    Объять необъятное ? Авторы этих книг шли к этому годами. А ты же хочешь это все сразу освоить. Стиль - это как почерк, он у каждого свой, и понятие красоты у каждого свое. По сему открывай прописи и тренируйся на кошках.. Формируй почерк ;)
     
    Mikl___ нравится это.
  8. SadKo

    SadKo Владимир Садовников

    Публикаций:
    8
    Регистрация:
    4 июн 2007
    Сообщения:
    1.610
    Адрес:
    г. Санкт-Петербург
    http://cellperformance.beyond3d.com/articles/2008/03/three-big-lies.html

    Выработайте свой стиль написания и больше такой фигнёй не занимайтесь.
     
    calidus и Mikl___ нравится это.
  9. Aoizora

    Aoizora Active Member

    Публикаций:
    0
    Регистрация:
    29 янв 2017
    Сообщения:
    352
    Как можно избавиться от директивы assume fs:nothing? Без нее код с mov eax, fs:[30h] не компилируется.

    Код (ASM):
    1. GetKernel32 proc
    2.     assume fs:nothing
    3.     mov esi, fs:[30h]        ; Get the address of PEB
    4.     mov esi, [esi + 0Ch]    ; Get PEB->Ldr
    5.     mov esi, [esi+ 1Ch]        ; Get PEB->Ldr.InInitializationOrderModuleList.Flink (1st module)
    6.     mov esi, [esi]            ; Get the 2nd module
    7.     mov esi, [esi]            ; Get the third module
    8.     mov eax, [esi + 08h]    ; Get the 3rd entries base address (kernel32.dll)
    9.     ret
    10. GetKernel32 endp
    Можно ли заменить mov esi, [esi] на lodsd?
     
  10. HESH

    HESH Active Member

    Публикаций:
    2
    Регистрация:
    20 мар 2008
    Сообщения:
    143
    В случае с fs - никак.
    Можно, но тогда следующие инструкции должны использовать в качестве базы уже не [esi], a [eax]
     
  11. rmn

    rmn Well-Known Member

    Публикаций:
    0
    Регистрация:
    23 ноя 2004
    Сообщения:
    2.329
    Да ладно. Вот же:
    Код (ASM):
    1. db  64h
    2. mov esi, [30h]
     
  12. HESH

    HESH Active Member

    Публикаций:
    2
    Регистрация:
    20 мар 2008
    Сообщения:
    143
    Ну так - да. А с использованием fs - никак. По дефолту assume fs:error
     
  13. Indy_

    Indy_ Well-Known Member

    Публикаций:
    4
    Регистрация:
    29 апр 2011
    Сообщения:
    4.775
    rmn,

    А ничего что использовать смещения структур без их и именования не есть хорошо ?
     
    HESH нравится это.
  14. Aoizora

    Aoizora Active Member

    Публикаций:
    0
    Регистрация:
    29 янв 2017
    Сообщения:
    352
    Проблема в том, что на асме не изобрели юнит-тесты, которые при рефакторинге и модификации кода контролируют, что ничего не сломалось.
     
  15. HESH

    HESH Active Member

    Публикаций:
    2
    Регистрация:
    20 мар 2008
    Сообщения:
    143
    Не надо надеяться на кого-то(что-то). Надейся в первую очередь на себя, не плоди говнокод, его и так хватает ! На реализованные кем-то системы, как правило, надеются говнокодеры, которые напишут дерьма, а потом удивляются, почему все такое тормозное и косячное.
     
  16. SadKo

    SadKo Владимир Садовников

    Публикаций:
    8
    Регистрация:
    4 июн 2007
    Сообщения:
    1.610
    Адрес:
    г. Санкт-Петербург
    Мало того, в реализованных кем-то системах зачастую ещё over 1000 багов, которые вы вряд ли сможете пофиксить самостоятельно по разным причинам (закрытый код, тухлое коммьюнити и т.д.).
     
  17. Aoizora

    Aoizora Active Member

    Публикаций:
    0
    Регистрация:
    29 янв 2017
    Сообщения:
    352
    Ты просто никогда не писал средние и большие системы для бизнеса, в которых производительность должна быть не высокой, а приемлемой, но должна быть возможность расширять функционал по требованию заказчика, который за это некисло платит. Говнокодеры это как раз те, кто сдувает пылинки с полированных яиц кота, а на практике пишет лапшу с высокой связностью и проводит сервисы через четыре архитектурных слоя. Из-за таких людей кодовая база становится неподдерживаемой и проект переписывают с нуля.

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

    Вот статьи по вирусописательству середины нулевых писали как раз говнокодеры-дауны, не имеющие понятия о чистом коде, принципе разделения обязанностей, layered архитектуре приложения и прочем. Ыыыы, ассемблер процедурки лапша ыыы и давай говнокод писать.

    Наконец, ничто (кроме лени и ретроградства) не мешает писать код эффективно и чисто, но для этого надо читать много книг, изучать инструменты и думать.
     
    Последнее редактирование: 11 янв 2018
    texaciri нравится это.
  18. TermoSINteZ

    TermoSINteZ Синоби даоса Команда форума

    Публикаций:
    2
    Регистрация:
    11 июн 2004
    Сообщения:
    3.549
    Адрес:
    Russia
    Aoizora, покажите хотя бы одну статью с открытым доступом по вирусописательству, где имеются все вышеперечисленные вами фичи .
    Если не сможете, то забирайте свои слова назад.
    Статьи эти пишутся для новичков и тем, кто хочет что-то понять, но сам не смог. Там затрагиваются механизмы заражения. Все остальное там не важно - оно не относится к вирмейкингу напрямую.
     
  19. Indy_

    Indy_ Well-Known Member

    Публикаций:
    4
    Регистрация:
    29 апр 2011
    Сообщения:
    4.775
    TermoSINteZ,

    Это не так, виксы не преследуют цель написания копирующегося кода, так же и нет там задач по инфектам/инжектам. Это было на этапе зарождения викс, когда системы были никак не защищены. Постепенно это всё изменилось и сейчас виксы не относятся к малвари. В малваре остались примитивные технологии, методы и подходы к задачам. Но вы правы, масштаб необходимых знаний невероятен, что бы туда лезть.

    Aoizora,

    > Ты просто никогда не писал средние и большие системы для бизнеса

    Что есчо за системы ?

    Имхо это самый примитивный кодинг, никаких требований к реализации нет, а все нужные детали заложены в IDE. Это реализуют школьники, используя визуальные средства разработки.
     
  20. HESH

    HESH Active Member

    Публикаций:
    2
    Регистрация:
    20 мар 2008
    Сообщения:
    143
    Это к каким же таким системам по ТЗ предъявляются требования, заведомо ограничивающие производительность ? :black_eye: Мне всегда казалось, что в бизнесе время - деньги. Мб к майнинг фермам тоже, я просто об этом не знаю? )))

    Это только подчеркивает твой возраст...

    P.S. А вот так было раньше :)

     
    Intro и Indy_ нравится это.