嵌ったポイント
ctypesを使うにあたり、変数の型の扱いで嵌りました。
ctypesで外部関数を実行する場合、argtypesで引数の型、restypeで戻り値の型を指定できます。
これを適切に行わないと外部関数が正しく動かない場合がありました。
以下は、型の指定を行わないで、Windows API「VirtualAllocEx/WriteProcessMemory」を実行した例です。
VirtualAllocExで割り当てたメモリ領域に、WriteProcessMemoryで書きこむ動作になります。
Pythonのエラーは発生しませんでしたが、WriteProcessMemoryの実行が998(ERROR_NOACCESS)で失敗しました。
<プログラム>
mem_address = kernel32.VirtualAllocEx(handle, 0, size, MEM_COMMIT, PAGE_READWRITE)
ret = kernel32.WriteProcessMemory(handle, mem_address, dll_pass.encode(), c_size_t(len(dll_pass)), byref(c_size_t(0)))
print("WriteProcessMemory(0以外なら成功): {}".format(ret))
print("GetLastError:{}\n".format(kernel32.GetLastError()))
<結果>
WriteProcessMemory(0以外なら成功): 0
GetLastError:998
ctypesで外部関数を実行した場合、デフォルトの動作では、戻り値はC言語のint型(32bit)として扱うそうです。
VirtualAllocExの戻り値は割り当てられたメモリのアドレスとなり、64bit環境だと64bitになるので、C言語のint型(32bit)に収まらず、引数として渡されたWriteProcessMemoryの実行がエラーになったと思われます。
次に、VirtualAllocExの戻り値をLPVOID型(任意の型へのポインタ)に指定してWriteProcessMemoryを実行すると、引数に関するPythonのエラーが発生します。
メモリアドレスを示す第2引数の型の不一致により、OverflowErrorが発生したという内容と思われます。
<プログラム>
kernel32.VirtualAllocEx.restype = LPVOID
mem_address = kernel32.VirtualAllocEx(handle, 0, size, MEM_COMMIT, PAGE_READWRITE)
ret = kernel32.WriteProcessMemory(handle, mem_address, dll_pass.encode(), c_size_t(len(dll_pass)), byref(c_size_t(0)))
print("WriteProcessMemory(0以外なら成功): {}".format(ret))
print("GetLastError:{}\n".format(kernel32.GetLastError()))
<結果>
ctypes.ArgumentError: argument 2: <class 'OverflowError'>: int too long to convert
最後に、WriteProcessMemoryの引数の型も指定すると、WriteProcessMemoryの実行が無事成功しました。
<プログラム>
kernel32.VirtualAllocEx.restype = LPVOID
mem_address = kernel32.VirtualAllocEx(handle, 0, size, MEM_COMMIT, PAGE_READWRITE)
kernel32.WriteProcessMemory.argtypes = [HANDLE, LPVOID, LPCVOID, c_size_t, POINTER(c_size_t)]
ret = kernel32.WriteProcessMemory(handle, mem_address, dll_pass.encode(), c_size_t(len(dll_pass)), byref(c_size_t(0)))
print("WriteProcessMemory(0以外なら成功): {}".format(ret))
print("GetLastError:{}\n".format(kernel32.GetLastError()))
<結果>
WriteProcessMemory(0以外なら成功): 1
GetLastError:0
ということで、ctypesで外部関数を実行する場合、argtypesで引数の型、restypeで戻り値の型を指定した方が良いようです。
厳密にどんな時に指定が必要かはよく分かっていないのですが、下記場合は最低でも型指定した方が良いと認識しました(全ての外部関数で指定した方が無難なのかも)。
- restypeの指定:外部関数の戻り値がC言語のint型として扱われると困る場合
- argtypesの指定:型指定で受け取った他外部関数の戻り値を引数に使用して外部関数を呼び出す場合