Статья DLL библиотеки – инструкция к метле

Ещё во-времена старушки дос стало очевидно, что некоторые программные модули лучше хранить не внутри исполняемого файла, а вынести их наружу и подгружать по мере необходимости. В то время такие "прицепы" называли оверлеями и с точки зрения экономии 1М-байтного адресного пространства это было разумно – одну большую программу кромсали на мелкие части, и эти части отрабатывали в памяти по очереди. Подобная техника докатилась и до наших дней, только теперь это динамически подгружаемые DLL.

По сути, нет смысла копать данную тему в очередной раз – всё давно расписано, поэтому добавим экшена и сделаем ставку на нестандартное их применение. Как показывает практика, фишка с защитой программ на основе статически прилинкованных DLL пользуется спросом среди коммерческого софта, значит пора сорвать с неё вуаль и познакомиться по-ближе.
---------------------------------------

Записки дилетанта

"Dynamic Link Library" или DLL – это часть исполняемого РЕ-файла в виде внешнего модуля. Он оформлен как ларчик с N-нным количеством уникальных для наших программ функций, которых нет в составе системных Win32-API. Программный экзо-скелет динамических библиотек идентичен исполняемым файлам экзе, однако есть и некоторые нюансы:

1. Наличие секции-экспорта в коде, которой могут похвастаться исключительно библиотеки. Приложение может вызывать (импортировать) только те функции из DLL, которые оглашены в её секции-экспорта ".edata". Однако бывают и внутренние функции, которые DLL не экспортирует во-внешний мир, используя только внутри своей тушки для производственных нужд – к их числу можно отнести, например функцию точки-входа в библиотеку DllEntryPoint(). По сути, в данной статье мы сделаем акцент только на этой процедуре инициализации.​
2. Второе отличие DLL от EXE – это не способность ими исполнять свой код самостоятельно, поскольку каждый участок кода обёрнут в отдельную функцию. Все эти функции тупо ждут своего часа, пока их услуги не станут восстребованы экзе-файлу и он не затребует их явно. Исключение здесь составляет только упомянутая точка-входа, с которой без нашего ведома активно общается загрузчик LDR, на протяжении всего времени работы приложения.​

Система предоставляет нам два способа подключения DLL к своим проектам – статический и динамический. В первом случае мы подключаем библиотеку и указываем импортируемые из неё функции на этапе компиляции РЕ-файла, и эти функции сразу загружаются в наше адресное пространство, вместе с приложением. Во-втором (динамическом) случае, можно загрузить функцию из DLL в произвольный момент времени, сыграв аккордом LoadLibrary(), GetProcAddress() и FreeLibrary().

Скомпоновать библиотеку довольно просто – пишем обычный ЕХЕ, только в шапке указываем директиву "format PE DLL". В результате, из выхлопной трубы fasm'a получим файл в формате *.dll. Однако при программировании пользовательских библиотек нужно учитывать ряд их особенностей, в частности релокацию образа в памяти.

Чтобы DLL не загрузилась поверх исполняемого приложения (конфликт базовых адресов), её ImageBase обязательно должна быть перемещаемой – достаточно добавить секцию .reloc в коде, об остальном компилятор позаботится сам. В этой секции будут собраны т.н. фиксапы (fixups) – адреса, к которым загрузчик должен будет внести поправки. Фиксапы применяются исключительно к инструкциям, которые обращаются по абсолютным адресам в памяти. Если адрес относительный (в пределах 127 байт), то он не требует модификации:

reloc_1.png


Такие отладчики как OllyDbg подчёркивают адреса, которые требуют коррекции после перемещения образа в памяти – на рис.выше их всего 4, и непосредственно опкод инструкции не учитывается (здесь push\call, хотя могут быть и условные\безусловные переходы). Размер самих фиксапов равен 12-бит (выделены красным), а это 2^12=4096 или одна страница виртуальной памяти. Соответственно фиксап не может адресовать блок памяти свыше 4 Кбайт. Другими словами, каждая страница (блок) имеет свой набор фиксов.


Точка входа в DLL-библиотеку

Теперь о насущном..
Подобно исполняемым экзе-приложениям, библиотеки тоже имеют свою точку входа – в доках MSDN эта функция известна как DllEntryPoint() (или DllMain в терминологии си). Здесь и кроется всё самое интересное, чему мы посвятим весь последующий разговор.

Любое обращение EXE-модуля к функциям из DLL происходит через системного посредника LdrLoadDLL(). В системном ансамбле, эта кошерная функция из Ntdll.dll играет огромную роль. Она не только загружает библиотеки в пространство юзера на этапе проецирования образа в память, но и обслуживает функции динамического вызова процедур типа LoadLibrary(), GetModuleHandle() и прочии, от которых мы ожидаем получить дескрипторы модулей. Вот её прототип:

C-подобный:
LdrLoadDll(
  PathToFile        ;// путь (опционально);
  Flags             ;// флаги (опционально);
  ModuleFileName    ;// имя библиотеки;
  ModuleHandle )    ;// возвращаемый дескриптор.

Диалог этого загрузчика с вызываемой библиотекой происходит по схеме "запрос-ответ". Любая библиотека должна иметь упомянутую функцию взаимодействия с загрузчиком DllEntryPoint(), от которой в регистре EAX лоадер ждёт или TRUE (библиотека способна обработать запрос), или FALSE (что-то пошло не так). Не соблюдение этих правил приводит к краху приложения.

LdrLoadDLL() может извещать библиотеку о четырёх событиях, которые происходят во-внешнем (по отношению к библиотеке) мире. Эта информация передаётся точке-входа в DLL в параметре fdwReason. Кроме события, загрузчик сразу передаёт либе её базу в памяти, и способ подключения к исполняемому файлу. Прототип описывается так:

C-подобный:
BOOL DllEntryPoint (
  hinstDLL         ;// виртуальная база данной библиотеки в памяти;
  fdwReason        ;// причина вызова;
  lpvReserved );   ;// способ подключения: статический(0), или динамический(1).
                   ;//   ..(статический = явно, динамический = не явно)

;// Возможные значения "fdwReason"
;//-------------------------------
• DLL_PROCESS_DETACH (0) – FreeLibrary() или DLL отключается от процесса,
• DLL_PROCESS_ATTACH (1) – первое подключении DLL к процессу, при его запуске,
• DLL_THREAD_ATTACH  (2) - процесс создаёт новый поток,
• DLL_THREAD_DETACH  (3) - процесс завершает поток.

Из всей этой братии, нам интересен лишь аргумент DLL_PROCESS_ATTACH = 1, благодаря которому статически присобачив библиотеку к нашему процессу, мы можем например, предварительно расшифровать основной код программы, обнаружить отладчик в фоне и т.д. Дело в том, что загрузчик проецирует DLL в пространство процесса задолго до точки-входа в программу, с которой начинают анализ все отладчики, а значит Оля пропустит этот этап между ног. Здесь уместно вспомнить про функции TLS-Callback, ..но поскольку загрузчик парсит импорт из библиотек вторым (а TLS аж десятым), то выигрыш тут на лицо.


DLL – промышленная реализация

Чтобы сформировать образ, приведём код типичной библиотеки. Пока она будет выводить на экран только сообщения об удачном подсоединению к родительскому процессу, а после его закрытия – покажет ещё одну мессагу, что мол родительский процесс отработал и отправляется на покой. Когда приложение вызывает библиотеку, в её стеке оказывается фрейм из трёх (не считая адреса-возврата) аргументов функции DllEntryPoint(), которые загрузчик кладёт туда на всеобщее обозрение – так это выглядит в отладчике:

dllStack.png


Соответственно, мы можем снимать эти аргументы прямо со-стека, и сразу проверять их – код ниже придерживается именно такой политики:

C-подобный:
format   pe console dll      ;// собираем DLL-модуль
include 'win32ax.inc'
entry    DllEntryPoint       ;// на точку-входа
;//-----
.data
attach  db  13,10
        db  ' Dll load done!...'     ,13,10    ;// флаг приложению, что DLL загрузилась
        db  '     Base: 0x%08X '     ,13,10,0  ;//   ..сразу покажем свою базу
detach  db  ' Process shuts down!...',13,10,0  ;// флаг, что DLL выгружается из памяти
mess    db  "     Hi, I'm a message from the library!",0  ;// строка для вызова из приложения!
frmt    db  '%s',0
;//-----
.code
start:
;//=== Точка входа в библиотеку! ==============
;// Проверяет на вызов своей тушки приложением
;// [esp+0]  = адрес возврата в загрузчик LDR
;// [esp+4]  = аргумент "hinstDLL"
;// [esp+8]  = аргумент "fdwReason"
;// [esp+12] = аргумент "lpvReserved"
;//============================================
proc  DllEntryPoint                   ;//====== Регистр букв имеет значение!!!
         mov     eax,[esp+8]             ;// EAX = fdwReason (причина вызова)
         cmp     eax,DLL_PROCESS_ATTACH  ;// тест на "атаку" либы!
         jnz     @fuck                   ;// если нет..
         mov     eax,[esp+4]             ;// иначе: EAX = hinstDLL (своя база в памяти)
        cinvoke  printf,attach,eax       ;// выводим мессагу.
         jmp     @thread                 ;// на выход..
@fuck:   cmp     eax,DLL_PROCESS_DETACH  ;// тест на "отступление"!
         jnz     @thread                 ;// если нет..
        cinvoke  printf,detach           ;// иначе: выводим мессагу.
@thread: mov     eax,1                   ;// если ни_то_ни_другое, значит это работа с потоками.
         ret                             ;// return TRUE загрузчику LDR !!!
endp                                  ;//======
;==============================================

;//=== Библиотека должна иметь как-минимум одну функцию (процедуру)
;// Здесь просто мессага, которую запросит основное приложение.
proc  Hello
        cinvoke  printf,mess    ;// мессага "Hi.."
         ret                    ;// выход по адресу-возврата.
endp

;//=== Секция-импорта из системных DLL ========
section '.idata' import data readable
library  msvcrt,'msvcrt.dll'
import   msvcrt, printf,'printf'

;//=== Секция-экспорта своих функций ==========
section '.edata' export data readable
export   'about.dll', Hello,'Hello'        ;// перечисляем их..

;//=== Секция-перемещения образа (релоков) ====
;// Если секция имеет флаг "Discardable",
;// она становится первым кандидатом на выгрузку из памяти,
;// когда система обнаружит её нехватку.
section '.reloc' fixups data discardable   ;// клондайк фиксапов

Теперь у нас есть либа, и нужно написать родительское приложение, которое будет статически привязывать к себе эту библиотеку. Во-первых, обратим внимание на имя новоиспечённой DLL – здесь, в секции-экспорта я определил его как "about.dll", это важно! Теперь просто импортируем эту библиотеку по имени, и вызываем из неё функцию примерно так:

C-подобный:
format   pe console
include 'win32ax.inc'
entry    start
;//-----
.data
frmt    db  '%s',0
;//-----
.code
start:
        cinvoke  Hello               ;// зовём функцию из about.dll !!!
        cinvoke  scanf,frmt,frmt+2   ;// ждём клаву..
        cinvoke  exit,0              ;// Game Over!
;//-----
section  '.idata' import data readable
library  msv,'msvcrt.dll', about,'about.dll'  ;// указать точное имя DLL!
import   msv, scanf,'scanf',exit,'exit'
import   about, Hello,'Hello'

dll_example.png



Тёмная сторона луны

Пробежавшись по макушкам кода библиотек, посмотрим на них из другой проекции..
Алгоритм работы загрузчика образов LDR плохо освещён в документации и это не удивительно – весь ядерный код мастдая, коммерческая тайна (будь она не ладна). Как это принято у Microsoft, она советует нам ознакомиться с третьей поправкой, восьмого исправления, четвёртой редакции от 32 февраля где сказано, что "..в военное время не только прямой угол может достигть 100 градусов, но и функция инициализации DllEntryPoint() может использоваться не по назначению". Самое главное: кто, где и когда объявляет это положение неизвестно, а значит мы вольны назначать его сами.

Мощь (и беспомощность) точки-входа в библиотеку в том, что некоторая часть театра действий происходит под управлением системных механизмов, отследить которые из прикладного уровня довольно сложно. В документации на РЕ-файл можно найти формат каталога секций "Data-Directories". В этом дире рассчитавшись на первый-второй выстроены в ряд все секции, которые обходит загрузчик образов LdrLoadDll() при инициализации приложения. Причём последовательность секций строго регламентируется. Вот как выглядит эта структура в представлении редактора PE-Explorer:

dataDir_12.png


Таким образом, импорт анализируется загрузчиком на самом начальном этапе, и большинство служебных структур прикладного уровня в этот момент даже не инициализированы ещё до конца – в частности, это относится к структуре PEB, не говоря уже о дочерней к ней структуре ТЕВ. Например, если мы внутри DllEntryPoint() захотим из РЕВ получить флаг-отладки нашего приложения "BeingDebugger", то потерпим фиаско (проверено на практике). На скамейку запасных сразу отправляется и функция IsDebuggerPresent(), которая читает этот-же флаг из РЕВ. Значит нужно спускаться на уровень ниже, а для защитных механизмов это только гуд.

Если развивать мысль дальше, то наша библиотека не единственная у приложения. Кроме неё, в память каждого процесса система загружает и свои либы Ntdll.dll (собственно в ней и живёт загрузчик LDR), а так-же библиотеку kernel32.dll. С очерёдностью загрузки в память системных библиотек можно ознакомится в отладчике WinDbg, озадачив его командой !peb – поле InMemoryOrderModuleList как-раз отрапортует нам об этом:

pebLdr.png


Здесь видно, что первым десантируется в память мой исполняемый файл "DLL_attach.exe", следом за ним системные библиотеки, и только потом моя пользовательская либа "about.dll". Повторюсь, что система выстраивает структуру РЕВ только когда окончательно покончит с окружением процесса, скидывая в неё результаты проделанной работы. А лог на рисунке выше, WinDbg парсит уже из рабочего процесса, поэтому РЕВ как-бы готова к употреблению.


DllEntryPoint() на страже приложения

Теперь будем мыслить так.. Если точка-входа в библиотеку с аргументом DLL_PROCESS_ATTACH отрабатывает на низком уровне, значит на её основе можно соорудить защитный механизм. Система вызывает DllEntryPoint() с аргументом ATTACH сразу после того-как DLL спроецирована на адресное пространство процесса – такая ситуация возможна всего один раз, и на протяжении всего "сеанса" больше не повторяется! В следующий раз, когда тред вызовет LoadLibrary() для уже спроецированной на память DLL, система просто увеличит счётчик обращения к ней и всё.

В предоставленном на суд примере, статически (явно) прилинкованная библиотека внутри DllEntryPoint() будет искать отладчик, и если обнаружит таковой, то вернёт ошибку. Как упоминалось выше, флаги-отладки из структуры РЕВ для этих целей уже не подходят, поэтому придётся искать обходные пути.

Одним из вариантов обнаружения факта отладки является проверка своего статуса в системе. Дело в том, что в дефолте, запущенный на исполнение процесс не имеет привилегии SeDebugPrivilege, зато ею обладает отладчик. Когда он загружает нас в своё тело, то автоматом передаёт и свою привилегию, чекнув которую мы можем определить этот факт. Есть куча способов узнать привилегию своего процесса, и мы воспользуемся самым простым – попытаемся открыть системный процесс csrss.exe.

CSRSS.EXE – это часть пользовательской подсистемы Win32, и при обычных обстоятельствах он не доступен прикладным задачам. Однако привилегия Debug снимает этот запрет, и мы можем открыть его функцией OpenProcess() со-всеми вытекающими последствиями. CSRSS (client\server run-time subsystem) отвечает за консоль, работу с потоками Thread, и за 16-битную среду MS-DOS (на х64 её кастрировали). Это процесс пользовательского режима, который перехватывает обращения к ядру и решает простые вопросы на уровне прикладных задач.

Проблема в том, что функции OpenProcess() требуется идентификатор PID открываемого процесса, т.е. нам нужно будет просканировать всю память и найти нужный процесс по его имени – тривиальная задача по обнаружению отладчика превращается в ад. В сети можно встретить разные варианты перечисления процессов – это CreateToolhelp32Snapshot(), обход в цикле через Process32First\Next(), EnumProcess() и тяжеловес NtQuerySystemInformation().

Однако получить PID именно процесса CSRSS.EXE можно специально предназначенной для этого функцией из Ntdll.dll под названием CsrGetProcessId() – у неё нет аргументов и в EAX она сразу возвращает столь необходимый нам PID. С использованием этой функции, проверка на отладчик укладывается в пару строк ассемблерного кода. Мы поместим её внутрь DllEntryPoint() и будем проверять запрос на DLL_PROCESS_ATTACH.

В общем случае, программа будет следовать такому алго..
Мы пишем приложение, которое запрашивает пароль. Если юзер введёт валидный пасс, то управление примет зашифрованная функция, которую расшифрует декриптор из внешней библиотеки, с непримечальным именем "about.dll". Алгоритм декриптора – самый примитивный ксор 1-байтныйм ключом, однако тут есть подвох! Пароль на валидность мы вообще не будем проверять, а декриптор сняв с него хэш-сумму сразу расшифрует ей критический блок в основном приложении. Теперь уже взломщик не сможет просто обратить условие проверки, и ему придётся осуществлять только брут, перебором всех возможных ключей.

Если юзер подсунет левый пароль и его хэш не совпадёт с тем, которым мы зашифровали блок, то рано или поздно процессор нарвётся на исключение, поскольку пойдёт пахать зашифрованный код. Чтобы защитить честь его мундира, для таких случаев мы устанавливаем SEH-обработчик, который и будет отлавливать эти исключения. То-есть, если SEH примет управление, значит пасс невалидный и мы подкорректировав значение регистра EIP в контексте, выводим мессагу Wrong и на выход..

Основная проблема тут – правильно зашифровать критический блок кода в основном приложении. Я уже приводил пример шифрования в hex-редакторе HIEW, поэтому повторяться не буду. Если возникнут вопросы, их всегда можно задать в комментах этой темы. Ключ – это сумма всех символов любой строки. Например, в данном случае я использовал пароль "codeby.net" и получил его 1-байтную хэш сумму = 0xEB. Для этого можно воспользоваться услугами редактора HxD и виндовым калькулятором:

hash.png


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

Код основного приложения:

C-подобный:
format   pe console
include 'win32ax.inc'
entry    start
;//-----
.data
capt    db  13,10,' Attach-crackme v0.1'
        db  13,10,' *********************'
        db  13,10,' Type pass: ',0
wrong   db  13,10,' Wrong Password!!!..',0
len     =   @endCrypt - @crypt
frmt    db  '%s',0
buff    db  ?
;//-----
section '.code' code readable writable executable  ;// доступ на запись!
start:
;// Устанавливаем SEH-обработчик исключений
;// выводит сообщение "WrongPass" и выходит из программы
         xor     ebx,ebx            ;//
         push    @trap              ;// указатель на функцию
         push    dword[fs:ebx]      ;//
         mov     [fs:ebx],esp       ;//

        cinvoke  printf,capt              ;// запрос на ввод пароля
        cinvoke  scanf,frmt,buff          ;// ..........^^^
         invoke  Decrypt,len,@crypt,buff  ;// (!)зовём функцию проверки-пароля из DLL

;//==== Начало шифруемого блока ============
@crypt:  nop                            ;// 0x90 = маркер начала
         call    @delta                 ;// кладём в стек адрес возврата
 db      ' Pass.....: OK!',13           ;// <-- (он указывает сюда).
 db      10,10,' Hard-Rock for ever..'
 db      13,10,' --------------------'
 db      13,10,'   Avalanch'
 db      13,10,'   Blind Guardian'
 db      13,10,'   Def Con Dos'
 db      13,10,'   Fear Factory'
 db      13,10,'   Hamlet'
 db      13,10,'   HammerFall'
 db      13,10,'   Iced Earth'
 db      13,10,'   Iron Maiden'
 db      13,10,'   Korn'
 db      13,10,'   Mago De Oz'
 db      13,10,'   Metallica'
 db      13,10,'   Rammstein'
 db      13,10,'   Rhapsody'
 db      13,10,'   Stratovarius',0
@delta:  pop     esi                    ;// ESI = указатель на сообщение
        cinvoke  printf,esi             ;// вывод его на консоль
         nop                            ;// маркер окончания
@endCrypt:
;//==== Конец шифруемого блока =============
@exit:  cinvoke  scanf,frmt,frmt+2  ;// конец программы!
        cinvoke  exit,0             ;//

;//==== SEH обработчик =====================
;// в контексте регистров меняет EIP на выход,
;// и выводит сообщение "WRONG Pass!"
@trap:   mov     esi,[esp+12]       ;// ESI = указатель на CONTEXT
         mov     eax,@exit          ;// EAX = адрес метки EXIT
         mov     [esi+0xb8],eax     ;// 0xb8 = смещение EIP в контексте
        cinvoke  printf,wrong       ;// мессага
         xor     eax,eax            ;// команда "ребут" диспетчеру-исключений
         ret                        ;// return..
;//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
section  '.idata' import data readable
library  msv,'msvcrt.dll', about,'about.dll'
import   msv, printf,'printf',scanf,'scanf',exit,'exit'
import   about, Decrypt,'Decrypt'

Теперь код библиотеки, которую обязательно нужно назвать "about.dll".

C-подобный:
format   pe console dll            ;// собираем DLL-модуль
include 'win32ax.inc'
entry    DllEntryPoint
;//-----
.data
mes0    db  ' Dll loaded done!...',13,10,0    ;// мессага-флаг, что DLL робит
frmt    db  ' Debug %04X',0
;//-----
.code
start:
;//=== Точка входа в библиотеку! =========================
;// Проверяет на ATTACH и отладчик.
;//=======================================================
proc  DllEntryPoint hinst,Reason,Reserved ;// три аргумента..
         mov     eax,[Reason]             ;// EAX = причина вызова DLL
         cmp     eax,DLL_PROCESS_ATTACH   ;// тест на первое подключение к процессу
         jnz     @noAttach                ;// выход, если не равно..
         invoke  CsrGetProcessId          ;// иначе: EAX = PID процесса CSRSS.EXE
         invoke  OpenProcess,PROCESS_VM_READ,0,eax  ;// пробуем его открыть..
         push    eax               ;// запомнить хэндл для закрытия
         or      eax,eax           ;// проверить на нуль выхлоп функции
         je      @ok               ;// если нуль, значит ошибка и нет отладчика!
         mov     eax,0xDEAD        ;// иначе: EAX слово "DEAD" хексами
        cinvoke  printf,frmt,eax   ;// мессага "Debug DEAD",
         jmp     $                 ;//  ..и виснем внутри EPoint (придумай своё)
@ok:     pop     eax               ;// если отладчика нет, EAX = хэндл csrss.exe
         invoke  CloseHandle,eax   ;// закроем его (контроль счётчика обращений)
        cinvoke  printf,mes0       ;// мессага "DLL удачно загружена!"
@noAttach:                         ;//
         mov     eax,1             ;// Return TRUE загрузчику LDR
         ret                       ;// возвращаем управление..
endp

;//=== Функция проверки пароля =======================
;// снимает с юзерского ввода хэш-сумму,
;// и использует её как ключ для расшифровки блока
;// Аргументы: len  = длина шифруемого блока,
;//            addr = адрес его начала,
;//            pass = указатель на пароль юзера.
;=====================================================
proc  Decrypt len,addr,pass    ;// функция с агрументами
         mov     esi,[pass]    ;// ESI = указатель на пароль
         xor     eax,eax       ;// EAX,ЕВХ = 0
         xor     ebx,ebx       ;//
@01:     lodsb                 ;// AL = очередной байт из ESI
         add     bx,ax         ;// ВХ = хэш сумма
         or      al,al         ;// проверить AL на нуль (конец)
         jnz     @01           ;// повторить, если нет..
                               ;// иначе: BX = хэш сумма юзера
         mov     esi,[addr]    ;// ESI = указатель на шифруемый блок
         mov     edi,esi       ;// EDI = он-же, для перезаписи
         mov     ecx,[len]     ;// ECX = длина блока в байтах
@02:     lodsb                 ;// AL = очередной байт из ESI
         xor     al,bl         ;// ксорим его 1-байтным хэш-ключом юзера
         stosb                 ;// записать проксоренный байт на место!
         loop    @02           ;// повторить по длинне ЕСХ..
         ret                   ;// возврат управления приложению!!!
endp                           ;// ...

;//=== Импорт =======
section '.idata' import data readable
library  msvcrt,'msvcrt.dll',nt,'ntdll.dll',krnl,'kernel32.dll'
import   msvcrt, printf,'printf'
import   nt,   CsrGetProcessId,'CsrGetProcessId'
import   krnl, OpenProcess,'OpenProcess',CloseHandle,'CloseHandle'

;//=== Экспорт ======
section '.edata' export data readable
export   'about.dll', Decrypt,'Decrypt'

;//=== Релоки =======
section '.reloc' fixups data discardable   ;// перемещаемая база DLL

result.png


Под занавес..

Для разминки мозгов, в скрепке предлагаю построенный на этом алгоритме крэкми. Здесь нужен брутом найти пароль, и чтобы он не занимал много времени, длина ключа такая-же 1-байт. Без проблем можно было увеличить его разрядность хоть до 4-х байт, однако сути это не меняет.. просто дольше нужно будет подбирать. Всех с наступающим 0х07E4 !!!
 

Вложения

  • crackmeDLL.zip
    1,6 КБ · Просмотры: 733

Aleks Binakril

Green Team
04.12.2019
38
5
BIT
0
прекрасно, не смотря на все решетки от майкрософта - ассемблер до сих пор рулит хоть и компилируется на процессорах империи Билли Гейтса
 
  • Нравится
Реакции: Marylin

Mikl___

Green Team
01.09.2019
34
52
BIT
128
Aleks Binakril
А в чем проблема? В статье использован fasm, кому-то нравится nasm, gas и т.д. Да и masm, хотя и создан в империи Билли Гейтса достаточно удобная штука...
Marylin,
большое спасибо за статью
good3.gif
 

Hardreversengineer

Green Team
20.08.2019
49
28
BIT
14
Aleks Binakril
А в чем проблема? В статье использован fasm, кому-то нравится nasm, gas и т.д. Да и masm, хотя и создан в империи Билли Гейтса достаточно удобная штука...
Marylin,
большое спасибо за статью Посмотреть вложение 36149
Я не могу вспомнить высказывание Криса Касперски на тему ассемблеров, да и найти не получается. Но лично на ваш взгляд, какой из этих диалектов лучше ?
Кстати мне понравились статьи этого автора с первого взгляда, когда увидел знакомый синтаксис своего любимого FASM.
 

Mikl___

Green Team
01.09.2019
34
52
BIT
128
Я не могу вспомнить высказывание Криса Касперски на тему ассемблеров, да и найти не получается. Но лично на ваш взгляд, какой из этих диалектов лучше ?
Hardreversengineer,
наверное, имеется ввиду статья Криса "гонки на вымирание, девяносто пятые выживают"? Тогда я приведу цитату из этой статьи
Сравнение компиляторов — дохлое дело. Религиозные войны. Фанатизм. Бенчмарки. Объективных критериев оценки ни у кого нет, да и не может быть по определению (что русскому хорошо…) Всегда найдутся условия, на которых ваш компилятор уделывает всех остальных. Комплексные тесты только запутывают дело. Отображаемая ими "среднегодовая" температура не имеет ничего общего ни с тропической жарой, ни с арктическими морозами. Может, человеку целочисленное приложение компилировать надо, а основной вклад в комплексный тест дают плавающие операции. . .
Требования, предъявляемые программистами к компилятору, совсем не те, что у пользователей. Лозунг "время трансляции имеет значение!" отвергается пользовательским сообществом как маразм, не требующий объяснения. В самом деле, какой процент своего времени тратит на перекомпиляцию рядовой линуксоид? А программист? Пользователю глубоко начхать час или два оно будет компилироваться. Главное, чтобы получился хороший машинный код. Все остальное несущественно. Программисты же на первое место выдвигают именно скорость трансляции, а к быстродействию собственной продукции они в общем-то равнодушны (даже если им же на ней и работать!).
IMHO, чем больше знаешь диалектов ассемблера, тем лучше... :) А в статьях Marylin мне нравится новый взгляд на системные вещи и нормальный язык автора, который рассказывает просто о сложных вещах...
 
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!