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); } }