Excuse the ads! We need some help to keep our site up.
List
Anti-cheating Engine for Android
- 해당 내용은 2년전에 분석하고 개발한 Anti-cheating Engine for Android에 대한 설명입니다.
- 여기에서 설명하는 내용들은 아주 기초적인 기술들에 대한 설명입니다.
- 아래 기능들은 최신 Cheat tool에서 우회 될 수 있습니다.
Check modification of binaries
- 아래와 같이 배포된 파일의 변조를 확인하기 위한 간단한 기능입니다.
- 파일 변조를 확인할 파일의 Hash 정보를 추출해서 서버에서 배포본의 Hash 정보와 동일한지 확인합니다.
- 해당 코드를 구현 할 때 주의 할 부분들이 있습니다.
- 변조 유무를 확인 할 파일의 경로는 암호화되어 서버 또는 파일에 전달,보관되어 있어야 합니다.
- 변조 유무를 확인 할 파일의 경로를 코드 또는 파일에 보관 할 경우 Reverse engineering을 통해 해당 경로 또는 파일명을 변경하여 우회할 수 있습니다.
- Hash 알고리즘으로 CRC와 같이 취약성이 존재하는 알고리즘을 사용해서는 안됩니다.
- 변조 유무를 확인 할 파일의 경로는 암호화되어 서버 또는 파일에 전달,보관되어 있어야 합니다.
CheckBinaryModification.h
bool ModFindHash(char *hash) {
long lSize;
size_t result;
int result_val;
char *buffer;
char patternFile[MAX_PATH] = "/mnt/sdcard/pattern";
FILE *fp = fopen(patternFile, "rb");
if (fp) {
fseek(fp, 0, SEEK_END);
lSize = ftell(fp);
rewind(fp);
buffer = (char*) malloc(sizeof(char) * lSize);
if (buffer == NULL) {
fputs("Memory error", stderr);
exit(2);
}
result = fread(buffer, 1, lSize, fp);
if (result != lSize) {
fputs("Reading error", stderr);
exit(3);
}
if (result > 0) {
if(strstr(buffer,hash)){
result_val = true;
}else{
result_val = false;
}
}
fclose(fp);
free(buffer);
} else {
return false;
}
return result_val;
}
void CheckModBinary(){
pid_t pid= getpid();
char sha256[65] = "";
char packageName[MAX_PATH];
char apkFilePath[MAX_PATH];
char libFilePath[MAX_PATH];
char tmpLibFilePath[MAX_PATH];
int zipcount = 0;
unsigned int crc;
DbgPrint("Modification Check");
getCmdline(pid,packageName,sizeof(packageName));
getApkFilePath(packageName,&apkFilePath);
sprintf(libFilePath, "/data/data/%s/lib", packageName);
while(1)
{
fileToSha256(&sha256,apkFilePath);
if(ModFindHash(sha256) == false)
{
DbgPrint("File Path : %s, SHA256 : %s - Modfication",apkFilePath,sha256);
DbgPrint("Modfication.");
sleep(10);
exit(0);
}
DIR *dirInfo;
struct dirent *dirEntry;
dirInfo = opendir(libFilePath);
if (NULL != dirInfo) {
while (dirEntry = readdir(dirInfo))
{
sprintf(tmpLibFilePath, "/data/data/%s/lib/%s", packageName,dirEntry->d_name);
fileToSha256(&sha256,tmpLibFilePath);
if (ModFindHash(sha256) == false) {
DbgPrint(".SO File Hash Check - Modfication.");
sleep(10);
exit(0);
}
}
closedir(dirInfo);
}
sleep(1000);
}
}
Check debug
- 아래와 같이 두가지 방식으로 디버깅 여부를 확인할 수 있습니다.
- 첫번째는 해당 프로세스의 "/proc/pid/cmdline" 파일의 내용을 확인하는 것 입니다.
- 프로그램이 GDB에 의해 실행 될 경우 PPID의 프로세스는 gdb이기 때문에 "cmdline" 파일을 이용해 gdb를 탐지 할 수 있습니다.
bool isCheckCmdline()
bool isCheckCmdline() {
char filePath[32], fileRead[128];
FILE* file;
snprintf(filePath, 24, "/proc/%d/cmdline", getppid());
file = fopen(filePath, "r");
fgets(fileRead, 128, file);
fclose(file);
if(!strcmp(fileRead, "gdb")) {
DbgPrint("Debugger(gdb) detected\n");
return true;
}
DbgPrint("Clear(Debug)\n");
return false;
}
- 두번째는 해당 프로세스의 "/proc/pid/status" 파일의 내용을 확인하는 것 입니다.
- status 파일에는 해당 프로세스의 상태, 스레드 정보, 메모리 정보, 등 많은 정보들이 저장되어 있습니다.
여기에서 Debug 여부를 확인하기 위해 "TracerPid"의 값을 이용 할 수 있습니다.
- 해당 프로세스가 다른 프로세스에 의해 추적 되고 있다면 추적하고 있는 프로세스의 PID가 저장됩니다.
- 추적이 되고 있지 않으면 '0' 이 저장됩니다.
bool isCheckTracerPid()
bool isCheckTracerPid() {
int TPid;
char buf[512];
const char *str = "TracerPid:";
size_t strSize = strlen(str);
FILE* file = fopen("/proc/self/status", "r");
while (fgets(buf, 512, file)) {
if (!strncmp(buf, str, strSize)) {
sscanf(buf, "TracerPid: %d", &TPid);
if (TPid != 0) {
DbgPrint("Debugger detected\n");
return true;
}
}
}
fclose(file);
DbgPrint("Clear(Debug)\n");
return false;
}
Check Speed hacking
Memory cheat에서 제공하는 Speed hack은 시스템에서 제공하는 시간 관련 함수(gettimeofday, ...)를 후킹하여 값을 조절함으로써 게임의 속도를 조절 할 수 있습니다.
- 다음과 같은 방법으로 Speed hack을 확인 할 수 있습니다.
- 시스템에서 제공하는 시간 관련 함수로 부터 기준 값(start time)이 될 시간 값을 받아옵니다.
- 그리고 sleep() 함수를 이용해 원하는 시간 만큼 프로세스를 정지 시킵니다.
- sleep()함수가 종료 된 후에 다시 시간 관련 함수로 부터 시간 값(end time)을 받아옵니다.
- 두 시간 값을 연산(end time - start time)을 통해 sleep() 함수에서 소비된 시간을 구 할 수 있습니다.
- 이렇게 연산된 값이 sleep() 함수에 전달한 시간 값과 비슷한지 확인함으로서 Speed hack 여부를 확인 할 수 있습니다.
Ex)
gettimeofday() 함수를 이용할 경우 sleep(100) 일 경우 연산된 값은 101000 보다 작거나 10000 보다 커야 합니다.
clock_gettime() 함수를 이용할 경우 sleep(100) 일 경우 연산된 값은 100001000 보다 작거나 100000000 보다 커야 합니다.
- 최근 Memory cheat에서는 시간과 관련된 대부분의 함수를 후킹하여 값을 변조하고 있기 때문에 해당 코드가 효과를 보기 어려울 수 있습니다.
CheckSpeedHack.h
int getTime1()
{
struct timeval tv;
struct timezone tz;
gettimeofday (&tv, &tz);
return (tv.tv_sec*1000 + tv.tv_usec/1000);
}
long getTime2()
{
struct timespec now;
clock_gettime(CLOCK_MONOTONIC,&now);
return now.tv_sec*1000000 + now.tv_nsec/1000;
}
void CheckSpeedHack(){
int start = 0, end = 0, time_data = 0;
int start2 = 0, end2 = 0, time_data2 = 0;
int cmp=1;
while(cmp){
start = getTime1();
start2 = getTime2();
sleep(100);
end = getTime1();
end2 = getTime2();
time_data = end - start;
time_data2 = end2 - start2;
DbgPrint("time_data : %d, end : %d , start : %d",time_data,end,start);
DbgPrint("time_data2 : %d, end : %d , start : %d",time_data2,end2,start2);
if(time_data > 101000 || time_data < 10000){
cmp = 0;
DbgPrint("SpeedHackDetect");
sleep(10);
exit(0);
}
if(time_data2 > 100001000 || time_data2 < 100000000){
cmp = 0;
DbgPrint("SpeedHackDetect");
sleep(10);
exit(0);
}
}
}
Check Virtual Machine
- 다음과 같은 방법으로 Virtual Machine을 확인 할 수 있습니다.
"/system/build.prop" 파일에 저장된 정보를 이용 할 수 있습니다.
- "/system/build.prop" 파일에 Android 시스템에 관한 다양한 정보들이 저장되어 있습니다.
- 특히 Virtual Machine의 경우 아래 항목들에 Virtual Machine의 이름을 설정하고 있으며, 이러한 정보를 이용해 Virtual Machine을 탐지 할 수 있습니다.
"ro.product.manufacturer"
"ro.product.model"
"ro.product.name"
"ro.product.device"
- 하지만 해당 항목의 값을 변경하는것으로 탐지를 우회 할 수 있습니다.
- 해당 방법 외에도 다양한 방법들이 있습니다.
- 각 Virtual Machine에서 설치 또는 실행 중인 프로세스의 여부 확인
- 시스템 구조를 분석하여 Virtual Machine에서 추가한 부분을 확인
- 등등
CheckVirtualMachine.h
void virtualMachine(char *filePath, char *searchStr,char *findStr){
FILE *fp;
char str[81];
fp = fopen(filePath, "r");
while(!feof(fp))
{
fgets(str, 80, fp);
if(strstr(str,searchStr) != NULL){
str[strlen(str)-1] = ',';
strcat(findStr,str);
}
}
fclose(fp);
}
bool IsBlackDevice(char *deviceName)
{
if(strstr(deviceName,"BlueStacks") != NULL)
{
DbgPrint(" BlueStacks : %s,%p",deviceName,deviceName);
return false;
}
if(strstr(deviceName,"Genymotion") != NULL)
{
DbgPrint(" Genymotion : %s,%p",deviceName,deviceName);
return false;
}
return true;
}
void CheckVirtualMachine(){
char findSearch[300];
virtualMachine("/system/build.prop","ro.product.manufacturer",&findSearch);
virtualMachine("/system/build.prop","ro.product.model",&findSearch);
virtualMachine("/system/build.prop","ro.product.name",&findSearch);
virtualMachine("/system/build.prop","ro.product.device",&findSearch);
if(IsBlackDevice(findSearch)) {
DbgPrint(" BlueStacks : Not Detect");
}else{
DbgPrint(" BlueStacks : Detect");
sleep(10);
exit(0);
}
}