Blog. Just Blog

Распаковка данных в формате LZ4 на Ассемблере

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Образ мышления: Assembler | Автор: ManHunter
Распаковка данных в формате LZ4 на Ассемблере
Распаковка данных в формате LZ4 на Ассемблере

Алгоритм сжатия LZ4 был разработан Yann Collet в 2011-м году. При небольшом размере упаковщика и распаковщика, LZ4 обладает очень высокой скоростью обработки данных и хорошей степенью компрессии, поэтому используется в большом числе серьезных проектов. На офсайте есть ссылки на реализации этого алгоритма на различных языках программирования, в том числе и вариант на 16-битном Ассемблере от Jim Leonard. Для использования в своих программах я адаптировал его функцию распаковки LZ4.

В конце статьи вы найдете упаковщик, который сжимает данные по алгоритму LZ4. К нему прилагался bat-файл для демонстрации возможностей, меня удивило, что, несмотря на возможность прямой упаковки файлов, в нем сжимаемый файл передается упаковщику в качестве потока данных через STDIN. Выяснилось, что при упаковке файлов в начало упакованных данных добавляется маркер 03 21 4C 18, а при упаковке потока данных маркер меняется на 02 21 4C 18. На сайте с документацией по LZ4 такой маркер заявлен как устаревший формат блоков упакованных данных, который использовался в ранних версиях алгоритма. Но, как ни странно, при тестировании упаковщика на текстовых данных именно этот формат дал наибольшую степень сжатия. Да и на прочих данных результат оказался ничуть не хуже нового формата. Алгоритм распаковки для нового и старого формата отличается, поэтому функция распаковки учитывает этот момент. Соответственно, упакованные данные должны быть в legacy-формате. Более высокое сжатие дает упаковщик LZ4X от Ильи Муравьева, причем маркер в заголовке сжатых данных записывается правильный.
  1. ;------------------------------------------------------------
  2. ; Распаковка данных в формате LZ4
  3. ;------------------------------------------------------------
  4. ; Оригинальный код: Jim Leonard, модификация: ManHunter / PCL
  5. ;------------------------------------------------------------
  6. ; На входе:
  7. ;   lpCompressed - указатель на упакованные данные
  8. ;   lpOut - указатель на буфер для распакованных данных
  9. ; На выходе:
  10. ;   EAX = размер распакованных данных
  11. ;------------------------------------------------------------
  12. proc lz4_unpack lpCompressed:DWORD, lpOut:DWORD
  13.         pusha
  14.         mov     esi,[lpCompressed]
  15.         mov     edi,[lpOut]
  16.         lodsd
  17.         cmp     eax,184C2102h
  18.         jne     .done
  19.         lodsd
  20.         mov     ebx,eax
  21.         add     ebx,esi
  22.         xor     eax,eax
  23.         xor     ecx,ecx
  24. .parsetoken:
  25.         lodsb
  26.         movzx   edx,al
  27. .copyliterals:
  28.         mov     ecx,4
  29.         shr     al,cl
  30.         call    .buildfullcount
  31. .doliteralcopy:
  32.         rep     movsb
  33.         cmp     esi,ebx
  34.         jae     .done
  35. .copymatches:
  36.         lodsw
  37.         xchg    edx,eax
  38.         and     al,0Fh
  39.         call    .buildfullcount
  40. .domatchcopy:
  41.         push    esi
  42.         mov     esi,edi
  43.         sub     esi,edx
  44.         add     ecx,4
  45.         rep     movsb
  46.         pop     esi
  47.         jmp     .parsetoken
  48. .buildfullcount:
  49.         cmp     al,0Fh
  50.         xchg    ecx,eax
  51.         jne     .builddone
  52. .buildloop:
  53.         lodsb
  54.         add     ecx,eax
  55.         cmp     al,0FFh
  56.         je      .buildloop
  57. .builddone:
  58.         retn
  59. .done:
  60.         sub     edi,[lpOut]
  61.         mov     [esp+28],edi
  62.         popa
  63.         ret
  64. endp
В качестве параметров передается указатель на упакованные данные и указатель на буфер-приемник. На выходе в регистре EAX возвращается количество байт, которое было извлечено из сжатых данных. Если исходные данные не соответствуют формату, то в EAX возвращается 0. Размер принимающего буфера в процессе распаковки никак не проверяется, об этом вы должны позаботиться самостоятельно.

В заголовке LZ4 хранится информация о размере упакованных данных, но нет никакой информации об исходных данных. Хорошо, когда размер распакованных данных заранее известен или оперативную память под них можно выделить с запасом. Но если объем данных заранее не определен, то перед распаковкой его надо обязательно узнать. Для таких случаев я немного модифицировал функцию распаковки, чтобы она просто возвращала число байт, которые были бы заполнены в памяти после декомпрессии.
  1. ;---------------------------------------------------
  2. ; Получение размера распакованных данных
  3. ;---------------------------------------------------
  4. ; На входе:
  5. ;   lpCompressed - указатель на упакованные данные
  6. ; На выходе:
  7. ;   EAX = размер распакованных данных
  8. ;---------------------------------------------------
  9. proc lz4_size lpCompressed:DWORD
  10.         pusha
  11.         mov     esi,[lpCompressed]
  12.         xor     edi,edi
  13.         lodsd
  14.         cmp     eax,184C2102h
  15.         jne     .done
  16.         lodsd
  17.         mov     ebx,eax
  18.         add     ebx,esi
  19.         xor     eax,eax
  20.         xor     ecx,ecx
  21. .parsetoken:
  22.         lodsb
  23.         movzx   edx,al
  24. .copyliterals:
  25.         mov     ecx,4
  26.         shr     al,cl
  27.         call    .buildfullcount
  28. .doliteralcopy:
  29.         add     edi,ecx
  30.         add     esi,ecx
  31.         cmp     esi,ebx
  32.         jae     .done
  33. .copymatches:
  34.         lodsw
  35.         xchg    edx,eax
  36.         and     al,0Fh
  37.         call    .buildfullcount
  38. .domatchcopy:
  39.         add     ecx,4
  40.         add     edi,ecx
  41.         jmp     .parsetoken
  42. .buildfullcount:
  43.         cmp     al,0Fh
  44.         xchg    ecx,eax
  45.         jne     .builddone
  46. .buildloop:
  47.         lodsb
  48.         add     ecx,eax
  49.         cmp     al,0FFh
  50.         je      .buildloop
  51. .builddone:
  52.         retn
  53. .done:
  54.         ; EAX = размер распакованных данных
  55.         mov     [esp+28], edi
  56.         popa
  57.         ret
  58. endp
В качестве единственного параметра передается указатель на упакованные данные, на выходе в регистре EAX количество байт, которое получится после распаковки. Если исходные данные не соответствуют формату, то в EAX возвращается 0.

В приложении пример программы с исходным текстом, которая извлекает из памяти иконку, упакованную по алгоритму LZ4, и выводит ее на форму. Там же в архиве оригинал ассемблерной функции распаковки от Jim Leonard, упаковщик LZ4 от Yann Collet с примерами и упаковщик LZ4X от Ильи Муравьева.

Пример программы с исходным текстом (FASM)Пример программы с исходным текстом (FASM)

LZ4.Unpack.Demo.zip (217,927 bytes)

В связи с популярностью алгоритма LZ4, он с небольшими модификациями используется в различных программных продуктах. Например, с его помощью упаковываются файлы с закладками браузера Mozilla Firefox. Там вся разница заключается только в заголовке, который идет перед упакованными данными. После 8-байтного маркера-идентификатора (ASCIIZ-строка "mozLz40") в заголовке записан DWORD с размером оригинальных данных, так что ничего дополнительно вычислять не придется. Размер упакованных данных равен общему размеру данных минус 12 байт заголовка. Функция распаковки будет следующей:
  1. ;------------------------------------------------------------
  2. ; Распаковка данных в формате mozlz4 / jsonlz4
  3. ;------------------------------------------------------------
  4. ; На входе:
  5. ;   lpCompressed - указатель на упакованные данные
  6. ;   dSize - размер упакованных данных
  7. ;   lpOut - указатель на буфер для распакованных данных
  8. ; На выходе:
  9. ;   EAX = размер распакованных данных
  10. ;------------------------------------------------------------
  11. proc mozlz4_unpack lpCompressed:DWORD, dSize:DWORD, lpOut:DWORD
  12.         pusha
  13.         mov     esi,[lpCompressed]
  14.         mov     edi,[lpOut]
  15.         lodsd
  16.         cmp     eax,'mozL'
  17.         jne     .done
  18.         lodsd
  19.         cmp     eax,'z40'
  20.         jne     .done
  21.         lodsd
  22.  
  23.         mov     ebx,[dSize]
  24.         sub     ebx,12
  25.         add     ebx,esi
  26.         xor     eax,eax
  27.         xor     ecx,ecx
  28. .parsetoken:
  29.         lodsb
  30.         movzx   edx,al
  31. .copyliterals:
  32.         mov     ecx,4
  33.         shr     al,cl
  34.         call    .buildfullcount
  35. .doliteralcopy:
  36.         rep     movsb
  37.         cmp     esi,ebx
  38.         jae     .done
  39. .copymatches:
  40.         lodsw
  41.         xchg    edx,eax
  42.         and     al,0Fh
  43.         call    .buildfullcount
  44. .domatchcopy:
  45.         push    esi
  46.         mov     esi,edi
  47.         sub     esi,edx
  48.         add     ecx,4
  49.         rep     movsb
  50.         pop     esi
  51.         jmp     .parsetoken
  52. .buildfullcount:
  53.         cmp     al,0Fh
  54.         xchg    ecx,eax
  55.         jne     .builddone
  56. .buildloop:
  57.         lodsb
  58.         add     ecx,eax
  59.         cmp     al,0FFh
  60.         je      .buildloop
  61. .builddone:
  62.         retn
  63. .done:
  64.         sub     edi,[lpOut]
  65.         mov     [esp+28],edi
  66.         popa
  67.         ret
  68. endp
В приложении пример программы с исходным текстом, которая извлекает из памяти иконку, упакованную по алгоритму MozLZ4, и выводит ее на форму. Там же в архиве утилиты от Avi Halachmi для работы с упакованными закладками Mozilla Firefox.

Пример программы с исходным текстом (FASM)Пример программы с исходным текстом (FASM)

MozLZ4.Unpack.Demo.zip (54,106 bytes)

Алгоритм LZ4 используется даже в Android-приложениях, а точнее при упаковке dll компоновщиком Xamarin. Тут тоже меняется только заголовок, а упакованные данные прекрасно извлекаются по уже знакомому вам методу. Заголовок состоит из 12 байт: 4 байта маркер "XALZ", затем какой-то четырехбайтный индекс и после него DWORD с размером исходных данных. Распаковывается практически так же, как и предыдущий пример.
  1. ;------------------------------------------------------------
  2. ; Распаковка данных в формате Xamarin LZ4
  3. ;------------------------------------------------------------
  4. ; На входе:
  5. ;   lpCompressed - указатель на упакованные данные
  6. ;   dSize - размер упакованных данных
  7. ;   lpOut - указатель на буфер для распакованных данных
  8. ; На выходе:
  9. ;   EAX = размер распакованных данных
  10. ;------------------------------------------------------------
  11. proc xamarin_unpack lpCompressed:DWORD, dSize:DWORD, lpOut:DWORD
  12.         pusha
  13.         mov     esi,[lpCompressed]
  14.         mov     edi,[lpOut]
  15.         lodsd
  16.         cmp     eax,'XALZ'
  17.         jne     .done
  18.         lodsd
  19.         lodsd
  20.  
  21.         mov     ebx,[dSize]
  22.         sub     ebx,12
  23.         add     ebx,esi
  24.         xor     eax,eax
  25.         xor     ecx,ecx
  26. .parsetoken:
  27.         lodsb
  28.         movzx   edx,al
  29. .copyliterals:
  30.         mov     ecx,4
  31.         shr     al,cl
  32.         call    .buildfullcount
  33. .doliteralcopy:
  34.         rep     movsb
  35.         cmp     esi,ebx
  36.         jae     .done
  37. .copymatches:
  38.         lodsw
  39.         xchg    edx,eax
  40.         and     al,0Fh
  41.         call    .buildfullcount
  42. .domatchcopy:
  43.         push    esi
  44.         mov     esi,edi
  45.         sub     esi,edx
  46.         add     ecx,4
  47.         rep     movsb
  48.         pop     esi
  49.         jmp     .parsetoken
  50. .buildfullcount:
  51.         cmp     al,0Fh
  52.         xchg    ecx,eax
  53.         jne     .builddone
  54. .buildloop:
  55.         lodsb
  56.         add     ecx,eax
  57.         cmp     al,0FFh
  58.         je      .buildloop
  59. .builddone:
  60.         retn
  61. .done:
  62.         sub     edi,[lpOut]
  63.         mov     [esp+28],edi
  64.         popa
  65.         ret
  66. endp
В приложении пример программы с исходным текстом, которая распаковывает dll, обработанную Xamarin и сохраняет распакованный файл в unpacked.dll.

Пример программы с исходным текстом (FASM)Пример программы с исходным текстом (FASM)

Xamarin.LZ4.Unpack.Demo.zip (26,703 bytes)


Поделиться ссылкой ВКонтакте
Просмотров: 2029 | Комментариев: 12

Внимание! Статья опубликована больше года назад, информация могла устареть!

Комментарии

Отзывы посетителей сайта о статье
ManHunter (20.03.2023 в 15:28):
Добавил пример распаковщика Xamarin
ManHunter (20.03.2023 в 14:18):
Добавил пример распаковщика mozlz4 / jsonlz4.
ManHunter (19.03.2023 в 18:59):
В Xamarin тоже ничего сложного, собственный заголовок + упакованные данные

4 byte magic header - 'XALZ'
4 byte индекс
4 byte размер оригинальных данных
дальше идут упакованные данные
ManHunter (19.03.2023 в 18:10):
По поводу мозилловского LZ4 нашлось вот что
https://github.com/avih/dejsonlz4
http://hg.mozilla.org/mozilla-...4/mfbt/lz4.h
http://hg.mozilla.org/mozilla-...4/mfbt/lz4.c
Это действительно самый обычный LZ4, только с модифицированным заголовком.

В обычном LZ4 заголовок
header db 02h,21h,4Ch,18h
       dd packed_size

а тут заголовок
header db 6Dh,6Fh,7Ah,4Ch,7Ah,34h,30h,00h
       dd original_size

Надо просто узнать размер сжатых данных (размер данных минус размер заголовка), дальше содержимое прекрасно распаковывается по приведенному здесь алгоритму.
g0b (24.09.2022 в 19:31):
>Да когда это 1.9.2 новомодным стал?
пардон, 1.9.4 - актуальная версия. 2.6.1 где-то на гитхабе в подвале
ManHunter (24.09.2022 в 13:12):
Да когда это 1.9.2 новомодным стал? Всегда был в тренде 2.6.1 или, на крайний случай 1.3. Необычайно информативный комментарий.
g0b (24.09.2022 в 10:22):
Это все здорово, но вот с новомодным 1.9.2 не пашет.
user (29.06.2021 в 11:06):
--Добавлено--

Ну, после небольших правок пример распаковывает и этот "XALZ" формат.

Респект.
user (29.06.2021 в 01:45):
Есть ещё такой Xamarin XALZ формат,
пакуют им, полдецы, PE-DLL'и для Android'а.
Вроде используется LZ4, но с заголовками непонятно.
Столкнулся с этой пакостью, надо распаковать и запаковать обратно,
пока ещё не решил как
котя (12.03.2021 в 17:07):
Ого, бомбаракета.

Автору - низкий поклон.

Забрал под unix
ManHunter (11.12.2020 в 10:04):
Только если будет пробегать рядом вместе с толковой документацией. Специально искать не планирую.

ЦитатаThis file format is in fact just plain LZ4 data with a custom header (magic number [8 bytes] and uncompressed file size [4 bytes, little endian]).

Так что отрезаешь заголовок и распаковываешь обычным LZ4. Может быть.
JSONLZ4 (11.12.2020 в 03:57):
А как насчет распаковки мозилловского формата JSONLZ4?
Там вроде LZ4 модифицированный какой-то

Добавить комментарий

Заполните форму для добавления комментария
Имя*:
Текст комментария (не более 2000 символов)*:

*Все поля обязательны для заполнения.
Комментарии, содержащие рекламу, ненормативную лексику, оскорбления и т.п., а также флуд и сообщения не по теме, будут удаляться. Нарушителям может быть заблокирован доступ к сайту.
Наверх
Powered by PCL's Speckled Band Engine 0.2 RC3
© ManHunter / PCL, 2008-2024
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.22 сек. / MySQL: 2 (0.0082 сек.) / Память: 4.5 Mb
Наверх