Social Reiot

Social Game Developer wandering in strange dungeon.

AntiCheatEngine

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
// 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