stat() 함수로 검사 (C function)

Objective-C 같은 경우 메세징 기법사용 + 런타임 의존의 이유로 메소드에 맵핑되는 함수의 구현부 주소를 런타임에 알아낸다. 이러한 특징으로 인해 후킹을 정말 쉽게 할 수 있다.
 
따라서 쉽게 식별가능하고 후킹도 가능한 objc 클래스가 아닌 좀 더 로우레벨인 C언어의 함수를 사용하여 파일 유무 검사를 수행하는 것이 권장된다.
(물론 Theos, frida 등으로 로우레벨 함수 후킹도 어렵지 않게 할 수 있긴하다.)
 
추가로 C언어 함수의 경우 메세징 기법과 런타임에 의존하지 않고, 빌드 시에 함수 구현부와 맵핑된다.
이 특징을 이용하면 함수 구현부의 주소공간 유효성 검증을 추가로 진행할 수 있으므로 더욱 추천된다.
stat() 함수를 이용한 파일 유무 검사는 아래 코드를 참조해서 하면 된다.
//정보가 담길 구조체 struct stat stat_info; //Cydia.app이 있는지 검사하고, 정보를 구조체에 담음. //파일 존재 시 return 0, 파일이 없다면 return -1 if (0 == stat("/Applications/Cydia.app", &stat_info)) { jailbroken = YES; }
 

struct stat 구조체

struct stat { mode_t st_mode; ino_t st_ino; dev_t st_dev; dev_t st_rdev; nlink_t st_nlink; uid_t st_uid; gid_t st_gid; off_t st_size; struct timespec st_atim; struct timespec st_mtim; struct timespec st_ctim; blksize_t st_blksize; blkcnt_t st_blocks; };
 
stat() 함수 호출 결과 파일에 대한 내용이 담기는 구조체다.
각 멤버변수에 대한 설명은 아래와 같다.
  • st_dev – identifier of device containing file
  • st_atime – time of last access
  • st_mtime – time of last modification
  • st_ctime – time of last status change
  • st_blksize – preferred block size for file system I/O, which can depend upon both the system and the type of file system
  • st_blocks – number of blocks allocated in multiples of DEV_BSIZE(usually 512 bytes).
 
탈옥 관련 파일 정보를 가져올 수 있다는 것 자체로 바로 거를 수 있지만, 구조체에 담긴 정보를 이용하려면 이용할 수도 있다. (일단 참고만)
 

탈옥 탐지 코드

코드는 앞서 살펴본 코드에서
NSFileManager를 이용해 검사하는 것이 아니라 로우레벨 C함수인 stat() 함수로 검사하도록 대체한다.
 
필요 헤더 : #include <sys/stat.h>
// // AppDelegate.m // JBDetection // // Created by night-ohl on 2020/07/20. // Copyright © 2020 night-ohl. All rights reserved. // #import "AppDelegate.h" #include <spawn.h> #include <sys/stat.h> @interface AppDelegate () @end @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. //stat() 함수에서 정보를 담을 구조체 struct stat stat_info; //파일 존재 시 return 0, 파일이 없다면 return -1 if (0 == stat("/Applications/Cydia.app", &stat_info)) { NSLog(@"cydia detected"); exit(1); } if (0 == stat("/private/var/lib/apt/", &stat_info)) { NSLog(@"/var/lib/apt/ detected"); exit(1); } if (0 == stat("/User/Applications/", &stat_info)) { NSLog(@"/User/Applications/ detected"); exit(1); } if (0 == stat("/Library/MobileSubstrate/MobileSubstrate.dylib", &stat_info)) { NSLog(@"MobileSubstrate.dylib detected"); exit(1); } if (0 == stat("/bin/bash", &stat_info)) { NSLog(@"/bin/bash detected"); exit(1); } if (0 == stat("/usr/sbin/sshd", &stat_info)) { NSLog(@"/user/sbin/sshd detected"); exit(1); } if (0 == stat("/etc/apt", &stat_info)) { NSLog(@"/etc/apt/ detected"); exit(1); } return YES; } ...생략... @end
 
notion image
소스코드는 NSFileManager의 fileExistAtPath: 메소드로 검사한 것과 크게 다르지 않다.
소스코드에 녹인 경우 - 매번 exit
 

후킹 트윅 실습 (우회)

반면 후킹 트윅 작성은 약간 달라진다.
여태 후킹하려는 특정 클래스를 명시하고, 메소드 구현을 바꾸었는데 c함수의 경우에는 클래스가 없다.
http://iphonedevwiki.net/index.php/Logos#.25hookf 링크에서 hookf 부분을 참조하자.

%hookf

notion image
[C funtion 후킹 시 문법] %hookf(리턴타입, 함수심볼이름, 함수인자 나열) { 후킹 코드 작성 }
 

stat() 함수 프로토타입

그러면 우리는 stat() 함수 프로토타입을 알아야 한다.
int stat(const char *filename, struct stat *buf); int lstat(const char *filename, struct stat *buf); int fstat(int filedesc, struct stat *buf);
 

트윅 코드 작성

#include <sys/stat.h> //빼먹지 말자 %hookf(int, stat, const char *filename, struct stat *buf) //stat 함수 프로토타입에 맞게 작성 { NSLog(@"[TEST] stat() called : %s", filename); //앱에서 탐지하던 탈옥 관련 문자열을 배열에 담음 char *arr[] = {"/Applications/Cydia.app", "/private/var/lib/apt/", "/User/Applications/", "/Library/MobileSubstrate/MobileSubstrate.dylib", "/bin/bash", "/usr/sbin/sshd", "/etc/apt"}; //탈옥관련 파일 문자열과 일치할 시 -1 반환 (없는척) for(int i=0; i<sizeof(arr)/sizeof(char*); i++){ if(!strcmp(filename, arr[i])) { NSLog(@"[TEST] %s Bypass~~!", filename); return -1; } } //나머지 문자열에 대해서은 원래 함수대로 동작 return %orig; }
notion image