본문 바로가기
리버싱

Why some Game Cheating Programs are hook RtlLookupFunctionTable() API or RtlLookupFunctionEntry()?

by 즉흥 2019. 5. 1.
728x90
반응형

 모 게임의 치팅 툴을 분석해봤는데 눈에 띄는 루틴이 있었다. RtlLookupFunctionTable() API 함수를 후킹하는 것이었는데 도대체 왜 이 함수를 후킹하는지 조사 및 확인한 결과를 기록한다.

 


 RtlLookupFunctionTable() API 함수는 RtlLookupFunctionEntry() API 함수 내부에서 호출되는데, RtlLookupFunctionEntry() API 함수에 대해 찾아보니 이 API 함수는 Windows 64bit 프로그램에서 예외처리를 핸들링할 때(SEH) 쓰이는 것을 알았다.

 

 

64-bit Structured Exception Handling (SEH) in ASM - CodeProject.pdf
3.67MB

원문(LINK)

...
Then it calls the RtlLookupFunctionEntry API, to obtain an entry in the Function Tables that correspond to the ControlPc RIP register value. A PROC is added to the Function Tables whenever the name of some LSH is added to the FRAME attribute of that PROC. If RtlLookupFunctionEntry returns a valid value (it usually does), we will obtain from it the Begin Address and End Address of some PROC.
...

 

 RtlLookupFunctionEntry() API 함수가 64bit SEH에 사용되는걸 알았느니, C++로 간단한 예제 코드를 작성하여 디버깅을 진행하였다.

 


 

목표: 64bit 프로그램의 SEH 핸들러를 변조하여 공격자가 원하는 코드를 실행

환경: Windows 10 64bit

 

1. 예제 코드 작성

#pragma optimize("", off)

void  test1()
{
	int a = 0, b = 0;

	try
	{
		printf("%d", a / b);
	}
	catch (...)
	{
		printf("1st exception handler");
	}
}
void  test2()
{
	int a = 0, b = 0;

	try
	{
		printf("%d", a / b);
	}
	catch (...)
	{
		printf("2nd exception handler");
	}
}
#pragma optimize("", on)

int main()
{
	test1();
	test2();
    return 0;
}

 

2. 예외 상황 발생

 Windows에서 64bit 프로그램의 경우, 예외가 발생하면 RtlLookupFunctionEntry() API 함수를 호출하여 예외처리 루틴이 저장되어 있는 .pdata 섹션의 주소를 얻는다.

 

DivideByZeroException 발생
호출된 RtlLookupFunctionEntry() API

 

 .pdata 섹션은 아래와 같이 RUNTIME_FUNCTION 구조체 배열로 구성되어 있는데, RUNTIME_FUNCTION 멤버인 BeginAddress와 EndAddress 사이에서 예외가 발생하면 UnwindInfoAddress 구조체를 참조하여 해당 영역의 예외처리 함수를 호출한다.

 

메모리에 매핑된 .pdata 섹션의 주소
.pdata 섹션에서 RUNTIME_FUNCTION 구조체를 확인할 수 있음. 값은 모두 RVA 형태로 저장되어 있음
.pdata 섹션 구성도

 정리해보면 예외 상황이 발생하면 callback 함수를 따라 RtlLookupFunctionEntry() API 함수가 호출되고, 이 API 함수는 예외 처리 루틴(RUNTIME_FUNCTION 구조체)이 저장된 .pdata 섹션의 시작주소를 리턴한다. 그리고 .pdata 섹션에 저장된 RUNTIME_FUNCTION 구조체를 참조하여 해당 루틴에 알맞은 예외처리 루틴을 호출하는 것을 알 수 있다.

 

따라서 .pdata 섹션의 데이터를 변조함으로써 SEH 핸들링을 공격자가 원하는 방향으로 변조할 수 있다. 실제로 모 게임 치팅 툴이 실행되고 난 후의 모 게임 메모리를 확인한 결과 .pdata 섹션이 통째로 변조된 것을 확인할 수 있었다.

 

3. 실습

64bit SEH 핸들링을 변조하는 방법을 몇 가지 생각해봤는데, 당장 떠오르는 것은 세 가지 정도였다.

 


첫 째, .pdata 섹션(RUNTIME_FUNCTION 구조체 배열)을 통째로 변조
둘 째, RtlLookupFunctionEntry() API 함수를 후킹함으로써 함수의 반환값을 공격자가 사전에 준비한 RUNTIME_FUNCTION 구조체 배열의 시작 주소로 변조
셋 째, UnwindInfo 구조체의 Exception Handler의 값(RVA)을 변조

 

실습해본건 가장 구현하기 빠른 세 번쨰 방법을 했다.

 

UnwindInfo 구조체는 RUNTIME_FUNCTION 마지막 멤버의 값을 포인터로 따라가면 찾을 수 있는데, UnwindInfo 구조체의 값을 변조함으로써 호출되는 예외처리 함수의 주소를 바꿀 수 있다.

 

UnwindInfo 구조체
구조체의 모든 값은 RVA 형태로 저장되어 있다
test1() 함수에서 test2()의 예외처리 함수가 호출된 모습

 

728x90
반응형

댓글