모 게임의 치팅 툴을 분석해봤는데 눈에 띄는 루틴이 있었다. 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 구조체의 값을 변조함으로써 호출되는 예외처리 함수의 주소를 바꿀 수 있다.
댓글