먼저 말하자면 과거의 검사 방법이다.
** 새 버전의 dyld 소스코드에서 RESTRICT 탐지 부분이 제거되어 iOS 10버전 이후로는 유효하지 않다. **
빌드 셋팅에서 Linker Flags에 아래의 설정을 찾고 추가한 후 빌드하면, 실행파일에 섹션이 하나 추가된다.
- -Wl
- -sectcreate
- __RESTRICT
- __raestrict
- /dev/null
MachOView로 헤더구조를 살펴보면 아래 그림과 같이 섹션이 추가된 것을 볼 수 있다.
__RESTRICT, __raestrict

Mach-O 파일에서 이 섹션이 발견되면, 동적 라이브러리 삽입에 실패한다.
일종의 보호 역할을 한다고 보면 되는데, 동작 원리는 DYLD 소스코드를 분석해서 알아보자.
DYLD 소스코드 분석
먼저 분석에 이용된 dyld 소스코드 버전은 519.2.2 이다.
메모리에 dylib을 로드하는 코드
// DYLD_INSERT_LIBRARIES 환경변수가 null이 아니라면 // 환경변수에 명시된 모든 dylib을 메모리로 로드한다. if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) { for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) loadInsertedDylib(*lib); }
소스코드를 살펴보면 DYLD_INSERT_LIBRARIES 환경변수 값을 가져오고, 거기에 명시된 dylib들은 모두 메모리로 로드하는 코드를 찾을 수 있다.
환경변수 값 필터링
하지만 라이브러리들을 메모리로 올리기 전, 환경변수 값이 보다 이전에 미리 검사 및 필터링 된다.
//processIsRestricted 값이 참이라면 관련 환경변수 값 다 지움. if ( gLinkContext.processIsRestricted ) { pruneEnvironmentVariables(envp, &apple); // set again because envp and apple may have changed or moved setContext(mainExecutableMH, argc, argv, envp, apple); }
processIsRestricted 값이 참이라면 현재 프로세스가 restricted 임에도 dylib를 삽입하려고 시도한다고 판단하고, 관련 환경변수 값을 지우는 pruneEnvironmentVariables 함수를 호출한다.
processIsRestricted 값 설정 부분
그렇다면 processIsRestricted 값은 어디서 설정될까?
// setuid 또는 setgid bit가 set된 프로세스 이거나 // 바이너리가 __RESTRICT 세그먼트를 가지고 있다면 // processIsRestricted 값을 true로 설정한다. if ( issetugid() || hasRestrictedSegment(mainExecutableMH) ) { gLinkContext.processIsRestricted = true; }
issetugid() 함수와 hasRestrictedSegment 함수를 이용하여 processIsRestricted 값을 설정한다.
- issetugid() 함수 : 라이브러리가 상승된 권한으로 프로그램에서 사용되고 있는지 알려주고, LD_LIBRARY_PATH, NLSPATH 등의 환경변수를 활용한 위험한 행동을 피할 수 있도록 하는 것에 목적을 둔 함수다.
- hasRestrictedSegment 함수 : 바이너리에 __RESTRICT 세그먼트와 그 안에 __restrict 섹션이 있는지 검사하는 함수다. 구현 코드는 아래를 참조하자.
hasRestrictedSegment 함수

Mach-O 헤더구조를 알아야 아래 소스코드 이해가 쉽다.
#if __MAC_OS_X_VERSION_MIN_REQUIRED static bool hasRestrictedSegment(const macho_header* mh) { //Mach-O 헤더에서 load_commands 개수를 받아옴. const uint32_t cmd_count = mh->ncmds; //load_commands의 위치를 계산. const struct load_command* const cmds = (struct load_command*)(((char*)mh)+sizeof(macho_header)); const struct load_command* cmd = cmds; //모든 load_commands를 검사. (__RESTRICT 세그먼트와 그 안에 __restrict 섹션을 찾기까지) for (uint32_t i = 0; i < cmd_count; ++i) { switch (cmd->cmd) { case LC_SEGMENT_COMMAND: { const struct macho_segment_command* seg = (struct macho_segment_command*)cmd; //dyld::log("seg name: %s\n", seg->segname); //__RESTRICT 세그먼트가 존재한다면, //그 안에 __restrict 섹션이 있는지를 찾고 있다면 종료. if (strcmp(seg->segname, "__RESTRICT") == 0) { const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command)); const struct macho_section* const sectionsEnd = §ionsStart[seg->nsects]; for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) { if (strcmp(sect->sectname, "__restrict") == 0) return true; } } } break; } cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); } return false; }
결론은 Mach-O 파일 구조에서 __RESTRICT 세그먼트와 그 안에 __restrict 섹션이 있는지를 검사하는 함수다.
최종적으로 __restrict 섹션을 찾았다면 true를 반환하고 아니라면 false를 반환한다.
결론
dyld 소스 코드를 분석한 결과
- 라이브러리가 상승된 권한으로 실행되고 있는지 - issetugid() 함수
- __RESTRICT 세그먼트와 내부에 __restrict 섹션이 존재하는지 여부를 판단하여 - hasRestrictedSegment 함수
- dyld 라이브러리의 로드를 막는다.