пятница, 18 июля 2025 г.

Байтитура Zip-файла

В старые добрые времена MS-DOS, когда hardware были большими, а software маленькими, были таланты (без всяких ковычек!) которые по подобному текстовому дампу говорили:

здесь открывается файл и что-то записывается в конец.
я таких талантов не имел, поэтому добавил в hiew дизассемблер.

Смотреть на дампы всяких файлов данных тоже было интересно но хотелось лучше видеть, что же там за конкретные данные внутри. Для этого я в те ламповые времена написал StructLook или сокращенно STL - смотрелку различных структурированных файлов.  (Кто ж знал, что английских букв мало и что MS так назовет свою библиотеку)

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

Например, вот так описывались компоненты Zip-файла:

--- 8< ---
/Zip
ZIPSIG:  w    1           Signature(PK)
#if zipsig != "KP"

:ERROR!!! Invalid signature PK

#else

ZIPSUBSIG:  w    'zipsubsig  directory type

#if( zipsubsig == 0x0403 )

ZIPVER:     w    1           Version needed to extract
            t16  `zipflags   General purpose bit flag
ZIPMTHD:    u16  'zipcompres Compression method
            td   1           Last mod file time (MS-DOS)
ZIPCRC:     d    1           CRC-32
ZIPSIZE:    u32  1           Compressed size
ZIPUNCMP:   u32  1           Uncompressed size
ZIPFNLN:    w    1           Filename length
ZIPXTRALN:  w    1           Extra field length
ZIPNAME:    c    ZIPFNLN     filename
ZIPXTRA:    c    ZIPXTRALN   extra field
            +    ZIPSIZE

#elseif( zipsubsig == 0x0201 )

ZIPCVER:    i8   1           Version made by
ZIPCOS:     u8   'ziphost    Host operating system
ZIPCVXT:    i8   1           Version needed to extract
ZIPCEXOS:   i8   1           O/S of version needed for extraction
            t16  `ZipFlags   General purpose bit flag
ZIPCMTHD:   u16  'zipcompres Compression method
            td   1           Last mod file time (MS-DOS)
ZIPCCRC:    d    1           CRC-32
....
--- 8< ---


И вот так StruсtLook показывал Zip-файл по этому описанию:

Время шло-бежало-летело.

И вот через 30 лет я написал Байтитуру (Bytitura, от Byte(en)+Partitura(it)).
Точнее, она была написана несколько лет назад только для личного использования, отчасти потому что язык описания от StructLook никак не добавлялся, отчасти потому что это была платформа для тренировок со всякими GUI и потоками и поэтому время от времени серьезно переписывалась.
Файлы описания были только как внешние DLL.
Потом смысл своего описателя полностью отпал при наличии той же Lua, и которую я наконец-то добавил в уже коммерческий продукт.
А в этом году добавились .sture-файлы - это текстовые файлы со стандартными С-шными структурами.
И теперь эта связка Lua + .sture-файл помогает достаточно просто описать то что надо увидеть, без расписывания каждого адреса и каждого байта.


И сейчас покажу как это делается на примере все того же Zip-файла.
Он выбран потому что простой, все его знают и внутри него есть разные части.

Начнем со .sture-файла.
В нем записаны С-структуры, которые понадобятся для разбора целевого файла. Имя .sture-файла совпадает с именем скрипта и подхватывается автоматически при выборе целевого файла в диалоге открытия. Один и тот же .sture-файл используется как для парзеров в .lua так и для .btt32

Байтитура знает только стандартные типы:
(signed/unsigned) char/short/int/long/long long  для десятичного представления
(signed/unsigned) BYTE/WORD/DWORD/QWORD  для шестнадцатиричного
float/double  для вещественного
WCHAR для юникода

есть два нюанса (куда же без них!)
char без указания signed/unsigned представляет текст, в т.ч. отдельный символ
BYTE без signed/unsigned отображается как дамп

типы можно переопределять через стандартный typedef, см (1)

в размере массива может быть имя целой переменой из этой же структуры, см (2)

--- 8< ---
typedef unsigned short ushort;                      (1)
typedef unsigned long  ulong;                       (1)

typedef struct{
  DWORD        marker; // если будет комментарий то отобразиться именно он
  ushort       version_needed_to_extract;
  WORD         general_purpose_bit_flag;
  WORD         compression_method;
  WORD         last_mod_file_time;
  WORD         last_mod_file_date;
  DWORD        crc_32;
  ulong        compressed_size;                     (2a)
  ulong        uncompressed_size;
  ushort       file_name_length;                    (2b)
  ushort       extra_field_length;                  (2c)
  char         file_name[ file_name_length ];       (2b)
  BYTE         extra_field[ extra_field_length ];   (2c)
  BYTE         compressed_data[ compressed_size ];  (2a)
  }ZIP_LOCAL;
 ....
--- 8< ---

Теперь к собственно Lua-скрипту.

Минимальный скрипт состоит из трех обязательных функций:

--- 8< ---
-- минимальный скрипт который загрузится
-- но не знает ни про какие файлы

BTT_API_VERSION = 130 -- следите за актуальностью !

-- возвращает версию API которой пользуется
function  ApiVersionUsed()
  return  BTT_API_VERSION
end

-- отвечает хосту знает ли этот файл
function  IKnowThisFile( filename )
  return -1
end

-- собственно разборщик файла
function  ParseFile( file_index, filename, base_address, codesize )
  return -1
end
--- 8< ---

Но поскольку мы хотим что-то полезное от скрипта, то придется открывать файл, читать двойное слово и проверять на совпадение с маркером.


--- 8< ---
-- константы из bytitura.h
BTT_API_VERSION            = 130  -- следите за актуальностью !
decode_engine_none         = 0
decode_engine_ia32         = 1
ascii_cp_ansi              = 0
ascii_cp_oem               = 1
ascii_cp_utf8              = 2

-- zip константы
ZIP_MARKER_CENTRAL         = 0x02014b50
ZIP_MARKER_LOCAL           = 0x04034b50
ZIP_MARKER_CENTRALEND      = 0x06054b50
ZIP_MARKER_DATADESCRIPTOR  = 0x08074b50

function  IKnowThisFile( filename )

-- Имя файла передается в utf-8.

  local  file = io.open( filename, "rb" )
  local  sign = ReadDword( file, 0 )
  file:close()

  if sign ~= ZIP_MARKER_LOCAL then
    return -1
  end

 return  0,                  -- базовый адрес
         0,                  -- это данные    
         decode_engine_none, -- кода нет
         0                   -- файловые флаги, например может ли пользователь менять базовый адрес
end
--- 8< ---


В Lua прочитать двойное слово оказалось еще тем квестом,

простой вариант

local dword = 12345
dword = file:read( 4 )

 

конечно же не работал

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

--- 8< ---
function  ReadDword( file, address )
  file:seek( "set", address )
  local  byte0 = file:read( 1 )
  local  byte1 = file:read( 1 )
  local  byte2 = file:read( 1 )
  local  byte3 = file:read( 1 )
  if byte3 == nil
    then return nil
  end
  local  dword = (string.byte(byte3)<<24)
               + (string.byte(byte2)<<16)
               + (string.byte(byte1)<<8)
               +  string.byte(byte0)
  return dword
end
--- 8< ---


хотя документация и говорила что-то про диапазон подстроки

string.byte (s [, i [, j]])
Returns the internal numerical codes of the characters s[i], s[i+1], ···, s[j].
The default value for i is 1; the default value for j is i.


но это совсем не про что хотелось бы

local text  = file:read(4)
local dword = string.byte( text, 1, 4 )


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

Буквально рыдал когда представлял с какой скоростью в интерпретаторе скриптового языка будет читаться каждое значение.

Ну да ладно, написал и оно даже работает как надо, теперь собственно к разбору Zip-файла.


--- 8< ---
function  ParseFile( file_index, filename, base_address, codesize )
-- file_index идентифицирует файл с которым работает скрипт
-- все вызовы к хосту проходят с ним

-- готовой функции в Lua нет, пишем свою
  local  filesize = Filesize( filename )
                                                           
-- сначала требуется определить сегмент(-ы) файла
-- будет один сегмент данных на весь файл
  BttFileAddDataSegment( file_index,
                         0,           -- виртуальный адрес
                         0,           -- файловый адрес
                         filesize,    -- размер в файле
                         0 )          -- виртуальный размер = размеру в файле

-- установим нужную кодировку для имен файлов
  BttFileStringAsciizCP( file_index, ascii_cp_oem )

-- чтобы не скучно ждать интерпретатора будем показывать хотя бы количество файлов
  local nfiles  = 0                                        

  local address = base_address
-- установим начальный адрес для разбора файла
  BttFileSetRunAddress( file_index, address )

  local  file = io.open( filename, "rb" )

  local  result = true
-- цикл пока result истинный
  while  result  do
-- читаем маркеры и смотрим ...
    local  marker = ReadDword( file, address )             

-- конец файла - конец цикла
    if     marker == nil                        then result = false

-- для соответствующих маркеров вызывается соответствующая структра из .sture-файла

    elseif marker == ZIP_MARKER_LOCAL           then result = BttFileRunStruct(  file_index, "ZIP_LOCAL"          )

                                                              nfiles = nfiles + 1
-- вызов функции для отображения количества насчитанных файлов
                                                              BttFileParseStage( file_index, string.format( "%i files", nfiles ) )

    elseif marker == ZIP_MARKER_DATADESCRIPTOR  then result = BttFileRunStruct(  file_index, "ZIP_DATADESCRIPTOR" )

    elseif marker == ZIP_MARKER_CENTRAL         then result = BttFileRunStruct(  file_index, "ZIP_CENTRAL"        )

    elseif marker == ZIP_MARKER_CENTRALEND      then reuslt = BttFileRunStruct(  file_index, "ZIP_CENTRALEND"     )

-- если прилетел незнакомый маркер, расскажем об этом в log-окне
    else                                             result = false
                                                              BttFileLog(        file_index, string.format( "Unknow marker %08X at address %X", marker, address ) )
    end

-- получим следующий адрес после применения структуры
    address = BttFileGetRunAddress( file_index )
-- цикл OFF
  end                                                      

  file:close()

-- в любом случае сообщаем что разбор прошел успешно
  return  1                                                

end
--- 8< ---


по сути, все делают вызовы BttFileRunStruct,  
BttFileSet/GetRunAddress обвязка,
а BttFileStringAsciizCP/BttFileParseStage/BttFileLog для рюшек

вызов этого скрипта покажет байтитуру Zip-файла,
т.е. куда относится каждый байт файла

празднолюбопытствующие могут скачать набор Lua + .sture для Zip-файла здесь,
естественно ни в каких Demo оно работать не будет.

В следующий раз расскажу об уникальной возможнности, и это не дизассемблер.

воскресенье, 29 июня 2025 г.

Hiewix переименован в Bytitura, Demo 2506

прежнее название для разборщика файлов было выбрано не совсем удачным,

очень многим показалось что это продолжение hiew в виндовом варианте.

новое - Bytitura, от слияния byte(анг)+partitura(ит)

страница и демо обновлены, теперь есть полный пример на Lua

 

Историческая справка:

Hiew дважды менял название, сначала он был ViHE (от View-HexEditor), потом HiVE



пятница, 20 июня 2025 г.

версия 8.87

 - fix: размер буфера отмены редактирования
 - Goto(F5) в String
 - Refer(F6) в hex mode для любых файлов
 - открываются файлы вида aux.c

воскресенье, 30 марта 2025 г.

Расшифровать за 100 секунд

Молодые пользователи спрашивают авторские примеры использования hiew.
Что ж, их есть у меня и сегодня будет пример использования Crypt = встроенной возможности hiew для (де)шифрования. 

Сейчас сам удивился, но до сих пор ничего про Crypt не писал. 
Crypt  впервые появился в ноябре 1993 года в версии 4.20 и был 16-битным. А когда в версии 6.00 был удален, столько недовольства и возмущений от пользователей не было ни до ни после. И в 6.01 вернулся.


Напримером будет HiEW Loader 1.1 (кто не знает - легко найдет)
В архиве он тоже называется hiew32.exe, других букв уже не осталось. Что ж, переименуем в loader.exe

Начинаем стандартно:

Hiew32.exe loader.exe
enter enter ;переход в режим кода
F8               ;просмотр заголовка PE

на широких экранах сразу видно что в последней строке кнопка F11 Tls активна,
(если в этом месте встретилось незнакомое слово из трех букв, самое время пойти почитать про Tls callback),  а значит есть еще исполняемый код до передачи управления в точку входа,

но для начала
F5         ;переход на точку входа
и видим мусор вместо кода.
(не знаю зачем все это пошифровано, да еще и через Tls, да еще и с проверкой "чтоб только раз наверняка", впрочем, и в самом коде загрузчика много таких "зачем?", но сейчас не об этом)

возвращаемся в заголовок PE
F8          ;просмотр заголовка PE
и смотрим чего там в Tls callback
F11         ;переход на этот самый Tls callback

разумеется, рассшифровка кода на точке входа


в регистрах esi/edi начало зашифрованного блока = точки входа
в ecx длина зашифрованного блока = 3c9

сам алгоритм шифрования - простой xor по одному байту и простой модификацией маски шифрования на каждом шаге и один-в-один переносится в хьюшный Crypt

вернемся на точку входа
3            ;переход на начало блока расшифровки
enter     ;возвращаем режим кода
F3         ;режим редактирования (в hiew всегда редактировался только один экран за раз)
F7         ;вызов Crypt

и переписываем сюда код расшифровки

 

расшифровка идет по одному байту, поэтому размер стоит Byte (при необходимости меняется через F2. А F3 меняет направление Forward/Backward)
последняя команда loop 2 завершает цикл, возвращаясь ко второй строке, в первой была инициализация

F7        ;вышли из режима редактирования

и чтобы проверить
F7       ; расшифровываем по одному байту за раз
(да-да, и зашли через F7 и вышли через F7 и расшифровка через F7)

F7 F7 .... F7 F7
и мусор шаг за шагом на глазах превращается в корректый код



ESC            ;отменяем все изменения, расшифруем все за раз
enter enter ;в режим дампа, там проще блок отмечать
*                  ;отмечаем начало блока
F5 +3с8      ;добавляем к текущему адресу размер зашифрованного блока,
                   ;поскольку один байт уже отмечен будет на единицу меньше



enter  ;переход на конец блока
*         ;отмечаем конец блока



Alt-F3    ;Crypt для выделенного блока
было немного тренировки, crypt ip (отмечен стрелочкой) стал после полного цикла на строку 2, вернем на инициализацию:
       ;crypt ip на строку назад
F5     ;и чтоб красиво, очистить все регистры заодно
F7     ;расшифровать блок



по проявившимуся тексту сразу видно, что все рашифровалось корректно

[          ;переход на начало блока
*         ;убрать пометку блока
enter  ;режим кода

и вот он, оригинальный код на точке входа



Tls не нужен, убираем

F8 F10   ;просмотр PE Directory
курсором вниз до TLS и
F7       ;очистить



Теперь имеем загрузчик без всяких шифрований и ненужных Tls.

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

Подробности о Crypt см. в документации.

суббота, 21 декабря 2024 г.

Больше хэшей - быстрых и разных

 Fernando Mercês обновил свой Hashes.hem до версии 2.00,

а FSC выдал Hash Calculator с вычислением

MD2, MD4, MD5, SHA1, SHA256, SHA384, SHA512, CRC-32/MPEG-2, parametrized CRC ("Rocksoft^tm Model CRC Algorithm")

вторник, 1 октября 2024 г.

версия 8.83

 - fix(8.82): вставка из буфера обмена для F5 .xxxx считалась невалидной
 - fix: кривой дамп для двухбайтовых кодовых страниц
 - калькулятор: #[bwdq] как big-endian

 смотрите в ящиках и спамах

вторник, 30 июля 2024 г.

Hiewix или StructLook 30 лет спустя

Hiewix как и StructLook в досовские времена помогает показать внутренности файла.
В StructLook был свой табличный язык для описания, в Hiewix - внешнее api.
На картинке - PE через это api.

Это начало с простыми целыми типами.
Как обычно, богатое развитие зависит от богатого финансирования.

Демо без ключевых возможностей: https://hiew.ru/files/HxDemo.zip 

Предложение до 1 октября этого года:
 для покупавших StructLook - бесплатно
 для имеющих как минимум вторичную действующую годовую регистрацию Hiew - 3000 рублей
 для владельцев действующей первичной годовой регистрации - 4000 рублей
 остальные - 5000 рублей

купившие в августе этого года получат обновления в течение 16 месяцев,
купившие в сентябре этого года - в течение 15 месяцев,
но если вдруг кто-то сильно быстро решится в оставшиеся 2 дня июля - 18 месяцев.

Вопросы и получение реквизитов карты - eugenys@gmail.com с темой Hiewix

mailto:eugenys@gmail.com?subject=Hiewix 


пятница, 19 июля 2024 г.

версия 8.82

- префикс 0x игнорируется при вставке из клипбоарда при редактировании чисел
- fix: шаблон поиска в ассемблерной строке может пропускать строки с комментариями

смотрите ящики и спамы

среда, 13 марта 2024 г.

версия 8.81

 - tabSize в inifile
 - fix: длинная строка в hem info давала некрасивый эффект
 - fix: поиск '*xxxx' сломался в какой-то предыдущей версии
 - минимальный HEM_FLAG_FILEMASK (since HEM SDK 0.50) как флаг для всех exe
 - Shift-Ctrl-Alt: шкала с 1

смотрите почтовые ящики

суббота, 5 августа 2023 г.

CopyAs и Hashes - два полезных HEM

 Fernando Mercês сделал два полезных модуля:

CopyAs - запись отмеченного блока в различном формате  

Hashes - вычисление хешей MD5, SHA-1, SHA-256 для блока либо всего файла

понедельник, 24 июля 2023 г.

версия 8.79

- Alt-S - копировать 16 (48 для Shift+) байтов в поиск
- Shift-Up/Shift-Down в Hex-mode - сдвинуть отмеченный блок назад/вперед

рассылка завершена.

четверг, 26 января 2023 г.

версия 8.78

- FIX: опять работает под Win9x :)
- FIX: креш если нажать fastkey на пустом списке файлов

рассылка завершена.