gmas80
gmas80

Reputation: 1268

Retrieve Python version from pyd built without such an information

On Windows, I have a Python library (*.pyd), and I need to check the linked Python version. It can be Python 2.7.x or 3.x.

When I run something like this using win32api (adapted from here: https://stackoverflow.com/a/20064736/2741329):

def get_pyd_python_version(pyd_path):
    """
    Get the python version required by the passed pyd library
    """
    import win32api
    print("- pyd path: %s" % str(pyd_path))
    try:
        info = win32api.GetFileVersionInfo(pyd_path, '\\')
    except win32api.error as e:
        print(str(e.strerror) + " > " + pyd_path)
        return ""

    python_version = "%s.%s.%s.%s" % (
        str(win32api.HIWORD(info['FileVersionMS'])),
        str(win32api.LOWORD(info['FileVersionMS'])),
        str(win32api.HIWORD(info['FileVersionLS'])),
        str(win32api.LOWORD(info['FileVersionLS'])))
    return python_version

I get: The specified resource type cannot be found in the image file.

The same function works as expected with other "properly" built pyd libraries (e.g., win32api.pyd).

Since Dependency Walker used with the incomplete pyd properly shows the DLL, I was wondering if there is a way to get programmatically (with Python) the version info from such a fact.

--- EDIT ---

Based both on pefile library and on @vyktor answer, I have got this working by downloading this fork: https://github.com/BlackXeronic/pefile_py3 and using such an approach (that also checks for the architecture):

from __future__ import absolute_import, print_function
import sys
sys.path.append('./pefile_py3-master')
import pefile

print("python env: v.%s.%s" % (sys.version_info.major, sys.version_info.minor))
print("pefile lib: v.%s" % pefile.__version__)

dll_path = r'C:/Python27/DLLs/bz2.pyd'
print("path to pyd: %s" % dll_path)

pe = pefile.PE(dll_path)
if pe.is_dll():
    for entry in pe.DIRECTORY_ENTRY_IMPORT:
        if "python" in entry.dll:
            if pe.FILE_HEADER.Machine == 0x14C:
                arch = "x86"
            elif pe.FILE_HEADER.Machine == 0x8664:
                arch = "x64"
            else:
                arch = "unknown"
            print(">>> importing %s: v.%s [%s] <<<" % (entry.dll, entry.dll[6:8], arch))

This is what I get using Python 3.4.3 interpreter:

python env: v.3.4
pefile lib: v.1.2.10-139
path to pyd: C:/Python27/DLLs/bz2.pyd
>>> importing python27.dll: v.27 [x86] <<<

Upvotes: 2

Views: 3936

Answers (1)

Vyktor
Vyktor

Reputation: 21007

I was testing this on C:\Python32\DLLs\bz2.pyd and I haven't seen any python related info in details (file just renamed to .dll):

bz2.pyc properties screenshot

The only relevant information to python version was inside IAT:

bz2.pyc imports screenshot

So you just have to parse PE file and look for import DLL names. There are few questions about loading IAT from file and I did this using pefile extension (suggested by gmas80 in a comment):

C:\Python27>python.exe
Python 2.7.2 (default, Jun 12 2011, 15:08:59) [MSC v.1500 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import pefile
>>> p = pefile.PE(r'C:\Python32\DLLs\bz2.pyd')
>>> p.is_dll()
True
>>> for entry in p.DIRECTORY_ENTRY_IMPORT:
...     print entry.dll
...
python32.dll
MSVCR90.dll
KERNEL32.dll
>>> p = pefile.PE(r'C:\Python27\DLLs\bz2.pyd')
>>> p.is_dll()
True
>>> for entry in p.DIRECTORY_ENTRY_IMPORT:
...     print entry.dll
...
python27.dll
MSVCR90.dll
KERNEL32.dll

And you just simply write a check that looks for pythonXX.dll string.

Unfortunately this only works for python 2.7 (maybe somebody could find a 3.2 fork of pefile).


As far as I know WinAPI itself doesn't offer simple way of looking for imports inside file, so using 3rd party extension that has all structures embedded is the best way of doing this.

Upvotes: 5

Related Questions