1
1
import ctypes
2
2
import ctypes .util
3
- from ctypes import c_void_p , c_size_t , c_int , py_object , pythonapi , CFUNCTYPE , c_ubyte
3
+ from ctypes import (
4
+ c_void_p , c_size_t , c_int , py_object , pythonapi , CFUNCTYPE , c_ubyte ,
5
+ c_long , c_ulong , c_ulonglong , POINTER )
4
6
import errno
5
7
import os
6
8
import platform
7
- import resource
8
9
import struct
9
10
import warnings
10
11
13
14
from .utils import cdata_ptr
14
15
from .refs import Py_INCREF
15
16
17
+ try :
18
+ import resource
19
+ except ImportError :
20
+ PAGE_SIZE = None # windows
21
+ else :
22
+ PAGE_SIZE = resource .getpagesize ()
16
23
17
- PAGE_SIZE = resource .getpagesize ()
18
24
25
+ IS_X86 = (platform .machine () in ('i386' , 'i486' , 'i586' , 'i686' , 'x86' , 'x86_64' ))
26
+ IS_WINDOWS = (platform .system () == 'Windows' )
27
+ IS_64BIT = ctypes .sizeof (c_void_p ) == ctypes .sizeof (c_ulonglong )
19
28
29
+ # <sys/mman.h> constants
20
30
PROT_NONE = 0 # The memory cannot be accessed at all
21
31
PROT_READ = 1 # The memory can be read
22
32
PROT_WRITE = 2 # The momory can be modified
23
33
PROT_EXEC = 4 # The memory can be executed
24
34
35
+ # Microsoft, what's the point of using bitmasks if you can't OR the values?
36
+ PAGE_NOACCESS = 0x01
37
+ PAGE_READONLY = 0x02
38
+ PAGE_READWRITE = 0x04
39
+ PAGE_WRITECOPY = 0x08
40
+ PAGE_EXECUTE = 0x10
41
+ PAGE_EXECUTE_READ = 0x20
42
+ PAGE_EXECUTE_READWRITE = 0x40
43
+
44
+ MS_PAGE_CONSTANTS = {
45
+ PROT_NONE : PAGE_NOACCESS ,
46
+ PROT_READ : PAGE_READONLY ,
47
+ PROT_WRITE : PAGE_READWRITE ,
48
+ (PROT_WRITE | PROT_READ ): PAGE_READWRITE ,
49
+ PROT_EXEC : PAGE_EXECUTE ,
50
+ (PROT_EXEC | PROT_READ ): PAGE_EXECUTE_READ ,
51
+ (PROT_EXEC | PROT_WRITE ): PAGE_EXECUTE_READWRITE ,
52
+ (PROT_EXEC | PROT_WRITE | PROT_READ ): PAGE_EXECUTE_READWRITE ,
53
+ }
54
+
55
+ # windows datatypes
56
+ BOOL = c_long
57
+ DWORD = c_ulong
58
+ SIZE_T = c_ulonglong if IS_64BIT else DWORD
59
+ LPVOID = c_void_p
60
+ PDWORD = POINTER (DWORD )
61
+
62
+
63
+ def mprotect_winapi (addr , size , flags ):
64
+ kernel32 = ctypes .windll .kernel32
65
+ VirtualProtect = kernel32 .VirtualProtect
66
+ VirtualProtect .argtypes = [LPVOID , SIZE_T , DWORD , PDWORD ]
67
+ VirtualProtect .restype = BOOL
68
+ old_prot = DWORD ()
69
+ prot = MS_PAGE_CONSTANTS [flags ]
70
+ ret = VirtualProtect (addr , size , prot , ctypes .byref (old_prot ))
71
+ if not ret :
72
+ raise ctypes .WinError ()
73
+
74
+
75
+ def mprotect_libc (addr , size , flags ):
76
+ libc = ctypes .CDLL (ctypes .util .find_library ('libc' ), use_errno = True )
77
+ libc .mprotect .argtypes = [c_void_p , c_size_t , c_int ]
78
+ libc .mprotect .restype = c_int
79
+ addr_align = addr & ~ (PAGE_SIZE - 1 )
80
+ memlen = PAGE_SIZE
81
+ # In the unlikely event that the first five bytes of the function occur
82
+ # across a page boundary, we need to call mprotect on two pages
83
+ if ((addr + size ) - addr_align ) > PAGE_SIZE :
84
+ memlen *= 2
85
+ ret = libc .mprotect (addr_align , memlen , flags )
86
+ if ret == - 1 :
87
+ e = ctypes .get_errno ()
88
+ raise OSError (e , errno .errorcodes [e ], os .strerror (e ))
89
+
25
90
26
- # TODO: Use VirtualProtect on windows
27
91
def mprotect (addr , size , flags ):
28
- if addr % PAGE_SIZE != 0 :
29
- raise Exception ("mprotect address must be aligned to a page boundary" )
30
92
if not isinstance (addr , seven .integer_types ):
31
93
raise ValueError ("addr must be an integer type" )
32
94
if not isinstance (size , seven .integer_types ):
@@ -38,13 +100,10 @@ def mprotect(addr, size, flags):
38
100
"flags must be a bitmask of PROT_NONE(%d), PROT_READ (%d), "
39
101
"PROT_WRITE (%d), and/or PROT_EXEC (%d)" % (
40
102
PROT_NONE , PROT_READ , PROT_WRITE , PROT_EXEC ))
41
- libc = ctypes .CDLL (ctypes .util .find_library ('libc' ), use_errno = True )
42
- libc .mprotect .argtypes = [c_void_p , c_size_t , c_int ]
43
- libc .mprotect .restype = c_int
44
- ret = libc .mprotect (addr , size , flags )
45
- if ret == - 1 :
46
- e = ctypes .get_errno ()
47
- raise OSError (e , errno .errorcodes [e ], os .strerror (e ))
103
+ if IS_WINDOWS :
104
+ mprotect_winapi (addr , size , flags )
105
+ else :
106
+ mprotect_libc (addr , size , flags )
48
107
49
108
50
109
def pycode_optimize (code , consts , name , lineno_obj ):
@@ -59,14 +118,6 @@ def pycode_optimize(code, consts, name, lineno_obj):
59
118
Override_PyCode_Optimize = quaternaryfunc (pycode_optimize )
60
119
61
120
62
- def is_x86 ():
63
- return platform .machine () in ('i386' , 'i486' , 'i586' , 'i686' , 'x86' , 'x86_64' )
64
-
65
-
66
- def is_windows ():
67
- return platform .system () == 'Windows'
68
-
69
-
70
121
class UnsupportedPlatformException (Exception ):
71
122
pass
72
123
@@ -80,21 +131,19 @@ def force_ord(char):
80
131
81
132
82
133
def override_cfunc (cfunc , new_cfunc ):
83
- if not is_x86 ():
134
+ """
135
+ Overrides a CFUNCTION by inserting a JMP instruction at the function's
136
+ address in memory.
137
+
138
+ If the new cfunction doesn't have the same prototype as the old one,
139
+ this will almost certainly cause a segfault.
140
+ """
141
+ if not IS_X86 :
84
142
raise UnsupportedPlatformException (
85
143
"override_cfunc() only works on x86 architectures" )
86
- if is_windows ():
87
- raise UnsupportedPlatformException (
88
- "override_cfunc() does not work on windows" )
89
144
old_cfunc_ptr = cdata_ptr (cfunc )
90
145
new_cfunc_ptr = cdata_ptr (new_cfunc )
91
- page_boundary = int ((old_cfunc_ptr // PAGE_SIZE ) * PAGE_SIZE )
92
- memlen = PAGE_SIZE
93
- # In the unlikely event that the first five bytes of the function occur
94
- # across a page boundary, we need to call mprotect on two pages
95
- if page_boundary + PAGE_SIZE - old_cfunc_ptr < 5 :
96
- memlen *= 2
97
- mprotect (page_boundary , memlen , PROT_READ | PROT_WRITE | PROT_EXEC )
146
+ mprotect (old_cfunc_ptr , 5 , PROT_READ | PROT_WRITE | PROT_EXEC )
98
147
offset = new_cfunc_ptr - old_cfunc_ptr
99
148
JMP = b'\xe9 '
100
149
opcodes = list (map (force_ord , JMP + struct .pack ('<l' , offset - 5 )))
0 commit comments