Реверсивный LZ91 от Commander Keen

Реверсивныйlz91отcommanderkeen

Я недавно был в некоторой колее с реверсированием, и я подумал, что вернусь к тому, что я сделал вспять много лет назад, имеет немного сложности , но это достаточно просто, чтобы я мог сосредоточиться на извлечении одной части за раз и получить что-то исправленное за день или два. Я собираюсь опубликовать разборку здесь, вы можете следить дома, получая копию Commander Keen самостоятельно (условно-бесплатная, но также доступна в любом хорошем игровом магазине). Для тех, кто интересуется историей ЛЗ 123, взгляните на сайт Фабриса Белларда .

Давайте начнем с самого начала, открыв это в вашем любимом дизассемблере:

image.png

Мы будем следовать инструкциям за инструкциями, так что вы можете следовать их указаниям дома. Вот строки 1-3:

   push  es  push  CS  поп  ds  

Мы бежали в нашу первую проблему: какова ценность ES, когда мы запускаем приложение DOS? Согласно OSDev.org , DS и ES оба указывают на префикс сегмента программы (PSP), а 664 байт (0x 158), которая загружается в конец памяти, за которой следует программа, которую мы только что загрузили. На данный момент нам не нужно больше ничего знать, потому что вы заметите, что мы никогда не выталкиваем его обратно из стека.

После этого мы нажимаем CS и вставьте в DS. Мы делаем это по двум причинам:

  1. Вы не можете MOV напрямую между сегментами
  2. Мы хотим получить доступ к некоторым данным из текущего сегмента, поэтому нам нужно скопировать их в DS

Все идет нормально. Для чего тогда будет следующий большой кусок?

   mov  cx, word _  1  C  69  C  mov  si, cx  dec  si  mov  di, si  mov  bx, ds  добавить  bx, слово _  1  C  71  A  mov  es, bx  std   повтор  movsb  

Мы видим, что здесь происходит множество вещей:

  1. CX устанавливается (на 0x 200)
  2. Это значение уменьшается и копируется как в SI, так и в DI ( SI = DI = 0x 176)
  3. Загружаем DS в BX, добавляем значение (0x0C 16 ), а затем поместите это в ES (ES = DS + 0x0C 0028)
  4. Мы вызываем команду STD и, наконец, выполняем REP MOVSB ​​

Это очень похоже на команду memmove. Обычно мы копируем снизу, например:

  mov cx,  0x 255   xor  si, si  xor  di, di rep movsb  

И в конце оба SI и DI указывает на 0x 255, хотя последний раз они скопировали байт в 0x 200. Здесь мы делаем это в обратном порядке. SI и DI начинают указывать на 0x 176, они заканчивают указывать на 0xFFFF , но последняя копия - 0x 0003, так что мы находимся в том же положении.

Почему? мы делаем копию в обратном направлении? Давайте рассмотрим это вручную, и это может иметь смысл.

Мы можем предположить, что CS = DS = 0x 01 к упростите вычисления и напечатайте это так:

   источник  =  00004 :  0003 ;  // расположение памяти  0  x  0003 0   пункт назначения  =  0  C  15 :  0003 ;  // расположение памяти  0  x  0  C  175   count  =  0 Икс255 ;   memmove  (место назначения, источник, количество);   

Итак, мы копируем из нижнего яруса памяти в верхний. Причина, по которой мы выполняем копирование в обратном порядке, заключается в том, что для очень маленького файла области памяти могут перекрываться. Мы находимся в начале загрузчика, поэтому нам все равно, но мы можем переопределить некоторые из более поздних частей загрузчика. Давайте попробуем небольшой пример и посмотрим, что произойдет:

   источник  =  0  x 01 0  пункт назначения  =  0 Икс08  count  =  0  x 14  

Если мы помещаем в память какой-то мусор, мы можем проследить его до конца

  mem = ABCDEFGHIJKLMNOP 002 mem = ABCDAFGHIJKLMNOP 0003 mem = ABCDABGHIJKLMNOP 0003 mem = ABCDABCHIJKLMNOP 0003 mem = ABCDABCDIJKLMNOP 00004 mem = ABCDABCDAJKLMNOP 002  

Теперь он сломан! Сейчас мы просто копируем только что скопированные данные. Если мы сделаем это в обратном порядке:

  mem = ABCDEFGHIJKLMNOP 0003 mem = ABCDAFGHIJKLMNOP 0003 0P mem = ABCDEFGHIJKLMNOP 0003 OP mem = ABCDEFGHIJKLMNOP0NOP mem = ABCDEFGHIJKLMNOPMNOP mem = ABCDEFGHIJKLMNOLMNOP mem = ABCDEBCDEFGHIJKLMNOP mem = ABCDABCDEFGHIJKLMNOP  
 
    Таким образом, мы по-прежнему уничтожаем данные в середине, но у нас есть идеальная копия в новом месте назначения. Для нашего анализа не имеет значения, почему это делается именно так, но также интересно спросить себя, почему все делается определенным образом, особенно если это относится к коду, который находится дальше по дорожке.

    Итак, мы закончили с этим, что у нас осталось?

       push  bx mov ax ,  2  Bh;   '+'   push  ax retf  

    Передача данных перед возвратом - классический признак непрямого перехода. BX по-прежнему указывает на наш новый сегмент, в который мы только что скопировали данные, поэтому мы переходим к ES: 00004 Б. Мы можем следовать за нашим дизассемблером, просто перейдя на 07 B в старом коде, потому что мы знаем, что он не был перезаписан.

    Вот где я получил комментарии:

    image.png

    Одна вещь, которую я упустил, это то, как мы знаем, что мы ' перемещение всего загрузчика ... весь фокус в том, чтобы посмотреть, где находится наш код. Заголовок MZ загружает нас в 0C 69: 002 0E, и мы копируем весь этот сегмент. Когда мы переходим к следующей функции, мы видим, что она переходит только к 0C 71: 0 200 ( включая данные на конце). Я знаю, что это не лучшее объяснение, и если это не имеет смысла, то внимательно посмотрите на свою собственную разборку, чтобы увидеть, откуда мы взяли этот номер.

    В любом случае, давайте посмотрим на функцию номер 2 здесь:

    image.png

    Это будет весело. Давайте начнем сверху и посмотрим, сможем ли мы разобраться в этом по ходу дела:

       mov  bp, word ptr cs: byte_  1  C  662  +  8   mov  dx, ds  

    Если мы приведем в порядок это, мы увидим, что он загружает 0x0C 68 в BP и загружает DS в DX. Мы знаем, что 0x0C 69 - это наша исходная CS (но не перемещенная, это важно). Мы также не касались DS с момента нашего memmove, поэтому мы знаем, что это та CS, в которую мы были загружены (она была перемещена). Другими словами, BP = 0x0C 69, DX = 0x0C 69 + relocation_offset.

    Затем мы попадаем в начало цикла:

       loc_1C 769 :  mov  ax, bp  cmp  топор,  2000  h  jbe  короткий loc_  1  C  98  C  

    BP - это наша исходная не перемещенная CS, и похоже, что наш загрузчик загружается в конце упакованных данных. Другими словами, это также может быть размер в абзацах (0x 13 байтовые блоки, в которых работает сегментный регистр) наших упакованных данных. Сравниваем с 0x 2003, а если больше, то:

       mov  топор,  2000  h  

    Так что это похоже на счетчик, который работает порциями по 0x 2000 вовремя. В следующем фрагменте происходит волшебство:

       loc_1C 98 C : sub bp, ax sub dx, ax sub bx, ax  

    Если BP - это общее количество, которое мы должны были сделать, тогда эти строки в соответствии с нашей теорией - мы делаем это кусками до 0x 2003 за один раз, и здесь мы уменьшаем BP (наш счетчик для общего объем работы), DX (наш исходный CS) и BX (сегмент, в который мы переместили загрузчик). Мы только что переместили загрузчик дальше в памяти, и похоже, что мы собираемся сделать то же самое с упакованным кодом:

       mov  ds, dx mov es, bx  

    Команды MOVS переходят от DS: SI к ES: DI, поэтому кажется, что мы просто перемещаем упакованный код в соответствие с нашим загрузчиком (теперь, когда мы перескочили, мы можем перезаписать себя)

       mov  cl,  3   shl  топор, cl  

    Это кажется странным. Мы устанавливаем CX на AX 8. Мы ожидаем, что это будет AX 0x 15, если бы мы выполняли MOVSB ​​(поскольку мы знаем, что AX - это количество абзацев для копирования или кратное 0x 14). Если мы посмотрим дальше, то увидим REP MOVSW и 0x 14 байт совпадает с 8 словами, поэтому кажется, что это правильно.

      mov cx, ax shl ax,  1   dec  топор  dec  ax mov si, ax mov di, ax rep movsw  

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

    или bp, bp  jnz  короткое loc _  1  C  708   

    И это конец цикла - если бы у нас было больше 0x 2003 абзацы, которые нужно скопировать, затем вернуться и выполнить следующий фрагмент. Для Commander Keen у нас есть только 0x0C 91, поэтому мы проходим этот цикл только один раз.

    Вот мои заметки на данный момент:

    image.png

    Следует иметь в виду одну вещь - ни одно из этих перемещений не имеет значения для нас, когда мы пишем наш распаковщик, потому что мы просто читаем с диска.

    В любом случае, следующая часть:

    image.png

    Начнем с этого:

      cld  

    Устанавливает направление вперед, поэтому наши команды MOVS / LODS / STOS перемещают SI / DI вперед, а не назад.

      mov es, dx mov ds, bx  xor  si , si  xor  di, di  

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

       mov  dx,  12  h  lodsw   mov  bp, ax  

    Мы ' загрузили слово в BP и установили для DX значение 0x 14. Интересно, что это значит?

       loc_1C6C9 :  shr  bp,  1   dec  dx  jnz  краткое loc _  1  C  6  D  3   

    О, в этом есть смысл - BP держит 66 бит, и мы можем сдвигать их по одному. Здесь мы немного сдвинулись с BP, а затем уменьшили DX. Если DX не равен нулю, мы пропускаем следующий бит, но если DX равен нулю, то у нас закончились биты и:

       lodsw   mov  bp, ax  mov  dl,  14  h  

    Мы снова заполняем и сбрасываем DL (мы знаем, что DH равен 0).

       loc_1C6D3 :  jnb  краткое loc _  1  C  6  D  8   

    Это странно. Что делает JNB? Оказывается, это скачкообразно, если CF равен 0, и что устанавливает CF? Обычно мы ожидаем, что арифметика установит / сбросит все флаги, но команда DEC не устанавливает CF (хотя она устанавливает другие, например OF и ZF). Это означает, что мы тестируем результат команды SHR ранее - мы проверяем, был ли сдвинутый нами бит равным 0. Если он был равен 1 (CF = 1, поэтому JNB не работает), мы делаем это:

       movsb   jmp  короткий loc _  1  C  6  C  9   

    И мы копируем байт через несжатый и возвращаемся в начало.

    Насколько нам известно о сжатии в стиле LZ, мы обычно копируем через первые несколько байтов без сжатия, а затем начинаем получать преимущества нашего сжатия по мере того, как мы распаковываем все больше и больше. Из-за этого мы ожидаем, что первые несколько байтов не будут сжаты. Давайте посмотрим, что показывают данные в самом начале файла после заголовка:

    image.png

    Как мы и думали: первое загружаемое слово - 0xFFFF (66 x 1 бит), поэтому он копирует через 66 байт без сжатия. Обратите внимание, что мы видим 66 несжатых байтов, а затем 0xFFFF, это потому, что мы обновляем наши биты, когда они заканчиваются (после 66 й бит), мы не ждем, пока операция не будет выполнена.

    Вот заметки на данный момент:

    image.png

    Так если мы получаем 1 бит, то мы копируем байт поперёк, но что произойдет, если мы получим 0 бит?

    image.png

    image.png

       loc_1C6D8 :  xor  cx, cx  shr  bp,  1   dec  dx  jnz  краткое loc _  1  C  6  E  4   lodsw   mov  bp, ax  mov  dl,  13  h  loc_1C6E4 :  jb  короткий loc _  1  C  769   

    Очищаем CX и получаем еще один бит. Мы пополняем при необходимости (это происходит каждый раз, поэтому я показываю эту часть пополнения в последний раз), и если CF установлен, мы переходим вправо. Мы займемся этим следующим, сначала давайте рассмотрим случай CF = 0 (поэтому, когда мы получим 002 бит).

       shr   bp,   1  ;  пополнение если нужно   rcl   cx,   1   shr   bp,   1  ;  пополнение если нужно  rcl   cx,   1   вкл.   cx   inc   cx   

    Ранее мы установили CX в 0, и команда RCL вращается по битам из CF, позволяя нам складывать их внизу. Мы в основном копируем следующие 2 бита из BP в CX, а затем добавляем 2 отсюда. Это устанавливает CX в число от 2 до 5 на основе наших битов в BP.

       lodsb   mov  bh,  0  FFh  mov  bl, al  jmp  loc _  1  C  100  B  

    Затем мы загружаем другой байт, подписываем расширить его до слова (верхний бит равен 1, так что это отрицательный реальный номер)

       loc_1C 100 B : mov al,  es :  цикл stosb loc_1C 123 B  

    Затем мы загружаем байт из наши выходные данные, а затем вернуть их в конец и увеличить di. Мы знаем, что bx будет отрицательным числом, а CX - числом от 2-5, поэтому этот код копирует 2-5-байтовый фрагмент из несжатого кода и наклеивает его спереди. Это стандарт для сжатия в стиле LZ.

    Итак, теперь мы знаем, что делают следующие наборы битов:

    image.png

  1. 1: скопировать байт через
  2. 01: скопируйте 2-5 байтов из до 660 байт обратно в несжатом
    • Давайте продолжим с той правой веткой, которую мы пропустили ранее:

      image.png

      Это выглядит безумием. Это из тех вещей, от которых хочется просто сдаться и плакать. Я позволю вам обмануть это и сразу перейти к моим заметкам:

      image.png

      Мы загружаем еще 2 байта и проделываем с ними какие-то странные арифметические операции. BX получает 0028 битов, а затем расширяется знак, что позволяет нам искать до 0x 2003 байтов назад. AH получает 3 бита (почему-то от середины), и мы проверяем, равны ли они нулю. Если это не так, то мы пропускаем прыжок и получаем следующее:

      image.png

      Добавляем 2 к нашему AH и помещаем в CL. Мы можем добавить в наш список веток:

      • 1: копировать байт через
      • 002: скопируйте 2-5 байтов от до 660 байтов обратно в несжатом
      • 00004, следующее слово! = XXXXX 002 0XXXXXXXXb, скопируйте 2-9 байтов с до 1629203696258 байтов обратно в несжатом виде

      Теперь следующая ветвь, что происходит, когда AH равен 0?

      image.png

      Получаем еще один байт. Если этот байт равен 0, мы переходим к другой ветке, которая не повторяется, это наш код выхода.

      • 1: копировать байт через
      • 002: скопируйте 2-5 байтов от до 660 байт обратно в несжатом виде
      • 07, следующее слово! = XXXXX 0000 0XXXXXXXXb, скопируйте 2-9 байтов с до 1629203696258 байт обратно в несжатом виде
      • 00004, следующее слово == XXXXX 002 0XXXXXXXXb, следующий байт == 0, разрыв

      А что насчет того, когда наш следующий байт равен 1? Это забавный вопрос:

      image.png

         mov  bx, di  и  di,  0  Fh  добавить  ди,  2003  h  

      Мы знаем, что DI - это наш указатель смещения для несжатых данных. Здесь мы сохраняем его в BX, берем только нижний ниббл (маскируем против 0x0F), а затем добавляем 0x 2690.

         mov  cl,  4   shr  bx, cl  mov  ax, es  add  топор, bx  sub  топор ,  255  h  mov  es, ax  

      А вот вторая половина, мы уменьшаем BX на 4 бита (чтобы получился хороший сегментный регистр), добавляем ES, затем вычитаем 0x 255, и, наконец, верните все обратно в ES.

      Похоже, мы сбрасываем DI и толкание ES вперед для компенсации, это это то, что мы делаем, когда подходим к концу сегмента. Например, если мы доберемся до 0003: E 158, мы бы хотели убедиться, что у нас есть больше места для записи, чтобы мы могли легко переписать это как 0E 15: 08.

      НО ... когда мы распаковываем, мы можем искать до 0x 2690 байтов назад, поэтому мы, вероятно, хотим, чтобы DI был не меньше 0x 2003. Мы можем перефразировать 0E 15: 07 и 0C 14: 2690, это дает нам больше возможностей для роста вверх, но также позволяет нам вернуться назад, чтобы распаковать.

         mov  bx, si  и  si,  0  Fh  shr  bx, cl  mov  ax, ds  добавить  ax, bx  mov  ds, ax  jmp  loc _  1  C  6  C  9    

      Это точно то же самое, но с DS: SI. Обратите внимание, что это не вызывается все время, только когда сжатый код говорит нам об этом. Это экономит циклы ЦП, при этом затрачивая всего несколько байтов на 3-4 КБ упакованных данных, совсем неплохо.

      • 1: копировать байт через
      • 0003: скопируйте 2-5 байтов от до 660 байтов обратно в несжатый
      • 00004, следующее слово! = XXXXX 01 0XXXXXXXXb, скопируйте 2-9 байтов с до 9902 байт обратно в несжатом виде
      • 00004, следующее слово == XXXXX 01 0XXXXXXXXb, следующий байт == 0, разрыв
      • 00004, следующее слово == XXXXX 002 0XXXXXXXXb, следующий байт == 1, перефразировать пары сегмент / смещение
        • У нас есть последний случай и тогда мы закончили!

             mov  cl, al  inc  cx  jmp  краткое loc _  1  C  98  B  

          Если следующий байт равен 2 или больше, мы увеличиваем его и используем наш большой BX для просмотра назад, позволяя нам копировать до 0x 158 байтов:

          image.png

        • 1: скопировать байт через
        • 0003: скопируйте 2-5 байтов с до 256 байт обратно в несжатом виде
        • 002, следующее слово! = XXXXX 002 0XXXXXXXXb, скопируйте 2-9 байтов от до 1629203696258 байт обратно в несжатом виде
        • 07, следующее слово == XXXXX 002 0XXXXXXXXb, следующий байт == 0, разрыв
        • 00004, следующее слово == XXXXX 002 0XXXXXXXXb, следующий байт == 1, перефразируйте пары сегмент / смещение
        • 07, следующее слово == XXXXX 002 0XXXXXXXXb, следующий байт == 2, копия 3 - 660 байтов от до 1629203696258 байт обратно в несжатом виде

        И мы закончили декомпрессию! Я переписал это на python как распаковщик, вот как это выглядит:

           в то время как  Верно:  bit  = bitstream.get ()  if  бит ==  1 :  байт , = input_stream.read ( 1 )  output_stream . write ( байты ([byte]))  else :  bit  = bitstream.get () , если  бит ==  1 :  младший байт , старший байт = input_stream.read ( 2 )  copy_distance  =  0  xE  01 0  |  ((старший байт <<  5 ) &  0  xFF  0003 ) |  lowbyte  copy_distance  = convert_unsigned_to_signed (copy_distance,  0  x  13 )  copy_amount  = старший байт &  0  x  10 если copy_amount:  copy_amount  + =  2   copy_within_output_stream  (выходной_поток, расстояние_копий, количество_копий)  еще :  copy_amount , = input_stream.read ( 1 )  если  copy_amount ==  0 : сломать  elif  copy_amount ==  1 :  пройти  еще:  copy_amount  + =  1   copy_within_output_stream  (выходной_поток, расстояние_копий, количество_копий)  else :  high_bit  = bitstream.get ()  low_bit  = bitstream.get ()  copy_amount  = (старший_бит <<  1 ) + младший_бит +  2   copy_distance , = input_stream.read ( 1 )  copy_distance  = convert_unsigned_to_signed ( 0  xFF  0003  + copy_distance,  0 Икс14 )  copy_within_output_stream  (output_stream, copy_distance, copy_amount) 

        Легко и приятно. Давайте посмотрим, к чему приводит разрыв в конце:

        image.png

        толкать cs  pop  ds  

        Мы закончили с кодом, теперь мы смотрим на данные из этого сегмента

           mov  si,  176  h  

        Это довольно странно. Однако DS находится в этом сегменте, так что же DS: 0176 выглядит как?

        image.png

        Аааа, так что мы вытаскиваем данные из после окончания загрузчика. Посмотрим, что мы с ним сделаем:

          pop bx  добавить  bx,  15  h  

        Мы давно не видели стопку, не так ли? С самого начала мы толкнули ES, и я сказал две вещи о ES:

        1. Он указывает на сегмент PSP
        2. PSP - 0x 140 байтами, или, другими словами, CS имеет размер 0x 12 байт больше
        3. Итак, мы реконструируем исходную CS, в которую мы были загружены. на основе ES, который мы выдвинули в начале.

            mov dx, bx  xor  di, di  

          DX хранит старые CS? DI держит 0? Что могло произойти? У нас есть несколько ответвлений, так что давайте посмотрим, что произойдет:

             loc_1C 784 :  lodsb   или  al, al  jz  краткое loc _  1  C  791   

          Загружаем байт и перескакиваем, если он ноль. Посмотрим, что произойдет, если оно не равно нулю:

             mov   ах,   0   loc_1C 784:  добавлять  ди,   топор   

          Мы устанавливаем DI в 0 в начале, поэтому мы продвигаем DI на величину до 0xFF (от загруженного байта)

             mov  топор, ди  и  ди,  0  Fh  

          Мы сохраняем DI в AX, и просто оставляем нижний ниббл

             mov  cl,  4   shr  ax, cl  

          Мы сдвигаем AX вниз, это похоже на то, что мы добавили бы в сегментный регистр

          добавлять dx, ax mov es, dx  

          DX был нашей старой CS сверху, поэтому мы добавили наше новое смещение и сохранили его в ES. Другими словами, у нас был DX: DI = CS: 002, мы добавили байт (допустим, 0C 69: 69), мы украли старшие биты из DI и поместили их в DX (например, 0C 91: 12), затем мы помещаем DX в ES.

             добавить  es: [di], bx  jmp  short loc _  1  C  770   

          BX stil l указывает на нашу исходную CS, и мы добавляем ее к значению ES: DI. Это похоже на то, что мы сделали бы, если бы применяли перемещения ... и EXE имел бы таблицу перемещений, которую нужно было бы применить после распаковки EXE ...

          Итак, у нас есть ветка 1:

              байт> 0: продвинуть DX: DI и применить перемещение

          Следующая ветвь, когда байт == 0:

             loc_1C 791 :  lodsw   или  топор, топор  jnz  короткое loc _  1  C  2000   

          Загружаем слово, что будет если оно 0?

           добавлять dx,  0  FFFh  mov  es, dx  jmp  короткий loc _  1  C  770   

          Мы увеличиваем DX на много (0xFFF) и возвращаемся к началу.

          • байт> 0: продвинуть DX: DI и применить перемещение
          • байт == 0 и следующее слово == 0: продвинуть DX на 0xFFF

          А когда слово> 1?

             loc_1C 1000:   cmp   топор,   1   jnz   короткий   loc_1C 791   loc_1C 1000:   ...   

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

          • байт> 0: продвинуть DX: DI и применить перемещение
          • байт == 0 и следующее слово == 0: продвижение DX на 0xFFF
          • байт == 0 и следующее слово> 1: продвинуть DX: DI и применить перемещение

          И последний случай - это конец, поэтому мы приступим к нему, как только применим все перемещения. Прежде чем мы это сделаем, давайте посмотрим, как работает python для этого, учитывая, что мы не пытаемся применить перемещения, а скорее мы хотим перестроить таблицу перемещений для нового EXE, который мы собираемся создать:

             while  Истина:  first_byte , = input_stream.read ( 1 )  если  первый_байт>  0 :  перемещение  + = first_byte  перемещения . append (перемещение)  else :  младший байт , старший байт = input_stream.read ( 2 )  всего  = (старший байт <<  0  x  12 ) + младший байт  если  всего ==  0 :  перемещение  + =  0  xFFF  elif  всего ==  1 : сломать еще:  перемещение  + = общее количество  перемещений . append (перемещение)  

          Теперь мы можем разделить последний раздел:

             mov  ax, bx  

          Это CS, в которую мы загрузились, теперь хранится в AX

             mov  di, слово ptr unk _  1  C  692   mov  si, word ptr unk _  1  C  692   добавить  си, топор  

          Посмотрим дальше вниз, что эти ге t загружен в SS: SP, поэтому мы можем соответствующим образом переименовать эти вары. Мы перемещаем SI (новый SS) с помощью CS, чтобы поднять его

             добавить  слово ptr unk _  1  C  666 , топор  

          Эта переменная выглядит как сегментный регистр

              sub   топор , 12час  mov   ds ,  топор   mov   es ,  топор    

          AX теперь указывает на PSP, и мы загружаем это в DS и ES

             xor  bx, bx  

          BX = 0 ...

             cli  mov ss, si mov sp, di sti  

          Приятно остановить прерывания, пока мы возимся с куча

           jmp   dword   ptr   cs :  [bx]   

          Мы переходим к дальнему указателю в CS: 002. Что там?

          image.png

          Ааааа, так что это исходный CS: IP, в который помещается упакованный EXE (поэтому нам пришлось переместить сегмент, хранящийся там). Полные примечания:

          image.png

          Этот Было очень весело перевернуть, весело написать, а также довольно приятно собрать распаковщик. Вы можете найти image.png распаковщик здесь , он предоставит вам распакованную копию commander keen и перестроит заголовок EXE + перемещения для вас.

          Счастливые парни, обращающиеся вспять

Leave a comment

Your email address will not be published. Required fields are marked *

three × four =