Wesnoth 해킹하기 (9) Code Caves with DLL
Wesnoth 해킹하기 (5)편 요약
Wesnoth 해킹하기 (5) 편에서 x64WinDbg를 이용하여 code cave을 만들었고 인게임에서 Terrain Description으로 진입 시 골드를 추가해주는 해킹을 해봤다.
Wesnoth 해킹하기 (5) Code Cave
Wesnoth 해킹하기 (4) 편에서는 assembly어의 기능을 유추함으로써 인게임에서 terrain description, recruit, recall 등에 해당하는 assembly어를 찾을 수 있었다. Wesnoth 해킹하기 (5)편에서는 code cave를 만들고 terr
bithub.tistory.com
Wesnoth 해킹하기 (5)편을 간략하게 요약하자면,
우리는 code cave를 0x134360E 번지에 만들었고
어셈블리어를 jmp 0x134360E를 대체함으로써 code cave에 진입했다.
Code cave에서는 아래와 같이 구성되었으며 2번째 줄이 보유골드를 999골드로 수정하는 코드였다.
pushad
mov dword ptr ds:[your gold address], 0x3E7 // our code cave codes
popad
mov eax, dword ptr ds:[ecx]
lea esi, dword ptr ds:[esi]
jmp 0xCCAF90
__asm, __declspec(naked)
Wesnoth 해킹하기 (9) 편에서는 DLL을 이용하여 code cave를 만들어보자.
DLL에서도 C++을 이용하여 다음과 같이 어셈블리어를 추가할 수 있다.
__asm {
pushad
}
__asm 키워드를 이용하여 어셈블리어를 추가해줄 수 있는데 이를 이용해 code cave를 만들 수 있다.
대략적인 형태는 다음과 같을 것이다.
code cave에 들어갈 함수를 codecave()로 만들고 앞에 &를 붙여 code cave를 참조할 수 있다.
void codecave() {
//our codecave
}
...
terrain_description_jump_location = &codecave;
위 코드는 assembly로 변환되면 대충 다음과 같은 형식일 것이다.
codecave:
push ebp
mov ebp, esp
...
mov esp, ebp
pop ebp
ret
이러한 프롤로그 에필로그 assembly어로 인해 게임이 충돌할 수가 있다.
그래서 우리는 __declspec(naked) 키워드를 사용할 것이다.
__declspec(naked) 키워드를 사용하면 프롤로그 에필로그 코드가 생성되지 않는다.
https://learn.microsoft.com/ko-kr/cpp/cpp/naked-cpp?view=msvc-170
naked(C++)
자세한 정보: naked(C++)
learn.microsoft.com
그래서 __declspec(naked) 키워드를 사용하여 코드를 수정하면, 다음과 같다.
__declspec(naked) void codecave() {
//our codecave
}
...
terrain_description_jump_location = &codecave;
DLL을 이용하여 code cave 만들기
우선 Visual Studio에서 빈 프로젝트 하나를 만들어주자.
프로젝트 이름은 DllCodeCave.
main.cpp를 생성하고 다음 코드를 입력하자.
#include <Windows.h>
DWORD ret_address = 0xCCAF90;
DWORD* player_base;
DWORD* game_base;
DWORD* gold;
__declspec(naked) void codecave() {
__asm {
pushad
}
// our code cave codes here
player_base = (DWORD*)(0x017EECB8 + 0x60);
game_base = (DWORD*)(*player_base + 0xA90);
gold = (DWORD*)(*game_base + 4);
*gold = 999;
__asm {
popad
mov eax, dword ptr ds : [ecx]
lea esi, dword ptr ds : [esi]
jmp ret_address
}
}
keypoint
여기서 jmp 0xCCAF90 대신 굳이 DWORD ret_address를 선언해가면서 jmp ret_address라고 한 이유는 간단하다.
Compiler는 프로세스가 로드되기 전까지 주소 0xCCAF90이 실제로 존재하는지 존재안하는지 알 수가 없다.
또한, jump의 길이에 따라 jmp 명령어가 각기 다른 opcode로 변환되는데 컴파일러는 jump의 길이도 알 수 없으니 오류가 뜨는 것이다. 그래서 jmp 0xCCAF90으로 작성하고 컴파일해보면 컴파일 오류가 뜬다.
근데 우리의 code cave는 __declspec(naked)로 인해 프롤로그 에필로그 코드를 생성하지 않으므르 local 변수를 선언할 수 없다.
그래서 global 변수로 ret_address를 선언하고 jmp 시 참조하는 것이다.
Redirection Function