2011. 9. 28. 09:07
원본은 www.iphonedevsdk.com 에 Shmoopi가 올린 것이며 이 글은 단순한 번역본에 불과합니다. 잘못된 번역 있으면 댓글로 알려주세요.

#원문링크 

2009년에 올려진 글이라 현재의 iOS에는 적용되지 않을 수도 있으므로 주의하기 바랍니다.

안녕 여러분! 이 글은 전세계에서 모아들인 iPhone/iPod Touch 크랙 방지에 대한 두 번째 튜토리얼이야. 크랙 방지에 처음 발을 들여놓은거라면 최근의 근황에 대해 약간의 정보를 알려주지. 앱스토어 크랙은 엄청나게 확산되고있어. 5백만에 달하는 유저가 크랙버전을 쓰고 있고, 개발자들은 매일매일 엄청난 돈을 잃고 있지. 어떤 회사들은 출시 다음날에 전체 유저 중 크랙버전 유저가 95%에 달한다고 보고하기도 했어.

* 이 튜토리얼은 전세계에서 모은 코드들로 이루어져있으니 각각의 코드에 대한 저작권은 그 코드를 만든 사람에게 있습니다. 또한 저는 해킹이나 크랙 방지에 대한 윤리적 접근은 가급적 피하려고 합니다. 댓글에서도 해당 내용에 대한 언급은 피해주시기 바랍니다.

좋아, 윤리적 접근은 내버려두고 한번 해보자!

첫번째니까 쉬운걸로 한번 해볼까나? 저번 시간에 SignerIdentity에 대해 체크했던건 기억나지? 그 방법은 새로 나온 해킹방법에 의해 곧 무용지물이 될테니까 이번엔 다른 영역으로 접근해보자.

Code:
#if !TARGET_IPHONE_SIMULATOR
int root = getgid();
if (root <= 10) {
	//Pirated
}
#endif
이 코드는 그닥 설명이 필요없을거야. 어플의 사용자가 iPhone Simulator인지 아닌지 검사하고, 프로세스 ID를 얻어와서 root인지 아닌지 검사하는거지. 보통 누군가가 네 어플을 크랙하려고 하면 gdb를 돌리기 위해서 자동으로 root 권한으로 실행하는 경우가 많거든. 그러니까 사용자가 root가 아니란걸 확실히 하자는거지. 이 방법의 문제점은.. 해킹을 하기 위해서 꼭 root로 실행될 필요는 없다는거야. 상당수의 크랙 어플들은 이 검사를 빠져나갈 수 있지.

다음 방법은 iPhoneDevSDK 멤버중 한명인 javaconvert가 고안한 거야

Code:
#define kInfoSize 500
//Place your NSLog Plist Size into the above Define statment
NSString* bundlePath = [[NSBundle mainBundle] bundlePath];
NSString* path = [NSString stringWithFormat:@"%@/Info.plist", bundlePath ];
NSDictionary *fileInfo = [[NSBundle mainBundle] infoDictionary];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSDictionary *fileAttributes = [fileManager fileAttributesAtPath:path traverseLink:YES];

if (fileAttributes != nil) {
	NSNumber *fileSize;
	if(fileSize = [fileAttributes objectForKey:NSFileSize]){
		NSLog(@"File Size:  %qi\n", [fileSize unsignedLongLongValue]);
		//Best to see the File Size and change it accordingly first
		NSString *cSID = [[NSString alloc] initWithFormat:@"%@%@%@%@%@",@"Si",@"gne",@"rIde",@"ntity",@""];
		BOOL checkedforPir = false;
		if([fileInfo objectForKey:cSID] == nil || [fileInfo objectForKey:cSID] != nil) {
			if([fileSize unsignedLongLongValue] == kInfoSize) {
				checkedforPir = true;
			}
		}
		if(!checkedforPir){
			//Pirated
		}
		[cSID release];
	}
}
여기서 사용된 방법은 첫번째 튜토리얼의 두 가지 방법을 결합한거야. plist 사이즈를 체크하는거랑 달콤한 함정을 설치하는방법이지.

그럼 이제 새로운 방법을 알아볼까?
Code:
NSString* bundlePath = [[NSBundle mainBundle] bundlePath];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:(@"%@/_CodeSignature", bundlePath)];
if (!fileExists) {
	//Pirated
	NSLog(@"Pirated");
}
BOOL fileExists2 = [[NSFileManager defaultManager] fileExistsAtPath:(@"%@/CodeResources", bundlePath)];
if (!fileExists2) {
	//Pirated
	NSLog(@"Pirated2");
}
BOOL fileExists3 = [[NSFileManager defaultManager] fileExistsAtPath:(@"%@/ResourceRules.plist", bundlePath)];
if (!fileExists3) {
	//Pirated
	NSLog(@"Pirated3");
}
쩔지? 나도 알아 ㅋㅋ 여기서 하고있는건 "_CodeSignature", "CodeResources", 그리고 "ResourceRules.plist" 파일들이 존재하는지를 검사하는거지. 누군가가 어플을 크랙하면 크랙한 사람의 개인정보를 집어넣기 위해서 저 파일들을 제거시켜버리거든. 최고로 완벽한 방법이라고 볼 순 없지만 꽤나 크랙하기 힘들게 만들 수 있어.

다음에 나올 방법은 첨단 기술이라고 볼 수 있지
Code:
NSString* bundlePath = [[NSBundle mainBundle] bundlePath];
NSString* path = [NSString stringWithFormat:@"%@/Info.plist", bundlePath];
NSString* path2 = [NSString stringWithFormat:@"%@/AppName", bundlePath];
NSDate* infoModifiedDate = [[[NSFileManager defaultManager] fileAttributesAtPath:path traverseLink:YES] fileModificationDate];
NSDate* infoModifiedDate2 = [[[NSFileManager defaultManager] fileAttributesAtPath:path2 traverseLink:YES] fileModificationDate];
NSDate* pkgInfoModifiedDate = [[[NSFileManager defaultManager] fileAttributesAtPath:[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"PkgInfo"] traverseLink:YES] fileModificationDate];
if([infoModifiedDate timeIntervalSinceReferenceDate] > [pkgInfoModifiedDate timeIntervalSinceReferenceDate]) {	
	//Pirated
}
if([infoModifiedDate2 timeIntervalSinceReferenceDate] > [pkgInfoModifiedDate timeIntervalSinceReferenceDate]) {	
	//Pirated
}
여기서 택한 방법은 info.plist 파일 TimeStamp를 PkgInfo 파일의 실행파일의 TimeStamp와 비교하는 거야. 이 비교를 통해 어플이 만들어진 후에 어떤 파일이 수정되었는지를 알아낼 수 있어. 해커가 어플을 크랙하려고 할 때 일반적으로 메인 파일이나 Info.plist 혹은 둘 다를 건드리게 되거든. 그럼 TimeStamp가 바뀌게 되지. 그들은 보통 PkgInfo 파일은 건드리지 않아. 따라서 이 두 파일의 TimeStamp가 같은걸 확인함으로써 어떤 파일도 수정되지 않았다는 걸 검증할 수 있는 셈이지. *만약에 이 방법을 시도했는데 문제가 생긴다면 00초에 프로젝트를 빌드하도록 해봐(3:14:59 말고 3:14:00 말이야). 이렇게 하지 않으면 정상적으로 만들어진 프로젝트에서 처음 생성된 파일과 나중에 생성된 파일에 시간차가 생길 수도 있거든.

자 그럼, 사라진 파일과 TimeStamp와 함께하는건 여기까지야. 이제부턴 사후처리가 아닌 사전방지를 목표로 움직여보자구
Code:
#import 
#import 

#import 
#import 


// The iPhone SDK doesn't have , but it does have ptrace, and it
// works just fine.
typedef int (*ptrace_ptr_t)(int _request, pid_t _pid, caddr_t _addr, int _data);
#if !defined(PT_DENY_ATTACH)
#define  PT_DENY_ATTACH  31
#endif  // !defined(PT_DENY_ATTACH)


void ZNDebugIntegrity() {
	// If all assertions are enabled, we're in a legitimate debug build.
#if TARGET_IPHONE_SIMULATOR || defined(DEBUG) || (!defined(NS_BLOCK_ASSERTIONS) && !defined(NDEBUG))
	return;
#endif
	
	// Lame obfuscation of the string "ptrace".
	char* ptrace_root = "socket";
	char ptrace_name[] = {0xfd, 0x05, 0x0f, 0xf6, 0xfe, 0xf1, 0x00};
	for (size_t i = 0; i < sizeof(ptrace_name); i++) {
		ptrace_name[i] += ptrace_root[i];
	}
	
	void* handle = dlopen(0, RTLD_GLOBAL | RTLD_NOW);
	ptrace_ptr_t ptrace_ptr = dlsym(handle, ptrace_name);
	ptrace_ptr(PT_DENY_ATTACH, 0, 0, 0);
	dlclose(handle);
}
그래, 살짝 복잡하지. 간단히 설명하자면 어플이 동작할 때에 디버거가 붙어있나를 살펴보는거야, 붙어있다면 디버거를 정지시키는거지. 어플을 크랙하기 위해선 디버거를 붙이고, 어플을 정지시키고, 메모리에서 덤프해와야하는데 이 방법으로 디버거를 떼내버리면 뱀의 머리를 치는 셈이지.

오늘의 마지막 방법이 최고의 방법이야
Code:
#import 
#import 
#import 

/* The encryption info struct and constants are missing from the iPhoneSimulator SDK, but not from the iPhoneOS or
 * Mac OS X SDKs. Since one doesn't ever ship a Simulator binary, we'll just provide the definitions here. */
#if TARGET_IPHONE_SIMULATOR && !defined(LC_ENCRYPTION_INFO)
#define LC_ENCRYPTION_INFO 0x21
struct encryption_info_command {
    uint32_t cmd;
    uint32_t cmdsize;
    uint32_t cryptoff;
    uint32_t cryptsize;
    uint32_t cryptid;
};
#endif

int main (int argc, char *argv[]);

static BOOL is_encrypted () {
    const struct mach_header *header;
    Dl_info dlinfo;
	
    /* Fetch the dlinfo for main() */
    if (dladdr(main, &dlinfo) == 0 || dlinfo.dli_fbase == NULL) {
        NSLog(@"Could not find main() symbol (very odd)");
        return NO;
    }
    header = dlinfo.dli_fbase;
	
    /* Compute the image size and search for a UUID */
    struct load_command *cmd = (struct load_command *) (header+1);
	
    for (uint32_t i = 0; cmd != NULL && i < header->ncmds; i++) {
        /* Encryption info segment */
        if (cmd->cmd == LC_ENCRYPTION_INFO) {
            struct encryption_info_command *crypt_cmd = (struct encryption_info_command *) cmd;
            /* Check if binary encryption is enabled */
            if (crypt_cmd->cryptid < 1) {
                /* Disabled, probably pirated */
                return NO;
            }
			
            /* Probably not pirated? */
            return YES;
        }
		
        cmd = (struct load_command *) ((uint8_t *) cmd + cmd->cmdsize);
    }
	
    /* Encryption info not found */
    return NO;
}
이 방법은 내가 지금까지 본 최고의 크랙 방지법 중 하나야. 실 따위는 붙어있지 않지(?) 여기서 하는 일은 실행파일을 가져와서 암호화가 되어있나 보는거야. 맥 터미널에서 "otool -l 실행파일이름" 을 해봐도 알 수 있어. 아니면 Dr.Touch의  Anti-Crack commercial을 써봐도 되고. 해커가 어플을 크랙할때 어플을 실행하려면 암호화를 벗겨내야 되는데, 이 간단한 방법으로 실행파일이 암호화되어있는지를 확인할 수 있어.

진짜 마지막으로 소개하고자 하는 이 방법은 exit을 숨기지 않는 거야.
Code:
close(0);
[[UIApplication sharedApplication] terminate];
[[UIApplication sharedApplication] terminateWithSuccess];
UIWebView *a = [UIWebView alloc];
UIWindow *b = [UIWindow alloc];
UIView *c = [UIView alloc];
UILabel *d = [UILabel alloc];
UITextField *e = [UITextField alloc];
UIImageView *f = [UIImageView alloc];
UIImage *g = [UIImage alloc];
UISwitch *h = [UISwitch alloc];
UISegmentedControl *i = [UISegmentedControl alloc];
UITabBar *j = [UITabBar alloc];
[a alloc];
[b alloc];
[c alloc];
[d alloc];
[e alloc];
[f alloc];
[g alloc];
[h alloc];
[i alloc];
[j alloc];
system("killall SpringBoard");
해커들이 어플을 크랙하려고 할 때 Hex Editor에서 두 번째로 많이 찾는 게 바로 Close(0) 일거야. Close(0)를 없애서 크랙되는걸 막기 위해서 가능한 한 많은 Close(0)를 만들어주자는거지. 어플이 왜 계속 close를 호출하는지 혼란을 줄 수 있을 뿐만 아니라 어플을 수정하는 것도 힘들게 만들 수 있어.



 


Posted by 땡보