Blog. Just Blog

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

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

Алгоритм LZSA - представитель "высшей лиги" среди алгоритмов упаковки данных. Разработан в 2018 году признанным гуру упаковки Emmanuel Marty. Целью создания алгоритма LZSA была большая скорость упаковки данных и очень высокая степень компрессии для использования, главным образом, на 8-битных платформах. Существует две разновидности алгоритма LZSA, которые отличаются скоростью работы и, соответственно, степенью сжатия и размером модуля распаковки.

LZSA позволяет сжимать файлы любого объема, но сами данные обрабатываются блоками по 64 килобайта. Из-за этой особенности ассемблерные функции распаковки поддерживают данные, исходный размер которых также не превышает 64 килобайт, а паковать файлы прилагаемыми утилитами надо обязательно с ключом "-r", то есть "raw block format". Кстати, в интернетах выложена только 64-битная версия упаковщика LZSA, 32-битный вариант мне пришлось собирать самостоятельно из авторских исходников. Оба варианта есть в прилагаемом архиве.

Я адаптировал ассемблерную функцию распаковки данных в формате LZSA1 на синтаксис FASM. Оптимизировать тут нечего, код и так практически идеальный.
  1. ;------------------------------------------------------------
  2. ; Распаковка данных в формате LZSA1
  3. ; Copyright (C) 2019 Emmanuel Marty
  4. ;------------------------------------------------------------
  5. ; На входе:
  6. ;   lpCompressed - указатель на упакованные данные
  7. ;   lpOut - указатель на буфер для распакованных данных
  8. ; На выходе:
  9. ;   EAX = размер распакованных данных
  10. ;------------------------------------------------------------
  11. proc lzsa1_unpack lpCompressed:DWORD,lpOut:DWORD
  12.         pusha
  13.  
  14.         mov     esi,[lpCompressed]
  15.         mov     edi,[lpOut]
  16.  
  17. .decode_token:
  18.         xor     eax,eax
  19.         lodsb
  20.         movzx   edx,al
  21.  
  22.         and     al,70h
  23.         shr     al,4
  24.  
  25.         cmp     al,07h
  26.         jne     .got_literals
  27.  
  28.         lodsb
  29.         add     al,07h
  30.         jnc     .got_literals
  31.         jne     .mid_literals
  32.  
  33.         lodsw
  34.         jmp     .got_literals
  35.  
  36. .mid_literals:
  37.         lodsb
  38.         inc     ah
  39.  
  40. .got_literals:
  41.         xchg    ecx,eax
  42.         rep     movsb
  43.  
  44.         test    dl,dl
  45.         js      .get_long_offset
  46.  
  47.         dec     ecx
  48.         xchg    eax,ecx
  49.         lodsb
  50.         jmp     .get_match_length
  51.  
  52. .get_long_offset:
  53.         lodsw
  54.  
  55. .get_match_length:
  56.         xchg    eax,edx
  57.         and     al,0Fh
  58.         add     al,3
  59.  
  60.         cmp     al,12h
  61.         jne     .got_matchlen
  62.  
  63.         lodsb
  64.         add     al,12h
  65.         jnc     .got_matchlen
  66.         jne     .mid_matchlen
  67.  
  68.         lodsw
  69.         test    eax,eax
  70.         je      .done_decompressing
  71.         jmp     .got_matchlen
  72.  
  73. .mid_matchlen:
  74.         lodsb
  75.         inc     ah
  76.  
  77. .got_matchlen:
  78.         xchg    ecx,eax
  79.         xchg    esi,eax
  80.         mov     esi,edi
  81.         movsx   edx,dx
  82.         add     esi,edx
  83.         rep     movsb
  84.         xchg    esi,eax
  85.         jmp     .decode_token
  86.  
  87. .done_decompressing:
  88.         sub     edi,[lpOut]
  89.         mov     [esp+28],edi
  90.         popa
  91.         ret
  92. endp
В качестве параметров передается указатель на упакованные данные и указатель на буфер-приемник. На выходе в регистре EAX возвращается количество байт, которое было извлечено из сжатых данных. Размер принимающего буфера в процессе распаковки никак не проверяется, об этом вы должны позаботиться самостоятельно.

Для резервирования памяти под распакованные данные надо сперва узнать их размер. В принципе, можно использовать блок памяти фиксированного размера 64 килобайта, но лучше все-таки делать это более точно. Я модифицировал функцию распаковки, чтобы она просто возвращала количество байт, которые будут занимать распакованные данные.
  1. ;------------------------------------------------------------
  2. ; Получение размера распакованных данных
  3. ;------------------------------------------------------------
  4. ; На входе:
  5. ;   lpCompressed - указатель на упакованные данные
  6. ; На выходе:
  7. ;   EAX = размер распакованных данных
  8. ;------------------------------------------------------------
  9. proc lzsa1_size lpCompressed:DWORD
  10.         pusha
  11.  
  12.         mov     esi,[lpCompressed]
  13.         xor     edi,edi
  14.  
  15. .decode_token:
  16.         xor     eax,eax
  17.         lodsb
  18.         movzx   edx,al
  19.  
  20.         and     al,70h
  21.         shr     al,4
  22.  
  23.         cmp     al,07h
  24.         jne     .got_literals
  25.  
  26.         lodsb
  27.         add     al,07h
  28.         jnc     .got_literals
  29.         jne     .mid_literals
  30.  
  31.         lodsw
  32.         jmp     .got_literals
  33.  
  34. .mid_literals:
  35.         lodsb
  36.         inc     ah
  37.  
  38. .got_literals:
  39.         xchg    ecx,eax
  40.         add     edi,ecx
  41.         rep     lodsb
  42.  
  43.         test    dl,dl
  44.         js      .get_long_offset
  45.  
  46.         dec     ecx
  47.         xchg    eax,ecx
  48.         lodsb
  49.         jmp     .get_match_length
  50.  
  51. .get_long_offset:
  52.         lodsw
  53.  
  54. .get_match_length:
  55.         xchg    eax,edx
  56.         and     al,0Fh
  57.         add     al,3
  58.  
  59.         cmp     al,12h
  60.         jne     .got_matchlen
  61.  
  62.         lodsb
  63.         add     al,12h
  64.         jnc     .got_matchlen
  65.         jne     .mid_matchlen
  66.  
  67.         lodsw
  68.         test    eax,eax
  69.         je      .done_decompressing
  70.         jmp     .got_matchlen
  71.  
  72. .mid_matchlen:
  73.         lodsb
  74.         inc     ah
  75.  
  76. .got_matchlen:
  77.         add     edi,eax
  78.         jmp     .decode_token
  79.  
  80. .done_decompressing:
  81.         mov     [esp+28],edi
  82.         popa
  83.         ret
  84. endp
В качестве единственного параметра передается указатель на упакованные данные, на выходе в регистре EAX количество байт, которое получится после распаковки.

Формат данных, упакованных по алгоритму LZSA2, отличается от LZSA1, функция для их распаковки, соответственно, тоже будет другой.
  1. ;------------------------------------------------------------
  2. ; Распаковка данных в формате LZSA2
  3. ; Copyright (C) 2019 Emmanuel Marty
  4. ;------------------------------------------------------------
  5. ; На входе:
  6. ;   lpCompressed - указатель на упакованные данные
  7. ;   lpOut - указатель на буфер для распакованных данных
  8. ; На выходе:
  9. ;   EAX = размер распакованных данных
  10. ;------------------------------------------------------------
  11. proc lzsa2_unpack lpCompressed:DWORD,lpOut:DWORD
  12.         pusha
  13.         push    ebp
  14.  
  15.         mov     esi,[lpCompressed]
  16.         mov     edi,[lpOut]
  17.  
  18.         xor     ebx,ebx
  19.         inc     bh
  20.         xor     ebp,ebp
  21.  
  22. .decode_token:
  23.         xor     eax,eax
  24.         lodsb
  25.         movzx   edx,al
  26.  
  27.         and     al,18h
  28.         shr     al,3
  29.  
  30.         cmp     al,03h
  31.         jne     .got_literals
  32.  
  33.         stdcall .get_nibble
  34.         add     al,cl
  35.         cmp     al,12h
  36.         jne     .got_literals
  37.  
  38.         lodsb
  39.         add     al,12h
  40.         jnc     .got_literals
  41.  
  42.         lodsw
  43. .got_literals:
  44.         xchg    ecx, eax
  45.         rep     movsb
  46.         test    dl,0C0h
  47.         js      .rep_match_or_large_offset
  48.         xchg    ecx,eax
  49.         jne     .offset_9_bit
  50.         cmp     dl,20h
  51.         stdcall .get_nibble_x
  52.         jmp     .dec_offset_top
  53. .offset_9_bit:
  54.         lodsb
  55.         dec     ah
  56.         test    dl,20h
  57.         je      .get_match_length
  58. .dec_offset_top:
  59.         dec     ah
  60.         jmp     .get_match_length
  61. .rep_match_or_large_offset:
  62.         jpe     .rep_match_or_16_bit
  63.         cmp     dl,0A0h
  64.         xchg    ah,al
  65.         stdcall .get_nibble_x
  66.         sub     al,2
  67.         jmp     .get_match_length_1
  68.  
  69. .rep_match_or_16_bit:
  70.         test    dl,20h
  71.         jne     .repeat_match
  72.         lodsb
  73.  
  74. .get_match_length_1:
  75.         xchg    ah,al
  76.         lodsb
  77.  
  78. .get_match_length:
  79.         xchg    ebp,eax
  80. .repeat_match:
  81.         xchg    eax, edx
  82.         and     al,07h
  83.         add     al,2
  84.         cmp     al,09h
  85.         jne     .got_matchlen
  86.  
  87.         stdcall .get_nibble
  88.         add     al,cl
  89.         cmp     al,18h
  90.         jne     .got_matchlen
  91.  
  92.         lodsb
  93.         add     al,18h
  94.         jnc     .got_matchlen
  95.         je      .done_decompressing
  96.  
  97.         lodsw
  98.  
  99. .got_matchlen:
  100.         xchg    ecx,eax
  101.         xchg    esi,eax
  102.         movsx   ebp,bp
  103.         lea     esi,[ebp+edi]
  104.         rep     movsb
  105.         xchg    esi,eax
  106.         jmp     .decode_token
  107.  
  108. .get_nibble_x:
  109.         cmc
  110.         rcr     al,1
  111.         stdcall .get_nibble
  112.         or      al,cl
  113.         rol     al,1
  114.         xor     al,0E1h
  115.         retn
  116.  
  117. .get_nibble:
  118.         neg     bh
  119.         jns     .has_nibble
  120.  
  121.         xchg    ebx,eax
  122.         lodsb
  123.         xchg    ebx,eax
  124.  
  125. .has_nibble:
  126.         mov     cl,4
  127.         ror     bl,cl
  128.         mov     cl,0Fh
  129.         and     cl,bl
  130.         retn
  131.  
  132. .done_decompressing:
  133.         pop     ebp
  134.         sub     edi,[lpOut]
  135.         mov     [esp+28],edi
  136.         popa
  137.         ret
  138. endp
Для определения объема памяти, необходимого для приема распакованных данных, можете воспользоваться следующей функцией.
  1. ;------------------------------------------------------------
  2. ; Получение размера распакованных данных
  3. ;------------------------------------------------------------
  4. ; На входе:
  5. ;   lpCompressed - указатель на упакованные данные
  6. ; На выходе:
  7. ;   EAX = размер распакованных данных
  8. ;------------------------------------------------------------
  9. proc lzsa2_size lpCompressed:DWORD
  10.         pusha
  11.         push    ebp
  12.  
  13.         mov     esi,[lpCompressed]
  14.         xor     edi,edi
  15.  
  16.         xor     ebx,ebx
  17.         inc     bh
  18.         xor     ebp,ebp
  19.  
  20. .decode_token:
  21.         xor     eax,eax
  22.         lodsb
  23.         movzx   edx,al
  24.  
  25.         and     al,18h
  26.         shr     al,3
  27.  
  28.         cmp     al,03h
  29.         jne     .got_literals
  30.  
  31.         stdcall .get_nibble
  32.         add     al,cl
  33.         cmp     al,12h
  34.         jne     .got_literals
  35.  
  36.         lodsb
  37.         add     al,12h
  38.         jnc     .got_literals
  39.  
  40.         lodsw
  41. .got_literals:
  42.         xchg    ecx, eax
  43.         add     edi,ecx
  44.         rep     lodsb
  45.  
  46.         test    dl,0C0h
  47.         js      .rep_match_or_large_offset
  48.         xchg    ecx,eax
  49.         jne     .offset_9_bit
  50.         cmp     dl,20h
  51.         stdcall .get_nibble_x
  52.         jmp     .dec_offset_top
  53. .offset_9_bit:
  54.         lodsb
  55.         dec     ah
  56.         test    dl,20h
  57.         je      .get_match_length
  58. .dec_offset_top:
  59.         dec     ah
  60.         jmp     .get_match_length
  61. .rep_match_or_large_offset:
  62.         jpe     .rep_match_or_16_bit
  63.         cmp     dl,0A0h
  64.         xchg    ah,al
  65.         stdcall .get_nibble_x
  66.         sub     al,2
  67.         jmp     .get_match_length_1
  68.  
  69. .rep_match_or_16_bit:
  70.         test    dl,20h
  71.         jne     .repeat_match
  72.         lodsb
  73.  
  74. .get_match_length_1:
  75.         xchg    ah,al
  76.         lodsb
  77.  
  78. .get_match_length:
  79.         xchg    ebp,eax
  80. .repeat_match:
  81.         xchg    eax, edx
  82.         and     al,07h
  83.         add     al,2
  84.         cmp     al,09h
  85.         jne     .got_matchlen
  86.  
  87.         stdcall .get_nibble
  88.         add     al,cl
  89.         cmp     al,18h
  90.         jne     .got_matchlen
  91.  
  92.         lodsb
  93.         add     al,18h
  94.         jnc     .got_matchlen
  95.         je      .done_decompressing
  96.  
  97.         lodsw
  98.  
  99. .got_matchlen:
  100.         add     edi,eax
  101.         jmp     .decode_token
  102.  
  103. .get_nibble_x:
  104.         cmc
  105.         rcr     al,1
  106.         stdcall .get_nibble
  107.         or      al,cl
  108.         rol     al,1
  109.         xor     al,0E1h
  110.         retn
  111.  
  112. .get_nibble:
  113.         neg     bh
  114.         jns     .has_nibble
  115.  
  116.         xchg    ebx,eax
  117.         lodsb
  118.         xchg    ebx,eax
  119.  
  120. .has_nibble:
  121.         mov     cl,4
  122.         ror     bl,cl
  123.         mov     cl,0Fh
  124.         and     cl,bl
  125.         retn
  126.  
  127. .done_decompressing:
  128.         pop     ebp
  129.         mov     [esp+28],edi
  130.         popa
  131.         ret
  132. endp
Все входные параметры у обеих функций точно такие же, как и для LZSA1.

В приложении примеры программы с исходными текстами, которые извлекают из памяти иконки, упакованные по алгоритму LZSA1 и LZSA2, а затем выводят их на форму. Там же в архиве оригинальный упаковщик LZSA от Emmanuel Marty для 32-битных и 64-битных систем.

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

LZSA.Unpack.Demo.zip (218,436 bytes)


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

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

Комментарии

Отзывы посетителей сайта о статье
ManHunter (29.04.2021 в 11:31):
Самое время задуматься о смене антивируса.
NeshAliNehrin (29.04.2021 в 11:29):
Моё почтение.
Касперский в очередной раз отправил архив в топку. Ругается на HEUR:Trojan.Win32.Genpak.vho в lzsa64.exe
Печально, что не удалось ознакомиться с исходниками в полном объёме, но это не критично. Объем и подача учебного материала по прежнему на высоком уровне.
Благодарю за культурный досуг.

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

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

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