2008-02-04

AntiCheatEngine

원래는 Cheat Engine 으로도 freeze 를 걸 수 없는 안전한 변수를 만들기 위해서 만든 클래스다. 여기에 CRC라든지 XOR도 넣고, 또 값이 바뀔 때마다 위치도 바꾸고 할려고 했는데, 가만히 생각해보니 속도도 제법 느리고 막상 함수 내에 아래와 같이 로컬 캐시가 있다면 그걸 바꿔버리면 전혀 쓸모 없어져서 그냥 포기버렸다.


float speed = ac.Get<float>("speed");
// -- inject code here --
check_next_pos(pos,speed);

어제 주문한 책들이 도착하면 다시 한번 도전해볼까 싶기도 하지만, assembly 쪽 지식이 전무해서...


// AntiCheatEngine.cpp : 콘솔 응용 프로그램에 대한 진입점을 정의합니다.
//

#include "stdafx.h"
#include <string>
#include <set>
#include <vector>
#include <list>
#include <hash_map>

#pragma warning(disable:4819)
#define BOOST_TEST_MAIN
#include <boost/format.hpp>
#include <boost/test/unit_test.hpp>

using std::string;

size_t GenerateIndex()
{
static size_t key = 0;
return key++;
}

class AntiCheat
{
typedef std::vector<size_t> INDEX_LIST;
typedef stdext::hash_map<string, INDEX_LIST > KEY_MAP;

public :

AntiCheat( size_t N = 1024*64 )
{
m_Buffer.assign(N,0);
m_UnusedIndex.assign(N,0);
std::generate(m_UnusedIndex.begin(),m_UnusedIndex.end(),GenerateIndex);
std::random_shuffle(m_UnusedIndex.begin(),m_UnusedIndex.end());
}

bool IsValid() const { return true; }

unsigned int GetCRC() { return m_CRC; }

// FIXME use ASSERT, dont check return value
template<class T>
bool Set( const string & name , const T & valueIn )
{
KEY_MAP::iterator itr = m_Keys.find(name);
if ( itr == m_Keys.end() )
{
INDEX_LIST idxList;
for ( size_t i = 0 ; i < sizeof(valueIn); i ++ )
{
if ( m_UnusedIndex.empty() )
return false;
size_t idx = m_UnusedIndex.back();
m_UnusedIndex.pop_back();
idxList.push_back(idx);
}

itr = m_Keys.insert(make_pair(name,idxList)).first;
}

INDEX_LIST & indexList = itr->second;

for ( size_t i = 0 ; i < indexList.size() ; i ++ )
{
size_t idx = indexList[i];
m_Buffer[idx] = reinterpret_cast<const char*>(&valueIn)[i];
}

return true;
}

template<typename T>
bool Get( const string & name, T & valueOut )
{
KEY_MAP::iterator itr = m_Keys.find(name);
if ( itr == m_Keys.end() )
{
return false;
}

INDEX_LIST & indexList = itr->second;

for ( size_t i = 0 ; i < indexList.size() ; i ++ )
{
size_t idx = indexList[i];
reinterpret_cast<char*>(&valueOut)[i] = m_Buffer[idx];
}
return true;
}

size_t GetUnusedIndexCount() const { return m_UnusedIndex.size(); }

unsigned int m_CRC;

INDEX_LIST m_UnusedIndex;
std::vector<unsigned char> m_Buffer;
KEY_MAP m_Keys;
};

//////////////////////////////////////////////////////////////////////////
template<>
bool AntiCheat::Set<string>( const string & name, const string & valueIn )
{
KEY_MAP::iterator itr = m_Keys.find(name);
if ( itr == m_Keys.end() )
{
INDEX_LIST idxList;
for ( size_t i = 0 ; i < valueIn.size(); i ++ )
{
if ( m_UnusedIndex.empty() )
return false;
size_t idx = m_UnusedIndex.back();
m_UnusedIndex.pop_back();
idxList.push_back(idx);
}

itr = m_Keys.insert(make_pair(name,idxList)).first;
}

INDEX_LIST & indexList = itr->second;

for ( size_t i = 0 ; i < indexList.size() ; i ++ )
{
size_t idx = indexList[i];
m_Buffer[idx] = valueIn[i];
}

return true;
}

template<>
bool AntiCheat::Get<string>( const string & name, string & valueOut )
{
KEY_MAP::iterator itr = m_Keys.find(name);
if ( itr == m_Keys.end() )
{
return false;
}

INDEX_LIST & indexList = itr->second;

// FIXME 최초 크기 보다 작은 사이즈는 저장할 수 없다?
valueOut.assign(indexList.size(),0);
for ( size_t i = 0 ; i < indexList.size() ; i ++ )
{
size_t idx = indexList[i];
valueOut[i] = m_Buffer[idx];
}
return true;
}

BOOST_AUTO_TEST_CASE( test1 )
{
AntiCheat ac(100);

BOOST_CHECK_EQUAL( ac.GetUnusedIndexCount(), 100 );

BOOST_CHECK( ac.IsValid() );

string strIn = "World", strOut;

{
ac.Set("string",strIn);
BOOST_CHECK( ac.IsValid() );
BOOST_CHECK( ac.Get<string>("string",strOut) );
BOOST_CHECK_EQUAL( strIn, strOut );
BOOST_CHECK_EQUAL( ac.GetUnusedIndexCount(), 100-strIn.size());
}

{
int intIn, intOut;

intIn = 1;
ac.Set("int",intIn);
BOOST_CHECK( ac.IsValid() );
BOOST_CHECK( ac.Get<int>("int",intOut) );
BOOST_CHECK_EQUAL( intIn, intIn );
BOOST_CHECK_EQUAL( ac.GetUnusedIndexCount(), 100-strIn.size()-sizeof(int));

intIn = -1;
ac.Set("int",intIn);
BOOST_CHECK( ac.IsValid() );
BOOST_CHECK( ac.Get<int>("int",intOut) );
BOOST_CHECK_EQUAL( intIn, intIn );
BOOST_CHECK_EQUAL( ac.GetUnusedIndexCount(), 100-strIn.size()-sizeof(int));
}

{
bool boolIn, boolOut;

boolIn = true;
ac.Set("bool",boolIn);
BOOST_CHECK( ac.IsValid() );
BOOST_CHECK( ac.Get<bool>("bool",boolOut) );
BOOST_CHECK_EQUAL( boolIn, boolOut );
BOOST_CHECK_EQUAL( ac.GetUnusedIndexCount(), 100-strIn.size()-sizeof(int)-sizeof(bool));

boolIn = true;
ac.Set("bool",boolIn);
BOOST_CHECK( ac.IsValid() );
BOOST_CHECK( ac.Get<bool>("bool",boolOut) );
BOOST_CHECK_EQUAL( boolIn, boolOut );
BOOST_CHECK_EQUAL( ac.GetUnusedIndexCount(), 100-strIn.size()-sizeof(int)-sizeof(bool));
}

{
float floatIn, floatOut;

floatIn = 1.234f;
ac.Set("float",floatIn);
BOOST_CHECK( ac.IsValid() );
BOOST_CHECK( ac.Get<float>("float",floatOut) );
BOOST_CHECK_EQUAL( floatIn, floatOut );
BOOST_CHECK_EQUAL( ac.GetUnusedIndexCount(), 100-strIn.size()-sizeof(int)-sizeof(bool)-sizeof(float));

floatIn = -56.78f;
ac.Set("float",floatIn);
BOOST_CHECK( ac.IsValid() );
BOOST_CHECK( ac.Get<float>("float",floatOut) );
BOOST_CHECK_EQUAL( floatIn, floatOut );
BOOST_CHECK_EQUAL( ac.GetUnusedIndexCount(), 100-strIn.size()-sizeof(int)-sizeof(bool)-sizeof(float));
}

}

/*

값을 바꾸면 Key 및 CRC 가 바뀐다
멤버 함수가 아닌 다른 경로로 값을 바꾸면 CRC 체크가 깨져야 한다.
AntiCheat 는 하나의 큰 버퍼를 가진다. (ex: 32k)
여기에 변수를 키값을 이용해서 저장하고 읽어낸다.
이때 각 변수들은 바이트 단위로 흩어진다. 이때 흩어지는 인덱스는 저장하는 순간 랜덤하게, 중복되지 않게 결정된다.
(이때문에 unused index set 이 필요함)
또한 각 변수들은 Verifier 라는 range-checker 와 연동될 수 있다.
키값 역시 다이나믹하게 결정된다. (가령 player.hp 의 경우 매크로에 의해서 __K(PlayerHP) -> 세션키와의 XOR 등등 )
Verify()를 호출하면 각 키에 대해서 Verify 한 다음,
특정 영역 마다 0xEF 등 boundry checker 를 넣어두고, 바뀌었는지 체크한다.

문자열일 경우 (길이 2바이트,본문) 이렇게 저장해야 한다. 만약 길이가 달라질 경우 인덱스를 새로 얻어 오거나 반납해야 한다.

*/

comments powered by Disqus