injectlib 0.0.2__tar.gz → 0.0.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. {injectlib-0.0.2 → injectlib-0.0.3}/MANIFEST.in +5 -1
  2. {injectlib-0.0.2/src/injectlib.egg-info → injectlib-0.0.3}/PKG-INFO +3 -2
  3. {injectlib-0.0.2 → injectlib-0.0.3}/README.md +14 -8
  4. {injectlib-0.0.2 → injectlib-0.0.3}/setup.py +22 -7
  5. injectlib-0.0.3/src/injectlib/__init__.py +0 -0
  6. {injectlib-0.0.2 → injectlib-0.0.3}/src/injectlib/api.py +16 -3
  7. injectlib-0.0.3/src/injectlib/cfuncs.py +245 -0
  8. injectlib-0.0.3/src/injectlib/channel.py +130 -0
  9. {injectlib-0.0.2 → injectlib-0.0.3}/src/injectlib/libs/dotnet/x64/bootstrap.dll +0 -0
  10. {injectlib-0.0.2 → injectlib-0.0.3}/src/injectlib/libs/dotnet/x64/worker_wpf.dll +0 -0
  11. {injectlib-0.0.2 → injectlib-0.0.3}/src/injectlib/libs/hook/x64/pywinmsg64.dll +0 -0
  12. injectlib-0.0.3/src/injectlib/libs/qt5/x64/qt5_srv.dll +0 -0
  13. injectlib-0.0.3/src/injectlib/libs/qt5/x64/qt5_srv.so +0 -0
  14. injectlib-0.0.3/src/injectlib/libs/qt5/x86/qt5_srv.dll +0 -0
  15. injectlib-0.0.3/src/injectlib/libs/qt6/x64/qt6_srv.dll +0 -0
  16. injectlib-0.0.3/src/injectlib/libs/qt6/x64/qt6_srv.so +0 -0
  17. injectlib-0.0.3/src/injectlib/linux_injector.py +112 -0
  18. {injectlib-0.0.2 → injectlib-0.0.3/src/injectlib.egg-info}/PKG-INFO +3 -2
  19. {injectlib-0.0.2 → injectlib-0.0.3}/src/injectlib.egg-info/SOURCES.txt +7 -1
  20. injectlib-0.0.3/src/injectlib.egg-info/requires.txt +3 -0
  21. injectlib-0.0.2/src/injectlib/cfuncs.py +0 -76
  22. injectlib-0.0.2/src/injectlib/channel.py +0 -69
  23. injectlib-0.0.2/src/injectlib/libs/qt/x64/qt_srv.dll +0 -0
  24. injectlib-0.0.2/src/injectlib.egg-info/requires.txt +0 -1
  25. {injectlib-0.0.2 → injectlib-0.0.3}/LICENSE +0 -0
  26. /injectlib-0.0.2/src/injectlib/__init__.py → /injectlib-0.0.3/pyproject.toml +0 -0
  27. {injectlib-0.0.2 → injectlib-0.0.3}/setup.cfg +0 -0
  28. {injectlib-0.0.2 → injectlib-0.0.3}/src/injectlib/injector.py +0 -0
  29. {injectlib-0.0.2 → injectlib-0.0.3}/src/injectlib/libs/dotnet/x64/Newtonsoft.Json.dll +0 -0
  30. {injectlib-0.0.2 → injectlib-0.0.3}/src/injectlib/libs/dotnet/x86/Newtonsoft.Json.dll +0 -0
  31. {injectlib-0.0.2 → injectlib-0.0.3}/src/injectlib/libs/dotnet/x86/bootstrap.dll +0 -0
  32. {injectlib-0.0.2 → injectlib-0.0.3}/src/injectlib/libs/dotnet/x86/worker_wpf.dll +0 -0
  33. {injectlib-0.0.2 → injectlib-0.0.3}/src/injectlib/libs/hook/x64/pywinmsg64u.dll +0 -0
  34. {injectlib-0.0.2 → injectlib-0.0.3}/src/injectlib/libs/hook/x86/pywinmsg32.dll +0 -0
  35. {injectlib-0.0.2 → injectlib-0.0.3}/src/injectlib/libs/hook/x86/pywinmsg32u.dll +0 -0
  36. {injectlib-0.0.2 → injectlib-0.0.3}/src/injectlib/sysinfo.py +0 -0
  37. {injectlib-0.0.2 → injectlib-0.0.3}/src/injectlib.egg-info/dependency_links.txt +0 -0
  38. {injectlib-0.0.2 → injectlib-0.0.3}/src/injectlib.egg-info/top_level.txt +0 -0
@@ -13,4 +13,8 @@ include src/injectlib/libs/hook/x86/pywinmsg32u.dll
13
13
  include src/injectlib/libs/hook/x64/pywinmsg64.dll
14
14
  include src/injectlib/libs/hook/x64/pywinmsg64u.dll
15
15
 
16
- include src/injectlib/libs/qt/x64/qt_srv.dll
16
+ include src/injectlib/libs/qt5/x86/qt5_srv.dll
17
+ include src/injectlib/libs/qt5/x64/qt5_srv.dll
18
+ include src/injectlib/libs/qt5/x64/qt5_srv.so
19
+ include src/injectlib/libs/qt6/x64/qt6_srv.dll
20
+ include src/injectlib/libs/qt6/x64/qt6_srv.so
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: injectlib
3
- Version: 0.0.2
3
+ Version: 0.0.3
4
4
  Summary: A set of Python modules to inject DLLs into applications for the Microsoft Windows
5
5
  Home-page: https://github.com/pywinauto/injectlib
6
6
  Author: Vasily Ryabov, Ilya Naumov, Boris Galochkin, Alexander Makarov and contributors
@@ -9,6 +9,7 @@ License: BSD-3-Clause
9
9
  Project-URL: GitHub, https://github.com/pywinauto/injectlib
10
10
  Keywords: windows gui .net inject testing test desktop dll wpf qt
11
11
  Platform: win32
12
+ Platform: linux
12
13
  Classifier: Development Status :: 5 - Production/Stable
13
14
  Classifier: Environment :: Console
14
15
  Classifier: Intended Audience :: Developers
@@ -28,7 +29,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
28
29
  Classifier: Topic :: Software Development :: Testing
29
30
  Requires-Python: >=3.7
30
31
  License-File: LICENSE
31
- Requires-Dist: pywin32
32
+ Requires-Dist: pywin32; platform_system == "Windows"
32
33
  Dynamic: author
33
34
  Dynamic: author-email
34
35
  Dynamic: classifier
@@ -52,28 +52,34 @@ Link to PyPi: <https://pypi.org/project/injectlib/>
52
52
  * MSBuild
53
53
  * MSVC >= 143
54
54
  * cmake >= 3.26.3
55
- * Qt >= 5.15.2, < 6
55
+ * Qt 5 >= 5.15.2, < 6
56
+ * Qt 6
56
57
 
57
58
  **NOTE**: You may use PyCharm + Visual Studio with C++/C# components for
58
59
  development
59
60
 
60
61
  ### Building Qt DLL
61
62
 
62
- This package compiles C++ binaries that link against **Qt 5** (>=5.15.2, <6).
63
- Qt is not bundled and must be installed separately before building.
63
+ This package compiles C++ binaries that link against **Qt 5** (>=5.15.2, <6) and **Qt 6**. Qt is not bundled and must be installed separately before building
64
64
 
65
65
  - **Windows** download options:
66
- - download via [Qt Online Installer](https://www.qt.io/download) and select the MSVC 2019 64-bit component. Registration required. Installation might be banned for some countries
67
- - via mirrors, for example, https://mirror.yandex.ru/mirrors/qt.io. Base package for Qt 5.15.2 can be found [here](https://mirror.yandex.ru/mirrors/qt.io/online/qtsdkrepository/windows_x86/desktop/qt5_5152/qt.qt5.5152.win64_msvc2019_64/5.15.2-0-202011130602qtbase-Windows-Windows_10-MSVC2019-Windows-Windows_10-X86_64.7z). No registration required.
66
+ - download via [Qt Online Installer](https://www.qt.io/download) and select the MSVC components. Registration required. Only 64-bit supported. Installation might be banned for some countries.
67
+ - via mirrors, for example, https://mirror.yandex.ru/mirrors/qt.io.
68
+ - Base package for Qt 5.15.2 x64 can be found [here](https://mirror.yandex.ru/mirrors/qt.io/online/qtsdkrepository/windows_x86/desktop/qt5_5152/qt.qt5.5152.win64_msvc2019_64/5.15.2-0-202011130602qtbase-Windows-Windows_10-MSVC2019-Windows-Windows_10-X86_64.7z). No registration required.
69
+ - Base package for Qt 5.15.2 x86 can be found [here](https://mirror.yandex.ru/mirrors/qt.io/online/qtsdkrepository/windows_x86/desktop/qt5_5152/qt.qt5.5152.win32_msvc2019/5.15.2-0-202011130602qtbase-Windows-Windows_10-MSVC2019-Windows-Windows_10-X86.7z). No registration required.
70
+ - Base package for Qt 6.9.3 can be found [here](https://mirror.yandex.ru/mirrors/qt.io/online/qtsdkrepository/windows_x86/desktop/qt6_693/qt6_693/qt.qt6.693.win64_msvc2022_64/6.9.3-0-202509261208qtbase-Windows-Windows_11_23H2-MSVC2022-Windows-Windows_11_23H2-X86_64.7z)
68
71
 
69
- Once installed, point CMake to your Qt installation by setting `CMAKE_PREFIX_PATH`:
72
+ Once installed, point CMake to your Qt installations by setting `CMAKE_PREFIX_PATH` and add the Qt runtime directories to `PATH`.
73
+
74
+ For example:
70
75
 
71
76
  ```bash
72
77
  # Windows (adjust version/compiler as needed)
73
- set CMAKE_PREFIX_PATH=C:/Qt/5.15.2/msvc2019_64
78
+ set CMAKE_PREFIX_PATH=C:\Qt\5.15.2\msvc2019;C:\Qt\5.15.2\msvc2019_64;C:\Qt\6.9.1\msvc2022_64
79
+ set PATH=C:\Qt\5.15.2\msvc2019\bin;C:\Qt\5.15.2\msvc2019_64\bin;C:\Qt\6.9.1\msvc2022_64\bin;%PATH%
74
80
  ```
75
81
 
76
- If CMake still cannot find Qt, verify the path contains a `lib/cmake/Qt5` subdirectory.
82
+ If CMake still cannot find Qt, verify the paths contain `lib/cmake/Qt5` and `lib/cmake/Qt6` subdirectories.
77
83
 
78
84
  ## License
79
85
 
@@ -12,6 +12,7 @@ from setuptools.dist import Distribution
12
12
  x86_cmake_arch_name, x64_cmake_arch_name = 'Win32', 'x64'
13
13
  x86_package_arch_name, x64_package_arch_name = 'x86', 'x64'
14
14
 
15
+ is_windows = sys.platform == 'win32'
15
16
  is_64bit = sys.maxsize > 2**32
16
17
  if is_64bit:
17
18
  arch_names_map = { x64_cmake_arch_name: x64_package_arch_name }
@@ -19,8 +20,19 @@ else:
19
20
  arch_names_map = { x86_cmake_arch_name: x86_package_arch_name }
20
21
 
21
22
  build_dirname = 'build_'
22
- build_dll_dirs = ['./backends/dotnet/', './backends/hook/', './backends/qt/']
23
- package_dll_dirs = ['./src/injectlib/libs/dotnet/', './src/injectlib/libs/hook/', './src/injectlib/libs/qt/']
23
+ if is_windows:
24
+ build_dll_dirs = ['./backends/dotnet/',
25
+ './backends/hook/',
26
+ './backends/qt5/']
27
+ package_dll_dirs = ['./src/injectlib/libs/dotnet/',
28
+ './src/injectlib/libs/hook/',
29
+ './src/injectlib/libs/qt5/']
30
+ else:
31
+ build_dll_dirs = ['./backends/qt5/']
32
+ package_dll_dirs = ['./src/injectlib/libs/qt5/']
33
+ if is_64bit:
34
+ build_dll_dirs.append('./backends/qt6/')
35
+ package_dll_dirs.append('./src/injectlib/libs/qt6/')
24
36
  cmake_dirs = build_dll_dirs
25
37
 
26
38
 
@@ -35,7 +47,10 @@ class SdistWithDlls(sdist):
35
47
 
36
48
  os.makedirs(build_dir, exist_ok=True)
37
49
 
38
- subprocess.check_call(['cmake', '-B ' + build_dir, '-S ' + cmake_dir, '-A ' + arch])
50
+ cmake_command = ['cmake', '-B ' + build_dir, '-S ' + cmake_dir]
51
+ if is_windows:
52
+ cmake_command.extend(['-A', arch])
53
+ subprocess.check_call(cmake_command)
39
54
  subprocess.check_call(['cmake', '--build', build_dir, '--config', 'Release'])
40
55
 
41
56
  # copy DLLs to the package
@@ -43,7 +58,7 @@ class SdistWithDlls(sdist):
43
58
  for build_dll_dir, package_dll_dir in zip(build_dll_dirs, package_dll_dirs):
44
59
  for root, _, files in os.walk(build_dll_dir + build_dirname + arch):
45
60
  for file in files:
46
- if file.endswith('.dll'):
61
+ if file.endswith(('.dll', '.so')):
47
62
  src_file_path = os.path.join(root, file)
48
63
  dest_file_path = os.path.join(package_dll_dir + arch_names_map.get(arch), file)
49
64
  os.makedirs(os.path.dirname(dest_file_path), exist_ok=True)
@@ -57,7 +72,7 @@ class BinaryDistribution(Distribution):
57
72
 
58
73
 
59
74
  setup(name='injectlib',
60
- version='0.0.2',
75
+ version='0.0.3',
61
76
  description='A set of Python modules to inject DLLs into applications for the Microsoft Windows',
62
77
  keywords="windows gui .net inject testing test desktop dll wpf qt",
63
78
  url="https://github.com/pywinauto/injectlib",
@@ -80,7 +95,7 @@ precision and coverage than standard OS APIs like MS UI Automation API or
80
95
  Win32 API. Other use cases are potentially possible at
81
96
  your own risk according to the law and the project license.
82
97
  """,
83
- platforms=['win32'],
98
+ platforms=['win32', 'linux'],
84
99
 
85
100
  packages=["injectlib"],
86
101
  package_dir={"": "src"},
@@ -106,7 +121,7 @@ your own risk according to the law and the project license.
106
121
  'Topic :: Software Development :: Libraries :: Python Modules',
107
122
  'Topic :: Software Development :: Testing',
108
123
  ],
109
- install_requires=['pywin32'],
124
+ install_requires=['pywin32; platform_system=="Windows"'],
110
125
  python_requires='>=3.7',
111
126
  cmdclass={'sdist': SdistWithDlls},
112
127
  )
File without changes
@@ -1,8 +1,12 @@
1
1
  import json
2
2
  import logging
3
+ import sys
3
4
 
4
- from .injector import Injector
5
5
  from .channel import Pipe
6
+ if sys.platform == "win32":
7
+ from .injector import Injector
8
+ else:
9
+ from .linux_injector import LinuxInjector as Injector
6
10
 
7
11
  logger = logging.getLogger(__package__)
8
12
 
@@ -51,6 +55,11 @@ class Singleton(type):
51
55
  class ConnectionManager(object, metaclass=Singleton):
52
56
  def __init__(self):
53
57
  self._pipes = {}
58
+ self._backend_config = {}
59
+
60
+ def register_backend(self, pid, backend_name, dll_name):
61
+ """Register backend to use when injecting into pid. Must be called before the first call_action for that pid."""
62
+ self._backend_config[pid] = (backend_name, dll_name)
54
63
 
55
64
  def _get_pipe(self, pid):
56
65
  if pid not in self._pipes:
@@ -65,8 +74,12 @@ class ConnectionManager(object, metaclass=Singleton):
65
74
  return pipe
66
75
  else:
67
76
  logger.info('Pipe {} not found, injecting dll to the process'.format(pipe_name))
68
- Injector(pid, 'dotnet', 'bootstrap')
69
- pipe.connect()
77
+ # Fallback to dotnet to keep compatibility
78
+ backend_name, dll_name = self._backend_config.get(pid, ('dotnet', 'bootstrap'))
79
+ Injector(pid, backend_name, dll_name)
80
+ if not pipe.connect():
81
+ raise InjectedRuntimeError(
82
+ "Injected server channel did not appear for pid {}".format(pid))
70
83
  return pipe
71
84
 
72
85
  def call_action(self, action_name, pid, **params):
@@ -0,0 +1,245 @@
1
+ """Some functions already exists in pywinauto, but for correctly
2
+ injector work needs full redefinition with ctypes compatible
3
+ """
4
+
5
+ import os
6
+ import sys
7
+ from ctypes import Structure, sizeof, alignment
8
+ from ctypes import c_char_p, c_wchar_p
9
+ from ctypes import POINTER
10
+
11
+ if sys.platform == "win32":
12
+ import win32con
13
+ from ctypes import windll
14
+ from ctypes.wintypes import BOOL, DWORD, HANDLE, LPVOID, LPCVOID
15
+ else:
16
+ import ctypes
17
+ from ctypes import c_int, c_long, c_ulonglong, c_void_p, byref
18
+
19
+
20
+ if sys.platform == "win32":
21
+ class SECURITY_ATTRIBUTES(Structure):
22
+ _fields_ = [
23
+ ('nLength', DWORD),
24
+ ('lpSecurityDescriptor', LPVOID),
25
+ ('bInheritHandle', BOOL),
26
+ ]
27
+
28
+ assert sizeof(SECURITY_ATTRIBUTES) == 12 or sizeof(SECURITY_ATTRIBUTES) == 24, sizeof(SECURITY_ATTRIBUTES)
29
+ assert alignment(SECURITY_ATTRIBUTES) == 4 or alignment(SECURITY_ATTRIBUTES) == 8, alignment(SECURITY_ATTRIBUTES)
30
+
31
+ PAGE_READWRITE = win32con.PAGE_READWRITE
32
+ WAIT_TIMEOUT = win32con.WAIT_TIMEOUT
33
+ PROCESS_ALL_ACCESS = win32con.PROCESS_ALL_ACCESS
34
+ VIRTUAL_MEM = (win32con.MEM_RESERVE | win32con.MEM_COMMIT)
35
+ LPCSTR = LPCTSTR = c_char_p
36
+ LPWTSTR = c_wchar_p
37
+ LPDWORD = PDWORD = POINTER(DWORD)
38
+ LPTHREAD_START_ROUTINE = LPVOID
39
+ LPSECURITY_ATTRIBUTES = POINTER(SECURITY_ATTRIBUTES)
40
+
41
+ OpenProcess = windll.kernel32.OpenProcess
42
+ OpenProcess.restype = HANDLE
43
+ OpenProcess.argtypes = (DWORD, BOOL, DWORD)
44
+
45
+ VirtualAllocEx = windll.kernel32.VirtualAllocEx
46
+ VirtualAllocEx.restype = LPVOID
47
+ VirtualAllocEx.argtypes = (HANDLE, LPVOID, DWORD, DWORD, DWORD)
48
+
49
+ ReadProcessMemory = windll.kernel32.ReadProcessMemory
50
+ ReadProcessMemory.restype = BOOL
51
+ ReadProcessMemory.argtypes = (HANDLE, LPCVOID, LPVOID, DWORD, DWORD)
52
+
53
+ WriteProcessMemory = windll.kernel32.WriteProcessMemory
54
+ WriteProcessMemory.restype = BOOL
55
+ WriteProcessMemory.argtypes = (HANDLE, LPVOID, LPCVOID, DWORD, DWORD)
56
+
57
+ CreateRemoteThread = windll.kernel32.CreateRemoteThread
58
+ CreateRemoteThread.restype = HANDLE
59
+ CreateRemoteThread.argtypes = (HANDLE, LPSECURITY_ATTRIBUTES, DWORD, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD)
60
+
61
+ GetModuleHandleA = windll.kernel32.GetModuleHandleA
62
+ GetModuleHandleA.restype = HANDLE
63
+ GetModuleHandleA.argtypes = (LPCTSTR,)
64
+
65
+ LoadLibraryA = windll.kernel32.LoadLibraryA
66
+ LoadLibraryA.restype = HANDLE
67
+ LoadLibraryA.argtypes = (LPCTSTR,)
68
+
69
+ GetModuleHandleW = windll.kernel32.GetModuleHandleW
70
+ GetModuleHandleW.restype = HANDLE
71
+ GetModuleHandleW.argtypes = (LPWTSTR,)
72
+
73
+ LoadLibraryW = windll.kernel32.LoadLibraryW
74
+ LoadLibraryW.restype = HANDLE
75
+ LoadLibraryW.argtypes = (LPWTSTR,)
76
+
77
+ GetProcAddress = windll.kernel32.GetProcAddress
78
+ GetProcAddress.restype = LPVOID
79
+ GetProcAddress.argtypes = (HANDLE, LPCTSTR)
80
+
81
+ WaitForSingleObject = windll.kernel32.WaitForSingleObject
82
+ WaitForSingleObject.restype = DWORD
83
+ WaitForSingleObject.argtypes = (HANDLE, DWORD)
84
+ else:
85
+ PTRACE_PEEKDATA = 2
86
+ PTRACE_POKEDATA = 5
87
+ PTRACE_CONT = 7
88
+ PTRACE_GETREGS = 12
89
+ PTRACE_SETREGS = 13
90
+ PTRACE_ATTACH = 16
91
+ PTRACE_DETACH = 17
92
+ RTLD_NOW = 2
93
+ RTLD_GLOBAL = 0x100
94
+ SIGTRAP = 5
95
+
96
+ libc = ctypes.CDLL(None, use_errno=True)
97
+ try:
98
+ libdl = ctypes.CDLL("libdl.so.2", use_errno=True)
99
+ except OSError:
100
+ libdl = libc
101
+
102
+ ptrace = libc.ptrace
103
+ ptrace.restype = c_long
104
+ ptrace.argtypes = (c_int, c_int, c_void_p, c_void_p)
105
+
106
+ waitpid = libc.waitpid
107
+ waitpid.restype = c_int
108
+ waitpid.argtypes = (c_int, POINTER(c_int), c_int)
109
+
110
+ dlopen = libdl.dlopen
111
+ dlopen.restype = c_void_p
112
+ dlopen.argtypes = (c_char_p, c_int)
113
+
114
+ class UserRegsStruct(Structure):
115
+ _fields_ = [
116
+ ("r15", c_ulonglong),
117
+ ("r14", c_ulonglong),
118
+ ("r13", c_ulonglong),
119
+ ("r12", c_ulonglong),
120
+ ("rbp", c_ulonglong),
121
+ ("rbx", c_ulonglong),
122
+ ("r11", c_ulonglong),
123
+ ("r10", c_ulonglong),
124
+ ("r9", c_ulonglong),
125
+ ("r8", c_ulonglong),
126
+ ("rax", c_ulonglong),
127
+ ("rcx", c_ulonglong),
128
+ ("rdx", c_ulonglong),
129
+ ("rsi", c_ulonglong),
130
+ ("rdi", c_ulonglong),
131
+ ("orig_rax", c_ulonglong),
132
+ ("rip", c_ulonglong),
133
+ ("cs", c_ulonglong),
134
+ ("eflags", c_ulonglong),
135
+ ("rsp", c_ulonglong),
136
+ ("ss", c_ulonglong),
137
+ ("fs_base", c_ulonglong),
138
+ ("gs_base", c_ulonglong),
139
+ ("ds", c_ulonglong),
140
+ ("es", c_ulonglong),
141
+ ("fs", c_ulonglong),
142
+ ("gs", c_ulonglong),
143
+ ]
144
+
145
+ def _wifstopped(status):
146
+ return (status & 0xff) == 0x7f
147
+
148
+ def _wstopsig(status):
149
+ return (status >> 8) & 0xff
150
+
151
+ def _ptrace(request, pid, address=0, data=0):
152
+ ctypes.set_errno(0)
153
+ result = ptrace(request, pid, c_void_p(address), c_void_p(data))
154
+ if result == -1 and ctypes.get_errno() != 0:
155
+ raise OSError(ctypes.get_errno(), os.strerror(ctypes.get_errno()))
156
+ return result
157
+
158
+ def ptrace_attach(pid):
159
+ _ptrace(PTRACE_ATTACH, pid)
160
+ status = c_int()
161
+ if waitpid(pid, byref(status), 0) < 0:
162
+ raise OSError(ctypes.get_errno(), os.strerror(ctypes.get_errno()))
163
+ if not _wifstopped(status.value):
164
+ raise RuntimeError("target did not stop after ptrace attach")
165
+
166
+ def ptrace_detach(pid):
167
+ _ptrace(PTRACE_DETACH, pid)
168
+
169
+ def ptrace_continue(pid):
170
+ _ptrace(PTRACE_CONT, pid)
171
+ status = c_int()
172
+ if waitpid(pid, byref(status), 0) < 0:
173
+ raise OSError(ctypes.get_errno(), os.strerror(ctypes.get_errno()))
174
+ if not _wifstopped(status.value):
175
+ raise RuntimeError("target did not stop after remote call")
176
+ return _wstopsig(status.value)
177
+
178
+ def ptrace_getregs(pid):
179
+ regs = UserRegsStruct()
180
+ _ptrace(PTRACE_GETREGS, pid, 0, ctypes.addressof(regs))
181
+ return regs
182
+
183
+ def ptrace_setregs(pid, regs):
184
+ _ptrace(PTRACE_SETREGS, pid, 0, ctypes.addressof(regs))
185
+
186
+ def read_target(pid, address, size):
187
+ data = bytearray()
188
+ word_size = sizeof(c_long)
189
+ offset = 0
190
+ while offset < size:
191
+ word = _ptrace(PTRACE_PEEKDATA, pid, address + offset, 0)
192
+ chunk = ctypes.string_at(byref(c_long(word)), word_size)
193
+ data.extend(chunk[:min(word_size, size - offset)])
194
+ offset += word_size
195
+ return bytes(data)
196
+
197
+ def write_target(pid, address, data):
198
+ word_size = sizeof(c_long)
199
+ offset = 0
200
+ while offset < len(data):
201
+ chunk = data[offset:offset + word_size]
202
+ if len(chunk) != word_size:
203
+ current = bytearray(read_target(pid, address + offset, word_size))
204
+ current[:len(chunk)] = chunk
205
+ chunk = bytes(current)
206
+ word = c_long.from_buffer_copy(chunk)
207
+ _ptrace(PTRACE_POKEDATA, pid, address + offset, word.value & ((1 << (word_size * 8)) - 1))
208
+ offset += word_size
209
+
210
+ def module_base_from_maps(pid, module_basename):
211
+ with open("/proc/{}/maps".format(pid), "r") as maps_file:
212
+ for line in maps_file:
213
+ parts = line.split()
214
+ if len(parts) < 6 or "x" not in parts[1]:
215
+ continue
216
+ path = parts[-1]
217
+ if os.path.basename(path) != module_basename:
218
+ continue
219
+ start = int(parts[0].split("-", 1)[0], 16)
220
+ offset = int(parts[2], 16)
221
+ return start - offset
222
+ return None
223
+
224
+ def local_module_for_address(address):
225
+ with open("/proc/{}/maps".format(os.getpid()), "r") as maps_file:
226
+ for line in maps_file:
227
+ parts = line.split()
228
+ if len(parts) < 6:
229
+ continue
230
+ start_s, end_s = parts[0].split("-", 1)
231
+ start = int(start_s, 16)
232
+ end = int(end_s, 16)
233
+ if start <= address < end:
234
+ return os.path.basename(parts[-1]), start - int(parts[2], 16)
235
+ return None, None
236
+
237
+ def remote_symbol_address(pid, local_symbol):
238
+ local_address = ctypes.cast(local_symbol, c_void_p).value
239
+ module_name, local_base = local_module_for_address(local_address)
240
+ if not module_name or not local_base:
241
+ return None
242
+ remote_base = module_base_from_maps(pid, module_name)
243
+ if not remote_base:
244
+ return None
245
+ return remote_base + (local_address - local_base)
@@ -0,0 +1,130 @@
1
+ import logging
2
+ import os
3
+ import socket
4
+ import sys
5
+ import time
6
+
7
+ logger = logging.getLogger(__package__)
8
+
9
+ if sys.platform == "win32":
10
+ import pywintypes
11
+ import win32file
12
+ import win32pipe
13
+ import winerror
14
+
15
+
16
+ class InjectedBrokenPipeError(Exception):
17
+ pass
18
+
19
+
20
+ if sys.platform == "win32":
21
+ class Pipe(object):
22
+ def __init__(self, name):
23
+ self.name = name
24
+ self.handle = None
25
+
26
+ def connect(self, n_attempts=30, delay=1):
27
+ for i in range(n_attempts):
28
+ try:
29
+ self.handle = win32file.CreateFile(
30
+ r'\\.\pipe\{}'.format(self.name),
31
+ win32file.GENERIC_READ | win32file.GENERIC_WRITE,
32
+ 0,
33
+ None,
34
+ win32file.OPEN_EXISTING,
35
+ 0,
36
+ None
37
+ )
38
+ win32pipe.SetNamedPipeHandleState(
39
+ self.handle,
40
+ win32pipe.PIPE_READMODE_MESSAGE | win32pipe.PIPE_WAIT,
41
+ None,
42
+ None,
43
+ )
44
+ logger.info('Connected to the pipe {}'.format(self.name))
45
+ break
46
+ except pywintypes.error as e:
47
+ if e.args[0] == winerror.ERROR_FILE_NOT_FOUND:
48
+ logger.warning('Attempt {}/{}: failed to connect to the pipe {}'.format(
49
+ i + 1, n_attempts, self.name))
50
+ time.sleep(delay)
51
+ else:
52
+ raise InjectedBrokenPipeError('Unexpected pipe error: {}'.format(e))
53
+ if self.handle is not None:
54
+ return True
55
+ return False
56
+
57
+ def transact(self, string):
58
+ try:
59
+ # TODO get preferred encoding from application
60
+ self.write(string, 'utf-8')
61
+ resp = win32file.ReadFile(self.handle, 64 * 1024)
62
+ return resp[1].decode('utf-8')
63
+ except pywintypes.error as e:
64
+ if e.args[0] == winerror.ERROR_BROKEN_PIPE:
65
+ raise InjectedBrokenPipeError("Broken pipe")
66
+ else:
67
+ raise InjectedBrokenPipeError('Unexpected pipe error: {}'.format(e))
68
+
69
+ def close(self):
70
+ win32file.CloseHandle(self.handle)
71
+
72
+ def write(self, string, encoding='utf-8'):
73
+ """Write string with the specified encoding to the named pipe."""
74
+ win32file.WriteFile(self.handle, string.encode(encoding))
75
+ win32file.FlushFileBuffers(self.handle)
76
+
77
+ else:
78
+ class Pipe(object):
79
+ def __init__(self, name):
80
+ self.name = name
81
+ self.path = "/tmp/injectlib_qt_{0}_{1}.sock".format(
82
+ os.getuid(), name.rsplit("_", 1)[-1])
83
+
84
+ def connect(self, n_attempts=30, delay=1):
85
+ for i in range(n_attempts):
86
+ client = None
87
+ try:
88
+ client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
89
+ client.connect(self.path)
90
+ return True
91
+ except OSError:
92
+ pass
93
+ finally:
94
+ if client is not None:
95
+ try:
96
+ client.close()
97
+ except Exception:
98
+ pass
99
+
100
+ if i < n_attempts - 1:
101
+ logger.warning('Attempt {}/{}: failed to connect to the socket {}'.format(
102
+ i + 1, n_attempts, self.path))
103
+ time.sleep(delay)
104
+ return False
105
+
106
+ def transact(self, string):
107
+ client = None
108
+ try:
109
+ client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
110
+ client.connect(self.path)
111
+ client.sendall(string.encode("utf-8"))
112
+ client.shutdown(socket.SHUT_WR)
113
+ chunks = []
114
+ while True:
115
+ chunk = client.recv(64 * 1024)
116
+ if not chunk:
117
+ break
118
+ chunks.append(chunk)
119
+ return b"".join(chunks).decode("utf-8")
120
+ except OSError as exc:
121
+ raise InjectedBrokenPipeError('Unexpected socket error: {}'.format(exc))
122
+ finally:
123
+ if client is not None:
124
+ try:
125
+ client.close()
126
+ except Exception:
127
+ pass
128
+
129
+ def close(self):
130
+ pass
@@ -0,0 +1,112 @@
1
+ import os
2
+ import platform
3
+ import struct
4
+
5
+ from . import cfuncs
6
+
7
+
8
+ class LinuxInjector(object):
9
+ """Load an injectlib shared object into a same-user Linux process."""
10
+
11
+ def __init__(self, pid, backend_name, dll_name):
12
+ self.pid = int(pid)
13
+ self.backend_name = backend_name
14
+ self.dll_name = dll_name
15
+ self._validate_process()
16
+ self.library_path = self._library_path()
17
+ self._inject_shared_object()
18
+
19
+ def _validate_process(self):
20
+ proc_dir = "/proc/{}".format(self.pid)
21
+ if not os.path.isdir(proc_dir):
22
+ raise RuntimeError("Target process does not exist: pid={}".format(self.pid))
23
+ if platform.machine().lower() not in ("x86_64", "amd64"):
24
+ raise RuntimeError("Linux Qt attach currently supports x86_64 only")
25
+
26
+ def _library_path(self):
27
+ arch = "x64"
28
+ path = os.path.join(
29
+ os.path.dirname(os.path.realpath(__file__)),
30
+ "libs",
31
+ self.backend_name,
32
+ arch,
33
+ "{}.so".format(self.dll_name),
34
+ )
35
+ if not os.path.isfile(path):
36
+ raise RuntimeError("Linux Qt server library is missing: {}".format(path))
37
+ return path
38
+
39
+ def _inject_shared_object(self):
40
+ remote_dlopen = cfuncs.remote_symbol_address(self.pid, cfuncs.dlopen)
41
+ if not remote_dlopen:
42
+ raise RuntimeError("Cannot resolve remote dlopen address for pid {}".format(self.pid))
43
+
44
+ cfuncs.ptrace_attach(self.pid)
45
+ saved_payload = None
46
+ saved_code = None
47
+ saved_regs = None
48
+ stack_addr = None
49
+ code_len = None
50
+ try:
51
+ regs = cfuncs.ptrace_getregs(self.pid)
52
+ saved_regs = cfuncs.UserRegsStruct()
53
+ for field, _ in cfuncs.UserRegsStruct._fields_:
54
+ setattr(saved_regs, field, getattr(regs, field))
55
+
56
+ code, path_addr = self._remote_dlopen_stub(remote_dlopen, regs.rsp)
57
+ code_len = len(code)
58
+ payload = self.library_path.encode("utf-8") + b"\0"
59
+ payload_len = ((128 + len(payload) + 7) // 8) * 8
60
+ stack_addr = (regs.rsp - payload_len - 128) & ~0xF
61
+ path_addr = stack_addr + 128
62
+ code, _ = self._remote_dlopen_stub(remote_dlopen, regs.rsp, path_addr)
63
+
64
+ saved_payload = cfuncs.read_target(self.pid, stack_addr, payload_len)
65
+ saved_code = cfuncs.read_target(self.pid, regs.rip, code_len)
66
+
67
+ stack_payload = bytearray(payload_len)
68
+ stack_payload[128:128 + len(payload)] = payload
69
+ cfuncs.write_target(self.pid, stack_addr, bytes(stack_payload))
70
+ cfuncs.write_target(self.pid, regs.rip, code)
71
+
72
+ regs.rip = saved_regs.rip
73
+ regs.rsp = stack_addr
74
+ cfuncs.ptrace_setregs(self.pid, regs)
75
+ stop_signal = cfuncs.ptrace_continue(self.pid)
76
+ if stop_signal != cfuncs.SIGTRAP:
77
+ raise RuntimeError("Remote dlopen stopped with unexpected signal {}".format(stop_signal))
78
+
79
+ after = cfuncs.ptrace_getregs(self.pid)
80
+ if after.rax == 0:
81
+ raise RuntimeError("Remote dlopen returned NULL for {}".format(self.library_path))
82
+ finally:
83
+ if saved_payload is not None and stack_addr is not None:
84
+ cfuncs.write_target(self.pid, stack_addr, saved_payload)
85
+ if saved_code is not None and saved_regs is not None and code_len is not None:
86
+ cfuncs.write_target(self.pid, saved_regs.rip, saved_code[:code_len])
87
+ if saved_regs is not None:
88
+ cfuncs.ptrace_setregs(self.pid, saved_regs)
89
+ cfuncs.ptrace_detach(self.pid)
90
+
91
+ if not self._target_has_library():
92
+ raise RuntimeError("Remote dlopen did not map {} in pid {}".format(
93
+ self.library_path, self.pid))
94
+
95
+ def _remote_dlopen_stub(self, remote_dlopen, stack_pointer, path_addr=None):
96
+ if path_addr is None:
97
+ path_addr = stack_pointer
98
+ code = bytearray()
99
+ code.extend(b"\x48\xb8")
100
+ code.extend(struct.pack("<Q", remote_dlopen))
101
+ code.extend(b"\x48\xbf")
102
+ code.extend(struct.pack("<Q", path_addr))
103
+ code.extend(b"\xbe")
104
+ code.extend(struct.pack("<I", cfuncs.RTLD_NOW | cfuncs.RTLD_GLOBAL))
105
+ code.extend(b"\xff\xd0")
106
+ code.extend(b"\xcc")
107
+ return bytes(code), path_addr
108
+
109
+ def _target_has_library(self):
110
+ real_library = os.path.realpath(self.library_path)
111
+ with open("/proc/{}/maps".format(self.pid), "r") as maps_file:
112
+ return any(real_library in line for line in maps_file)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: injectlib
3
- Version: 0.0.2
3
+ Version: 0.0.3
4
4
  Summary: A set of Python modules to inject DLLs into applications for the Microsoft Windows
5
5
  Home-page: https://github.com/pywinauto/injectlib
6
6
  Author: Vasily Ryabov, Ilya Naumov, Boris Galochkin, Alexander Makarov and contributors
@@ -9,6 +9,7 @@ License: BSD-3-Clause
9
9
  Project-URL: GitHub, https://github.com/pywinauto/injectlib
10
10
  Keywords: windows gui .net inject testing test desktop dll wpf qt
11
11
  Platform: win32
12
+ Platform: linux
12
13
  Classifier: Development Status :: 5 - Production/Stable
13
14
  Classifier: Environment :: Console
14
15
  Classifier: Intended Audience :: Developers
@@ -28,7 +29,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
28
29
  Classifier: Topic :: Software Development :: Testing
29
30
  Requires-Python: >=3.7
30
31
  License-File: LICENSE
31
- Requires-Dist: pywin32
32
+ Requires-Dist: pywin32; platform_system == "Windows"
32
33
  Dynamic: author
33
34
  Dynamic: author-email
34
35
  Dynamic: classifier
@@ -1,12 +1,14 @@
1
1
  LICENSE
2
2
  MANIFEST.in
3
3
  README.md
4
+ pyproject.toml
4
5
  setup.py
5
6
  src/injectlib/__init__.py
6
7
  src/injectlib/api.py
7
8
  src/injectlib/cfuncs.py
8
9
  src/injectlib/channel.py
9
10
  src/injectlib/injector.py
11
+ src/injectlib/linux_injector.py
10
12
  src/injectlib/sysinfo.py
11
13
  src/injectlib.egg-info/PKG-INFO
12
14
  src/injectlib.egg-info/SOURCES.txt
@@ -23,4 +25,8 @@ src/injectlib/libs/hook/x64/pywinmsg64.dll
23
25
  src/injectlib/libs/hook/x64/pywinmsg64u.dll
24
26
  src/injectlib/libs/hook/x86/pywinmsg32.dll
25
27
  src/injectlib/libs/hook/x86/pywinmsg32u.dll
26
- src/injectlib/libs/qt/x64/qt_srv.dll
28
+ src/injectlib/libs/qt5/x64/qt5_srv.dll
29
+ src/injectlib/libs/qt5/x64/qt5_srv.so
30
+ src/injectlib/libs/qt5/x86/qt5_srv.dll
31
+ src/injectlib/libs/qt6/x64/qt6_srv.dll
32
+ src/injectlib/libs/qt6/x64/qt6_srv.so
@@ -0,0 +1,3 @@
1
+
2
+ [:platform_system == "Windows"]
3
+ pywin32
@@ -1,76 +0,0 @@
1
- """Some functions already exists in pywinauto, but for correctly
2
- injector work needs full redefinition with ctypes compatible
3
- """
4
-
5
- import win32con
6
- from ctypes import Structure, sizeof, alignment
7
- from ctypes import c_char_p, c_wchar_p
8
- from ctypes import POINTER
9
- from ctypes import windll
10
- from ctypes.wintypes import BOOL, DWORD, HANDLE, LPVOID, LPCVOID
11
-
12
-
13
- class SECURITY_ATTRIBUTES(Structure):
14
- _fields_ = [
15
- ('nLength', DWORD),
16
- ('lpSecurityDescriptor', LPVOID),
17
- ('bInheritHandle', BOOL),
18
- ]
19
-
20
-
21
- assert sizeof(SECURITY_ATTRIBUTES) == 12 or sizeof(SECURITY_ATTRIBUTES) == 24, sizeof(SECURITY_ATTRIBUTES)
22
- assert alignment(SECURITY_ATTRIBUTES) == 4 or alignment(SECURITY_ATTRIBUTES) == 8, alignment(SECURITY_ATTRIBUTES)
23
-
24
- PAGE_READWRITE = win32con.PAGE_READWRITE
25
- WAIT_TIMEOUT = win32con.WAIT_TIMEOUT
26
- PROCESS_ALL_ACCESS = win32con.PROCESS_ALL_ACCESS
27
- VIRTUAL_MEM = (win32con.MEM_RESERVE | win32con.MEM_COMMIT)
28
- LPCSTR = LPCTSTR = c_char_p
29
- LPWTSTR = c_wchar_p
30
- LPDWORD = PDWORD = POINTER(DWORD)
31
- LPTHREAD_START_ROUTINE = LPVOID
32
- LPSECURITY_ATTRIBUTES = POINTER(SECURITY_ATTRIBUTES)
33
-
34
- OpenProcess = windll.kernel32.OpenProcess
35
- OpenProcess.restype = HANDLE
36
- OpenProcess.argtypes = (DWORD, BOOL, DWORD)
37
-
38
- VirtualAllocEx = windll.kernel32.VirtualAllocEx
39
- VirtualAllocEx.restype = LPVOID
40
- VirtualAllocEx.argtypes = (HANDLE, LPVOID, DWORD, DWORD, DWORD)
41
-
42
- ReadProcessMemory = windll.kernel32.ReadProcessMemory
43
- ReadProcessMemory.restype = BOOL
44
- ReadProcessMemory.argtypes = (HANDLE, LPCVOID, LPVOID, DWORD, DWORD)
45
-
46
- WriteProcessMemory = windll.kernel32.WriteProcessMemory
47
- WriteProcessMemory.restype = BOOL
48
- WriteProcessMemory.argtypes = (HANDLE, LPVOID, LPCVOID, DWORD, DWORD)
49
-
50
- CreateRemoteThread = windll.kernel32.CreateRemoteThread
51
- CreateRemoteThread.restype = HANDLE
52
- CreateRemoteThread.argtypes = (HANDLE, LPSECURITY_ATTRIBUTES, DWORD, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD)
53
-
54
- GetModuleHandleA = windll.kernel32.GetModuleHandleA
55
- GetModuleHandleA.restype = HANDLE
56
- GetModuleHandleA.argtypes = (LPCTSTR,)
57
-
58
- LoadLibraryA = windll.kernel32.LoadLibraryA
59
- LoadLibraryA.restype = HANDLE
60
- LoadLibraryA.argtypes = (LPCTSTR,)
61
-
62
- GetModuleHandleW = windll.kernel32.GetModuleHandleW
63
- GetModuleHandleW.restype = HANDLE
64
- GetModuleHandleW.argtypes = (LPWTSTR,)
65
-
66
- LoadLibraryW = windll.kernel32.LoadLibraryW
67
- LoadLibraryW.restype = HANDLE
68
- LoadLibraryW.argtypes = (LPWTSTR,)
69
-
70
- GetProcAddress = windll.kernel32.GetProcAddress
71
- GetProcAddress.restype = LPVOID
72
- GetProcAddress.argtypes = (HANDLE, LPCTSTR)
73
-
74
- WaitForSingleObject = windll.kernel32.WaitForSingleObject
75
- WaitForSingleObject.restype = DWORD
76
- WaitForSingleObject.argtypes = (HANDLE, DWORD)
@@ -1,69 +0,0 @@
1
- import pywintypes
2
- import time
3
- import win32file
4
- import win32pipe
5
- import winerror
6
- import logging
7
-
8
- logger = logging.getLogger(__package__)
9
-
10
-
11
- class InjectedBrokenPipeError(Exception):
12
- pass
13
-
14
-
15
- class Pipe(object):
16
- def __init__(self, name):
17
- self.name = name
18
- self.handle = None
19
-
20
- def connect(self, n_attempts=30, delay=1):
21
- for i in range(n_attempts):
22
- try:
23
- self.handle = win32file.CreateFile(
24
- r'\\.\pipe\{}'.format(self.name),
25
- win32file.GENERIC_READ | win32file.GENERIC_WRITE,
26
- 0,
27
- None,
28
- win32file.OPEN_EXISTING,
29
- 0,
30
- None
31
- )
32
- win32pipe.SetNamedPipeHandleState(
33
- self.handle,
34
- win32pipe.PIPE_READMODE_MESSAGE | win32pipe.PIPE_WAIT,
35
- None,
36
- None,
37
- )
38
- logger.info('Connected to the pipe {}'.format(self.name))
39
- break
40
- except pywintypes.error as e:
41
- if e.args[0] == winerror.ERROR_FILE_NOT_FOUND:
42
- logger.warning('Attempt {}/{}: failed to connect to the pipe {}'.format(i + 1, n_attempts,
43
- self.name))
44
- time.sleep(delay)
45
- else:
46
- raise InjectedBrokenPipeError('Unexpected pipe error: {}'.format(e))
47
- if self.handle is not None:
48
- return True
49
- return False
50
-
51
- def transact(self, string):
52
- try:
53
- # TODO get preferred encoding from application
54
- self.write(string, 'utf-8')
55
- resp = win32file.ReadFile(self.handle, 64 * 1024)
56
- return resp[1].decode('utf-8')
57
- except pywintypes.error as e:
58
- if e.args[0] == winerror.ERROR_BROKEN_PIPE:
59
- raise InjectedBrokenPipeError("Broken pipe")
60
- else:
61
- raise InjectedBrokenPipeError('Unexpected pipe error: {}'.format(e))
62
-
63
- def close(self):
64
- win32file.CloseHandle(self.handle)
65
-
66
- def write(self, string, encoding='utf-8'):
67
- """Write string with the specified encoding to the named pipe."""
68
- win32file.WriteFile(self.handle, string.encode(encoding))
69
- win32file.FlushFileBuffers(self.handle)
@@ -1 +0,0 @@
1
- pywin32
File without changes
File without changes