How to create Python ctypes structures for MS Wind

2019-07-24 04:27发布

问题:

I'm trying to translate this SO answer code into Python (and a bit from the other answer there) with the help of official documentation for PACKAGE_ID and PACKAGE_INFO.

I'm getting full package names by GetPackageFullName, but some of my structures aren't formed well as OpenPackageInfoByFullName gives Unicode characters that are jibberish to me. Also PackageIdFromFullName returns ERROR_INSUFFICIENT_BUFFER when called.

EDIT: As Paul Cornelius noticed, my code had issues with PACKAGE_INFO_REFERENCE. Now I have a problem converting buffer I got to structure or I messed up something before that. I provided a code to be run in Windows 10, Edge or Store app should be open prior to run (or open some other app and change the hardcoded text in code). The output shows that packageName (as the example field) is None:

import ctypes
import ctypes.wintypes

from win32api import OpenProcess, CloseHandle
from win32con import PROCESS_QUERY_INFORMATION, PROCESS_VM_READ
from win32gui import EnumChildWindows, EnumWindows
from win32process import GetWindowThreadProcessId

ERROR_SUCCESS = 0x0
ERROR_INSUFFICIENT_BUFFER = 0x7A
PACKAGE_FILTER_ALL_LOADED = 0x00000000
PACKAGE_FILTER_HEAD = 0x00000010
PACKAGE_INFORMATION_FULL = 0x00000100
PROCESS_QUERY_LIMITED_INFORMATION = 0x1000


class PACKAGE_INFO_REFERENCE(ctypes.Structure):

    _fields_ = [("reserved", ctypes.c_void_p)]


class PACKAGE_SUBVERSION(ctypes.Structure):

    _fields_ = [
        ("Revision", ctypes.wintypes.ATOM),
        ("Build", ctypes.wintypes.ATOM),
        ("Minor", ctypes.wintypes.ATOM),
        ("Major", ctypes.wintypes.ATOM),
    ]


class PACKAGE_VERSION(ctypes.Union):

    _fields_ = [("Version", ctypes.c_uint64), ("DUMMYSTRUCTNAME", PACKAGE_SUBVERSION)]


class PACKAGE_ID(ctypes.Union):

    _fields_ = [
        ("reserved", ctypes.c_uint32),
        ("processorArchitecture", ctypes.c_uint32),
        ("version", PACKAGE_VERSION),
        # ("VersionRevision", ctypes.wintypes.ATOM),
        # ("VersionBuild", ctypes.wintypes.ATOM),
        # ("VersionMinor", ctypes.wintypes.ATOM),
        # ("VersionMajor", ctypes.wintypes.ATOM),
        ("name", ctypes.c_wchar_p),
        ("publisher", ctypes.c_wchar_p),
        ("resourceId", ctypes.c_wchar_p),
        ("publisherId", ctypes.c_wchar_p),
    ]


class PACKAGE_INFO(ctypes.Union):

    _fields_ = [
        ("reserved", ctypes.c_uint32),
        ("flags", ctypes.c_uint32),
        ("path", ctypes.c_wchar_p),
        ("packageFullName", ctypes.c_wchar_p),
        ("packageFamilyName", ctypes.c_wchar_p),
        ("packageId", PACKAGE_ID),
    ]


def append_to_collection(element, collection):
    collection.append(element)
    return True


def get_children(hwnd):
    children = []
    try:
        EnumChildWindows(hwnd, append_to_collection, children)
    except:
        pass
    return children


def package_full_name_from_handle(handle):
    length = ctypes.c_uint()
    retval = ctypes.windll.kernel32.GetPackageFullName(
        handle, ctypes.byref(length), None
    )
    assert retval == ERROR_INSUFFICIENT_BUFFER

    full_name = ctypes.create_unicode_buffer(length.value + 1)
    retval = ctypes.windll.kernel32.GetPackageFullName(
        handle, ctypes.byref(length), full_name
    )
    assert retval == ERROR_SUCCESS

    return full_name


def package_path_from_full_name(full_name):
    length = ctypes.c_uint()
    retval = ctypes.windll.kernel32.GetPackagePathByFullName(
        ctypes.byref(full_name), ctypes.byref(length), None
    )
    assert retval == ERROR_INSUFFICIENT_BUFFER

    package_path = ctypes.create_unicode_buffer(length.value)
    retval = ctypes.windll.kernel32.GetPackagePathByFullName(
        ctypes.byref(full_name), ctypes.byref(length), ctypes.byref(package_path)
    )
    assert retval == ERROR_SUCCESS

    return package_path


def package_family_name_from_full_name(full_name):
    length = ctypes.c_uint()
    retval = ctypes.windll.kernel32.PackageFamilyNameFromFullName(
        ctypes.byref(full_name), ctypes.byref(length), None
    )
    assert retval == ERROR_INSUFFICIENT_BUFFER

    family_name = ctypes.create_unicode_buffer(length.value)
    retval = ctypes.windll.kernel32.PackageFamilyNameFromFullName(
        ctypes.byref(full_name), ctypes.byref(length), ctypes.byref(family_name)
    )
    assert retval == ERROR_SUCCESS

    return family_name


def package_info_reference_from_full_name(full_name):
    package_info_reference = ctypes.pointer(PACKAGE_INFO_REFERENCE())
    retval = ctypes.windll.kernel32.OpenPackageInfoByFullName(
        ctypes.byref(full_name), 0, ctypes.byref(package_info_reference)
    )
    assert retval == ERROR_SUCCESS

    return package_info_reference


def package_info_buffer_from_reference(package_info_reference):
    length = ctypes.c_uint(0)
    count = ctypes.c_uint()

    retval = ctypes.windll.kernel32.GetPackageInfo(
        package_info_reference,
        PACKAGE_FILTER_HEAD,
        ctypes.byref(length),
        None,
        ctypes.byref(count),
    )
    assert retval == ERROR_INSUFFICIENT_BUFFER

    buffer = ctypes.create_string_buffer(length.value)
    retval = ctypes.windll.kernel32.GetPackageInfo(
        package_info_reference,
        PACKAGE_FILTER_HEAD,
        ctypes.byref(length),
        ctypes.byref(buffer),
        ctypes.byref(count),
    )
    assert retval == ERROR_SUCCESS

    return buffer


def get_package(hwnd):
    hprocess = None
    _, pid = GetWindowThreadProcessId(hwnd)
    children = get_children(hwnd)
    for child in children:
        _, child_pid = GetWindowThreadProcessId(child)
        if child_pid != pid:
            # hprocess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, False, child_pid)
            hprocess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, child_pid)
            break

    if hprocess is None:
        return

    full_name = package_full_name_from_handle(hprocess.handle)
    if not (
        "Microsoft.MicrosoftEdge" in full_name.value
        or "Microsoft.WindowsStore" in full_name.value
    ):
        return None

    print("\n      full name:", full_name.value)
    package_path = package_path_from_full_name(full_name)
    print("   package path:", package_path.value)
    family_name = package_family_name_from_full_name(full_name)
    print("    family name:", family_name.value)
    package_info_reference = package_info_reference_from_full_name(full_name)
    print(" info reference:", package_info_reference.contents.reserved)

    package_info_buffer = package_info_buffer_from_reference(package_info_reference)
    package_info = PACKAGE_INFO()
    ctypes.memmove(
        ctypes.addressof(package_info), package_info_buffer, ctypes.sizeof(package_info)
    )
    print("packageFullName:", package_info.packageFullName)

    CloseHandle(hprocess)
    ctypes.windll.kernel32.ClosePackageInfo(package_info_reference)

def get_windows():
    hwnds = []
    EnumWindows(append_to_collection, hwnds)
    return hwnds


if __name__ == "__main__":
    for hwnd in get_windows():
        get_package(hwnd)

And the output is:

(venv) C:\dev\examples>python uwp.py

      full name: Microsoft.MicrosoftEdge_44.17763.1.0_neutral__8wekyb3d8bbwe
   package path: C:\Windows\SystemApps\Microsoft.MicrosoftEdge_8wekyb3d8bbwe
    family name: Microsoft.MicrosoftEdge_8wekyb3d8bbwe
 info reference: 4128769
packageFullName: None

      full name: Microsoft.WindowsStore_11805.1001.49.0_x64__8wekyb3d8bbwe
   package path: C:\Program Files\WindowsApps\Microsoft.WindowsStore_11805.1001.49.0_x64__8wekyb3d8bbwe
    family name: Microsoft.WindowsStore_8wekyb3d8bbwe
 info reference: 6619137
packageFullName: None

I would be grateful if someone takes a look at my code and reveals my mistakes.

回答1:

I "translated" your code to full ctypes (without the win32 package).

I patched alongside debugging it, but basically:

  • Some structures were declared as Union.
  • Lot of mismatch between pointer to types and types.
  • The ctypes.memove seemed ok, but I prefer <ctypes_struct>.from_buffer instead (shorter).

Tested on Windows 10 1903 (x64) and python 3.7.0 (x64).

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import ctypes
import ctypes.wintypes


ERROR_SUCCESS = 0x0
ERROR_INSUFFICIENT_BUFFER = 0x7A
APPMODEL_ERROR_NO_PACKAGE = 15700

PACKAGE_FILTER_ALL_LOADED = 0x00000000
PACKAGE_FILTER_HEAD = 0x00000010
PACKAGE_INFORMATION_FULL = 0x00000100
PROCESS_QUERY_LIMITED_INFORMATION = 0x1000

PROCESS_QUERY_INFORMATION = 0x0400
PROCESS_VM_READ =  0x0010


class PACKAGE_INFO_REFERENCE(ctypes.Structure):
    _fields_ = [
        ("reserved", ctypes.c_void_p)
    ]


class PACKAGE_SUBVERSION(ctypes.Structure):

    _fields_ = [
        ("Revision", ctypes.wintypes.USHORT),
        ("Build", ctypes.wintypes.USHORT),
        ("Minor", ctypes.wintypes.USHORT),
        ("Major", ctypes.wintypes.USHORT),
    ]


class PACKAGE_VERSION_U(ctypes.Union):
    _fields_ = [
        ("Version", ctypes.c_uint64),
        ("DUMMYSTRUCTNAME", PACKAGE_SUBVERSION),
    ]


class PACKAGE_VERSION(ctypes.Structure):
    _anonymous_ = ("u", )
    _fields_ = [
        ("u", PACKAGE_VERSION_U),
    ]


class PACKAGE_ID(ctypes.Structure):

    _fields_ = [
        ("reserved", ctypes.c_uint32),
        ("processorArchitecture", ctypes.c_uint32),
        ("version", PACKAGE_VERSION),
        ("name", ctypes.c_wchar_p),
        ("publisher", ctypes.c_wchar_p),
        ("resourceId", ctypes.c_wchar_p),
        ("publisherId", ctypes.c_wchar_p),
    ]


class PACKAGE_INFO(ctypes.Structure):

    _fields_ = [
        ("reserved", ctypes.c_uint32),
        ("flags", ctypes.c_uint32),
        ("path", ctypes.c_wchar_p),
        ("packageFullName", ctypes.c_wchar_p),
        ("packageFamilyName", ctypes.c_wchar_p),
        ("packageId", PACKAGE_ID),
    ]


_user32 = ctypes.WinDLL("user32", use_last_error=True)
_get_windows_thread_process_id = _user32.GetWindowThreadProcessId
_get_windows_thread_process_id.argtypes = (ctypes.wintypes.HWND, ctypes.POINTER(ctypes.wintypes.DWORD))
_get_windows_thread_process_id.restype = ctypes.wintypes.DWORD

WNDENUMPROC  = ctypes.WINFUNCTYPE(ctypes.wintypes.BOOL, ctypes.wintypes.HWND, ctypes.wintypes.LPARAM)

_enum_child_windows = _user32.EnumChildWindows
_enum_child_windows.argtypes = (ctypes.wintypes.HWND, WNDENUMPROC, ctypes.wintypes.LPARAM)
_enum_child_windows.restype = ctypes.wintypes.BOOL

_enum_windows = _user32.EnumWindows
_enum_windows.argtypes = (WNDENUMPROC, ctypes.wintypes.LPARAM)
_enum_windows.restype = ctypes.wintypes.BOOL

_kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
_open_process = _kernel32.OpenProcess
_open_process.argtypes = (ctypes.wintypes.DWORD, ctypes.wintypes.BOOL, ctypes.wintypes.DWORD)
_open_process.restype = ctypes.wintypes.HANDLE

_close_handle = _kernel32.CloseHandle
_close_handle.argtypes = (ctypes.wintypes.HANDLE, )
_close_handle.restype = ctypes.wintypes.BOOL

_get_package_info = _kernel32.GetPackageInfo
_get_package_info.argtypes = (
    PACKAGE_INFO_REFERENCE,
    ctypes.c_uint32,
    ctypes.POINTER(ctypes.c_uint32),
    ctypes.POINTER(ctypes.c_uint8),
    ctypes.POINTER(ctypes.c_uint32)
)
_get_package_info.restype = ctypes.wintypes.LONG

_get_package_full_name = _kernel32.GetPackageFullName
_get_package_full_name.argtypes = (ctypes.wintypes.HANDLE, ctypes.POINTER(ctypes.c_uint32), ctypes.wintypes.LPCWSTR)
_get_package_full_name.restype = ctypes.wintypes.LONG

_get_package_path_by_full_name = _kernel32.GetPackagePathByFullName
_get_package_path_by_full_name.argtypes = (ctypes.wintypes.LPCWSTR, ctypes.POINTER(ctypes.c_uint32), ctypes.wintypes.LPCWSTR)
_get_package_path_by_full_name.restype = ctypes.wintypes.LONG

_package_family_name_from_full_name = _kernel32.PackageFamilyNameFromFullName
_package_family_name_from_full_name.argtypes = (
    ctypes.wintypes.LPCWSTR,
    ctypes.POINTER(ctypes.c_uint32),
    ctypes.wintypes.LPCWSTR)
_package_family_name_from_full_name.restype = ctypes.wintypes.LONG

_open_package_info_by_full_name = _kernel32.OpenPackageInfoByFullName
_open_package_info_by_full_name.argtypes = (
    ctypes.wintypes.LPCWSTR,
    ctypes.c_uint32,
    ctypes.POINTER(PACKAGE_INFO_REFERENCE)
)
_open_package_info_by_full_name.restype = ctypes.wintypes.LONG

_close_package_info = _kernel32.ClosePackageInfo
_close_package_info.argtypes = (
    PACKAGE_INFO_REFERENCE,
)
_close_package_info.restype = ctypes.wintypes.LONG



def get_children(hwnd):
    children = []
    def append_to_collection(element, param):
        children.append(element)
        return True

    func = WNDENUMPROC(append_to_collection)
    _enum_child_windows(hwnd, func, 0)

    return children


def package_full_name_from_handle(handle):
    length = ctypes.c_uint()
    ret_val = _get_package_full_name(handle, ctypes.byref(length), None)
    if ret_val == APPMODEL_ERROR_NO_PACKAGE:
        print(f"package_full_name_from_handle: handle {handle:#x} has no package.")
        return None

    full_name = ctypes.create_unicode_buffer(length.value + 1)
    ret_val = _get_package_full_name(handle, ctypes.byref(length), full_name)
    if ret_val != ERROR_SUCCESS:
        err =  ctypes.WinError(ctypes.get_last_error())
        print(f"package_full_name_from_handle: error -> {str(err)}")
        return None

    return full_name


def package_path_from_full_name(full_name):
    length = ctypes.c_uint()
    retval = _get_package_path_by_full_name(full_name, ctypes.byref(length), None)
    if retval != ERROR_INSUFFICIENT_BUFFER:
        raise ctypes.WinError(ctypes.get_last_error())

    package_path = ctypes.create_unicode_buffer(length.value)
    retval = _get_package_path_by_full_name(full_name, ctypes.byref(length), package_path)
    if retval != ERROR_SUCCESS:
        raise ctypes.WinError(ctypes.get_last_error())

    return package_path


def package_family_name_from_full_name(full_name):
    length = ctypes.c_uint()
    retval = _package_family_name_from_full_name(full_name, ctypes.byref(length), None)
    if retval != ERROR_INSUFFICIENT_BUFFER:
        raise ctypes.WinError(ctypes.get_last_error())

    family_name = ctypes.create_unicode_buffer(length.value)
    retval = _package_family_name_from_full_name(full_name, ctypes.byref(length), family_name)
    if retval != ERROR_SUCCESS:
        raise ctypes.WinError(ctypes.get_last_error())

    return family_name


def package_info_reference_from_full_name(full_name):
    package_info_reference = ctypes.pointer(PACKAGE_INFO_REFERENCE())
    retval = _open_package_info_by_full_name(full_name, 0, package_info_reference)
    if retval != ERROR_SUCCESS:
        raise ctypes.WinError(ctypes.get_last_error())

    return package_info_reference


def package_info_buffer_from_reference(package_info_reference):
    length = ctypes.c_uint(0)
    count = ctypes.c_uint()

    retval = _get_package_info(
        package_info_reference.contents,  # package_info_reference is already a pointer. We want its content.
        PACKAGE_FILTER_HEAD,
        ctypes.byref(length),
        None,
        ctypes.byref(count),
    )
    if retval != ERROR_INSUFFICIENT_BUFFER:
        raise ctypes.WinError(ctypes.get_last_error())

    buffer = ctypes.create_string_buffer(length.value)
    buffer_bytes = ctypes.cast(buffer, ctypes.POINTER(ctypes.c_uint8))
    retval = _get_package_info(
        package_info_reference.contents,
        PACKAGE_FILTER_HEAD,
        ctypes.byref(length),
        buffer_bytes,
        ctypes.byref(count),
    )
    if retval != ERROR_SUCCESS:
        raise ctypes.WinError(ctypes.get_last_error())

    return buffer, length


def get_package(hwnd):
    pid = ctypes.wintypes.DWORD()
    _get_windows_thread_process_id(
        hwnd,
        ctypes.byref(pid)
    )

    hprocess = _open_process(PROCESS_QUERY_LIMITED_INFORMATION, False, pid)
    full_name = package_full_name_from_handle(hprocess)
    if not full_name:
        return
    else:
        print(full_name.value)

    '''
    children = get_children(hwnd)
    for child in children:
        child_pid = ctypes.wintypes.DWORD(0)
        _get_windows_thread_process_id(child, ctypes.byref(child_pid))
        if child_pid != pid:
            hprocess = _open_process(PROCESS_QUERY_LIMITED_INFORMATION, False, child_pid)
            break

    if hprocess is None:
        return

    full_name = package_full_name_from_handle(hprocess)
    if full_name is None:
        return

    if not ("Microsoft.MicrosoftEdge" in full_name.value or "Microsoft.WindowsStore" in full_name.value):
    return None
    '''

    print("=" * 79)
    print("full name: ", full_name.value)
    package_path = package_path_from_full_name(full_name)
    print("package path: ", package_path.value)
    family_name = package_family_name_from_full_name(full_name)
    print("family name:", family_name.value)
    package_info_reference = package_info_reference_from_full_name(full_name)
    print("info reference:", package_info_reference.contents.reserved)

    package_info_buffer, length = package_info_buffer_from_reference(package_info_reference)
    # size_package_info = ctypes.sizeof(PACKAGE_INFO)
    # print(f"PACKAGE_INFO size: {size_package_info:#x}")
    # print(f"num package info: {length.value / size_package_info}")
    package_info = PACKAGE_INFO.from_buffer(package_info_buffer)
    print("packageFullName:", package_info.packageFullName)
    print("=" * 79)

    _close_handle(hprocess)
    _close_package_info(package_info_reference.contents)

def get_windows():
    hwnds = []
    def append_to_collection(element, param):
        hwnds.append(element)
        return True
    func = WNDENUMPROC(append_to_collection)
    _enum_windows(func, 0)
    return hwnds


if __name__ == "__main__":
    for hwnd in get_windows():
        get_package(hwnd)

Sample output:

===============================================================================
full name:  Microsoft.Windows.Cortana_1.12.3.18362_neutral_neutral_cw5n1h2txyewy
package path:  C:\Windows\SystemApps\Microsoft.Windows.Cortana_cw5n1h2txyewy
family name: Microsoft.Windows.Cortana_cw5n1h2txyewy
info reference: 2882563979504
packageFullName: Microsoft.Windows.Cortana_1.12.3.18362_neutral_neutral_cw5n1h2txyewy
===============================================================================