1.1 파이썬 설치
현재 책에서는 2.5버전으로 나와있고 이클립스를 이용하고 있지만 난 파이참을 활용하도록 하겠다.
파이참 설치와 시작하기, PyCharm Visual Studio VS Code 비교, 파이썬 특화 IDE
간단한 프로그램 구현은 주피터 노트북으로 무리 없이 테스트 해 보고 있었지만 작성한 프로그램 수와 규모...
blog.naver.com
설치 방법은 위 블로그를 참조하면 된다.
1.2 라이브러리
1.2.1 ctypes
ctypes 라이브러리는 동적 링크 라이브러리 함수의 호출을 가능하게 하고, 복잡한 C 데이터 타입을 사용할 수 있게 하며 메모리를 관리하는 로우레벨 함수들을 제공한다.
1.2.2 동적 라이브러리
동적 링크 라이브러리(DLL)이란 컴파일된 바이너리로서 프로세스가 실행될 때 해당 프로세스에 동적으로 링크된다. 이는 외부에 익스포트 함수를 제공하며 익스포트 함수의 이름을 이용해 해당 함수의 실제 메모리상 주소를 구한다.
- cdll() : 표준 cdecl 호출 규약을 이용하는 함수를 익스포트 하는 라이브러리를 로드하는데 사용한다.
- windll() : MS Win32 API 가 사용하는 stdcall 호출 규약을 이용하는 함수를 익스포트 하는 라이브러리를 로드하는데 사용한다.
- oledll() : windll() 방법과 동일하지만 익스포트 함수가 반환하는 값이 HRESULT라 가정한다. HRESULT는 MS 컴포넌트 객체 모델(COM)에서 에러 메시지를 반환하기 위해 특별히 사용되는 것이다.
1.2.3 printf()
C 런타임 함수인 printf()를 이용해 테스트 메시지를 출력해보도록 하겠습니다. 윈도우에서 C런타임 라이브러리는 msvcrt.dll 입니다.
from ctypes import *
import time
# msvcrt.dll 로드
msvcrt = cdll.msvcrt
# 바이트 문자열로 변환
message_string = b"Hello World!\n"
# 포맷 문자열도 바이트 문자열로 변환
msvcrt.printf(b"Testing : %s", message_string)
책에 나와있는 코드를 그대로 쓰면 오류가 발생합니다. 그 이유는 Python 3 버전에서의 문자열 처리 방식과 관련이 있습니다. ctypes를 사용할 때 C 함수는 바이트 문자열(char*)을 기대하지만, Python 3.x의 문자열은 기본적으로 유니코드(str)입니다. 이로 인해 첫 번째 문자만 처리되거나 잘못된 결과가 나올 수 있습니다.
※ 함수 호출 규약
10장 함수 호출 규약
10.1 함수 호출 규약함수 호출 규약(Calling Convention) : "함수를 호출할 때 파라미터를 어떤 식으로 전달하는가?"에 대한 규약스택이란 프로세스에서 정의된 메모리 공간이며 PE헤더에 그 크기가 명
hhack.tistory.com
위 게시물을 참고하자.
1.2.3 C 데이터 타입
C 타입 | 파이썬 타입 | ctypes |
char | 한 문자 | c_char |
wchar_t | 유니코드 한 문자 | c_wchar |
char | int / long | c_byte |
unsigned char | int / long | c_ubyte |
short | int / long | c_short |
unsigned short | int / long | c_ushort |
int | int / long | c_int |
unsigned int | int / long | c_uint |
long | int / long | c_long |
unsigned long | int / long | c_ulong |
long long | int / long | c_longlong |
unsigned long long | int / long | c_ulonglong |
float | float | c_float |
double | float | c_double |
char * (NULL terminated) | string 또는 None | c_char_p |
wchar_t * (NULL terminated) | unicode 또는 None | c_wchar_p |
void * | int/long 또는 None | c_void_p |
1.2.4 레퍼런스를 통한 파라미터 전달
C와 C++ 에서는 함수의 파라미터로 포인터를 전달하는 것이 일상적이다. ctypes에서 포인터를 함수의 파라미터로 전달하려면 byref() 함수를 사용한다. 즉, function_main(byref(parameter))와 같은 형태로 호출하면 된다.
[C언어]
#include <stdio.h>
void increment(int* value) {
*value += 1;
}
[Python]
from ctypes import *
# Windows에서는 msvcrt.dll이나 사용자가 만든 .dll 파일 로드
libc = CDLL("libexample.dll") # C 코드가 컴파일된 DLL 파일
# C 함수의 파라미터 타입 설정
libc.increment.argtypes = [POINTER(c_int)]
libc.increment.restype = None
# 파이썬에서 C 타입 변수 생성
value = c_int(10)
# C 함수 호출 시 byref() 사용
libc.increment(byref(value))
# 결과 출력
print(value.value) # 출력: 11
1.2.5 구조체와 유니언 정의
구조체와 유니언은 Win32 API 뿐만 아니라 리눅스의 libc에서 자주 사용되는 중요한 데이터 타입이다.
(1) 구조체
구조체는 단순히 동일하거나 서로 다른 데이터 타입의 변수들을 모아놓은 것이다. 구조체의 어느 한 멤버 변수에 접근하고자 할 때는 beer_recipe.amt_barley 처럼 '.'을 이용한다. 즉, beer_recipe.amt_barley는 beer_recipe 구조체의 amt_barley 멤버 변수에 접근한다.
[C언어]
struct beer_recipe {
int amt_barley;
int amt_water;
}
[Python]
from ctypes import *
class beer_recipt(Structure):
_fields_ = [
("amt_barley", c_int),
("amt_water", c_int),
]
(2) 유니언
유니언은 구조체와 매우 유사하다. 하지만 유니언에서는 모든 멤버 변수가 동일한 메모리 공간을 공유한다. 따라서 유니언에서는 서로 다른 데이터 타입에 동일한 값을 저장하는 것이 가능하다.
[C언어]
union {
long barley_long;
int barley_int;
char barley_char[8];
}barldy_amount;
[Python]
from ctypes import *
class barley_amount(Union):
_fields_ = [
("barley_long", c_long), # long 타입 필드
("barley_int", c_int), # int 타입 필드
("barley_char", c_char * 8), # char 배열 (길이 8)
]
barley_aount 유니언의 barley_int 멤버 변수에 66을 할당했다면 다른 멤버 변수인 barley_char를 이용해 그 값에 해당하는 문자를 출력할 수 있다.
from ctypes import *
class barley_amount(Union):
_fields_ = [
("barley_long", c_long), # long 타입 필드
("barley_int", c_int), # int 타입 필드
("barley_char", c_char * 8), # char 배열 (길이 8)
]
# 사용자 입력 받기 (Python 3.x에서는 input() 사용)
value = input("Enter the amount of barley to put into the beer vat: ")
# 입력 값을 정수로 변환하여 BarleyAmount 구조체 생성
my_barley = barley_amount(int(value))
# 결과 출력 (f-string 사용)
print(f"Barley amount as a long: {my_barley.barley_long}")
print(f"Barley amount as an int: {my_barley.barley_int}")
print(f"Barley amount as a char: {my_barley.barley_char.decode('utf-8', errors='ignore')}")
보는 바와 같이 유니온에서는 하나의 값을 할당해 그 값을 세가지 형태로 표현하는 것이 가능하다. barley_char 변수의 출력값이 B인 이유는 숫자 66에 해당하는 ASCII 값이 B이기 때문이다.
barley_char 멤버 변수 정의를 보면 ctypes에서 배열을 어떻게 정의하는지 확실히 알 수 있다. ctypes에서는 배열에 할당하고자 하는 배열 요소의 개수를 해당 배열 요소 타입에 곱하는 형태로 배열을 정의한다. 따라서 barley_char 멤버 변수는 8개의 요소를 가지는 문자 배열로 정의된 것이다.
'게임해킹 > 파이썬 해킹 프로그래밍' 카테고리의 다른 글
3장 윈도우 디버거 개발(1) - 디버기 (0) | 2025.01.31 |
---|---|
2장 디버거 (0) | 2024.12.29 |