Перейти к содержанию
darkIT

Нужен совет знатока winapi + python (ctypes)

Рекомендуемые сообщения

Привет. Очень нужна помощь. Сразу скажу - код не мой. Задача: извлечь ресурсы Icon, IconGroup и VersionInfo из одного .exe, и вставить в другой.  

Spoiler

import ctypes import ctypes.wintypes # ctypes functions LoadLibraryEx = ctypes.windll.kernel32.LoadLibraryExW FreeLibrary = ctypes.windll.kernel32.FreeLibrary EnumResourceNames = ctypes.windll.kernel32.EnumResourceNamesA EnumResourceNameCallback = ctypes.WINFUNCTYPE( ctypes.wintypes.BOOL, ctypes.wintypes.HMODULE, ctypes.wintypes.LONG, ctypes.wintypes.LONG, ctypes.wintypes.LONG) FindResource = ctypes.windll.kernel32.FindResourceA LoadResource = ctypes.windll.kernel32.LoadResource FreeResource = ctypes.windll.kernel32.FreeResource SizeofResource = ctypes.windll.kernel32.SizeofResource LockResource = ctypes.windll.kernel32.LockResource UnlockResource = lambda x: None # hehe CloseHandle = ctypes.windll.kernel32.CloseHandle LoadString = ctypes.windll.user32.LoadStringA BeginUpdateResource = ctypes.windll.kernel32.BeginUpdateResourceA EndUpdateResource = ctypes.windll.kernel32.EndUpdateResourceA UpdateResource = ctypes.windll.kernel32.UpdateResourceA GetLastError = ctypes.windll.kernel32.GetLastError # resource types RT_CURSOR = 1 # Hardware-dependent cursor resource. RT_BITMAP = 2 # Bitmap resource. RT_ICON = 3 # Hardware-dependent icon resource. RT_MENU = 4 # Menu resource. RT_DIALOG = 5 # Dialog box. RT_STRING = 6 # String-table entry. RT_FONTDIR = 7 # Font directory resource. RT_FONT = 8 # Font resource. RT_ACCELERATOR = 9 # Accelerator table. RT_RCDATA = 10 # Application-defined resource (raw data.) RT_MESSAGETABLE = 11 # Message-table entry. RT_VERSION = 16 # Version resource. RT_DLGINCLUDE = 17 # Allows a resource editing tool to associate a string with an .rc file. Typically, the string is the name of the header file that provides symbolic names. The resource compiler parses the string but otherwise ignores the value. For example, RT_PLUGPLAY = 19 # Plug and Play resource. RT_VXD = 20 # VXD. RT_ANICURSOR = 21 # Animated cursor. RT_ANIICON = 22 # Animated icon. RT_HTML = 23 # HTML resource. RT_MANIFEST = 24 # Side-by-Side Assembly Manifest. RT_GROUP_CURSOR = RT_CURSOR + 11 # Hardware-independent cursor resource. RT_GROUP_ICON = RT_ICON + 11 # Hardware-independent icon resource. # LoadLibrary flags DONT_RESOLVE_DLL_REFERENCES = 0x1 LOAD_LIBRARY_AS_DATAFILE = 0x2 LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x20 # locales # LOCAL_EN_US = 0x0800 LOCAL_EN_US = 1033 class ResourceEditor(object): def __init__(self, filename): self.filename = filename def update_resources(self, resources): language = LOCAL_EN_US update_handle = BeginUpdateResource(self.filename, False) for type, name, data in resources: print(type, name, language, len(data)) ret = UpdateResource(update_handle, type, name, language, data, len(data)) #ВЫЗОВ GetLastError # print(ctypes.windll.kernel32.GetLastError()) ret = EndUpdateResource(update_handle, False) print(ret) return ret == 1 def get_resources(self, resource_types): """Retrieves the manifest(s) embedded in the current executable""" self.module = LoadLibraryEx(self.filename, 0, DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_IMAGE_RESOURCE) if self.module == 0: raise Exception("Can't read resources from file %s" % self.filename) manifests = [] def callback(hModule, lpType, lpName, lParam): hResource = FindResource(hModule, lpName, lpType) size = SizeofResource(hModule, hResource) hData = LoadResource(hModule, hResource) try: ptr = LockResource(hData) try: manifests.append((lpType, lpName, ctypes.string_at(ptr, size))) finally: UnlockResource(hData) finally: FreeResource(hData) return True for resource_type in resource_types: EnumResourceNames(self.module, resource_type, EnumResourceNameCallback(callback), None) FreeLibrary(self.module) return manifests def clone_file(source, dest): re_from = ResourceEditor(source) re_to = ResourceEditor(dest) resources = [] resources += re_from.get_resources([RT_GROUP_ICON, RT_ICON, RT_VERSION]) # add the contents of the source file to resource RT_RCDATA 1 # resources += [(RT_RCDATA, 1, open(source, "rb").read())] return re_to.update_resources(resources) clone_file("SOURCE FILE PATH", "DEST FILE PATH")

 

Код не работает, потому-что функция UpdateResourceA возвращает ошибку 87. Я уже понял, что это из за неправильного аргумента, но я не могу найти из за какого конкретно

Ведь когда я смотрю документацию, в коде вроде-бы все нормально сделано. Знаю, что не лучшее решение делать все эти вещи на питоне, но это единственный язык которым я умею пользоваться. Если кто-нибудь, обращался к winapi из питона, буду признателен за совет. Не советуйте читать документацию msdn, я уже этим занимаюсь третий день, несмотря на то что, она для меня кажется пугающе сложной.

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Не пробовал питоновскую либу pefile ? Мб там то, что тебе нужно, сделать проще, чем через винапи (я ее плохо знаю, работал только с секциями импорта и т.д., но вещь очень хорошая).

Так в коде хз что, могу разве что на чистый Си попробовать переписать (питон не люблю).

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Цитата
9 hours ago, Quake3 said:

Не пробовал питоновскую либу pefile ? Мб там то, что тебе нужно, сделать проще, чем через винапи (я ее плохо знаю, работал только с секциями импорта и т.д., но вещь очень хорошая).

Так в коде хз что, могу разве что на чистый Си попробовать переписать (питон не люблю).

Знаю про pefile, крутая либа. Но через winapi функции BeginUpdateResource, UpdateResource, EndUpdateResource - намного проще.

В pefile, можно только побайтово дописать их в секцию, при этом нужно все смещения вручную выставлять, а для этого надо структуру pe файла хорошо знать, а она для меня еще более сложная, чем документация msdn ;) Короче, очень сложно жить. Надо мне не выебываться, а начать писать на си.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

UPD: Обнаружил, что функция UpdateResourceA, выдает ошибку 87 только потому, что перед ней функция BeginUpdateResourceA выдает ошибку 2, что в документации msdn значит: ERROR_FILE_NOT_FOUND, а это дает конкретики и упрощает задачу. Проблема решена, в параметрах BeginUpdateResourceA, путь к файлу должен быть представлен в виде строки байтов, вместо обычной строки.  

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Ну видишь, надо проверять каждый вызов апи (кроме тех, что возвращают void), а то искал одну ошибку, а была другая. И все же, желательно кодить на каком-то другом ЯП. Не хочешь Си, попробуй шарп, он проще, и в студии есть автодополнение к винапи. Хотя, я просто не люблю питон ) В нем нет типизации, вот что плохо.

Касаемо строк - я хз, какие строки юзает питон, но в винапи используются два вида:

1. Обычные ANSI строки (1 байт - 1 символ), функция в конце имени имеет букву А.

2. Юникод строки (UTF-16 LE ), функции с W - это нативный формат для винды (родной,т.к. в ядре NT все в юникоде, Анси функции в основном просто заглушки). Впрочем, для РЕ файла анси как раз норм.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Цитата
1 hour ago, Quake3 said:

Ну видишь, надо проверять каждый вызов апи (кроме тех, что возвращают void), а то искал одну ошибку, а была другая. И все же, желательно кодить на каком-то другом ЯП. Не хочешь Си, попробуй шарп, он проще, и в студии есть автодополнение к винапи. Хотя, я просто не люблю питон ) В нем нет типизации, вот что плохо.

Касаемо строк - я хз, какие строки юзает питон, но в винапи используются два вида: 

1. Обычные ANSI строки (1 байт - 1 символ), функция в конце имени имеет букву А.

2. Юникод строки (UTF-16 LE ), функции с W - это нативный формат для винды (родной,т.к. в ядре NT все в юникоде, Анси функции в основном просто заглушки). Впрочем, для РЕ файла анси как раз норм.

Ух, чувак. Ты прямо сейчас ответил на вопрос, который меня мучал последние 2 дня. Я то думаю, почему, например, LoadLibrary аж 4 штуки типа LoadLibraryA, LoadLibraryW, LoadLibraryExW итд. Теперь все стало понятнее) Вообще, я пока разбирался как обращаться к Winapi из питона, сам понял что это муть. Ибо ctypes тоже люди пишут, и многие функцие либо не работают, либо работают некорректно. И легче будет для этих дел попрактиковаться писать на си, и обращаться к winapi напрямую, чем тратить время и нервы на решение проблем и недочетов в ctypes.  

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Цитата
29 минут назад, darkIT сказал:

Я то думаю, почему, например, LoadLibrary аж 4 штуки

Много функций в винапи являются заглушками, т.к. в отличии от современных говноскриптов, история винапи идет с начала 90ых; и нельзя просто так взять и удалить функцию. Но добавлять новый функционал надо, поэтому можно встретить много функций вида CreateFile2, CreateFileEx и подобное.

Цитата
30 минут назад, darkIT сказал:

И легче будет для этих дел попрактиковаться писать на си, и обращаться к winapi напрямую, чем тратить время и нервы на решение проблем и недочетов в ctypes.

Поставь себе Visual Studio 2015 (есть фришная, там такой же функционал, как в pro) visual assist (на рутрекере см. крякнутый). И пробуй - будет автодополнение, в студии есть встроенный отладчик (жмешь F10-F11 и пошагово идешь по коду, выводятся значения переменных). Явно будет продуктивней, чем вот угадывать, какой параметр передать в питон.

 

ctypes может быть удобным, если надо какой-то РоС закодить или тест, или какой-то функционал к IDA / Dbg. Но учится лучше на нормальном нативном коде.

 

Будут вопросы по винапи / Си - спрашивай здесь.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Цитата
2 hours ago, Quake3 said:

ANSI строки

ты наверное имел ввиду ASCII? анси это же просто ассоциация стандартов

 

Цитата
2 hours ago, Quake3 said:

функции с W - это нативный формат для винды (родной,т.к. в ядре NT все в юникоде

не знал что у винды ядро на юникоде, жесть. а зачем было так хардкодить если можно было как в линуксе обойтись с вспомогательным байтом и работать в utf-8?

 

 

Цитата
1 hour ago, darkIT said:

Ух, чувак. Ты прямо сейчас ответил на вопрос, который меня мучал последние 2 дня. Я то думаю, почему, например, LoadLibrary аж 4 штуки типа LoadLibraryA, LoadLibraryW, LoadLibraryExW итд. Теперь все стало понятнее) Вообще, я пока разбирался как обращаться к Winapi из питона, сам понял что это муть. Ибо ctypes тоже люди пишут, и многие функцие либо не работают, либо работают некорректно. И легче будет для этих дел попрактиковаться писать на си, и обращаться к winapi напрямую, чем тратить время и нервы на решение проблем и недочетов в ctypes.  

это правильное решение, работать на таком хоть и не сильно лоу, но всетаки уровне винапи используя питон довольно бессмысленно. я если честно вообще не особо понимаю, как код выше из ресурс секции сам достает и определяет в каком смещении лежит нужный тебе ресурс, тк не вдавался так глубого в изучение PE. по твоей теме читай https://www.codeproject.com/Articles/47708/Modify-Update-resources-of-an-Exe-DLL-on-the-fly

 

основная проблема в том, что айди ресурса обычно хардкодится на этапе компиляции и заменить его вот так сходу через UpdateResource можно только заранее зная чему он собственно равен. судя по всему тебе нужно использовать https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-enumresourcenamesa

или

https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-enumresourcetypesexa

 

 

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Цитата
2 hours ago, h3xp1017 said:

ты наверное имел ввиду ASCII? анси это же просто ассоциация стандартов

 

не знал что у винды ядро на юникоде, жесть. а зачем было так хардкодить если можно было как в линуксе обойтись с вспомогательным байтом и работать в utf-8?

 

 

это правильное решение, работать на таком хоть и не сильно лоу, но всетаки уровне винапи используя питон довольно бессмысленно. я если честно вообще не особо понимаю, как код выше из ресурс секции сам достает и определяет в каком смещении лежит нужный тебе ресурс, тк не вдавался так глубого в изучение PE. по твоей теме читай https://www.codeproject.com/Articles/47708/Modify-Update-resources-of-an-Exe-DLL-on-the-fly

 

основная проблема в том, что айди ресурса обычно хардкодится на этапе компиляции и заменить его вот так сходу через UpdateResource можно только заранее зная чему он собственно равен. судя по всему тебе нужно использовать https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-enumresourcenamesa

или

https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-enumresourcetypesexa

 

 

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

 

Спасибо за ссылку. А код выше как раз и использует EnueResourceNamesA Вот кстати рабочая версия кода, ищет ресурсы, удаляет их. Затем дописывает ресурсы из источника PE и вставляет их. У меня успешно сработаботало в 5 из 5 PE файлах. Только, если исходный PE файл имеет несколько VersionInfo с разными языками, то нужно для их удалении передать нужный LanguageIdentifier в UpdateResource, значение взять отсюда xttps://docs.microsoft.com/en-us/windows/win32/intl/language-identifier-constants-and-strings В моем случае там только EN_US и RU, потому-что я использую этот скрипт для смены иконки PE файлах [Не смейтесь ;)] которые создает iexpress.exe на Windows RU  

Spoiler

import ctypes import ctypes.wintypes # ctypes functions LoadLibraryEx = ctypes.windll.kernel32.LoadLibraryExW FreeLibrary = ctypes.windll.kernel32.FreeLibrary EnumResourceNames = ctypes.windll.kernel32.EnumResourceNamesA EnumResourceNameCallback = ctypes.WINFUNCTYPE( ctypes.wintypes.BOOL, ctypes.wintypes.HMODULE, ctypes.wintypes.LONG, ctypes.wintypes.LONG, ctypes.wintypes.LONG) FindResource = ctypes.windll.kernel32.FindResourceA LoadResource = ctypes.windll.kernel32.LoadResource FreeResource = ctypes.windll.kernel32.FreeResource SizeofResource = ctypes.windll.kernel32.SizeofResource LockResource = ctypes.windll.kernel32.LockResource UnlockResource = lambda x: None # hehe BeginUpdateResource = ctypes.windll.kernel32.BeginUpdateResourceA EndUpdateResource = ctypes.windll.kernel32.EndUpdateResourceA UpdateResource = ctypes.windll.kernel32.UpdateResourceA GetLastError = ctypes.windll.kernel32.GetLastError # Resource Types RT_ICON = 3 RT_VERSION = 16 RT_GROUP_ICON = RT_ICON 11 # LoadLibrary flags DONT_RESOLVE_DLL_REFERENCES = 0x1 LOAD_LIBRARY_AS_DATAFILE = 0x2 LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x20 # Language Identifiers LOCAL_EN_US = 1033 LOCAL_RU = 1049 class ResourceEditor(object): def __init__(self, filename): self.filename = filename def update_resources(self, resources): language = LOCAL_EN_US filename = bytes(self.filename, encoding='utf8') update_handle = BeginUpdateResource(filename, False) for type_, name, data in resources: ret = UpdateResource(update_handle, type_, name, language, data, len(data)) ret = EndUpdateResource(update_handle, False) print(ret) return ret == 1 def delete_resources(self, resources): filename = bytes(self.filename, encoding='utf8') update_handle = BeginUpdateResource(filename, False) for type_, name, data in resources: if type_ == 16: UpdateResource(update_handle, type_, name, LOCAL_EN_US, None, 0) UpdateResource(update_handle, type_, name, LOCAL_RU, None, 0) else: ret = UpdateResource(update_handle, type_, name, LOCAL_EN_US, None, 0) ret = EndUpdateResource(update_handle, False) return ret == 1 def get_resources(self, resource_types): self.module = LoadLibraryEx(self.filename, 0, DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_IMAGE_RESOURCE) if self.module == 0: raise Exception("Can't read resources from file %s" % self.filename) manifests = [] def callback(hModule, lpType, lpName, lParam): hResource = FindResource(hModule, lpName, lpType) size = SizeofResource(hModule, hResource) hData = LoadResource(hModule, hResource) try: ptr = LockResource(hData) try: manifests.append((lpType, lpName, ctypes.string_at(ptr, size))) finally: UnlockResource(hData) finally: FreeResource(hData) return True for resource_type in resource_types: EnumResourceNames(self.module, resource_type, EnumResourceNameCallback(callback), None) FreeLibrary(self.module) return manifests def clone_file(source, dest): re_from = ResourceEditor(source) re_to = ResourceEditor(dest) del_resources = [] del_resources = re_to.get_resources([RT_GROUP_ICON, RT_ICON, RT_VERSION]) resources = [] resources = re_from.get_resources([RT_GROUP_ICON, RT_ICON, RT_VERSION]) re_to.delete_resources(del_resources) re_to.update_resources(resources) clone_file("p.exe", "pp.exe")

 

Visual Studio, кстати уже скачивается ;) Может, ребята, посоветуете еще и книженцию по си, чтоб с минимумом воды была и давала базу? А то книг очень много, разных годов. Глаза разбегаются.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Цитата
26 минут назад, darkIT сказал:

Может, ребята, посоветуете еще и книженцию по си, чтоб с минимумом воды была и давала базу?

От создателей языка - K

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

×