모 게임의 치팅 툴을 분석해봤는데 눈에 띄는 루틴이 있었다. RtlLookupFunctionTable() API 함수를 후킹하는 것이었는데 도대체 왜 이 함수를 후킹하는지 조사 및 확인한 결과를 기록한다.
RtlLookupFunctionTable() API 함수는 RtlLookupFunctionEntry() API 함수 내부에서 호출되는데, RtlLookupFunctionEntry() API 함수에 대해 찾아보니 이 API 함수는 Windows 64bit 프로그램에서 예외처리를 핸들링할 때(SEH) 쓰이는 것을 알았다.
원문(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 섹션의 주소를 얻는다.


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



정리해보면 예외 상황이 발생하면 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 구조체의 값을 변조함으로써 호출되는 예외처리 함수의 주소를 바꿀 수 있다.



댓글