본문 바로가기

CTF & Wargame/CTF

CTF/Codegate2018Qual/Welcome to droid/Write-up

반응형

# 문제 설명

- 안드로이드 문제를 많이 풀어보지 않았지만, 공부한것이 있어서 풀 수 있었습니다.

- MainActivity, Main2Activity, Main3Activity, Main4Activity이 주 클래스고 메인 클래스는 MainActivity입니다.

- 순서대로 ID, Password를 입력하면서 다음 액티비티로 넘어갑니다.

- ID는 제가 입력하는 값으로 아무 값으로 입력할 수 있으나, 이 값은 Main2Activity로 넘어갈 때 인텐트 값으로 제공되어 Password를 결정하게 됩니다.

- Main3Activity에서 시리얼키를 입력해야하는데, 시리얼키는 Random 클래스를 통해 생성되어 매번 Next 버튼을 눌러서, 클래스안의 함수를 호출할 떄마다 바뀌며 항상 바뀌는 값을 맞춰 주던가 다른 방법으로 우회해야 합니다.

- 마지막 Main3Activity에서 시리얼키 입력을 우회하여 Main4Activity 액티비티로 들어가게 되면, nativelib.so에서 만들어지는 FLAG가 EditText위에 적히게 됩니다.


# 환경 구성

- Windows 10

- Android Studio : IDE

- apk-signer-1.8.5  : 재서명을 할 수 있는 GUI 도구 입니다. 아주 편하게 쓰고 있습니다. 재서명을 했을 때 리소스 정렬도 해주어야 하는데 이 기능을 제공 해주는 편리한 도구 입니다.



- dex2jar : dex 추출기

- jd-gui : 디컴파일러

- dextools-softdx : 오덱스 과정을 편하게 할 수 있는 도구



# 문제 풀이

- 첫 시작 액티비티에서 "abcdefghijk"를 입력합니다. onClick 메소드 안의 조건문 식을 보면, 입력되는 값의 길이가 10이상 26이하 일때 다음 액티비티로 넘어가는 것을 확인할 수 있습니다. 그래서 11자리가 되는 "abcdefghijk"를 입력합니다.

- 아래 "hi there"이 아닌 Hello from C++은 nativelib에서 불러온 문자열입니다.

- 혹시나 MainActivity에서 먼저 NDK를 로딩해버리면 플래그가 뜨지 않을 까 해서 접근해본 방법이 였습니다.


- 첫 액티비티 입력 후 아래 소스에서 "abcdefghijk"라는 아이디에 대한 고정 PW를 만듭니다.

- 디컴파일러로 확인한 Java 코드를 이클립스 프로젝트로 생성 한 후 App에서 분리하여 PW 고정 값을 만들어 냅니다.


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
package pro1;
 
import java.util.Random;
 
public class test1 {
    
   public static String encode(String arg10) {
        int v1 = 0;
        String v3 = "";
        char[] v6 = new char[]{'c''o''d''e''g''a''t''e''2''0''1''8''h''u''r''r''a''y''!''H''A''H''A''H''A''L''O''L'};
        int v7 = arg10.length();
        int v4 = 0;
        String v5;
        for(v5 = ""; v4 < v7; v5 = v5 + String.valueOf(v0)) {
            int v2 = arg10.charAt(v4) ^ String.valueOf(v6).charAt(v7 + v4);
            int v0;
            for(v0 = 0; v0 < v6.length - 1++v0) {
                if(String.valueOf(v2).equals(Character.valueOf(String.valueOf(v6).charAt(v0)))) {
                    v2 = new Random().nextInt();
                }
            }
 
            for(v0 = 0; v0 < v2; v0 = v0 + v0 + 1) {
            }
 
            ++v4;
        }
 
        String v0_1 = v3;
        while(v1 < v7) {
            v0_1 = v0_1 + (v5.charAt(v1) ^ v1);
            ++v1;
        }
 
        return v0_1;
    }
 
    public static void main(String[] args) {
        String ret= "";
        ret = test1.encode("abcdefghijk");
        boolean check;
        check = ret.equals("4951535049545552575859");
        # check : true , ret : 4951535049545552575859
        System.out.println(check);
        System.out.println(ret);
        
    }
 
}
 
cs


- 제가 입력한 아이디로 생성되는 고정 PW는 "4951535049545552575859"입니다. 이 값을 Main2Activity에서 PW로 입력합니다.



- 다음 액티비티는 시리얼 키를 우회하는 부분 입니다.  코드 진행에 상관없이  제가 원하는 "crack"이라는 문자열로 리턴되도록 수정합니다.

- 디컴파일러로는 Java 코드를 수정할 수 없고 어셈블리어와 같이 JVM에서 저레벨 언어 역할을 하는 Smali Code를 수정하여 "crack"이라는 문자열을 넣어 줄 수 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
public String k() {
        char[] v1 = new char[]{'a''b''c''d''e''f''g''h''i''j''k''l''m''n''o''p''q''r''s''t''u''v''w''x''y''z''0''1''2''3''4''5''6''7''8''9'};
        StringBuffer v2 = new StringBuffer();
        Random v3 = new Random();
        int v0;
        for(v0 = 0; v0 < 20++v0) {
            v2.append(v1[v3.nextInt(v1.length)]);
        }
 
        v2.toString();
        return "crack";
    }
 
 
cs


- Main3Activity의 바이트코드 소스 입니다. 라인 4를 보시면 이 함수 안에서 사용하는 레지스터 갯수를 선언했고, 레지스터가 v0, v1, v2 ... 와 같은 형식으로 쓰이는 것을 보실 수 있습니다.

- 코드 해석을 전체로 본다면 조건 분기에 해당 합니다. 일반적인 어셈블리어 조건 분기와 같이 25번째 라인의 if-ge v0, v4 :cond_23 명령이 조건 분기를 하도록 합니다. v0이 v4보다 크다면(조건이 참이면) :cond_23으로 분기하라는 뜻 입니다. 아니라면 다음 라인을 실행합니다. 거짓이고 다음 라인을 실행하였고 명령어 포인터가 내려가다보면 :cond_23을 만나기 바로 이전에 goto :goto_12를 통해 해당 조건 블럭을 빠져나갑니다.

- 이 소스에서 한 명령어 단위를 해석하는 것은 제 경험적인 방법으로는 레지스터에 값이 입력되고(const vA, 등등) > 함수 호출(invoke) > 결과 반환(move-result) 으로 해석합니다. 


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
 
# virtual methods
.method public k()Ljava/lang/String;
    .registers 6
 
    const/16 v0, 0x24
 
    new-array v1, v0, [C
 
    fill-array-data v1, :array_28
 
    new-instance v2, Ljava/lang/StringBuffer;
 
    invoke-direct {v2}, Ljava/lang/StringBuffer;-><init>()V
 
    new-instance v3, Ljava/util/Random;
 
    invoke-direct {v3}, Ljava/util/Random;-><init>()V
 
    const/4 v0, 0x0
 
    :goto_12
    const/16 v4, 0x14
 
    if-ge v0, v4, :cond_23
 
    array-length v4, v1
 
    invoke-virtual {v3, v4}, Ljava/util/Random;->nextInt(I)I
 
    move-result v4
 
    aget-char v4, v1, v4
 
    invoke-virtual {v2, v4}, Ljava/lang/StringBuffer;->append(C)Ljava/lang/StringBuffer;
 
    add-int/lit8 v0, v0, 0x1
 
    goto :goto_12
 
    :cond_23
    invoke-virtual {v2}, Ljava/lang/StringBuffer;->toString()Ljava/lang/String;
 
    move-result-object v0
 
    const-string v5, "crack"
    
    return-object v5
 
 
cs


- 원래 는 42번째 라인과 44번째 라인이 하나의 명령어로 실행 되어 return-object 명령에 v0가 리턴되지만 이전 명령이 끝나는 부분 바로 아래에 제가 리턴하고자 하는 문자열 "crack"을 만들고 그 값을 담고 있는 레지스터를 리턴해 주 었습니다.

- 이제 다시 smali 소스를 오덱싱하여 apk로 만들고 실행하여 Main3Activity에서 crack을 입력하면 플래그가 뜹니다.



# Flag

- FLAG {W3_w3r3_Back_70_$n7een!!!}



# 오류

- 에뮬레이터 x86_64와 x86에서 돌아가는 것에 차이가 있었습니다. 에뮬레이터를 많이 사용해봤던터라 x86에뮬레이터보다는 x86_64가 체감상 빠르다는걸 느끼고 x86_64로 진행 했었는데, x86_64환경 에서 진행하면 마지막 Main3Activity를 우회하고 Main4Activity가 시작될 때 NDK 안의 stringFromJNI()함수를 찾지 못합니다. .so 파일을 보면 실제로 찾는 메소드 경로는 java.lang.String com.example.puing.a2018codegate.Main4Activity.stringFromJNI() 이지만, 실제로 저장되는 메소드 이름은(NDK에서는 java의 패키지경로_메소드로 함수이름을 정합니다.) java.lang.String com.example.puing.a2018codegate.MainActivity.stringFromJNI()이여서 찾지 못합니다. x86 에뮬레이터에서는 정상적으로 찾아지는 경로였고, 문제가 만들어질 당시 수정되지않은 문제 같습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
02-03 07:33:25.813 12383-12383/? E/art: No implementation found for java.lang.String com.example.puing.a2018codegate.Main4Activity.stringFromJNI() (tried Java_com_example_puing_a2018codegate_Main4Activity_stringFromJNI and Java_com_example_puing_a2018codegate_Main4Activity_stringFromJNI__)
02-03 07:33:25.813 12383-12383/? E/AndroidRuntime: FATAL EXCEPTION: main
                                                   Process: com.example.puing.a2018codegate, PID: 12383
                                                   java.lang.UnsatisfiedLinkError: No implementation found for java.lang.String com.example.puing.a2018codegate.Main4Activity.stringFromJNI() (tried Java_com_example_puing_a2018codegate_Main4Activity_stringFromJNI and Java_com_example_puing_a2018codegate_Main4Activity_stringFromJNI__)
                                                       at com.example.puing.a2018codegate.Main4Activity.stringFromJNI(Native Method)
                                                       at com.example.puing.a2018codegate.Main4Activity.onCreate(Unknown Source)
                                                       at android.app.Activity.performCreate(Activity.java:6237)
                                                       at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1107)
                                                       at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2369)
                                                       at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476)
                                                       at android.app.ActivityThread.-wrap11(ActivityThread.java)
                                                       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344)
                                                       at android.os.Handler.dispatchMessage(Handler.java:102)
                                                       at android.os.Looper.loop(Looper.java:148)
                                                       at android.app.ActivityThread.main(ActivityThread.java:5417)
                                                       at java.lang.reflect.Method.invoke(Native Method)
                                                       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
                                                       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
cs




반응형