4 min read

Windows Trampoline

최근에 약간 실력을 키우기 위해서 아시는분에게 과제를 받고있다.

위 과제는 Foo을 호출시시작과 끝에 Boo와 Hoo과 출력되게 만들어야한다는것이다.

처음에는 std::cout와 std::endl 오버로딩으로 풀었지만 이는 답이 아니였다. 그래서 생각해낸게 코드패치이다.

출처 : https://mblogthumb-phinf.pstatic.net/20130609_171/cor2quard_1370767395360NYXOX_PNG/1.png?type=w2

코드 패치란 파일이나 메모리상의 코드영역을 수정하는 것이며 이를 통해 기존의 동작이 아닌 별도의 동작을 하게되는것이다.

 {

	DWORD oldProect;
	VirtualProtect((LPVOID)Foo, FUN_SIZE, PAGE_EXECUTE_READWRITE, &oldProect);

	memcpy(orginCord.data(), (void*)Foo, FUN_SIZE);

	BYTE pBuf[5] = { 0xE9,0, };
	DWORD jmpaddress = relativityFunctoin((std::uint64_t)Foo, (std::uint64_t)FNA);
	memcpy(&pBuf[1], &jmpaddress, 4);


	memcpy(Foo, pBuf, FUN_SIZE);
	VirtualProtect((LPVOID)Foo, FUN_SIZE, oldProect, &oldProect);
}

코드 패치하는 코드는 위와 같다.

VirtualProtect((LPVOID)Foo, FUN_SIZE, PAGE_EXECUTE_READWRITE, &oldProect);

memcpy(orginCord.data(), (void*)Foo, FUN_SIZE);

기본적으로 코드영역은 읽기 영역이기 때문에 쓰기 영역으로 바꾸어 주는 작업이 별도로 필요하다. VirtualProtect은 가상메모리 영역의 속성을 변경해주는 함수이다. 그후 함수 부분의 5바이트을 백업을 해둔다.

	BYTE pBuf[5] = { 0xE9,0, };
	DWORD jmpaddress = relativityFunctoin((std::uint64_t)Foo, (std::uint64_t)FNA);
	memcpy(&pBuf[1], &jmpaddress, 4);

이 부분은 코드영역에 쓸 데이터를 작성해주는 부분이다.

pbuf의 [0]번지에 0x59인 JMP가 0x59이기때문이고 그뒤에 값은 4바이트인 상대 경로를 적는다.

상대 주소 계산은 다음과 같다.

DWORD relativityFunctoin(std::uint64_t orginFn, std::uint64_t newOrg) {
	return newOrg - orginFn - 5;
}

-5을 하는이유는 코드 패치하는 영역이 5바이트를 사용하기 때문이다.

	memcpy(Foo, pBuf, FUN_SIZE);
	VirtualProtect((LPVOID)Foo, FUN_SIZE, oldProect, &oldProect);

그 후에 코드영역에 데이터을 써주고 아까전에 쓰기권한을 일기 권한으로 설정을 해주면 준비는 끝난다.

나머지는 Hook을 풀어주는 간단한 작업이고 코드만 봐두 이해가 가능할거같다 설명을 생략하였다.

완성된 코드는 과제는 다음과 같다.

// ConsoleApplication6.cpp : This file contains the 'main' function. Program execution begins and ends there.
//

#include <iostream>
#include <string>
#include <vector>
#include <Windows.h>
using namespace std;

void Foo(string val1, int val2, int val3, int val4, int val5, int val6, int val7, int val8, int val9) {
	cout << val1 << val2 << val3 << val4 << val5 << val6 << val7 << val8 << val9 << endl;
}

void Boo() {
	cout << "Boo" << endl;
}

void Hoo() {
	cout << "Hoo" << endl;
}
void UnHook();
void FNA() {
	Boo();
	UnHook();
	Foo("Test", 1, 2, 3, 4, 5, 6, 7, 8);
	Hoo();
}

DWORD relativityFunctoin(std::uint64_t orginFn, std::uint64_t newOrg) {
	return newOrg - orginFn - 5;
}


const int FUN_SIZE = 5;
std::vector<char> orginCord(FUN_SIZE, 0);
void UnHook() {
	DWORD oldProect;
	VirtualProtect((LPVOID)Foo, FUN_SIZE, PAGE_EXECUTE_READWRITE, &oldProect);
	memcpy(Foo, orginCord.data(), FUN_SIZE);
	VirtualProtect((LPVOID)Foo, FUN_SIZE, oldProect, &oldProect);

}
void Hook() {

	DWORD oldProect;
	VirtualProtect((LPVOID)Foo, FUN_SIZE, PAGE_EXECUTE_READWRITE, &oldProect);

	memcpy(orginCord.data(), (void*)Foo, FUN_SIZE);

	BYTE pBuf[5] = { 0xE9,0, };
	DWORD jmpaddress = relativityFunctoin((std::uint64_t)Foo, (std::uint64_t)FNA);
	memcpy(&pBuf[1], &jmpaddress, 4);


	memcpy(Foo, pBuf, FUN_SIZE);
	VirtualProtect((LPVOID)Foo, FUN_SIZE, oldProect, &oldProect);
}
void Koo() {
	Hook();
}
void wmain() {
	auto i = Hook;
	i();
	for (int i = 0; i < 100; ++i) {

	}
	Koo();
	Foo("Test", 1, 2, 3, 4, 5, 6, 7, 8);

}

이 예제에서는 작은 프로그램이여서 JMP 명령어을 사용하였지만 x64로 컴파일된 큰 프로젝트는 JMP을 사용하면 안된다. 방법을 찾고싶으면 https://www.sysnet.pe.kr/2/0/12148 이글을 참조하자.