서론

 iOS, C/C++에서 main함수보다 먼저 실행하고 싶은 코드가 있을 수 있다. 가령, iOS앱 시작 전에 디바이스 정보를 먼저 얻어온 후 앱을 시작하는 경우나, 사용 Class의 초기화 등의 경우가 이에 해당한다.

 이런 상황에서 __attribute__((constructor)) 옵션을 지정한 함수를 선언하여 해결할 수 있다.


본론

 1. __attribute__((constructor)) 란?

 서론부에서 설명한 것 처럼 GCC에서 제공하는 옵션이다. 이 옵션을 지정한 함수는 main함수 실행 이전에 호출되어 함수의 로직을 수행하게 된다.

 1-1) __attribute__((constructor))을 지정한 함수 선언 방법 예시 

1
2
3
4
void __attribute__((constructor)) before_main( void )
{
/* Things to do before main function */
}


위와 같이 간단한 형태로 정의 할 수 있다.


 1-2) __attribute__((constructor))가 여러개인 경우의 처리

만약 메인 이전에 수행하고싶은 함수의 개수가 두 개 이상인 경우의 처리는 어떻게 하면 될까? 

이러한 경우에는 함수의 Priority를 지정할 수 있는 방법이 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int my_constructor(void) __attribute__((constructor));
int my_constructor2(void) __attribute__((constructor(101)));
int my_constructor3(void) __attribute__((constructor(102)));
int my_constructor(void/* This is the 3rd constructor */
{                        /* function to be called */
    ...
    return 0;
}
int my_constructor2(void/* This is the 1st constructor */
{                         /* function to be called */
    ...
    return 0;
}
int my_constructor3(void/* This is the 2nd constructor */
{                         /* function to be called */
    ...
    return 0;
}


위의 예시 코드에서 볼 수 있듯이 우선순위를 지정하지 않고 default로 선언을 한 경우에는 가장 후 순위로 지정되는 것을 알 수 있으며, 여러 개의 constructor함수가 default선언이 된 경우, 스택 방식으로 먼저 발견되는 함수가 나중에 호출되는 순서를 가진다.


결론

 GCC옵션 중 __attribute__((constructor))를 이용하여 메인 함수 이전에 실행되는 함수를 선언하는 방법에 대해서 알아보았다.

이 옵션과 반대의 옵션인  __attribute__((desstructor)) 옵션을 주면 메인 함수 이후에 실행되는 함수 또한 선언하여 사용 할 수 있고, 이 두 함수의 조합으로 메인 함수 이전과 이후의 처리가 필요한 경우 자유롭게 코드를 더 할 수 있게 된다. 

'Study > iOS' 카테고리의 다른 글

iOS Hooking#2(Frida)  (0) 2017.04.19
iOS Hooking#1(Logos)  (1) 2017.04.18
2장 iOS 해킹 기초 (1)  (0) 2016.06.18
자주쓰는 데이터형 변환  (0) 2016.06.01
[iOS/GCC] __attribute__((constructor)) / __attribute__((desstructor))  (0) 2016.05.13
[iOS] MD5해시 생성 / SHA256해시 생성  (0) 2016.05.09

서론

 MD5와 SHA256 해시 함수들은 주로 파일의 무결성을 검증하는데 사용되어지고 있는 해시 함수들이며, 이 둘은 단방향 암호화 함수이기 때문에 현재까지는 출력 값을 복호화하여 입력 값을 알아내기는 매우 어렵다.

 이 둘의 원리와 구현방법을 다룬다면 정말 좋겠지만, 이 글에서는 구체적인 알고리즘이나 구현 방법에 대해서는 다루지 않고, 둘의 전반적인 개요와 iOS에서 제공하고 있는 구조체와 함수들을 이용하여 파일과 메모리에 대해서 MD5 및 SHA256 해시 값을 얻어내는 방법에 대해서 알아볼 것이다.


1. MD5 해시란?

 MD5(Message-Digest algorithm 5)는 128비트 암호화 해시 함수이다.  주로 프로그램이나 파일이 원본 그대로인지를 확인하는 무결성 검사 등에 사용된다. 1996년에 MD5의 설계상 결함이 발견되었고, 2004년에는 더욱 심한 암호화 결함이 발견되었고. 2006년에는 노트북 컴퓨터 한 대의 계산 능력으로 1분 내에 해시 충돌을 찾을 정도로 빠른 알고리즘이 발표되기도 하였다. 현재는 MD5 알고리즘을 보안 관련 용도로 쓰는 것은 권장하지 않으며, 심각한 보안 문제를 야기할 수도 있다. 2008년 12월에는 MD5의 결함을 이용해 SSL인증서를 변조하는 것이 가능하다는 것이 발표되었다.


2. SHA256 해시란?

 SHA(Secure Hash Algorithm, 안전한 해시 알고리즘) 함수들은 서로 관련된 암호학적 해시함수들의 모음이다. SHA 함수군에 속하는 최초의 함수는 공식적으로 SHA라고 불리지만, 나중에 설계된 함수들과 구별하기 위하여 SHA-0이라고도 불린다. 2년 후 SHA-0의 변형인 SHA-1이 발표되었으며, 그 후에 4종류의 변형, 즉 SHA-224, SHA-256, SHA-384, SHA-512가 더 발표되었다. 이들을 통칭해서 SHA-2라고 하기도 한다. SHA-256은 256 비트이다.


 간단하게 종합적으로 요약해보면 두 함수 모두 단방향 암호화 해시함수이며, MD5보다는 SHA-2 즉 SHA-256이 더 안전하다고 보인다.


본론

1. 대상 통째로 해시 생성

위의 코드들은 XCode상에서 사용할 수 있는 CommonDigest.h 헤더파일의 일부이다. 위의 함수와 구조체를 이용해서 해시값을 구할 것이다.


결론 부터 이야기 하자면 CC_MD5함수를 이용하여 해시 값을 구할 수 있다.

unsigned char *CC_MD5(const void *source, CC_LONG len, unsigned char *dest)

CC_MD5함수와 CC_SHA256함수는 unsigned char * 를 리턴하고 있으나, 일반적으로 아래 예시처럼 리턴 값을 사용하고 있지 않지만 리턴 값은 md파라미터를 통해 전달된 포인터를 반환한다.

첫 번째 인자 : 해싱 할 데이터의 포인터

두 번째 인자 : 해싱 할 데이터의 사이즈

세 번째 인자 : 해싱 한 데이터를 저장 할 수 있는 포인터

실제 MD5 해싱소스 코드를 보면 아래와 같다. (아래 내용의 MD5를 SHA256으로 치환하고, result의 배열인덱스를 0~ 31 까지 받아내면 SHA256소스가 된다.)

펌 1)

#import <CommonCrypto/CommonDigest.h>

NSString *md5(NSString *str) {
    const char *cStr = [str UTF8String];
    unsigned char result[CC_MD5_DIGEST_LENGTH];
    CC_MD5( cStr, strlen(cStr), result );
    return [NSString stringWithFormat:@"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
        result[0], result[1],
        result[2], result[3],
        result[4], result[5],
        result[6], result[7],
        result[8], result[9],
        result[10], result[11],
        result[12], result[13],
        result[14], result[15]
    ];
}

NSString *digest = md5(@"test");
NSLog(@"MD5 TEST %@", digest);


2. 버퍼를 이용하여 해시 생성

 예를 들어 크기가 큰 파일의 해시 값을 구한다고 가정할 때, 위의 방법을 이용하면 파일 전체를 메모리에 올려야 하기때문에 저사양 단말에서는 큰 부담을 가지게 될 수 있다. 따라서 파일의 일부를 버퍼를 이용하여 해시 값을 업데이트함으로써, 메모리사용량을 줄 일 수 있다.


이 때 사용할 구조체 및 함수는

CC_MD5_CTX / CC_SHA256_CTX

CC_MD5_Init() / CC_SHA256_Init()

CC_MD5_Update() / CC_SHA256_Update()

CC_MD5_Final() / CC_SHA256_Final() 를 사용할 것이고, 개념적인 순서로 나열해보면, 

1) CC_MD5_CTX / CC_SHA256_CTX 구조체를 선언 하여 컨텍스트를 선언한다.

2) CC_MD5_Init() / CC_SHA256_Init() 컨텍스트 변수를 초기화한다.

3) CC_MD5_Update() / CC_SHA256_Update()를 이용하여 버퍼의 내용만큼 해시를 업데이트한다.

4) CC_MD5_Final() / CC_SHA256_Final() 모든 과정을 마무리하여 해싱작업을 마친다.


위의 과정을 이용한 코드는 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
static int md5useUpdate(char* data, long len, char** outData)
{
    unsigned char hashBuf[BUF_LEN] = {0,};       //업데이트에 사용할 버퍼
    long curSize = 0;                            //현재 까지 읽은 데이터 사이즈
    long remainSize = len;                       //남아있는 데이터 사이즈
    
    //md5 컨텍스트 선언 및 초기화
    CC_MD5_CTX md5;
    CC_MD5_Init(&md5);
    
    //더 읽어야할 데이터가 남아있다면
    while(curSize < len){
        
        if(remainSize < BUF_LEN){       //남은 사이즈가 버퍼보다 작을 경우, 남아있는 사이즈만큼만 메모리 복사 및 해시 업데이트
            memcpy(hashBuf, (char *)data, remainSize);
            CC_MD5_Update(&md5, hashBuf, (CC_LONG)remainSize);
        }
        
        else{                           //일반적인 경우 버퍼사이즈(1024)만큼 메모리 복사 및 해시 업데이트
            memcpy(hashBuf, (char *)data, BUF_LEN);
            CC_MD5_Update(&md5, hashBuf, (CC_LONG)BUF_LEN);
        }
        
        data += BUF_LEN;                //메모리 읽은 후 포인터 이동
        curSize += BUF_LEN;             //현재까지 읽은 사이즈 계산
        remainSize -= BUF_LEN;          //남아있는 사이즈 계산
    }
    
    unsigned char digest[CC_MD5_DIGEST_LENGTH] = {0,};
    CC_MD5_Final (digest, &md5);
    char *tmpOut = *outData;
   
    
    for(int i = 0; i < CC_MD5_DIGEST_LENGTH; i++)
    {
        sprintf(tmpOut, "%02x", digest[i]);
        tmpOut += 2;
    }
 
    return 0;
}


 정의한 함수 md5useUpdate는 해싱할 데이터와 사이즈, 해싱한 문자열을 기록할 데이터의 포인터를 입력받는다. 자세한 내용은 주석으로 설명을 대체하였다. 위의 코드 역시 약간의 수정으로 쉽게 SHA256 해싱이 가능 하도록 변환할 수 있다.


결론

 XCode내에서 제공하는 함수를 이용하여 MD5 및 SHA256해시를 생성하는 방법에 대해서 알아보았다. 내부적인 알고리즘까지 깊게 다루지는 못하였지만, 주어진 API를 이용하여 해시를 생성할 수 있었고, 이를 응용할 수 있는 방법은 사용자의 비밀번호를 저장하는 방법에 평문으로 저장하기 보다는 해시 값을 이용하여 저장하고, 파일의 해시 값을 비교하여 파일의 무결성을 검증하는 등 무궁무진할 것 같다. 



'Study > iOS' 카테고리의 다른 글

iOS Hooking#2(Frida)  (0) 2017.04.19
iOS Hooking#1(Logos)  (1) 2017.04.18
2장 iOS 해킹 기초 (1)  (0) 2016.06.18
자주쓰는 데이터형 변환  (0) 2016.06.01
[iOS/GCC] __attribute__((constructor)) / __attribute__((desstructor))  (0) 2016.05.13
[iOS] MD5해시 생성 / SHA256해시 생성  (0) 2016.05.09

1. 애플리케이션 인증서란?

 1) 어플리케이션 인증서는 개발자가 어플리케이션 마켓에 등록할 때 어플리케이션의 신뢰성을 나타내기 위해 사용하는 것이다.   모든 어플리케이션은 설치되기 전에 인증서와 함께 서명되어야 한다.

 2) 안드로이드 어플리케이션 서명은 Jar서명을 수정한 것이며 Jar서명은 암호 해시 함수를 어플리케이션의 구성요소에 적용하는 방식으로 동작한다. 이때 정해진 해시들은 인증서와 함께 배포된다. 인증서는 개발자의 개인키를 이용해서 암호화가 되고, 이것은 서명된 인증서라는 것을 의미한다.


2. apk 압축해제 및 구성요소 확인

 1) apk를 zip으로 확장자를 변경한 후 압축을 해제한다.

 

  2) 공개키 인증서와 서명파일이 존재하는 META-INF폴더로 이동한다.

META-INF 폴더는 어플리케이션 무결성을 성립하는 데 도움이 되기 때문에 매우 중요한 리소스이다. 

MANIFEST.MF :  CERT.SF파일과 매우 유사한 리소스를 정의하고있다. 

CERT.SF : 이 파일은 어플리케이션 서명으로 처리되는 어플리케이션의 모든 리소스를 포함하고 있다. 추가로 Jar암호 서명 공간을 가진다.

펌-1 )

MANIFEST.MF / CERT.SF 의 차이점은 MANIFEST.MF는 각 엔트리(실제 파일)에 대한 SHA1-Digest의  Base64로 인코딩된 값을  나타내고,

CERT.SF는 MANIFEST.MF에 기입되어 있는 각 요소들 예를들어 아래와 같이 "\r\n"를 포함한다.

-----------------------------------------------------------------------------------
Name: res/drawable-xhdpi/ic_launcher.png\r\n
SHA1-Digest: 8nhRYA3Pn75x/54RW/GQ97miQkk=\r\n

\r\n

----------------------------------------------------------------------------------- 
의 Digest의  Base64로 인코딩된 값을  나타낸다.
추가로, CERT.SF의 최상단에 기입되어 있는 SHA1-Digest-Manifest는 MANIFEST.MF의 SHA1-Digest의 Base64로 인코딩된 값을 나타낸다.

///////////////////////////////////////////////

CERT.RSA : X.509 v3 인증서이고, 내부 정보는 keytool에 의해 구조화된다.

- Owner : 공개키 소유자를 정의, 소유자 관련 국가/조직에 대한 기본 정보 포함

- Issuer : 소유의 공개키와 관련된 X.509 인증서의 발급자를 정의

- Serial Number : 발행된 인증서의 식별자

- Valid from ... until : 인증서 관련 속성이 검증되는 기간

- Certificate fingerprints: 인증서의 다이제스트 합, 인증서가 변조되지 않았다는 것을 확인하는데 사용


CERT.RSA파일을 확인하려면 openssl이 설치 되어 있어야한다.

openssl pkcs7 -inform DER -in META-INF/CERT.RSA -noout -print_cert -text

내용은 위와 같다.


3. 재서명할 키스토어 생성

윈도우와 유닉스/리눅스에서 새로운 키스토어를 생성하려면 다음 명령어를 수행한다.

keytool -genkey -v -keystore [name of keystore] -alias [your_keyalias] -keyalg RSA -keysize 2048 -validity [number of days]

keytool은 키스토어에 암호를 설정하도록 도와주며 입력사항은 잊지 않아야한다. 

인증서 생성 중 위와 같은 질의사항이 있고, 과정이 끝나면 키스토어가 생성된다.


4. 생성한 키스토어로 서명

서명전에 재서명할 apk파일에서 위에서 처럼 압축 해제한 후 META-INF를 삭제하고 다시 압축, 확장자 apk로 변경의 과정을 거친다.

jarsigner -verbose -sigalg MD5withRSA -digestalg SHA1 -keystore [name of your keysotre] [your .apk file] [your key alias]


5. 재서명한 내역 확인

재서명이 제대로 이루어졌는지 확인하기 위해서 apk를 압축해제한 후 openssl로 CERT.RSA파일을 확인한다.


추가 ) jarsigner: unable to sign jar: java.util.zip.ZipException: invalid entry compressed size (expected 766 but got 770 bytes) 등의 에러 메시지가 발생 하는 경우 apk안의 META-INF폴더를 삭제한 후 서명을 시도한다.



펌-1 )의 출처 : http://shloves.tistory.com/entry/CERTSF-MANIFESTMF-%ED%8C%8C%EC%9D%BC%EC%9D%98-%EB%8B%A4%EB%A5%B8%EC%A0%90

  1. 2017.08.24 09:26

    비밀댓글입니다

+ Recent posts