🤛🏽

File Integrity Checks

무결성 검사 관련해서는 두 가지 주제가 있다.
 
  1. Source Code Integrity Checks
  1. 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 루틴을 추가하는 것이다.
 
  1. mach_header는 시그니처를 생성에 사용되는 instruction data의 시작지점을 계산하기 위해 파싱된다.
  1. load_command를 하나씩 검사하며 __TEXT 세그먼트를 찾는다.
  1. __TEXT 세그먼트를 찾았다면, 다시 __text 섹션을 찾는다.
  1. __text 섹션의 주소와 크기를 구한다.
  1. __text 섹션 전체를 CC_MD5 계산하여 계산된 시그니처를 얻는다.
  1. 원본 시그니처와 계산된 시그니처를 비교하여 무결성 판단을 한다.
 
//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); } } }
 

소스코드 무결성 검사 우회

  1. anti-debugging 함수는 패치하고, 원하지 않는 행위는 NOP등으로 대체한다.
  1. 코드 무결성 평가에 사용되는 hash값을 찾아서 변경 후의 값과 동일하게 변경한다.
  1. Frida로 file system API들에 후킹을 걸어, 검사 시에는 변경된 파일이 아닌 원본 파일의 핸들을 가져와서 비교하도록 만듦.
 

2. File Storage Integrity Checks

어플리케이션에 의해 파일들이 저장될 때 항상 Integrity 가 지켜져야 한다.
(Keychain, UserDefaults/NsUserDefaults, SQLite db, Realm db 등)
 
애플리케이션 스토리지 자체의 무결성을 보장할 때, 지정된 키 값 쌍 또는 디바이스에 저장된 파일에 대해 HMAC 또는 서명을 생성할 수 있다. CommonCrypto로 HMAC을 만들기 좋다.
 
  1. NSMutableData로 데이터를 받아옴
  1. key를 받아옴 (가능하다면 키체인으로부터)
  1. 해쉬 값 계산
  1. 데이터 뒤에 해쉬 값 붙임
  1. 결과 저장
 

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];
 

파일 저장소 무결성 검사 우회

(이해를 잘 못했음)
  1. Retrieve the data from the device, as described in the "Device Binding" section.
  1. Alter the retrieved data and return it to storage.
 

3. 검증 및 평가

source ode integrity checks

 
  1. 수정을 가하지 않은 상태에서 모든 기능이 정상적으로 잘 동작하는 지부터 확인
  1. optool로 바이너리 패치 후 apple DRM을 우회하고 정상적으로 동작하도록 re-sign 까지 한다.
  1. 이 때 앱이 바이너리 변조를 감지할 수 있는지, 또 어떤 식으로 응답을 하는지를 확인한다.
  1. 서버에 기록을 남기는 등의 작업까지 하면 더 좋지만, '적어도' 유저 에게 알림창을 띄운다던지 혹은 바로 앱을 종료시킨다던지 정도는 해야한다.
 
  • 검사 매커니즘이 API 하나만 후킹하면 우회되는 정도로 쉽게 돼 있는가?
  • 정적 동적 분석 시 anti-debugging 함수를 식별하기에 얼마나 어렵게 되어 있는가?
  • defense를 우회하는 데 커스텀 코드를 작성해야 한다면 시간은 얼마나 걸리나?
  • 검수자가 느끼기에 매커니즘 우회의 어려움은 어느 정도인가?
 

storage integrity checks

 
  • 보안 매커니즘이 쉽게 우회 가능한가? (파일 내용이나 키-값 쌍을 바꾸는 것으로 우회가 되는지)
  • HMAC 또는 비대칭 비밀 키를 얻는 것이 얼마나 어려운가?
  • defense를 우회하는 데 커스텀 코드를 작성해야 한다면 시간은 얼마나 걸리나?
  • 검수자가 느끼기에 매커니즘 우회의 어려움은 어느 정도인가?