1. Cycript

Cycript는 iOS에서 실행되고 있는 애플리케이션을 동적으로 수정하고 분석을 할 수 있게 해주는 SDK이다.

문법은 기본적으로 Objective-C / JavaScript를 혼합하여 사용 가능하다.

 


2. Cycript Hooking


2-1) view계층 확인하기

ex) UIApp.keyWindow.recursiveDescription()


view의 계층을 출력하여준다. NSLog로 출력하여 큰 화면으로 보면 아래와 같이 이쁘게 보인다.



2-2) subviews()

ex) UIApp.keyWindow.subviews()[index]


window는 하나 이상의 view를 가지며, view 역시 하나 이상의 view의 집합이다. 각 하위 view들의 접근은 subviews()[index]의 형태로 접근 가능하고, 2-1에서 확인한 계층을 참조하여 각 요소들을 찾을 수 있다.



2-3) _viewDelegate()

ex) UIApp.keyWindow.subviews()[2].subviews()[0]._viewDelegate()


_viewDelegate를 이용하여 해당 view의 controller에 접근 가능하다.


2-4) replacing existing objective-C methods

ex) Game2ViewController.prototype['recognizeAnswer'] 

= function() { NSLog(@"[JSACH]HOOKED"); return 1;}


target ViewController를 찾아 prototype배열에 후킹할 method에 접근하여 위와 같이 재정의해줌으로써 replacing가능함



참고) Cycript에서 사용하는 API나 문법, Trick들이 정리되어있는 링크

 

 - iOS Tricks

     http://iphonedevwiki.net/index.php/Cycript_Tricks


 - Cycript공식 홈페이지

http://www.cycript.org/


참고로 이 글은 Cycript 0.9.594버전을 기준으로 작성을 하였다.



3. 결과


3-1) 메서드 재정의


1
Game2ViewController.prototype['recognizeAnswer'= function() { NSLog(@"[JSBACH]HOOKED"); NSLog(@"[JSBACH] return SUCCESSED"); return 1;}





3-2) 메서드 후킹 로그


3-3) 후킹 성공 결과




4. 후기

frida와 Logos 그리고 오늘 Cycript까지 정리하면서 처음에 계획했던 후킹 방법 세가지에 대해 모두 간단하게 겪어(?)보았다. 각 방법의 장단점이 어느 정도 감이 오는 것 같아서 매우 뿌듯하고 흡족스럽다!


개인적으로 생각하는 frida와 Logos, Cycript를 비교하면 이렇다고 생각한다.


 항목

Logos 

Frida 

Cycript 

설치의 편의성

 

 

상 

수정사항 반영의 편의성

 

 

상 

후킹 사항 유지력 

 

하 

중 

 참조 자료의 양

중 

상 


4-1) 설치의 편의성

후킹하기 위한 각 툴들의 환경설치에 대한 평가 항목으로 선정해보았고, 개인적으로 소요된 시간과 환경의 까탈스러움 등을 고려하여 평가해보았다.


Logos의 경우 Mac사용자라면 간편한 편일 수 있으나, 본인은 Kali Linux에 환경을 설치하면서 꽤나 애를 먹었던 기억이 나서 낮은 '하'로 선정하였다.


Frida는 비교적 편리하게 설치하였으나, 단말에 Server Binary를 올려야하는 점과 Cycript만큼 간단하지 않았던 것같아(ㅎㅎ) '중'으로 점수를 매겼다.


Cycript의 경우 Cycia에서 패키지 설치만으로 모든 설치가 정상적으로 완료 되어 '상'으로 책정하였다.



4-2) 수정사항 반영의 편의성

후킹 코드 작성 중 수정사항이 생긴경우 재 반영하는 방법의 편의성에 대한 평가 항목이다.


Logos는 .deb파일을 재설치하고 Spring Board가 재시동되어야 하는 불편함이 있어 '하'가 되었다.


Frida의 경우 script를 종료 시킨 후 수정, 재실행의 과정이 있어야 하고.. 아무래도 Cycript만큼 간단하지 않았던것 같아 '중'으로 하였다.


Cycript는 후킹 script만 재정의 해주면 되므로 가장 간단하다고 판단하여 '상'으로 하였다.


4-3) 후킹 사항 유지력(?)

항목이름이 이상한거 같긴한데.. 적절한 명칭이 떠오르지않아서ㅠ... 한번 후킹 코드를 넣었을 때, 얼마나 유지가 되는지(?)에 대한 항목이다.


Logos는 한번 .deb설치가 되면 삭제가 될때까지 유지가 된다. 즉, 개발한 패키지를 한번 설치를 하고난 후 특별한 조치를 취하지 않아도 후킹코드가 유지된다. 가장 유지력이 좋다고 생각하여 '상'으로 책정하였다.


Frida는 후킹 script가 종료되기 전까지 유지가 되고, Cycript는 후킹 Process가 종료되기 전까지 유지 되므로 각각 '하'와 '중'으로 하였다.


4-4) 참조 자료의 양

비교 항목 중 가장 주관적인 항목으로 본인이 검색해 본 경험에 따라 그냥 매겼다.

아무래도 요즘 많이 떠오르고있는 Frida가 자료가 가장 많아 '상', Cycript와 Logos는 비슷 한것 같지만

Cycript가 예전 자료가 너무 많이나와서 사실상 볼 만한 자료가 거의 없었다. 따라서 더 애를 많이 먹었던 것 같아 '하'로 책정하였다.





다음 목표는 Logos를 이용하여 tsprotector와 비슷한 JB우회 패키지를 만들어 보고자 한다. 

fopen과 같은 file관련 API들, System call등을 후킹하여야 할 것으로 예상된다~


'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

1. Frida

Inject JavaScript to explore native apps on Windows, macOS, Linux, iOS, Android, and QNX.

[출처] https://www.frida.re/


Frida는 자바스크립트를 이용하여 Windows, macOS, Linux, iOS, Android와 QNX와 같은 다양한 플랫폼에서 후킹을 할 수 있는 플랫폼이다. 파이썬 틀에 인젝션할 코드를 자바스크립트로 작성하여 파이썬으로 실행 할 수 있다. 



2. Frida Hooking

iOS Hooking#1 에서 분석한 동일한 앱을 대상으로 진행을 하였으므로 앱 분석은 생략한다.


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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# -*- conding: utf-8 -*-
import frida
import sys 
 
PACKAGE_NAME = "kr.history"
 
#send실행 시 출력할 format정의
def on_message(message, data):
    try:
        if message:
            print("[JSBACH] {0}".format(message["payload"]))
    except Exception as e:
        print(message)
        print(e)
 
 
def do_hook():
 
    hook = ""
    //Objective-C가 실행 가능한 환경인지 검사
    if(ObjC.available){
        //해당 attach된 프로세스의 메모리에 올라가있는 클래스들을 가져올 수있고,
        //아래 for문은 타겟 클래스가 존재하는지 검사했다.
        for(var className in ObjC.classes){
            if(className == "Game2ViewController"){
                send("Found our target class : " + className);
            }
        }
    
        //Hooking을 진행할 메서드 객체를 가져온다.
        var hook_method = ObjC.classes.Game2ViewController["- recognizeAnswer"];
        send("print hook_method : " + hook_method);
        
        Interceptor.attach(hook_method.implementation, {
        
            //onEnter는 후킹함수 진입 시 실행되며, args[0]에는 self객체가
            //args[1]에는 selector객체가 들어있어 접근 가능하며
            //args[2]에는 해당 함수의 매개변수들이 들어있다.
            //매개변수를 변경하고 싶다면 이곳에서 변경한다.
            onEnter: function(args){
                
                var receiver = new ObjC.Object(args[0]);
                send("Target class : " + receiver.$className);
                send("Target superclass : " + receiver.$superClass.$className);
                var sel = ObjC.selectorAsString(args[1]);
                send("typeof sel = " + typeof sel);
                send("Hooked the target method : " + sel);
            },
            //onLeave는 함수 종료전 처리를 할 수 있다.
            //retVal에는 원래의 return값이 들어있고, return값을 변경하고 싶다면
            //이곳에서 변경한다.
            onLeave: function(retVal){
                //오답 시 리턴 값
                var wrong   = -1;
                //정답 시 리턴 값
                var correct = 1; 
                //retVal은 Object객체이며, int값으로 사용하기 위해 toInt32()를 사용
                var orig_rtn = retVal.toInt32();
                if(-1 == orig_rtn){
                    send("answer is Wrong!! : " + orig_rtn);
                    send("answer is replaced!!");
                    //값을 변경하기위해 replace()를 사용하여 변경함
                    retVal.replace(correct);
                }
                else{
                    send("answer is Correct!! : " + orig_rtn);
                }
            }
        });
    }
    //Objective-C 실행 환경이 아닌 경우 로그 출력
    else{
        console.log("Objective-C Runtime is not available!");
    }
    """
 
    return hook
 
if __name__ == '__main__':
 
    try:
        #연결할 단말을 찾는다.
        device = frida.get_device_manager().enumerate_devices()[-1]
 
        #타겟으로 할 앱의 패키지명으로 단말에서 실행되고 있는 pid를 가져온다.
        pid = device.spawn([PACKAGE_NAME])
        print("[JSBACH] {} is starting. (pid : {})".format(PACKAGE_NAME, pid))
 
        #위에서 얻어온 pid에 attach한다.
        session = device.attach(pid)
        device.resume(pid)
 
        #후킹코드를 injection한다.
        script = session.create_script(do_hook())
        script.on('message', on_message)
        script.load()
        sys.stdin.read()
    except KeyboardInterrupt:
        sys.exit(0)



코드에 대한 설명은 주석으로 대체합니다.


3. 결과





4. 코드 작성하며 발생했던 사소한 문제점들

4-1) frida client / server간 버전 충돌 문제

frida --version / frida-server --version 으로 확인한 메이저버전이 다를 경우 frida가 제대로 동작하지 않는다.


4-2) 32bit 단말 문제

실습에 활용한 단말은 iPhone 5로 32bit 단말이였는데, Frida최신버전(9.1.27)버전에서 제대로 훜이 걸리지않음

-> 8.2.2버전으로 낮추어 설치하여 정상적으로 동작 (pip install frida==8.2.2)


4-3) retVal 값 format

onLeave에있는 retVal값을 toString()을 이용해 사용하면 헥사 값이 나온다. (위 예제에서는 오답인 경우 0xffffffff, 정답인 경우 0x1) 따라서 toInt32()함수를 이용해서 int로만들어 주었다.

0xffffffff를 parseInt()를 사용하여 출력하면 unsigned int형의 최대값인 4294967295가 출력되었다.


4-4) onEnter

onEnter에서 args에 접근하여 receiver와 receiver의 superclass를 출력할 때, 많은 예제코드에서 

$className이 생략되어있었는데, 타입 변환 에러가 나면서 제대로 출력되지 않았으며, frida API를 참조해서 

$className을 추가하여 해결함

[참조] https://www.frida.re/docs/javascript-api/#objc


4. 후기

Logos와 비교했을 때, 후킹 코드의 빌드나 설치가 별도로 필요 없어서 가볍다는 느낌이 들었다.

python과 JavaScript로 iOS후킹 코드를 짤 수 있다는 게 신기했고,

탈옥상태가 아닌 폰에서도 후킹이 가능하다는 점도 신기했다.

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

iOS Hooking#3(Cycript)  (3) 2017.05.06
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

1. Logos

Logos is a component of the Theos development suite that allows method hooking code to be written easily and clearly, using a set of special preprocessor directives.


-[출처]http://iphonedevwiki.net/index.php/Logos


로고스는 Theos컴포넌트로 로고스를 이용하면 iOS 메소드 후킹 코드를 쉽고 깔끔하게 작성할 수 있다.

로고스를 이용한 후킹을 실습하기 위해 퀴즈앱을 대상으로 진행해보았다.


2. App Logic

2-1) 앱 실행 화면

앱은 퀴즈와 함께 해당 퀴즈 정답의 글자 수가 표시되고, 아래 글자를 클릭하여 정답을 맞추는 형식을 갖추고 있다.


2-2) iDA 정적 분석


Clutch를 이용하여 복호화한 Binary를 iDA로 열어 코드를 분석하면 위와 같은 코드를 발견 할 수 있는데, recognizeAnswer라는 메소드의 결과 값에 따라 congratulationViewVisible여부를 결정 하는 것을 알 수 있다.


recognizeAnswer메소드의 내용은 다음과 같다.


위 코드에서는 조금 잘렸는데, v2는 Game2ViewController의 self객체이고, answer_nospace의 길이가 0이 아니면 answer_nospace의 길이만큼 while문을 돌면서 userAnswer와 answer_nospace를 한글자씩 비교한다.

그 후 모두 매치하면 v15를 1로 만들고 매치하지 않으면 break한 후 v15를 -1로 만드게 되고 이 v15값을 리턴한다. 즉 유저의 정답과 문제의 정답을 비교하여 정답인 경우 1을 리턴하고, 오답인 경우 -1을 리턴하는 메서드임을 알 수 있다. 


2-3) Class-dump

2-2에서 확인한 Game2ViewController의 멤버변수나 메서드들을 Class-dump로 확인해 보았고, 우리가 주시해야하는 것들 부분만 발췌하였다.

answer_nospace는 앱에서 가지고있는 (분석한 결과 앱내 DB에서 조회해오는 값) 정답에 해당하는 변수이고, userAnswer는 유저가 입력한 답안 변수이다.



recognizeAnswer메서드는 멤버변수 값들로만 리턴 값을 정할 수 있으므로 특별한 Input값은 필요하지 않은 형태로 존재함을 확인 할 수있다.



3. Logos Hooking

위 분석 결과를 바탕으로 recognizeAnswer를 후킹하여 어떤 답안이 입력되든 정답처리가 될 수 있도록 Logos를이용한 후킹코드를 작성해 보았다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
  
@interface Game2ViewController : UIViewController{
  - (int)recognizeAnswer;
@end
%hook Game2ViewController
- (int)recognizeAnswer{
    int ret = %orig;
    int correct = 1;
    NSLog(@"[JSBACH]recognizeAnswer is Hooked\n"); 
    if(-1 == ret){
         NSLog(@"[JSBACH]answer is Wrong!! : %d", ret);
    }    
    else{
         NSLog(@"[JSBACH]answer is Correct!! : %d", ret);
    }
 
    return correct;
}
%end



@interface Game2ViewController : UIViewController{

}


- (int)recognizeAnswer;

@end


후킹 타겟으로 하는 클래스/인터페이스와 해당 메서드의 form을 정의해준다.

우리가 후킹할 Game2ViewController는 UIViewController의 sub이므로 Game2ViewController : UIViewController부분이 있고, class-dump의 결과에서 확인했던 것과 같이 - (int)recognizeAnswer 메서드 형태로 정의해준다.


%hook Game2ViewController 후킹할 클래스/인터페이스를 지정하고 %end로 후킹코드의 끝을 내준다.

%orig는 원래 메서드의 리턴값을 가져올 수 있으며, argument가 존재하는 경우에는 %orig(arg1, arg2, ...)형식으로 호출 가능하다.


여기서는 본래의 리턴값을 가져와서 정답/오답여부를 로그로 표출하고, 무조건 정답처리 할 수 있도록 코딩하였다.



3. 후킹 결과



정상적으로 후킹 되어 로깅이 되고있는 모습이다.



원래 정답은 비파형 / 세형이지만 아무 값이나 오답을 입력한 후 테스트 하였고 정답처리가 됨을 확인하였다. :)



4. 후기

Logos를 이용해서 트윅제작, 시스템콜 후킹, UI버튼을 추가하는 등의 다양한 여러작업이 가능하다고 들었는데, 천천히 하나씩 해보고 싶다. 우선 다음 게시글은 Frida를 이용해서 똑같은 행위를 하는 후킹코드를 작성 해볼 예정이다.





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

iOS Hooking#3(Cycript)  (3) 2017.05.06
iOS Hooking#2(Frida)  (0) 2017.04.19
2장 iOS 해킹 기초 (1)  (0) 2016.06.18
자주쓰는 데이터형 변환  (0) 2016.06.01
[iOS/GCC] __attribute__((constructor)) / __attribute__((desstructor))  (0) 2016.05.13

서론

탈옥된 단말에는 파일을 전송하고, 그 파일을 실행시킬 수 있다.

2장 iOS해킹 기초 에서는 간단하게 hello world를 출력하는 프로그램을 탈옥단말에서 실행시키는 것을 실습하게 되어있다. 책에 있는 방법을 따라가면서 실습을 진행하려고 하였으나, 책이 12년도에 출판된 책이라서 OS환경이 달라 똑같이 진행하는데는 무리가 있었고, 다른 방법을 찾아가면서 실습을 마칠 수 있었다.


본론

탈옥 단말에서 hellow world를 출력하는 프로그램을 실행하기 위한 순서는 아래와 같다.

1. hellow world를 출력하는 소스 코드 작성

2. 컴파일

3. 파일전송을 통해 탈옥단말로 파일 전송

4. 파일 실행


1. hellow world를 출력하는 .c 파일 작성

처음에 소스코드가 간단하기 때문에 vi로 작성하였다.


2. 컴파일

gcc로 컴파일 하여 실행파일을 만들어냈다.


3. sftp를 이용하여 파일전송

책에서는 scp를 이용한 방법으로 써져있었지만, FileZilla로 sftp를 이용하여 파일을 전송하였다.


4. 파일실행

파일을 실행했더니, 아래와 같은 에러 문구가 나오고 실행이 되지 않았다.

이유를 곰곰히 생각해보니, 파일이 arm cpu에서 실행될 수 없는 파일이라는 것을 깨닫게 되었고, 직접확인해본 결과

file의 타겟 cpu타입이 다르다는 것을 확인할 수 있었다.

file 명령어를 통해 확인해 본 결과로 컴파일된 test라는 프로그램은 Mach-O 64-bit바이너리이고, 타겟 cpu는 x86_64였다.


문제 해결을 위해 arm cpu를 타깃으로 컴파일할 수 있는 방법에 대해서 여러가지 찾아보았으나, 책에서 제시하고 있는 llvm-gcc는 OS버전이 올라가면서 경로에서 찾을 수 없었다. arm cpu를 타깃으로 컴파일할 수 있는 arm-eabi-gcc를 설치하고 컴파일 해보니 arm타깃으로 컴파일이 되기는 하나 ELF파일 형식을 가지고 있어서 실행이 될 것같지는 않았다.

한 가지 들었던 생각은 어찌됐건 XCode에서 앱을 만들면 ipa안에 실행 바이너리가 들어있으니까, llvm-gcc가 XCode안에 들어있지 않을까 하고 생각하기는 했지만 찾을 수는 없었고, 조언을 통해 XCode로 컴파일을 하는 방법을 시도해보았다.

 

iOS 앱 프로젝트를 하나 생성한 후 Supporting Files안에 있는 main.m을 위의 사진과 같이 만들어서 실행 바이너리를 얻을 수 있었다.


한 가지 유의할 점은 앱을 빌드할 때 타깃이 시뮬레이터인경우 생성되는 바이너리는 타깃 cpu가 x86_64로 생성된다. 생각해보면 시뮬레이터는 컴퓨터에서 돌아가는 환경이니 당연한 결과인 것 같고, 원하는 arm 타깃 바이너리를 만들기 위해서는 실 디바이스를 연결해서 빌드하는 과정이 필요했다.


이렇게 생성한 바이너리를 동일한 3, 4과정을 거쳐 실행을 한 결과 아주 잘 동작하는 것을 확인할 수 있었다!


결과

정리하고 나니 삽질의 흔적이 많이 보이지는 않지만 저 mach-o arm 실행 바이너리를 얻기위해 많은 것을 시도했다. 이 과정을 통해서 컴파일러들에 대해서 조금은 더 알게되었고 막연했던 무언가가 윤곽이 보이는 것 같은 느낌이든다. 

지금은 간단하게 hellow world를 출력하는 코드를 삽입한 것일 뿐이지만, 특정 위치의 파일을 읽어 온다거나 어떤 메모리의 덤프를 뜬다거나 하는 코드 또한 동작시킬 수 있을 것 같고, 나중에 꼭 쓰게될 기술일거라고 생각한다.

다음 블로깅에는 삽입한 코드를 특정 이벤트 발생 시 실행 시킬수 있는 plist를 작성해서 실습을 진행해볼 예정이다. 


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

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

서론

iOS에서 개발을 하다보면, C <-> Objective C 간 데이터 형변환을 하게되는 경우가 빈번한 것 같다. 예를 들어 C에서의 문자열은 Char * / Char[] 데이터형을 사용하지만, Objective C에서는 NSString객체를 사용한다. 또한 NSString 객체를 Char * / Char[]의 형태로 역변환해서 사용하는 경우도 빈번하기 때문에 경우의 수도 많고, 외워서 쓰기에는 불편함이 있어서 정리해서 모아보는 글을 작성해보기로 하였다.


본론

1 ) unsigned char * ( C ) 와 NSData (Objective C) 

1-1) unsigned char * to NSData

1
2
3
unsigned char * byteString = "byteString";
int byteStringLen = strlen(byteString);
NSData * data = [NSData dataWithBytes:byteString length:byteStringLen];


byteString이 바이트 data라고 가정할 때, 위와 같이 변환 할 수 있다. 만약, 문자열을 바이트 데이터로 바꾸고 싶다면 base64디코딩 과정을 거쳐야 한다.


1-2) NSData to unsigned char *

1
2
NSData * data;
unsigned char* bytesForData = [data bytes];


예시에서는 값이 들어 있지 않지만, NSData형의 data라는 변수에 값이 있을 때 위와 같이 변환 할수 있다.


2 ) char * ( C ) 와 NSString (Objective C)

1-1) char * to NSString

1
2
NSString * nsString = @"Hello Bach!";
char * cString = [nsString UTF8String];


NSString에서 제공하는 UTF8String 메서드는 NSString문자열을 char * 데이터형을 반환해준다.


1-2) NSString to char *

1
2
char * cString = "Hello Bach!";
NSString *nsString = [NSString stringWithUTF8String:cString];


NSString 문자열 변수 또한 NSString에서 제공하는 stringWithUTF8String 메서드를 통해서 char * 데이터형으로 변환할 수 있다.


결론

개인적으로 Objective C에서 프로그래밍을 하면서 느낀점은 C와 같이 혼용해서 사용할 수 있기 때문에 편한 점도 있지만 불편한 점 또한 있고, 그 불편한 점 중에 한가지가 데이터형태가 일치하지 않기 때문에 변환 과정이 필요하다는 것이다. 간단하게 가장 많이 사용하는(본인이) 형변환 두 가지만을 다루었는데, 다른 데이터 형 변환 중 자주 쓰이는 변환이 생긴다면 더 추가할 생각이다.

'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
[iOS/GCC] __attribute__((constructor)) / __attribute__((desstructor))  (0) 2016.05.13
[iOS] MD5해시 생성 / SHA256해시 생성  (0) 2016.05.09
서론

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

+ Recent posts