Blog. Just Blog

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

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

Алгоритм компрессии NRV (Not Really Vanished) разработан автором знаменитого упаковщика UPX Markus F.X.J. Oberhumer. Алгоритм используется в этом упаковщике, а также в нескольких вариантах входит в состав библиотеки с открытым кодом UCL. Высокая скорость обработки данных сочетается с очень хорошей степенью компрессии данных.

Но, как говорится, "гладко было на бумаге, да забыли про овраги". При всем уважении к Маркусу, его стиль программирования, основанный на большом количестве макросов и инклудов, мне не нравится. Сперва пришлось угрохать кучу времени, чтобы из исходников скомпилировать работоспособную утилиту для упаковки данных. Затем из набора чудовищных ассемблерных макроконструкций и заведомо неработоспособных бинарных данных надо было собрать три варианта работающих распаковщиков. После этого внезапно выяснилось, что алгоритм блочный и данные распаковываются по каждому блоку отдельно, из-за этого пришлось снова модифицировать все три алгоритма. В результате на выходе мы имеем оригинальную утилиту для упаковки данных по трем разновидностям алгоритма NRV, ассемблерную функцию для подсчета размера данных и три полноценных ассемблерных распаковщика для каждого варианта алгоритма NRV.

Упакованные данные начинаются с "магического" 8-байтного заголовка, затем идет DWORD c различными флагами, затем байт с указанием использованного алгоритма упаковки, байт выбранной степени компрессии, DWORD с выбранным размером блока (обычно 40000h байт), DWORD с размером исходных данных, DWORD с размером упакованных данных. Затем идут уже сами упакованные данные. Если их исходный размер оказался больше размера одного блока, то дальше идет один или несколько следующих блоков с кратким заголовком, который состоит только из DWORD с размером исходных данных и DWORD с размером упакованных данных. Затем снова упакованные данные и так далее. Завершает всю эту конструкцию DWORD нулевым значением, а затем опционально DWORD с контрольной суммой исходных данных, если в параметрах упаковки было указано использование CRC.

Чтобы выяснить размер памяти, которую требуется выделить для приема распакованных данных, надо просто просуммировать размер всех исходных блоков из каждого заголовка. При обработке надо не забывать применять к размерам команду BSWAP. Для любого из трех разновидностей алгоритма эта функция будет одинаковой.
  1. ;---------------------------------------------------
  2. ; Получение размера распакованных данных
  3. ;---------------------------------------------------
  4. ; На входе:
  5. ;   lpCompressed - указатель на упакованные данные
  6. ; На выходе:
  7. ;   EAX = размер распакованных данных
  8. ;---------------------------------------------------
  9. proc nrv_size lpCompressed:DWORD
  10.         pusha
  11.         mov     esi,[lpCompressed]
  12.         add     esi,18
  13.         xor     ebx,ebx
  14. .loc_scan:
  15.         lodsd
  16.         bswap   eax
  17.         or      eax,eax
  18.         jz      .loc_done
  19.         add     ebx,eax
  20.         lodsd
  21.         bswap   eax
  22.         add     esi,eax
  23.         jmp     .loc_scan
  24. .loc_done:
  25.         mov     [esp+28],ebx
  26.         popa
  27.         ret
  28. endp
В качестве единственного параметра передается указатель на упакованные данные, на выходе в регистре EAX количество байт, которое получится после распаковки.

Алгоритмы упаковки показывают различные результаты в зависимости от типа и размера исходных данных. На небольших объемах данных степень компрессии сильно варьируется, на больших файлах преимущество каждой более старшей версии алгоритма становится заметнее. Вот три варианта распаковщиков данных для nrv2b, nrv2d и nrv2e.
  1. ;------------------------------------------------------------
  2. ; Распаковка данных в формате UCL (nrv2b)
  3. ; Copyright (C) Markus F.X.J Oberhumer
  4. ; Модификация: ManHunter / PCL
  5. ;------------------------------------------------------------
  6. ; На входе:
  7. ;   lpCompressed - указатель на упакованные данные
  8. ;   lpOut - указатель на буфер для распакованных данных
  9. ; На выходе:
  10. ;   EAX = размер распакованных данных
  11. ;------------------------------------------------------------
  12. proc nrv2b_unpack lpCompressed:DWORD,lpOut:DWORD
  13.         pusha
  14.  
  15.         mov     esi,[lpCompressed]
  16.         add     esi,26
  17.         mov     edi,[lpOut]
  18. .decompr_block:
  19.         xor     ecx,ecx
  20.         mul     ecx
  21.         dec     edx
  22.         jmp     .decompr_start
  23. .decompr_literal:
  24.         movsb
  25. .decompr_start:
  26.         stdcall .get_bit
  27.         jc      .decompr_literal
  28.         push    1
  29.         pop     ebx
  30. .decompr_match:
  31.         stdcall .get_bit
  32.         adc     ebx,ebx
  33.         stdcall .get_bit
  34.         jnc     .decompr_match
  35.  
  36.         sub     ebx,3
  37.         jb      .decompr_same_off
  38.         shl     ebx,8
  39.         mov     bl,[esi]
  40.         inc     esi
  41.         xor     ebx,0FFFFFFFFh
  42.         jnz     .decompr_continue
  43.  
  44.         lodsd
  45.         or      eax,eax
  46.         jz      .decompr_end
  47.         lodsd
  48.         jmp     .decompr_block
  49.  
  50. .decompr_continue:
  51.         xchg    edx,ebx
  52. .decompr_same_off:
  53.         stdcall .get_bit
  54.         adc     ecx, ecx
  55.  
  56.         stdcall .get_bit
  57.         adc     ecx,ecx
  58.         jnz     .decompr_literals
  59.  
  60.         inc     ecx
  61. .decode_check_len:
  62.         stdcall .get_bit
  63.         adc     ecx,ecx
  64.  
  65.         stdcall .get_bit
  66.         jnc     .decode_check_len
  67.  
  68.         inc     ecx
  69.         inc     ecx
  70. .decompr_literals:
  71.         cmp     edx,0FFFFF300h
  72.         adc     ecx,1
  73.         push    esi
  74.         lea     esi,[edi+edx]
  75.         rep     movsb
  76.         pop     esi
  77.         jmp     .decompr_start
  78.  
  79. .get_bit:
  80.         add     al,al
  81.         jnz     .get_bit_exit
  82.         lodsb
  83.         stc
  84.         adc     al,al
  85. .get_bit_exit:
  86.         retn
  87. .decompr_end:
  88.         sub     edi,[lpOut]
  89.         mov     [esp+28],edi
  90.         popa
  91.         ret
  92. endp
  1. ;------------------------------------------------------------
  2. ; Распаковка данных в формате UCL (nrv2d)
  3. ; Copyright (C) Markus F.X.J Oberhumer
  4. ; Модификация: ManHunter / PCL
  5. ;------------------------------------------------------------
  6. ; На входе:
  7. ;   lpCompressed - указатель на упакованные данные
  8. ;   lpOut - указатель на буфер для распакованных данных
  9. ; На выходе:
  10. ;   ebx = размер распакованных данных
  11. ;------------------------------------------------------------
  12. proc nrv2d_unpack lpCompressed:DWORD,lpOut:DWORD
  13.         pusha
  14.  
  15.         mov     esi,[lpCompressed]
  16.         add     esi,26
  17.         mov     edi,[lpOut]
  18. .decompr_block:
  19.         xor     ecx,ecx
  20.         mul     ecx
  21.         dec     edx
  22.         jmp     .decompr_start
  23. .decompr_literal:
  24.         movsb
  25. .decompr_loop:
  26.         add     al,al
  27.         jnz     .decompr_gotbit
  28. .decompr_start:
  29.         lodsb
  30.         stc
  31.         adc     al,al
  32. .decompr_gotbit:
  33.         jb      .decompr_literal
  34.         xor     ebx,ebx
  35.         inc     ebx
  36. .decompr_match:
  37.         stdcall .get_bit
  38.         adc     ebx,ebx
  39.         stdcall .get_bit
  40.         jb      .decode_check_off
  41.         dec     ebx
  42.         stdcall .get_bit
  43.         adc     ebx,ebx
  44.         jmp     .decompr_match
  45. .decode_check_off:
  46.         sub     ebx,3
  47.         jb      .decompr_same_off
  48.         shl     ebx,8
  49.         mov     bl,[esi]
  50.         inc     esi
  51.         xor     ebx,0FFFFFFFFh
  52.         jnz     .decompr_continue
  53.  
  54.         lodsd
  55.         or      eax,eax
  56.         jz      .decompr_end
  57.         lodsd
  58.         jmp     .decompr_block
  59. .decompr_continue:
  60.         sar     ebx,1
  61.         mov     edx,ebx
  62.         jmp     .decompr_got_off
  63. .decompr_same_off:
  64.         stdcall .get_bit
  65. .decompr_got_off:
  66.         adc     ecx,ecx
  67.         stdcall .get_bit
  68.         adc     ecx,ecx
  69.         jnz     .decompr_got_len
  70.         inc     ecx
  71. .decode_check_len:
  72.         stdcall .get_bit
  73.         adc     ecx,ecx
  74.         stdcall .get_bit
  75.         jnb     .decode_check_len
  76.         add     ecx,2
  77. .decompr_got_len:
  78.         cmp     edx,0FFFFFB00h
  79.         adc     ecx,1
  80.         push    esi
  81.         lea     esi,[edi+edx]
  82.         rep     movsb
  83.         pop     esi
  84.         jmp     .decompr_loop
  85.  
  86. .get_bit:
  87.         add     al,al
  88.         jnz     .get_bit_exit
  89.         lodsb
  90.         stc
  91.         adc     al,al
  92. .get_bit_exit:
  93.         retn
  94.  
  95. .decompr_end:
  96.         sub     edi,[lpOut]
  97.         mov     [esp+28],edi
  98.  
  99.         popa
  100.         ret
  101. endp
  1. ;------------------------------------------------------------
  2. ; Распаковка данных в формате UCL (nrv2e)
  3. ; Copyright (C) Markus F.X.J Oberhumer
  4. ; Модификация: ManHunter / PCL
  5. ;------------------------------------------------------------
  6. ; На входе:
  7. ;   lpCompressed - указатель на упакованные данные
  8. ;   lpOut - указатель на буфер для распакованных данных
  9. ; На выходе:
  10. ;   ebx = размер распакованных данных
  11. ;------------------------------------------------------------
  12. proc nrv2e_unpack lpCompressed:DWORD,lpOut:DWORD
  13.         pusha
  14.  
  15.         mov     esi,[lpCompressed]
  16.         add     esi,26
  17.         mov     edi,[lpOut]
  18. .decompr_block:
  19.         xor     ecx,ecx
  20.         mul     ecx
  21.         dec     edx
  22.  
  23.         jmp     .decompr_start
  24. .decompr_literal:
  25.         movsb
  26. .decompr_loop:
  27.         add     al,al
  28.         jnz     .decompr_gotbit
  29. .decompr_start:
  30.         lodsb
  31.         stc
  32.         adc     al,al
  33. .decompr_gotbit:
  34.         jb      .decompr_literal
  35.         xor     ebx,ebx
  36.         inc     ebx
  37. .decompr_match:
  38.         stdcall .get_bit
  39.         adc     ebx,ebx
  40.         stdcall .get_bit
  41.         jb      .decode_check_off
  42.         dec     ebx
  43.         stdcall .get_bit
  44.         adc     ebx,ebx
  45.         jmp     .decompr_match
  46. .decode_check_off:
  47.         sub     ebx,3
  48.         jb      .decompr_same_off
  49.         shl     ebx,8
  50.         mov     bl,[esi]
  51.         inc     esi
  52.         xor     ebx,0FFFFFFFFh
  53.         jnz     .decompr_continue
  54.  
  55.         lodsd
  56.         or      eax,eax
  57.         jz      .decompr_end
  58.         lodsd
  59.         jmp     .decompr_block
  60. .decompr_continue:
  61.         sar     ebx,1
  62.         mov     edx,ebx
  63.         jnb     .decompr_got_off
  64. .decompr_mlen1:
  65.         stdcall .get_bit
  66.         adc     ecx,ecx
  67.         jmp     .decompr_got_len
  68.  
  69. .decompr_same_off:
  70.         stdcall .get_bit
  71.         jc      .decompr_mlen1
  72. .decompr_got_off:
  73.         inc     ecx
  74.         stdcall .get_bit
  75.         jc      .decompr_mlen1
  76. .decode_check_len:
  77.         stdcall .get_bit
  78.         adc     ecx, ecx
  79.         stdcall .get_bit
  80.         jnb     .decode_check_len
  81.         add     ecx,2
  82. .decompr_got_len:
  83.         cmp     edx,0FFFFFB00h
  84.         adc     ecx,2
  85.         push    esi
  86.         lea     esi,[edi+edx]
  87.         rep     movsb
  88.         pop     esi
  89.         jmp     .decompr_loop
  90.  
  91. .get_bit:
  92.         add     al,al
  93.         jnz     .get_bit_exit
  94.         lodsb
  95.         stc
  96.         adc     al,al
  97. .get_bit_exit:
  98.         retn
  99.  
  100. .decompr_end:
  101.         sub     edi,[lpOut]
  102.         mov     [esp+28],edi
  103.  
  104.         popad
  105.         ret
  106. endp
В качестве параметров в каждую из функций распаковки передается указатель на упакованные данные и указатель на буфер-приемник. На выходе в регистре EAX возвращается количество байт, которое было извлечено из сжатых данных. Размер принимающего буфера в процессе распаковки никак не проверяется, об этом вы должны позаботиться самостоятельно.

В приложении примеры программ с исходными текстами, которые извлекают из памяти иконки, упакованные по трем разновидностям алгоритма NRV, а затем выводят их на форму. Там же в архиве оригинальная утилита для упаковки и распаковки данных от Markus F.X.J. Oberhumer.

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

NRV.Unpack.Demo.zip (39,287 bytes)


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

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

Комментарии

Отзывы посетителей сайта о статье
Комментариeв нет

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

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

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