게임 분석중, 게임을 실행한 상태에서 디버거를 실행하니 다음과 같은 메시지가 나타났다.
게임 자체에서 치트를 방지하는 기능을 구현한 것 같았고, 이것을 우회해야 했다.
그 과정을 간단히 정리하기 위한 글이다.
OS : Windows 10
Image : Microsoft Visual C++ ver 5.0/6.0 / x86 architecture
Type : 2D MMO RPG
프로그램은 Updater.exe
가 먼저 실행된 후 Updater.exe
가 게임 클라이언트를 fork하는 방식으로 실행된다.
게임 클라이언트가 실행되자마자 안티치트 기능을 수행하는 쓰레드가 생성되므로, 디버거를 바로 attach해서 확인할 수는 없었다.
생각한 방법으로는 process explorer를 이용해 게임 클라이언트를 suspend시킨 후에 디버거를 attach한다.
그 후 원하는 지점에 브레이크 포인트를 걸고 프로세스를 resume하여 코드를 분석한다.
나는 안티치트 코드를 추적하기 위해 MessageBox
를 백트레이싱하였다.
앞서 말한 방법대로 게임 클라이언트를 suspend한 후에 x64dbg를 attach시킨다.
그리고 MessageBox
에 브레이크 포인트를 설정하고 프로세스를 진행시키면, EIP가 MessageBox
에서 중지되고 다음처럼 스택의 값을 확인할 수 있다.
ESP가 위치한 Caller의 코드를 따라가보면
MessageBox
에 전달되는 상수값들로 인해, 이 코드 영역이 안티치트 기능을 수행하는 함수의 내부라는 것을 유추할 수 있었다.
코드 양이 방대하여 x64dbg로 분석하기는 힘들었다.
IDA로 로드하여 안티치트 코드를 확인했다.
다행히 머리가 아파지는 코드는 없었다.
다음처럼 실행중인 윈도우 정보를 가져와서
sub_404836
함수를 호출하여 윈도우 중 치팅 프로그램이 존재하는지 검사한다.
sub_404836
에 전달되는 ESP+8는 바로 위에서 repne scasb
를 이용해 구한 상수의 길이를 나타내며 ESP+4는 윈도우 정보, ESP는 검사할 치팅 프로그램의 이름이 저장된 상수값을 나타낸다.
검사하는 프로그램은 다양하다.
방법은 두 가지가 있다.
GetWindow
를 후킹하여 0을 반환시켜서 안티 치트 코드가 실행되지 않도록 하거나, 아니면 검사함수인 sub_404836
을 후킹하고 -1을 반환시켜서 치팅 프로그램이 아닌 것처럼 속이는 방법이다.
frida를 이용하여 작성할 것이며, 첫 번째는 다음과 같은 코드로 구현 가능하다.
function hookByName(dllName, funcName, ctRet){
Interceptor.attach(Module.findExportByName(dllName, funcName), {
onEnter: function(args){
//send('Hooked function is called [' + dllName + ' :: ' + funcName + ']');
},
onLeave: function(retval){
//send('ret: ' + retval);
retval.replace(ctRet);
return retval;
}
});
}
hookByName('User32.DLL', 'GetWindow', 0);
다음은 검사를 우회시키는 후킹 코드이다.
Interceptor.attach(ptr(0x404836), {
onEnter: function(args){
send('Hooked function is called []');
},
onLeave: function(retval){
send('ret: ' + retval);
retval.replace(-1);
return retval;
}
});
하지만, 사용할 때에는 첫 번째 코드를 사용하는 것이 좋겠다.
GetWindow
를 후킹하면 안티치트 함수의 코드가 실행되지 않고 종료되지만, sub_404836
을 후킹할 경우 모든 코드가 실행되어 속도가 저하될 수있기 때문이다.
위 스크립트를 로드하면 치트엔진이나 디버거를 실행해도 클라이언트가 종료되지 않는 것을 확인할 수 있다.
질문이 있습니다.
디스코드 하시나요?
jeongsangin9697 친구추가 가능하실까요