당시 저는 DLL 로딩 경로를 후킹하는 사용자 모드 PoC를 구현했습니다. 지금 저장소 코드를 다시 보며 실제로 구현한 경로, 당시 기대했던 효과, 구조적으로 놓친 부분을 나누어 정리했습니다.
목표
기본 프로젝트에서는 Palworld 프로세스 내부에 안티치트 DLL을 먼저 로드한 뒤, Microsoft Detours로 Windows API의 시작 부분을 패치해 호출을 검사했습니다. 당시 구현의 중심은 다음 두 가지였습니다.
LoadLibraryA호출에서 DLL 이름을 허용 목록과 비교
- 디버거·분석 도구의 흔적을 여러 사용자 모드 API로 확인
동작 구조
구현한 것
DLL 로딩 경로 필터
알려진 게임 DLL 이름을
std::set에 저장하고, Hooked_LoadLibraryA에서 요청된 문자열을 비교했습니다. 등록되지 않은 이름이면 원래 함수를 호출하지 않고 NULL을 반환했습니다.이 구현은 대상 프로세스 안에서 LoadLibraryA를 통과하는 호출을 관찰하는 실험으로는 의미가 있었습니다. 동시에 문자열 표준화, 전체 경로와 파일명 비교, 정상 DLL 업데이트 대응처럼 실제 제품에서 필요한 정책 설계가 빠져 있었습니다.
여러 방식의 디버거 흔적 확인
IsDebuggerPresent, CheckRemoteDebuggerPresent, NtQueryInformationProcess, 예외 처리, 디버그 레지스터를 사용했습니다. WinDbg 창 클래스와 Cheat Engine 창 제목을 열거하는 검사도 추가했습니다.이들은 하나의 신호에만 의존하지 않는 연습이었지만 모두 사용자 모드 휴리스틱입니다. 창 제목 변경, API 패치, 반환값 변조 등으로 우회될 수 있고 오탐 가능성도 있습니다.
지금 다시 보며 바로잡은 핵심
CreateRemoteThread 후킹의 위치
당시 DLL은 게임 프로세스 내부의
CreateRemoteThread도 후킹했습니다. 하지만 전형적인 외부 인젝터는 인젝터 자신의 프로세스에서 CreateRemoteThread를 호출합니다. 따라서 게임 안에 설치한 이 후킹은 그 외부 호출 자체를 직접 가로채지 못합니다.원격 스레드가 게임 프로세스의 패치된
LoadLibraryA 진입점을 실행하는 특정 경로에서는 로딩 요청을 관찰할 가능성이 있지만, 이를 “CreateRemoteThread 기반 주입 전체 차단”으로 표현할 수는 없습니다.LoadLibrary 계열 시그니처 불일치
당시 소스에는
LoadLibraryW·LoadLibraryEx 계열을 같은 훅 함수에 연결하려던 실험 코드도 남아 있습니다. ANSI·Unicode와 인자 개수가 다른 함수에 동일한 시그니처를 사용하는 것은 안전하지 않습니다. 그래서 저는 동작을 다시 확인한 LoadLibraryA PoC만 제 성과로 적었습니다.왜 커널 단계로 넘어갔나
사용자 모드 DLL은 자신보다 높은 권한의 공격자나 커널 드라이버를 신뢰할 수 없습니다. 수동 매핑처럼
LoadLibrary를 사용하지 않는 경로도 놓칩니다. 무엇보다 외부 메모리 변조의 공통 선행 조건인 프로세스 핸들 권한 부여 시점을 직접 통제하지 못합니다.이 한계가 다음 단계의 설계 질문이 되었습니다.
API 한두 개를 후킹하는 대신, 운영체제가 Palworld 프로세스 핸들을 만들어 주는 시점에 위험 권한을 줄일 수 있을까?
남은 한계
- 안티치트 DLL 자체를 먼저 주입해야 하는 부트스트랩 문제가 있습니다.
- 수동 매핑과 직접 시스템 호출을 다루지 못합니다.
- 허용 목록이 이름 문자열 중심이라 경로·서명·해시 검증이 없습니다.
- 후킹 무결성, 탐지 결과 대응, 로그 수집 체계가 없습니다.
- 5초 주기 검사에 대한 성능·오탐 측정이 없습니다.
결론
제가 이 PoC에서 얻은 가장 큰 성과는 강한 방어 성능이 아니라, 사용자 모드에서 보이는 것과 보이지 않는 것을 직접 구현하며 확인한 경험입니다. 놓치는 경로를 이해한 뒤, 다음 고도화에서는 공격자가 핸들을 얻는 커널 객체 관리 경계로 방어 지점을 옮겼습니다.
당시 원본 기록
아래 하위 문서는 2024년 당시 제가 작성한 개인보고서 원문입니다. 사진·코드·시행착오는 그대로 보존했고, 지금 다시 보며 바로잡은 내용은 위 본문에 따로 적었습니다.