Blog. Just Blog

Распаковка ZIP-архивов на Ассемблере

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

Если система выполняет какие-то действия "из коробки", то с очень большой вероятностью ваше приложение также сможет их использовать, надо только узнать как. Начиная с Windows XP, Проводник может работать с архивами в формате ZIP, как будто это обычные каталоги. Файлы и подкаталоги из них извлекаются обычным копированием. Давайте научимся делать то же самое.

Для начала нам потребуется солидная пачка GUID'ов, интерфейсов, структур и констант. Надеюсь, что подобный факт для вас уже давно не является чем-то сверхъестественным. Разработчик FASM, как я понимаю, принципиально остановил наполнение инклудов на уровне Windows 2000.
  1. ; GUID {000214E6-0000-0000-C000-000000000046}
  2. IID_IShellFolder \
  3.     dd 0000214E6h
  4.     dw 00000h
  5.     dw 00000h
  6.     db 0C0h, 000h, 000h, 000h, 000h, 000h, 000h, 046h
  7.  
  8. ; IID_IShellFolder Interface
  9. struct IShellFolder
  10.     ; IUnknown
  11.     QueryInterface   dd ?   ; 000h
  12.     AddRef           dd ?   ; 004h
  13.     Release          dd ?   ; 008h
  14.     ; IShellFolder
  15.     ParseDisplayName dd ?   ; 00Ch
  16.     EnumObjects      dd ?   ; 010h
  17.     BindToObject     dd ?   ; 014h
  18.     BindToStorage    dd ?   ; 018h
  19.     CompareIDs       dd ?   ; 01Ch
  20.     CreateViewObject dd ?   ; 020h
  21.     GetAttributesOf  dd ?   ; 024h
  22.     GetUIObjectOf    dd ?   ; 028h
  23.     GetDisplayNameOf dd ?   ; 02Ch
  24.     SetNameOf        dd ?   ; 030h
  25. ends
  26.  
  27. ; GUID {0000000B-0000-0000-C000-000000000046}
  28. IID_IStorage \
  29.     dd 00000000Bh
  30.     dw 00000h
  31.     dw 00000h
  32.     db 0C0h, 000h, 000h, 000h, 000h, 000h, 000h, 046h
  33.  
  34. ; IID_IStorage Interface
  35. struct IStorage
  36.     ; IUnknown
  37.     QueryInterface  dd ?   ; 000h
  38.     AddRef          dd ?   ; 004h
  39.     Release         dd ?   ; 008h
  40.     ; IStorage
  41.     CreateStream    dd ?   ; 00Ch
  42.     OpenStream      dd ?   ; 010h
  43.     CreateStorage   dd ?   ; 014h
  44.     OpenStorage     dd ?   ; 018h
  45.     CopyTo          dd ?   ; 01Ch
  46.     MoveElementTo   dd ?   ; 020h
  47.     Commit          dd ?   ; 024h
  48.     Revert          dd ?   ; 028h
  49.     EnumElements    dd ?   ; 02Ch
  50.     DestroyElement  dd ?   ; 030h
  51.     RenameElement   dd ?   ; 034h
  52.     SetElementTimes dd ?   ; 038h
  53.     SetClass        dd ?   ; 03Ch
  54.     SetStateBits    dd ?   ; 040h
  55.     Stat            dd ?   ; 044h
  56. ends
  57.  
  58. ; IEnumSTATSTG Interface
  59. struct IEnumSTATSTG
  60.     ; IUnknown
  61.     QueryInterface dd ?   ; 000h
  62.     AddRef         dd ?   ; 004h
  63.     Release        dd ?   ; 008h
  64.     ; IEnumSTATSTG
  65.     Next           dd ?   ; 00Ch
  66.     Skip           dd ?   ; 010h
  67.     Reset          dd ?   ; 014h
  68.     Clone          dd ?   ; 018h
  69. ends
  70.  
  71. ; IID_IStream Interface
  72. struct IStream
  73.     ; IUnknown
  74.     QueryInterface dd ?   ; 000h
  75.     AddRef         dd ?   ; 004h
  76.     Release        dd ?   ; 008h
  77.     ; IStream
  78.     Read           dd ?   ; 00Ch
  79.     Write          dd ?   ; 010h
  80.     Seek           dd ?   ; 014h
  81.     SetSize        dd ?   ; 018h
  82.     CopyTo         dd ?   ; 01Ch
  83.     Commit         dd ?   ; 020h
  84.     Revert         dd ?   ; 024h
  85.     LockRegion     dd ?   ; 028h
  86.     UnlockRegion   dd ?   ; 02Ch
  87.     Stat           dd ?   ; 030h
  88.     Clone          dd ?   ; 034h
  89. ends
  90.  
  91. struct ULARGE_INTEGER
  92.      LowPart  dd ?
  93.      HighPart dd ?
  94. ends
  95.  
  96. struct STATSTG
  97.     pwcsName          dd ?
  98.     type              dd ?
  99.     cbSize            ULARGE_INTEGER
  100.     mtime             FILETIME
  101.     ctime             FILETIME
  102.     atime             FILETIME
  103.     grfMode           dd ?
  104.     grfLocksSupported dd ?
  105.     clsid             rd 4
  106.     grfStateBits      dd ?
  107.     reserved          dd ?
  108. ends
  109.  
  110. STGTY_STORAGE = 1
  111. STGTY_STREAM  = 2
  112. STGM_READ     = 0x00000000
  113. STGM_WRITE    = 0x00000001
  114. STGM_CREATE   = 0x00001000
  115.  
  116. ERROR_ALREADY_EXISTS = 183
Процесс извлечения файлов из архива будет производиться в два этапа. Сперва с помощью функции ILCreateFromPath создается список идентификаторов элементов, связанный с файлом архива. Здесь важно помнить, что путь к файлу обязательно должен быть полным. Затем через функцию SHBindToParent получаем интерфейс для работы с хранилищем. Эти действия аналогичны действиям с обычными каталогами. В приведенном примере файлы будут извлекаться в папку с именем архива, но при необходимости это все можно поменять.
  1.         invoke  CoInitialize,0
  2.  
  3.         ; Получить хэндл кучи
  4.         invoke  GetProcessHeap
  5.         mov     [hHeap],eax
  6.  
  7.         ; Получить полный путь к файлу архива
  8.         invoke  GetFullPathName,filename,MAX_PATH,szZipFileName,NULL
  9.  
  10.         ; Создать объект из файла
  11.         invoke  ILCreateFromPath,szZipFileName
  12.         or      eax,eax
  13.         jz      loc_exit
  14.         mov     [pidl],eax
  15.  
  16.         ; Связать дочерний объект с родительским
  17.         invoke  SHBindToParent,[pidl],IID_IShellFolder,psfParent,pidlChild
  18.         or      eax,eax
  19.         jnz     loc_clear1
  20.  
  21.         ; Связать объект с хранилищем
  22.         mov     eax,[psfParent]
  23.         mov     eax,[eax]
  24.         stdcall dword [eax+IShellFolder.BindToObject],[psfParent],\
  25.                 [pidlChild],NULL,IID_IStorage,storage
  26.         or      eax,eax
  27.         jnz     loc_clear2
  28.  
  29.         ; Выделить память в куче для имени папки извлечения
  30.         invoke  HeapAlloc,[hHeap],HEAP_ZERO_MEMORY,MAX_PATH*2
  31.         mov     ebx,eax
  32.         invoke  lstrcpy,ebx,szZipFileName
  33.         invoke  PathRemoveExtension,ebx
  34.  
  35.         ; Извлечь архив в папку
  36.         stdcall ZipExtract,[storage],ebx
  37.  
  38. loc_clear2:
  39.         ; Прибраться за собой
  40.         mov     eax,[psfParent]
  41.         mov     eax,[eax]
  42.         stdcall dword [eax+IShellFolder.Release],[psfParent]
  43.  
  44. loc_clear1:
  45.         invoke  ILFree,[pidl]
  46.  
  47. loc_exit:
  48.         invoke  CoUninitialize
После подготовки архива можно начинать извлекать из него файлы. Для этого я нарисовал вот такую рекурсивную функцию. Рекурсия здесь нужна, чтобы обрабатывать вложенные подкаталоги и распаковывать архив в точности по его внутренней структуре. В качестве параметров передается объект текущего уровня каталогов архива и путь для распаковки, соответствующий этому каталогу. По окончанию работы функция освобождает память, занятую строкой пути, и объект каталога.
  1. ; Рекурсивная функция для извлечения файлов из ZIP-архива
  2. proc ZipExtract dStorage:DWORD, lpFolder:DWORD
  3.         locals
  4.                 enumerator dd ?
  5.                 subfolder  dd ?
  6.                 stream     dd ?
  7.                 fetched    dd ?
  8.                 extracted  dd ?
  9.         endl
  10.  
  11.         ; Создать заданный (под)каталог для распаковки
  12.         invoke  CreateDirectory,[lpFolder],NULL
  13.         or      eax,eax
  14.         jnz     @f
  15.         ; Каталог уже есть, все хорошо
  16.         invoke  GetLastError
  17.         cmp     eax,ERROR_ALREADY_EXISTS
  18.         jne     .loc_ret
  19. @@:
  20.         ; Создать перечислитель
  21.         lea     eax,[enumerator]
  22.         push    eax
  23.         mov     eax,[dStorage]
  24.         mov     eax,[eax]
  25.         stdcall dword [eax+IStorage.EnumElements],[dStorage],\
  26.                 0,0,0
  27.         or      eax,eax
  28.         jnz     .loc_ret
  29. .loc_scan_zip:
  30.         ; Перебрать файлы и подкаталоги
  31.         lea     eax,[fetched]
  32.         push    eax
  33.         mov     eax,[enumerator]
  34.         mov     eax,[eax]
  35.         stdcall dword [eax+IEnumSTATSTG.Next],[enumerator],\
  36.                 1,stat
  37.         or      eax,eax
  38.         jnz     .loc_done_scan
  39.  
  40.         ; Это подкаталог?
  41.         cmp     [stat.type],STGTY_STORAGE
  42.         jne     @f
  43.  
  44.         ; Создать новый путь с подкаталогом
  45.         invoke  HeapAlloc,[hHeap],HEAP_ZERO_MEMORY,MAX_PATH*2
  46.         mov     ebx,eax
  47.         invoke  wsprintf,ebx,mask,[lpFolder],[stat.pwcsName]
  48.         add     esp,16
  49.  
  50.         ; Открыть его как новое хранилище
  51.         lea     eax,[subfolder]
  52.         push    eax
  53.         mov     eax,[dStorage]
  54.         mov     eax,[eax]
  55.         stdcall dword [eax+IStorage.OpenStorage],[dStorage],\
  56.                 [stat.pwcsName],0,STGM_READ,0,0
  57.  
  58.         ; Извлечь содержимое подкаталога
  59.         stdcall ZipExtract,[subfolder],ebx
  60.  
  61.         ; Перейти к следующей записи
  62.         jmp     .loc_next
  63. @@:
  64.         ; Это файл?
  65.         cmp     [stat.type],STGTY_STREAM
  66.         jne     .loc_next
  67.  
  68.         ; Создать новый путь с каталогом и именем файла
  69.         invoke  HeapAlloc,[hHeap],HEAP_ZERO_MEMORY,MAX_PATH*2
  70.         mov     ebx,eax
  71.         invoke  wsprintf,ebx,mask,[lpFolder],[stat.pwcsName]
  72.         add     esp,16
  73.  
  74.         ; Создать объект потока данных
  75.         lea     eax,[stream]
  76.         push    eax
  77.         mov     eax,[dStorage]
  78.         mov     eax,[eax]
  79.         stdcall dword [eax+IStorage.OpenStream],[dStorage],\
  80.                 [stat.pwcsName],0,STGM_READ,0
  81.  
  82.         ; Связать объект потока с файлом
  83.         lea     eax,[extracted]
  84.         invoke  SHCreateStreamOnFile,ebx,STGM_WRITE+STGM_CREATE,eax
  85.  
  86.         ; Извлечь файл
  87.         mov     eax,[stream]
  88.         mov     eax,[eax]
  89.         stdcall dword [eax+IStream.CopyTo],[stream],\
  90.                 [extracted],\
  91.                 [stat.cbSize.HighPart],[stat.cbSize.LowPart],\
  92.                 NULL,cbSize
  93.  
  94.         ; Прибраться за собой
  95.         mov     eax,[extracted]
  96.         mov     eax,[eax]
  97.         stdcall dword [eax+IStream.Release],[extracted]
  98.  
  99.         mov     eax,[stream]
  100.         mov     eax,[eax]
  101.         stdcall dword [eax+IStream.Release],[stream]
  102.  
  103.         ; Освободить запись в куче
  104.         invoke  HeapFree,[hHeap],0,ebx
  105. .loc_next:
  106.         ; Освободить память, выделенную под имя файла
  107.         invoke  CoTaskMemFree,[stat.pwcsName]
  108.         jmp     .loc_scan_zip
  109. .loc_done_scan:
  110.         ; Освободить перечислитель
  111.         mov     eax,[enumerator]
  112.         mov     eax,[eax]
  113.         stdcall dword [eax+IEnumSTATSTG.Release],[enumerator]
  114. .loc_ret:
  115.         ; Освободить объект (под)каталога
  116.         mov     eax,[dStorage]
  117.         mov     eax,[eax]
  118.         stdcall dword [eax+IStorage.Release],[dStorage]
  119.  
  120.         ; Освободить запись в куче
  121.         invoke  HeapFree,[hHeap],0,[lpFolder]
  122.         ret
  123. endp
Через метод Next перечислителя поочередно перебираются файлы и подкаталоги, находящиеся на текущем уровне каталогом архива. Для каждого найденного объекта заполняется структура STATSTG. При ее обработке первым делом проверяется тип найденного объекта. Для каталога рекурсивно вызывается функция извлечения, а найденные файлы сохраняются на диск. Все прочие элементы архива, если таковые присутствуют, пропускаются. При сохранении файла функцией SHCreateStreamOnFile создается связанный с ним поток, после чего файл копируется в папку назначения.

При обработке архива совершенно не обязательно извлекать из него все файлы. Например, вы можете найти какой-то конкретный файл, проверяя его имя. Или же вместо копирования файла в папку назначения можно просто прочитать его содержимое и использовать в своей программе. Это очень удобно в том случае, когда ваша программа хранит в архиве какие-то данные.

В приложении пример программы с исходным текстом, которая распаковывает из архива все файлы с вложенными подкаталогами.

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

Extract.ZIP.Archive.Demo.zip (10,730 bytes)


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

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

Комментарии

Отзывы посетителей сайта о статье
Иван Коссей (19.03.2024 в 00:02):
Есть где-то пример распаковки ZIP с использованием микрософтовских функций CAB или ZLIB? Я сделал в ТАСМ32, и у меня не работает. Вылетает сразу в начале обработки потока с сообщением о неправильных данных. Работает прекрасно с вызовом UNZIP.
ManHunter (20.07.2023 в 19:35):
morgot, интересный способ, надо попробовать. Спасибо за наводку!
morgot (19.07.2023 в 17:51):
Интересно, почему негрософт не сделал нормальные документированные функции архивации? По факту, кроме убогих САВ, ничего нет. Заархивировать вот пробовали https://www.vbforums.com/showt...d-IDropTarge , но там изврат, по факту.
ManHunter (12.07.2023 в 15:44):
Сперва надо поискать интерфейс по названию или по фрагменту названия в GUID Helper, если он не найдет, что маловероятно, то да, только ручками. Ну и опять же, если не найдет, то желательно сразу сообщить мне с указанием источников. Попробую распарсить и добавить. Это ж и в моих интересах тоже.
Grey (12.07.2023 в 15:40):
Извини, не правильно вопрос поставил, он не конкретно к этой статье, статья - супер, а к СОМ-интерфейсам вообще.
Например есть интерфейс в dll или tlb библиотеке, как мне из него взять структуру для фасм, я полагаю только ручками переписывать из редактора midl или аналогичного.
ManHunter (12.07.2023 в 15:32):
Все равно не понял. В исходнике же есть примеры вызовов методов по таким структурам. А так все распарсено в соответствии с порядком следования методов в заголовочных файлах и т.п.
Grey (12.07.2023 в 15:28):
struct IShellFolder
    ; IUnknown
    QueryInterface   dd ?   ; 000h
    AddRef           dd ?   ; 004h
    Release          dd ?   ; 008h
    ; IShellFolder
    ParseDisplayName dd ?   ; 00Ch
.... и т.д.
в от источниках дают С или IDL вручную на фасм переделываешь?
ManHunter (12.07.2023 в 15:24):
Не понял вопрос. Все взято из официальных источников
Grey (12.07.2023 в 15:22):
Пропустил). А очередность и названия методов/функций?
ManHunter (12.07.2023 в 14:58):
GUID Helper же
Grey (12.07.2023 в 14:55):
Определение интерфейса из IDL в Fasm руками переводится или есть гдето на просторах программка для этих целей?

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

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

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