무결성 검사 관련해서는 두 가지 주제가 있다.
- Source Code Integrity Checks
- File Storage Integrity Checks
1. Source Code Integrity Checks
기본적으로 Apple에서 DRM 검사를 통해 무결성 검사를 수행한다. 파일에 저장된 시그니처와 계산한 시그니처가 다르다면 실행을 시킬 수 없다. 하지만 클라이언트 검증인 이상 작정한 공격자로부터 완벽한 방어법은 없다.
Apple DRM 우회
1. 코드패치를 한 바이너리를 가지고 re-packaging 한 후,
2. developer 혹은 enterprise certificate를 이용하여 re-signing 하는 것으로써 우회가 가능하다.
보완
여기서 공격자를 좀 더 힘들게 만드는 방법은, 시그니처가 runtime에도 여전히 일치하는지 검사하는 custom check 루틴을 추가하는 것이다.
- mach_header는 시그니처를 생성에 사용되는 instruction data의 시작지점을 계산하기 위해 파싱된다.
- load_command를 하나씩 검사하며 __TEXT 세그먼트를 찾는다.
- __TEXT 세그먼트를 찾았다면, 다시 __text 섹션을 찾는다.
- __text 섹션의 주소와 크기를 구한다.
- __text 섹션 전체를 CC_MD5 계산하여 계산된 시그니처를 얻는다.
- 원본 시그니처와 계산된 시그니처를 비교하여 무결성 판단을 한다.
//Dl_info 구조체 #ifndef _RLD_INTERFACE_DLFCN_H_DLADDR #define _RLD_INTERFACE_DLFCN_H_DLADDR struct Dl_info { const char * dli_fname; void * dli_fbase; const char * dli_sname; void * dli_saddr; int dli_version; int dli_reserved1; long dli_reserved[4]; }; #endif
int xyz(char *dst) { const struct mach_header * header; Dl_info dlinfo; if (dladdr(xyz, &dlinfo) == 0 || dlinfo.dli_fbase == NULL) { NSLog(@" Error: Could not resolve symbol xyz"); [NSThread exit]; } while(1) { header = dlinfo.dli_fbase; // Pointer on the Mach-O header struct load_command * cmd = (struct load_command *)(header + 1); //첫번째 load command를 담음. //Mach-O 파일구조에서 load_commands를 하나씩 살피며 loop 돌림. //__Text 세그먼트를 로드하는 load_commands를 먼저 찾고. //__Text 세그먼트 내의 __text 섹션을 찾을 때까지 반복됨. for (uint32_t i = 0; cmd != NULL && i < header->ncmds; i++) { //지금 보고 있는 load commands가 세그먼트인지 확인 (__Text 세그먼트 먼저 찾고, 거기서 섹션을 볼 거니까) if (cmd->cmd == LC_SEGMENT) { struct segment_command * segment = (struct segment_command *)cmd; // 현재 보고 있는 세그먼트가 __TEXT segment load command인지 먼저 확인하고, // 찾았다면 이제는 섹션을 보고 __text section load command를 찾는다. if (!strcmp(segment->segname, "__TEXT")) { struct section * section = (struct section *)(segment + 1); for (uint32_t j = 0; section != NULL && j < segment->nsects; j++) { if (!strcmp(section->sectname, "__text")) break; //__text section load command를 찾았으면 목표를 달성했으므로 스탑. section = (struct section *)(section + 1); } // __text 섹션의 주소, 크기, 가상주소 구하기 uint32_t * textSectionAddr = (uint32_t *)section->addr; uint32_t textSectionSize = section->size; uint32_t * vmaddr = segment->vmaddr; char * textSectionPtr = (char *)((int)header + (int)textSectionAddr - (int)vmaddr); //현재 text section의 시그니처를 계산하고 문자열로 저장한 후 저장돼있는 원본 시그니처와 비교한다. unsigned char digest[CC_MD5_DIGEST_LENGTH]; CC_MD5(textSectionPtr, textSectionSize, digest); // calculate the signature for (int i = 0; i < sizeof(digest); i++) // fill signature sprintf(dst + (2 * i), "%02x", digest[i]); // (여기 코드를 알아서 수정해야 하는듯?) // return strcmp(originalSignature, signature) == 0; // verify signatures match return 0; } } cmd = (struct load_command *)((uint8_t *)cmd + cmd->cmdsize); } } }
소스코드 무결성 검사 우회
- anti-debugging 함수는 패치하고, 원하지 않는 행위는 NOP등으로 대체한다.
- 코드 무결성 평가에 사용되는 hash값을 찾아서 변경 후의 값과 동일하게 변경한다.
- Frida로 file system API들에 후킹을 걸어, 검사 시에는 변경된 파일이 아닌 원본 파일의 핸들을 가져와서 비교하도록 만듦.
2. File Storage Integrity Checks
어플리케이션에 의해 파일들이 저장될 때 항상 Integrity 가 지켜져야 한다.
(Keychain, UserDefaults/NsUserDefaults, SQLite db, Realm db 등)
애플리케이션 스토리지 자체의 무결성을 보장할 때, 지정된 키 값 쌍 또는 디바이스에 저장된 파일에 대해 HMAC 또는 서명을 생성할 수 있다. CommonCrypto로 HMAC을 만들기 좋다.
- NSMutableData로 데이터를 받아옴
- key를 받아옴 (가능하다면 키체인으로부터)
- 해쉬 값 계산
- 데이터 뒤에 해쉬 값 붙임
- 결과 저장
HMAC 생성
//NSMtableData 형태로 데이터 받아옴. NSMutableData* actualData = [getData]; //데이터 받아오는 코드 수정해야함. //키 가져오기 (가능하다면 키체인으로부터) NSData* key = [getKey]; //키 받아오는 코드 수정해야함. //Hash값 계산하기 NSMutableData* digestBuffer = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH]; CCHmac(kCCHmacAlgSHA256, [actualData bytes], (CC_LONG)[key length], [actualData bytes], (CC_LONG)[actualData length], [digestBuffer mutableBytes]); //데이터 뒤에 계산한 해쉬값 붙이기 [actualData appendData: digestBuffer];
NSData를 써도 되지만 길이변경이 안되므로 append 할 때 새로운 버퍼를 할당해야함.
HMAC 검증
NSData* hmac = [data subdataWithRange:NSMakeRange(data.length - CC_SHA256_DIGEST_LENGTH, CC_SHA256_DIGEST_LENGTH)]; NSData* actualData = [data subdataWithRange:NSMakeRange(0, (data.length - hmac.length))]; NSMutableData* digestBuffer = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH]; CCHmac(kCCHmacAlgSHA256, [actualData bytes], (CC_LONG)[key length], [actualData bytes], (CC_LONG)[actualData length], [digestBuffer mutableBytes]); return [hmac isEqual: digestBuffer];
파일 저장소 무결성 검사 우회
(이해를 잘 못했음)
- Retrieve the data from the device, as described in the "Device Binding" section.
- Alter the retrieved data and return it to storage.
3. 검증 및 평가
source ode integrity checks
- 수정을 가하지 않은 상태에서 모든 기능이 정상적으로 잘 동작하는 지부터 확인
- optool로 바이너리 패치 후 apple DRM을 우회하고 정상적으로 동작하도록 re-sign 까지 한다.
- 이 때 앱이 바이너리 변조를 감지할 수 있는지, 또 어떤 식으로 응답을 하는지를 확인한다.
- 서버에 기록을 남기는 등의 작업까지 하면 더 좋지만, '적어도' 유저 에게 알림창을 띄운다던지 혹은 바로 앱을 종료시킨다던지 정도는 해야한다.
- 검사 매커니즘이 API 하나만 후킹하면 우회되는 정도로 쉽게 돼 있는가?
- 정적 동적 분석 시 anti-debugging 함수를 식별하기에 얼마나 어렵게 되어 있는가?
- defense를 우회하는 데 커스텀 코드를 작성해야 한다면 시간은 얼마나 걸리나?
- 검수자가 느끼기에 매커니즘 우회의 어려움은 어느 정도인가?
storage integrity checks
- 보안 매커니즘이 쉽게 우회 가능한가? (파일 내용이나 키-값 쌍을 바꾸는 것으로 우회가 되는지)
- HMAC 또는 비대칭 비밀 키를 얻는 것이 얼마나 어려운가?
- defense를 우회하는 데 커스텀 코드를 작성해야 한다면 시간은 얼마나 걸리나?
- 검수자가 느끼기에 매커니즘 우회의 어려움은 어느 정도인가?