개요
안녕하세요. LINE에서 AIR ARMOR 개발을 담당하고 있는 심민영입니다. AIR ARMOR는 LINE GAME PLATFORM 중 하나인 AIR를 구성하는 보안 솔루션 중 하나입니다.
이전 포스팅, ‘AIR GO에 안드로이드 9 APK 서명 scheme v3 적용하기‘에서는 승훈 님이 Android의 서명 구조인 APK Signing에 대해 설명드렸는데요. 저는 이번 포스팅에서 iOS의 보안 구조 중 하나인 ‘코드 서명’에 대해서 설명하려고 합니다. 코드 서명은 파일의 무결성을 검증하고 서명자(개발자)를 확인하는 역할을 합니다. Mach-O 형식의 iOS 바이너리 파일의 무결성을 검증하고 서명자를 확인하는 작업엔 뒤에서 설명할 ‘code signature 구조체’를 이용합니다. 그럼 우선 iOS 코드 서명이 무엇인지부터 먼저 말씀드리겠습니다.
iOS 코드 서명
Apple 앱스토어에서 설치한 앱 혹은 테스트하기 위해 별도로 빌드한 앱은 코드 서명을 해야 iOS 기기에서 실행할 수 있습니다. 또한 코드 서명은 반드시 Apple에서 발급한 인증서로 진행되어야 합니다.
앱스토어에서 설치한 앱은 아래와 같이 Apple의 인증서로 코드 서명되어 있습니다.

개발자가 테스트하거나 배포하는 앱은 Apple이 발급한 개발자 인증서로 코드 서명합니다.


Apple의 인증서가 아닌 Apple이 발급한 ‘개발자 인증서’로 코드 서명한 앱을 기기에 설치할 때는 ‘프로비저닝 프로파일(provisioning profile)’이 반드시 필요합니다. 프로비저닝 프로파일에 명시된 기기에 프로비저닝 프로파일을 설치해야 Apple의 인증서로 코드 서명된 앱이 아니더라도 기기에서 실행할 수 있기 때문입니다.
프로비저닝 프로파일
프로비저닝 프로파일은 기기에서 앱을 실행하고 특정 서비스를 사용하고자 할 때 사용되는 파일입니다. 프로비저닝 프로파일은 Xcode에서 자동 생성하거나 ‘Apple Developer Program‘에서 생성할 수 있습니다(저는 Xcode에서 자동생성하는 것을 추천합니다).
아래 화면은 샘플 앱을 Ad Hoc으로 배포할 때 나타나는 창입니다.

위 화면에서 Automatically manage signing을 선택하면 프로비저닝 프로파일을 자동으로 생성하여 빌드합니다. 자동 생성된 프로파일은 ~/Library/MobileDevice/Provisioning Profiles
경로에서 확인할 수 있습니다.
$ ls ~/Library/MobileDevice/Provisioning\ Profiles 1axxxx49-xxxx-xxxx-xxxx-c2xxxxxxxxff.mobileprovision a8xxxxec-xxxx-xxxx-xxxx-5cxxxxxxxx22.mobileprovision
Manually manage signing을 선택하면 아래와 같이 수동으로 생성한 프로파일을 직접 선택해야 합니다. 이때 자동 생성한 프로비저닝 프로파일을 수정하여 수동 적용하는 것은 불가능합니다.

프로비저닝 프로파일의 내용
프로비저닝 프로파일은 빌드된 앱(.ipa
파일) 내부에서 찾을 수 있습니다. .ipa
파일의 압축을 풀면 Payload
디렉터리 안에 embedded.mobileprovision
란 이름의 파일이 있는데요(예: Payload/sample.app/embedded.mobileprovision
). 이 파일이 프로비저닝 프로파일입니다. 아래 명령어로 내용을 확인할 수 있고 텍스트 에디터에서 바로 파일을 열어서 확인하는 것도 가능합니다.
$ security cms -D -i embedded.mobileprovision
프로비저닝 프로파일엔 인증서와 기기 목록, Entitlements
항목, 유효기간 등이 명시되어 있습니다. 만약 실행 환경이 프로비저닝 프로파일에 명시된 자격 조건과 맞지 않는다면 앱을 실행할 수 없습니다.
인증서
프로비저닝 프로파일 내 DeveloperCertificates
항목을 보면 base64로 인코딩되어 있는 문자열이 보입니다.

DeveloperCertificates
항목openssl
명령어가 base64로 인코딩된 데이터를 제대로 해석하지 못하기 때문에 openssl
명령어가 해석할 수 있는 PEM(Privacy-Enhanced Mail) 파일 포맷에 맞추기 위해 해당 문자열을 복사하여 상단에 -----BEGIN CERTIFICATE-----
를 추가하고 하단에 -----END CERTIFICATE-----
를 추가하여 파일로 저장합니다(저는 ‘sample.txt’란 이름으로 저장했습니다).
openssl
명령어를 이용하면 아래와 같이 파일로 저장된 인증서의 정보를 확인할 수 있습니다.
$ openssl x509 -in sample.txt -text Certificate: Data: Version: 3 (0x2) Serial Number: XXXXXX... (XXXXXX... ) Signature Algorithm: sha256WithRSAEncryption Issuer: C = US, O = Apple Inc., OU = Apple Worldwide Developer Relations, CN = Apple Worldwide Developer Relations Certification Authority Validity Not Before: Oct 26 09:45:00 2018 GMT Not After : Oct 26 09:45:00 2019 GMT Subject: UID = XXXXXXXX, CN = iPhone Developer: XXXXXX (XXXXXXXX), OU = XXXXXXXX, O = XXXXXX, C = US Subject Public Key Info: . . .
Entitlements
인증서 정보를 확인한 후 바로 아래를 보시면 Entitlements
항목이 존재합니다.

Entitlements
항목Entitlements
항목에는 어떤 서비스를 사용할 수 있는지에 대한 자격 증명이 명시되어 있습니다(위 프로비저닝 프로파일 샘플은 디버그용 빌드이기 때문에 프로세스 디버깅이 가능하도록 get-task-allow
항목이 true
로 설정되어 있습니다).
위 항목 내 배열(array
)은 Xcode 빌드 설정의 Capabilities 탭에서 무엇을 선택했느냐에 따라 달라집니다. 아래 그림과 같이 Access WiFi Information 기능과 Apple Pay 기능을 추가해 보겠습니다.

아래처럼 추가한 기능에 맞게 key
와 value
가 Entitlements
항목에 추가됩니다.

Entitlements
항목유효 기간
프로비저닝 프로파일의 유효기간은 ExpirationDate
항목에서 확인할 수 있습니다. 이 항목에 명시된 유효기간이 지나면 앱을 실행할 수 없습니다.

ExpirationDate
항목기기 목록
실행 가능한 기기 목록은 ProvisionedDevices
항목에서 확인할 수 있는데요. 앱은 이 목록에 있는 기기에서만 실행할 수 있습니다.

ProvisionedDevices
항목Ad Hoc 등 모든 기기에서 실행 가능한 앱의 프로비저닝 프로파일에는 아래와 같이 ProvisionsAllDevices
항목이 true
로 설정되어 있습니다.

ProvisionsAllDevices
항목바이너리 파일의 무결성 검증
서명된 앱의 Mach-O 바이너리 파일에는 코드 서명과 관련된 구조체가 있습니다. 이 구조체를 이용하여 Mach-O 바이너리 파일의 무결성을 검증할 수 있습니다.
Mach-O 바이너리 파일
먼저 Mach-O 바이너리 파일에 대해서 간략하게 설명하고 넘어가겠습니다. iOS에서 동작하는 앱(.ipa
파일)의 내부에는 Mach-O 바이너리 파일이 있는데요. Mach-O는 iOS와 macOS 계열에서 사용하는 실행 파일 포맷입니다. iOS는 이 Mach-O 바이너리 파일을 실행하여 앱을 구동합니다.
$ file sample sample: Mach-O 64-bit executable x86_64 $ ./sample sample!
Mach-O 바이너리 파일 얻기
ipa
파일의 압축을 풀면 Payload
디렉터리 안에 Info.plist
라는 파일이 있습니다(예: Payload/sample.app/Info.plist
). 이 파일을 열어보면 Executable file이란 항목이 보이는데요. Executable file은 앱이 실행될 때 가장 먼저 실행되는 Mach-O 바이너리 파일을 의미합니다.

Info.plist
파일이를 통해 Payload/sample.app/iOSSample-mobile
파일이 앱이 실행될 때 가장 먼저 실행되는 Mach-O 바이너리 파일이라는 것을 알 수 있습니다.
아래는 샘플 Mach-O 바이너리 파일의 구조를 간략하게 나타낸 그림입니다.

machHeader
machHeader
의 Magic
값을 확인하면 해당 파일이 Mach-O인지 아닌지 식별할 수 있습니다.

machHeader
구조체구조체의 내용을 아래 표로 정리해 봤습니다.
항목 | 설명 | 샘플 | 참고 URL |
magic | Mach-O 파일인지 식별하며 byte order도 결정합니다. | 0xfeedface: 32비트 크기의 Mach-O 파일이며 byte order는 host pc의 endian을 따라갑니다. | <mach-o/loader.h> |
cpu_type | cpu 유형을 나타냅니다. | 0xc: arm | <mach/machine.h> |
cpu_subtype | cpu 하위 유형을 나타냅니다. | 0x9: arm v7 | <mach/machine.h> |
file_type | 파일 종류를 나타냅니다. | 0x2: 실행 파일 형식입니다. | <mach-o/loader.h> |
num_load_commands | load_command의 개수를 나타냅니다. | 34: 총 34개의 load command를 가지고 있습니다. | |
size_of_load_commands | load_command의 전체 크기를 바이트 단위로 나타냅니다. | 3792: 34개의 load command를 모두 합한 크기가 3792바이트 입니다. | |
flags | Mach-O 파일 포맷의 부가 기능들 중 어떤 것을 사용하는지 나타냅니다. | <mach-o/loader.h> |
표1. machHeader
구조체의 상세 내용
LoadCommand
LoadCommand
는 machHeader
뒤에 위치하고 있습니다. machHeader
를 파싱하여 Mach-O 파일로 판명되면 LoadCommand
부분을 파싱합니다.

LoadCommand
구조체 리스트LoadCommand
에는 SEGMENT
들의 위치, 동적 라이브러리의 이름, 심볼 테이블 위치 등 많은 정보들이 담겨 있습니다. 이번 포스팅에선 그중 코드 서명과 관련된 LC_CODE_SIGNATURE
(CODE_SIGNATURE
의 LoadCommand
)만 확인해 보겠습니다.
LC_CODE_SIGNATURE
Mach-O의 코드 서명 정보와 관련된 LoadCommand
입니다.

CODE_SIGNATURE
의 LoadCommand
otool
명령어를 사용해서 LC_CODE_SIGNATURE
에 대한 정보를 얻을 수 있습니다.
$ otool -l Payload/iOSSample-mobile.app/iOSSample-mobile | grep LC_CODE_SIGNATURE -A3 cmd LC_CODE_SIGNATURE cmdsize 16 dataoff 3188480 datasize 59808
dataoff
는 코드 서명 정보와 관련된 구조체인 CodeSignature
의 offset을 의미합니다. 이 정보를 하나씩 따라가면 코드 서명 정보를 전부 찾을 수 있습니다. 이번 포스팅에서 직접 찾는 방법은 생략하겠습니다.
아래는 CodeSignature
의 대략적인 구조입니다.

CodeSignature
구조체CodeDirectory
위에서 찾은 CodeSignature
구조체에서 CodeDirectory
항목을 찾을 수 있습니다. CodeDirectory
는 특정 파일과 실행 바이너리 파일 조각들의 해시값들이 담겨져 있는 부분입니다. 샘플 앱 바이너리 파일의 CodeDirectory
를 확인해 보았습니다.
CodeDirectory
구조체구조체 목록 하단에서 HashSlot
유형의 codeHash
와 specialHash
를 확인할 수 있습니다.
codeHash
codeHash
는 바이너리 파일을 pageSize
(0x1000
)만큼 나눈 부분들에 대한 해시값을 나타냅니다. 다음 그림은 샘플 앱의 codeHash
에 대해 간략히 나타낸 것입니다.

codeHash
만약 코드를 수정하면 수정한 코드가 포함된 페이지의 해시값이 달라집니다. 이 해시값은 codeHash
에 저장되어 있는 해시값과는 다르기 때문에 iOS는 코드가 수정된 사실을 알 수 있습니다.
specialHash
이번에는 specialHash
를 살펴보겠습니다. specialHash
를 간단하게 정리하면 아래 표와 같습니다.
Index | Contains | Hash (sample) |
---|---|---|
0 | Entitlement (bound in code signature) | 4b255acb014ab5dc8cd63f5120baeef19309e340 |
1 | Application Specific (largely unused) | 0000000000000000000000000000000000000000 |
2 | Resource Directory (_CodeResources) | 35b65bb61f6b617e9a944cdada31cb78b47ab393 |
3 | Internal requirements | cdbf07382a5a26998e34dc9d80070fc5db8c9230 |
4 | Bound Info.plist (Manifest) | 5108d83c00eb7f294fb73914abdb0a1c977b92f2 |
표2. specialHash
설명과 예제
각 영역이 어디를 가리키는지 확인해 보겠습니다.
Entitlement
CodeDirectory
는 바이너리 파일 내에 있는 Entitlement
부분의 해시값을 가지고 있습니다. Entitlement
는 아래 그림과 같이 CodeSignature
구조체 안에서 찾을 수 있습니다.

Entitlement
구조체Entitlement
항목을 전부 복사하여 따로 파일에 저장합니다(magic
, length
, data
전부 포함합니다).

Entitlement
항목을 텍스트 파일로 저장shasum
명령어를 사용하여 파일의 sha-1 해시값을 확인할 수 있습니다.
$ shasum entitlement.txt 4b255acb014ab5dc8cd63f5120baeef19309e340 entitlement.txt
‘표2. specialHash
설명과 예제’에서 specialHash[0]
의 값과 동일합니다.
Application Specific
Apple Mach-O 코드 서명에 관련된 코드를 보면 Application Specific은 아직 사용되고 있지 않습니다.
Resource Directory
ipa
내 _CodeSignature/CodeResources
파일을 가리킵니다(예: Payload/sample.app/_CodeSignature/CodeResources
).

Payload/sample.app/_CodeSignature/CodeResources
파일 내용CodeResources
파일은 앱 내에 존재하는 리소스 파일들에 대한 체크섬이 나열되어 있습니다. shasum
명령어로 파일의 해시값을 확인해보겠습니다.
$ shasum Payload/iOSSample-mobile.app/_CodeSignature/CodeResources 35b65bb61f6b617e9a944cdada31cb78b47ab393 Payload/iOSSample-mobile.app/_CodeSignature/CodeResources
‘표2. specialHash
설명과 예제’에서 specialHash[2]
의 값과 동일합니다.
Internal requirements
파일 내에 존재하는 Requirements
부분을 가리킵니다. Requirement
는 코드 서명을 검증하기 위한 규칙을 나타냅니다. 규칙은 여러 개가 존재할 수 있습니다. Requirement
의 개수는 Requirements
구조체 내에 명시되어 있습니다. Requirements
에 관한 정보는 codesign
명령어를 사용하여 확인할 수 있습니다.
$ codesign --display -r- Payload/iOSSample-mobile.app/iOSSample-mobile Executable=Payload/iOSSample-mobile.app/iOSSample-mobile designated => identifier "armor.sdk.sample.cocos2dx.ios" and anchor apple generic and certificate leaf[subject.CN] = "iPhone Distribution: XXXXXXX (XXXXXXX)" and certificate 1[field.1.2.840.113635.100.6.2.1] /* exists */
체크섬을 확인하기 위해서 Requirements
와 Requirement
항목을 전부 복사하여 파일로 저장합니다.

requirements
, requirement
구조체shasum
명령어로 파일의 해시값을 확인해보겠습니다.
$ shasum requirements.txt cdbf07382a5a26998e34dc9d80070fc5db8c9230 requirements.txt
‘표2. specialHash
설명과 예제’에서 specialHash[3]
의 값과 동일합니다.
Bound Info.plist
Bound Info.plist
는 .app
디렉터리 내 Info.plist
파일을 가리킵니다. Info.plist
는 앱 이름, 앱 버전, 앱 아이콘 파일 경로 등 애플리케이션에 대한 기본 정보들을 포함하고 있습니다. shasum
명령어로 파일의 해시값을 확인해보겠습니다.
$ shasum Payload/iOSSample-mobile.app/Info.plist 5108d83c00eb7f294fb73914abdb0a1c977b92f2 Payload/iOSSample-mobile.app/Info.plist
‘표2.specialHash
설명과 예제’에서 specialHash[4]
의 값과 동일합니다.
위 내용을 통해 코드 외 다른 정보들의 무결성도 검증한다는 것을 알 수 있습니다.
BlobWrapper
BlobWrapper
안에는 CMS(Cryptographic Message Syntax) 서명이 있습니다. BlobWrapper
정보는 jtool
명령어를 사용하여 볼 수 있습니다.
$ jtool --sig -v Payload/iOSSample-mobile.app/iOSSample-mobile ... Blob 4: Type: 10000 @41788: Blob Wrapper (4802 bytes) (0x10000 is CMS (RFC3852) signature)
BlobWrapper
은 앞서 기술한 CodeDirectory
를 서명한 데이터와 데이터를 서명한 인증서를 포함하고 있습니다. 인증서의 정보를 확인하기 위해 바이너리 파일에서 BlobWrapper
의 data
부분만 추출하여 파일로 저장합니다.

BlobWrapper
구조체이 파일은 openssl
명령어를 사용하여 대략적인 내용을 확인할 수 있습니다.
$ openssl pkcs7 -inform der -in blobwrapper.txt -print -noout PKCS7: type: pkcs7-signedData (1.2.840.113549.1.7.2) d.sign: version: 1 md_algs: algorithm: sha256 (2.16.840.1.101.3.4.2.1) parameter: NULL contents: type: pkcs7-data (1.2.840.113549.1.7.1) d.data: <ABSENT> cert: cert_info: version: 2 serialNumber: 134752589830791184 signature: algorithm: sha1WithRSAEncryption (1.2.840.113549.1.1.5) parameter: NULL issuer: C=US, O=Apple Inc., OU=Apple Certification Authority, CN=Apple Root CA validity: notBefore: Feb 7 21:48:47 2013 GMT notAfter: Feb 7 21:48:47 2023 GMT subject: C=US, O=Apple Inc., OU=Apple Worldwide Developer Relations, CN=Apple Worldwide Developer Relations Certification Authority key: algor: algorithm: rsaEncryption (1.2.840.113549.1.1.1) parameter: NULL public_key: (0 unused bits) 0000 - 30 82 01 0a 02 82 01 01-00 ca 38 54 a6 cb 0.........8T.. . . .
CodeDirectory
의 내용을 변경하면 동일한 인증서로 다시 서명해도 CMS 서명이 달라집니다. 따라서 공격자가 코드를 변경한 후 CodeDirectory
의 내용을 변경된 코드에 맞춰 수정한다고 해도 CMS 데이터는 키를 가지고 있는 서명자 이외에는 변경할 수 없기 때문에 CMS 서명을 검증하면 앱의 무결성을 보장할 수 있습니다.
정리

iOS는 바이너리 파일과 관련 파일들의 해시값을 확인하는 것에서 끝나지 않고, 코드 서명을 검증하여 앱의 무결성까지 확인합니다. iOS는 이런 과정을 통해 변조된 앱이 임의의 사용자 기기에서 실행되는 것을 미연에 방지합니다. AIR ARMOR에서는 이번 포스팅에서 설명드린 Mach-O 실행 파일의 코드 서명 원리를 이용한 보안 기능 외의 더 많은 보안 기능들을 제공하고 있으니 한번 확인해 보시기 바랍니다. 긴 글 읽어주셔서 감사합니다.