Анализ keygenme от Ra$cal на базе виртуальной машины

    0. Инфо


    Страница KeygenMe на crackmes.de
    Crackme with simple vm. Key check algorithm is simple, so main target — vm.
    difficult of pcode is growing from start to end. first part seems like emulator, but then it looks like like machine with another logic, registers, commands =)
    Good luck and have fun.
    Difficulty: 4 — Needs special knowledge
    Platform: Windows
    Language: C/C++

    Начнем свое исследование с просмотра кода, который выполняется когда мы нажимаем кнопку “Check” на форме. Так как в основе CrackMe лежит обычное диалоговое окно, то первым делом смотрим в его зарегистрированную оконную функцию.


    Рис 1. Часть оконной функции диалога, отвечающая за обработку WM_COMMAND.

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


    Рис 2. Функция Check вызываемая из оконной функции.

    Смотря на большое число команд NOP (403F3E – 4035A9 = 995h) можно предположить что исходный код алгоритма, который был завиртуален размещался изначально здесь, а в дальнейшем был затерт.
    Функция sub_4017C0 является оберткой над вызовом виртуальной машины, основной цикл которой расположен в функции sub_401890 псевдокод которой показан ниже.

    sub_401890
    Рис 3. Основной цикл виртуальной машины в функции sub_401890.

    Все эти функции внутри switch/case являются реализациями команд.

    Сам виртуализированный код хранится в секции .rvmpc, начиная с адреса 00407000.

    1. Формат команд


    Определение структуры виртуальной машины, определение ее архитектуры, а также формата используемых ею команд — это первая и самая главная задача при девиртуализации кода.
    Данная виртуальная машина является регистровой, для временного хранения переменных используется стек.
    Формат команд у этой виртуальной машины не фиксированной длины и довольно избыточен. У команд всех типов имеется общий заголовок следующего вида:
    Смещение Тип Размер Описание
    +0 word 2 код операции
    +2 dword 4 ID команды
    +6 byte 1 размер аргументов
    +7 dword 4 ID следующей команды
    +Bh dword 4 неизвестно

    Поле “ID команды” содержит уникальное значение для всего байткода. Соответственно для перемещения к следующей команде виртуальная машина берет значение из поля “ID следующей команды” и ищет во всем массиве кода команду, у которой ID совпадет с указанной. После чего используя switch/case от значения поля “код операции” происходит интерпретация каждой из команд.

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

    opcode Действие
    0x00 call
    0x01 call
    0x02 Условный переход
    0x03 push dword
    0x04 sub/add ESP
    0x05 push dword
    0x06 mov [esp], dword
    0x07 jmp ID
    0x08 mov [ebp+?], dword
    0x09 native
    0x0A native
    0x0B -
    0x0C Модификация внутреннего флага dw4052AC
    0x0D -
    0x0E -
    0x0F -
    0x10 mov REGn,dword-reg / mov REGn, dword [reg+X]
    0x11 mov dword [addr], (reg/dword) / mov dword [REGn], (reg/dword)
    0x12 Различные команды пересылок mov
    0x13 Различные команды пересылок mov
    0x14 MOV _32[A], unpack(REGn)
    0x15 MOV REGn, pack(_32[A])
    0x16 XOR _32[A][i], _32[B][j]
    0x17 mov [REGn], reg / mov reg, [REGn]
    0x18 XOR _32[A][c:d], _32[B][e:f]
    0x19 MOV _32[A], 0
    Команды 0x14 – 0x19 работают с дополнительной областью памяти, позволяющей производить побитовые операции на 32-битными числами.

    2. Дизассемблирование кода ВМ


    Теперь когда у нас есть описание формата команд и мы знаем какие функции они выполняют стоит написать дизассемблер для данной виртуальной машины. Команды могут быть совсем не похожи на систему команд ранее известных архитектур, поэтому можно транслировать в понятные Вам мнемоники.

    Исходники дизассемблера с использованием библиотеки BeaEngine (+exe)
    Дизассемблерный листинг команд составил у меня 961 строку.

    3. Девиртуализация


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

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

    Получившийся исходник на x86 asm
    Исходник собирается при помощи FASM.

    Так например пара команд
    MOV REGn, EBP-x
    MOV reg, [REGn]
    превращаются в одну
    MOV reg, [EBP-x]
    

    и команды перемещения в обратную сторону
    MOV REGn, EBP-x
    MOV [REGn], reg
    превращаются в
    MOV [EBP-x], reg

    Больше же всего сокращается конструкция вида:
    MOV REG9, DWORD PTR [ebp-E0]
    MOV eax, DWORD PTR [REG9]
    MOV [REG2], eax
    MOV _32[1], 0
    MOV _32[0], unpack(REG2)
    XOR _32[0][0:7], _32[1][18:1F]
    XOR _32[0][8:F], _32[1][10:17]
    XOR _32[0][10:17], _32[1][8:F]
    XOR _32[0][18:1F], _32[1][0:7]
    MOV REG2, pack(_32[1])
    MOV eax, [REG2]
    MOV REG9, DWORD PTR [ebp-E0]
    MOV DWORD PTR [REG9], eax
    На x86 ассемблере она будет выглядеть так:
    MOV eax, DWORD PTR [ebp-E0]
    BSWAP eax
    MOV DWORD PTR [ebp-E0], eax

    После чего натравил на скомпилированный EXE плагин Hex-Rays и получаю исходник как на картинке ниже:

    декомпилированный код
    Рис 4. Декомпилированный код пересобранный под x86 архитектуру.

    4. Обращение алгоритма


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

    Ключ имеет следующий вид:
    SSSS-11111111HHHHHHHH22222222-???
    , где:
    SSSS – числовое значение в 10тичной системе счисления;
    11111111, 22222222, HHHHHHHH – значения в 16ричной системе счисления;
    ??? – произвольное значение, произвольной длины

    Алгоритм:
    1. Считаем сумму от символов введенного имени
      for ( i = 0; i < nLenName; i++ )
          dwNameSum += name[i]
      
    2. Сравниваем полученное значение с первым блоком ключа;
    3. Считаем хэш от имени компьютера
      for ( j = 0; j < nCompNameLen; j++ ) {
        dwHashCompName ^= j ^ szCompName[j];
        dwHashCompName = __ROL__(dwHashCompName, 3);
      }
      
    4. От секций 11111111, 22222222 и HHHHHHHH получаем из бинарное представление (hexdec)– аналог dwHash1, dwHash2, dwHash3.
    5. Для каждого из значений производим операцию:
      dwHashN = dwHashN xor dwHashCompName xor htonl(dwHashCompName)
    6. Хэш от ключа считается следующим образом:
      dwHashSerial = dwHash3 xor dwHash1 xor htonl(dwHash1) xor dwHash2 xor htonl(dwHash2) xor dwHashCompName xor htonl(dwHashCompName)
    7. Хэш от имени считается по алгоритму:
      for ( idx = 0; idx < nLenName; idx++ ) {
          if ( idx % 2 )
              dwHashName ^= 0x4F620AEC ^ (idx + dwHash1) ^ szName [idx];
          else
              dwHashName ^= 0x4F620AEC ^ (dwHash2 - idx) ^ szName[idx];
          dwHashName = __ROL__(dwHashName, idx);
      }
      

    Все что нужно нам сделать – это сгенерировать случайно 2 значения для блоков 1 и 2. После чего считаем dwHashName и dwHashSerial, принимая dwHash3 равным 0. И останется только посчитать недостающее значение по формуле dwHash3 = dwHashName ^ dwHashSerial

    Ну и конечно же результат всего исследования:



    Скачать EXE и исходники кейгена

    P.S. Яндекс палит как-то прямые линки, поэтому если показывает 404, открывайте ссылки в новой вкладке.

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 5

      +2
      Все идет по кругу.
      Лет 15 назад была серия крякмисов от HardWisdom (NAND) SolarDesigner (NOR ) и некоторых других которые реализовывали всю свою логику в соответствии со своим названием.

      Следующим витком было построение виртуальной машины внутри виртуальной машины (уровень вложенности можно было варьировать).

      При всем этом решали такие задачи не в лоб (анализ логики работы виртуальной машины), а написанием своей виртуальной машины которая эвристическими методами приводила код крякми к читабельному виду.
        +18
        Вопрос автору:
        Какую цель Вы преследовали когда писали эту статью? Какой тип статьи и на кого она рассчитана? Возможно это учебная статья? Возможно вы хотели сказать, что Вы круты и смогли разобраться в работе сложного бинаря?

        Я к тому что, если эта статья учебная, то нужно было показывать «ученику» ход ваших рассуждений и умозаключений, какие выдвигались гипотезы в процессе исследования и почему, затем как они проверялись и какие впервую очередь. То что понятно Вам не обязательно понятно «ученику», если он обладает этими знаниями, то он Вашу статью читать не будет.

        Пример:
        >>Функция sub_4017C0 является оберткой над вызовом виртуальной машины, основной цикл которой расположен в функции sub_401890.
        Сразу же после этих слов идет кусок разобранной виртуалки. Прям вот так утром проснулись, а в голове уже полное понимание как работает оно и реверсить ничего не надо! ;)

        P.S.: Моя критика связана с желанием иметь более серьезные статьи на хабре связанных с реверсингом. Реверсеры — это достойный уважения народ!
          –9
          Автор в своей статье изложил тезисы к решению данного крякми. Подобного рода статьи для получения полного удовольствия от прочтения требуют выполнения следующих условий:
          1) Располагать базовыми навыками реверсинга
          2) Скачать разбираемый кракми, открыть его в своём любимом отладчике / дизассемблере и параллельно с рассуждениями автора постепенно его (крякми) «раскручивать».

          Судя по вашему комментарию у вас нехватка п. (1) и лень делать п. (2). За сим от всей души желаю вам совершенствоваться, бороться с ленью и тогда вы будете вот так вот утром просыпаться, а в голове уже полное понимание как оно работает и реверсить ничего не надо.
            +1
            Может преследовались все сразу?
            Честно говоря меня уже давно просили написать про ВМ статейку, нужен был только пример.

            >>Сразу же после этих слов идет кусок разобранной виртуалки.
            Это и есть почти весь псевдокод функции sub_401890.

            >>Моя критика связана с желанием иметь более серьезные статьи на хабре связанных с реверсингом.
            К сожалению, хабр слишком публичное место для статей по разбору ВМ коммерческих протекторов. Да и в одну статью такое точно не поместится.
              0
              По моему, все понятно, последовательно и логично изложено, имеются исходники и листинти. От «ученика» необходимо только желание разобраться. Хорошая статья.

            Only users with full accounts can post comments. Log in, please.