리버싱을 하다보면 필요에 따라 자신만의 툴을 만드는 경우가 종종 있습니다.

툴을 만들다보면 윈도우 API 를 사용해야 할 때가 있는데...

파이썬에서 ctypes 로 윈도우 API 를 사용하는 방법을 정리해봤습니다.


우선은 ctypes 와 ctypes.wintypes(자료형 모음) 를 임포트 해줍니다.

from ctypes import *
from ctypes.wintypes import *


윈도우 API  는 대체로 windll 을 이용하면 되는데요~

아래처럼 모듈 이름과 함수 이름을 적어주면 됩니다.

# Type 1
GetModuleFileName = windll.kernel32.GetModuleFileNameW
GetModuleHandle = windll.kernel32.GetModuleHandleW

# Type 2
Kernel32 = windll.kernel32

GetModuleFileName = Kernel32.GetModuleFileNameW
GetModuleHandle = Kernel32.GetModuleHandleW


입력하기 쉽게 "GetModuleFileName", "GetModuleHandle" 로 했을 뿐...

windll.kernel32.GetModuleFileNameW / Kernel32.GetModuleFileNameW 이나

windll.kernel32.GetModuleHandleW / Kernel32.GetModuleHandleW 를 그대로 사용해도 됩니다.

사용하려는 API 가 많을 경우 Type 2 처럼 사용하는게 더 편하지 않을까 생각되네요 ^^;;;


API 사용 예 #1 - GetModuleHandleW

from ctypes import *
from ctypes.wintypes import *

Kernel32 = windll.kernel32

print("[*] GetModuleHandleW [*]")
Kernel32_BaseAddr = Kernel32.GetModuleHandleW("KERNEL32.DLL")
print("    - KERNEL32.DLL = 0x%X" % Kernel32_BaseAddr)

GetModuleHandleW 호출 결과

가장 단순한 형태의 사용 예입니다. 그냥 인자를 넣어주기만 하면 되는거죠~ :)

참고로 64비트 파이썬에서는 GetModuleHandleW 를 호출하기 전에...

restype 을 직접 지정해줘야 주소값을 제대로 가져옵니다.

Kernel32.GetModuleHandleW.restype = c_void_p
Kernel32_BaseAddr = Kernel32.GetModuleHandleW("KERNEL32.DLL")


API 사용 예 #2 - GetModuleFileNameW

from ctypes import *
from ctypes.wintypes import *

Kernel32 = windll.kernel32

path = create_unicode_buffer(MAX_PATH)

print("[*] GetModuleFileNameW [*]")
Kernel32.GetModuleFileNameW(0, path, MAX_PATH)
print("    - Path = %s" % path.value)

GetModuleFileNameW 호출 결과

윈도우 API 중에는 GetModuleFileName 처럼 데이터를 담을 버퍼를 인자로 받아서

그 버퍼에 데이터를 넘겨주는 방식도 있습니다.


create_string_buffer, create_unicode_buffer 로 데이터를 담을 수 있는 객체를 만들 수 있는데요...

create_string_buffer 는 C 언어의 "char *", 파이썬의 "bytes" 와 대응되며,

create_unicode_buffer 는 C 언어의 "wchar *", 파이썬의 "str" 과 대응됩니다.

~A 계열 함수를 사용할 때는 create_string_buffer 를 사용하고,

~W 계열 함수를 사용할 때는 create_unicode_buffer 를 사용하면 됩니다.

객체에 담겨진 실제 데이터는 value 를 통해 얻을 수 있습니다.

create_string_buffer / create_unicode_buffer


API 사용 예 #3 - CreateProcessW

from ctypes import *
from ctypes.wintypes import *


class PROCESS_INFORMATION(Structure):
    _fields_ = [("hProcess", HANDLE),
                ("hThread", HANDLE),
                ("dwProcessId", DWORD),
                ("dwThreadId", DWORD)]


class STARTUPINFO(Structure):
    _fields_ = [('cb', DWORD),
                ('lpReserved', LPWSTR),
                ('lpDesktop', LPWSTR),
                ('lpTitle', LPWSTR),
                ('dwX', DWORD),
                ('dwY', DWORD),
                ('dwXSize', DWORD),
                ('dwYSize', DWORD),
                ('dwXCountChars', DWORD),
                ('dwYCountChars', DWORD),
                ('dwFillAttribute', DWORD),
                ('dwFlags', DWORD),
                ('wShowWindow', WORD),
                ('cbReserved2', WORD),
                ('lpReserved2', LPBYTE),
                ('hStdInput', HANDLE),
                ('hStdOutput', HANDLE),
                ('hStdError', HANDLE)]


Kernel32 = windll.kernel32

startupinfo = STARTUPINFO()
processinfo = PROCESS_INFORMATION()

print("[*] CreateProcessW [*]")
Kernel32.CreateProcessW("C:\\Windows\\NOTEPAD.exe", None, None, None, 0, 0,
                        None, None, byref(startupinfo), byref(processinfo))
print("    - hProcess = %X" % processinfo.hProcess)
print("    - dwProcessId = %d (%X)" % (processinfo.dwProcessId, processinfo.dwProcessId))

CreateProcessW 호출 결과

앞의 두 예제와 비교하면 코드의 양이 꽤 깁니다...;;; ( 이게 다 구조체 때문임.. =_=;;;; )


파이썬은 C 언어의 '구조체' 를 그대로 사용할 수 없기 때문에...

'구조체' 를 인자로 받는 API 를 사용하기 위해서는 추가 작업이 필요합니다.

ctypes 의 "Structure" 클래스를 상속받아서 임의의 클래스를 만든 다음...

"_fields_" 에 구조체 멤버들을 추가해주면 됩니다.

함수 인자에 참조 연산자('&')를 사용하는 경우가 있는데 파이썬은 "byref" 를 이용하면 됩니다.



이상의 세가지 형태의 API 사용 방법을 숙지하고 있으면...

대부분의 윈도우 API 는 문제없이 사용할 수 있을거라 생각합니다. @_@;;;