diff --git a/README.md b/README.md new file mode 100644 index 0000000..5793128 --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +# PAD (Process Android Dumper) +This dumper is made for il2cpp game but you can use it in any app you want + +## How To Use +- Run the process +- Open PADumper +- Put process name manually or you can click `Select Apps` to select running apps +- Put the ELF Name or you can leave it with default name `libil2cpp.so` +- Check `global-metadata.dat` if you want dump unity metadata from memory +- Dump and wait process to finish +- Result will be in `/sdcard/PADumper/[Process]/[startAddress-nameFile]` + +## Credits +- [libsu](https://github.com/topjohnwu/libsu) diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..cb72cbd --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,77 @@ +plugins { + id 'com.android.application' + id 'kotlin-android' + id 'kotlin-parcelize' +} + +android { + compileSdk 32 + namespace "com.dumper.android" + + defaultConfig { + applicationId "com.dumper.android" + minSdk 21 + targetSdk 32 + versionCode 2 + versionName "2.0.6" + } + + signingConfigs { + debug { + storeFile file("keystore.jks") + keyAlias "PADumper" + storePassword "012345" + keyPassword "012345" + } + release { + storeFile file("keystore.jks") + keyAlias "PADumper" + storePassword "012345" + keyPassword "012345" + } + } + + buildTypes { + debug { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + signingConfig signingConfigs.debug + } + release { + minifyEnabled true + shrinkResources true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + signingConfig signingConfigs.release + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + buildFeatures { + viewBinding true + } + + kotlinOptions { + jvmTarget = '1.8' + } + +} + +dependencies { + //Ui + implementation "androidx.core:core-ktx:1.8.0" + implementation "androidx.fragment:fragment-ktx:1.4.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1" + implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.1" + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.1" + implementation "com.google.android.material:material:1.6.1" + implementation "com.afollestad.material-dialogs:core:3.3.0" + + //Root + def libsuVersion = '5.0.2' + implementation "com.github.topjohnwu.libsu:core:${libsuVersion}" + implementation "com.github.topjohnwu.libsu:service:${libsuVersion}" +} diff --git a/app/keystore.jks b/app/keystore.jks new file mode 100644 index 0000000..6c035eb Binary files /dev/null and b/app/keystore.jks differ diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..99a829d --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/assets/SoFixer/SoFixer32 b/app/src/main/assets/SoFixer/SoFixer32 new file mode 100644 index 0000000..349172e Binary files /dev/null and b/app/src/main/assets/SoFixer/SoFixer32 differ diff --git a/app/src/main/assets/SoFixer/SoFixer64 b/app/src/main/assets/SoFixer/SoFixer64 new file mode 100644 index 0000000..db45ef0 Binary files /dev/null and b/app/src/main/assets/SoFixer/SoFixer64 differ diff --git a/app/src/main/java/com/dumper/android/core/App.kt b/app/src/main/java/com/dumper/android/core/App.kt new file mode 100644 index 0000000..dba6f23 --- /dev/null +++ b/app/src/main/java/com/dumper/android/core/App.kt @@ -0,0 +1,18 @@ +package com.dumper.android.core + +import android.app.Application +import android.content.Context +import com.dumper.android.BuildConfig +import com.topjohnwu.superuser.Shell + +class App : Application() { + override fun attachBaseContext(base: Context?) { + super.attachBaseContext(base) + + Shell.enableVerboseLogging = BuildConfig.DEBUG + Shell.setDefaultBuilder( + Shell.Builder.create().setFlags(Shell.FLAG_MOUNT_MASTER) + ) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/dumper/android/core/MainActivity.kt b/app/src/main/java/com/dumper/android/core/MainActivity.kt new file mode 100644 index 0000000..c84b87e --- /dev/null +++ b/app/src/main/java/com/dumper/android/core/MainActivity.kt @@ -0,0 +1,128 @@ +package com.dumper.android.core + +import android.content.Intent +import android.content.Intent.ACTION_VIEW +import android.net.Uri +import android.os.* +import android.view.Menu +import androidx.activity.viewModels +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.commit +import com.dumper.android.R +import com.dumper.android.core.RootServices.Companion.IS_FIX_NAME +import com.dumper.android.core.RootServices.Companion.IS_FLAG_CHECK +import com.dumper.android.core.RootServices.Companion.LIBRARY_DIR_NAME +import com.dumper.android.core.RootServices.Companion.LIST_FILE +import com.dumper.android.core.RootServices.Companion.MSG_DUMP_PROCESS +import com.dumper.android.core.RootServices.Companion.MSG_GET_PROCESS_LIST +import com.dumper.android.core.RootServices.Companion.PROCESS_NAME +import com.dumper.android.databinding.ActivityMainBinding +import com.dumper.android.dumper.Fixer +import com.dumper.android.messager.MSGConnection +import com.dumper.android.messager.MSGReceiver +import com.dumper.android.ui.ConsoleFragment +import com.dumper.android.ui.MemoryFragment +import com.dumper.android.ui.viewmodel.ConsoleViewModel +import com.dumper.android.ui.viewmodel.MainViewModel +import com.topjohnwu.superuser.ipc.RootService + +class MainActivity : AppCompatActivity() { + private lateinit var mainBind: ActivityMainBinding + val mainVm: MainViewModel by viewModels() + val console: ConsoleViewModel by viewModels() + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + super.onCreateOptionsMenu(menu) + menuInflater.inflate(R.menu.appbar_menu, menu) + return true + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + mainBind = ActivityMainBinding.inflate(layoutInflater) + initService() + + with(mainBind) { + setContentView(root) + setSupportActionBar(toolbar) + + if (savedInstanceState == null) { + supportFragmentManager + .beginTransaction() + .add(R.id.contentContainer, MemoryFragment.instance) + .commit() + } + + + bottomBar.setOnItemSelectedListener { + supportFragmentManager.commit { + setCustomAnimations( + R.anim.fade_in, + R.anim.fade_out, + R.anim.fade_in, + R.anim.fade_out + ) + replace( + R.id.contentContainer, + when (it.itemId) { + R.id.action_memory -> MemoryFragment.instance + R.id.action_console -> ConsoleFragment.instance + else -> throw IllegalArgumentException("Unknown item selected") + }, null + ) + } + true + } + + toolbar.setOnMenuItemClickListener { + if (it.itemId == R.id.github) { + startActivity( + Intent( + ACTION_VIEW, + Uri.parse("https://github.com/BryanGIG/PADumper") + ) + ) + } + true + } + } + } + + private fun initService() { + Fixer.extractLibs(this) + if (mainVm.remoteMessenger == null) { + mainVm.dumperConnection = MSGConnection(this) + val intent = Intent(this, RootServices::class.java) + RootService.bind(intent, mainVm.dumperConnection) + mainVm.receiver = Messenger(Looper.myLooper()?.let { Handler(it, MSGReceiver(this)) }) + } + } + + fun sendRequestAllProcess() { + val message = Message.obtain(null, MSG_GET_PROCESS_LIST) + message.replyTo = mainVm.receiver + mainVm.remoteMessenger?.send(message) + } + + fun sendRequestDump(process: String, dump_file: Array, autoFix: Boolean, flagCheck: Boolean) { + val message = Message.obtain(null, MSG_DUMP_PROCESS) + + message.data.apply { + putString(PROCESS_NAME, process) + putStringArray(LIST_FILE, dump_file) + putBoolean(IS_FLAG_CHECK, flagCheck) + if (autoFix) { + putBoolean(IS_FIX_NAME, true) + putString(LIBRARY_DIR_NAME, "${filesDir.path}/SoFixer") + } + } + + message.replyTo = mainVm.receiver + mainVm.remoteMessenger?.send(message) + } + + override fun onDestroy() { + super.onDestroy() + RootService.unbind(mainVm.dumperConnection) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/dumper/android/core/RootServices.kt b/app/src/main/java/com/dumper/android/core/RootServices.kt new file mode 100644 index 0000000..6726998 --- /dev/null +++ b/app/src/main/java/com/dumper/android/core/RootServices.kt @@ -0,0 +1,78 @@ +package com.dumper.android.core + +import android.content.Intent +import android.os.* +import android.util.Log +import com.dumper.android.dumper.Dumper +import com.dumper.android.dumper.process.Process +import com.dumper.android.utils.TAG +import com.topjohnwu.superuser.ipc.RootService + + +class RootServices : RootService(), Handler.Callback { + override fun onBind(intent: Intent): IBinder { + val h = Handler(Looper.getMainLooper(), this) + val m = Messenger(h) + return m.binder + } + + override fun handleMessage(msg: Message): Boolean { + val reply = Message.obtain() + val data = Bundle() + + when (msg.what) { + MSG_GET_PROCESS_LIST -> { + val process = Process(this).getAllProcess() + reply.what = MSG_GET_PROCESS_LIST + data.putParcelableArrayList(LIST_ALL_PROCESS, process) + } + + MSG_DUMP_PROCESS -> { + val requestData = msg.data + reply.what = MSG_DUMP_PROCESS + val logOutput = StringBuilder() + val process = requestData.getString(PROCESS_NAME) + val listFile = requestData.getStringArray(LIST_FILE) + val isFlagCheck = requestData.getBoolean(IS_FLAG_CHECK) + val isAutoFix = requestData.getBoolean(IS_FIX_NAME, false) + if (process != null && listFile != null) { + val dumper = Dumper(process) + for (file in listFile) { + dumper.file = file + logOutput.appendLine(dumper.dumpFile(isAutoFix, isFlagCheck)) + } + data.putString(DUMP_LOG, logOutput.toString()) + } else { + data.putString(DUMP_LOG, "[ERROR] Data Error!") + } + } + else -> { + data.putString(DUMP_LOG, "[ERROR] Unknown command") + } + } + + reply.data = data + try { + msg.replyTo.send(reply) + } catch (e: RemoteException) { + Log.e(TAG, "Remote error", e) + } + return false + } + + override fun onUnbind(intent: Intent): Boolean { + return false + } + + companion object { + const val MSG_DUMP_PROCESS = 1 + const val MSG_GET_PROCESS_LIST = 2 + const val DUMP_LOG = "DUMP_LOG" + const val LIBRARY_DIR_NAME = "NATIVE_DIR" + const val LIST_ALL_PROCESS = "LIST_ALL_PROCESS" + const val PROCESS_NAME = "PROCESS" + const val LIST_FILE = "LIST_FILE" + const val IS_FLAG_CHECK = "IS_FLAG_CHECK" + const val IS_FIX_NAME = "FIX_ELF" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/dumper/android/core/SplashActivity.kt b/app/src/main/java/com/dumper/android/core/SplashActivity.kt new file mode 100644 index 0000000..4324107 --- /dev/null +++ b/app/src/main/java/com/dumper/android/core/SplashActivity.kt @@ -0,0 +1,31 @@ +package com.dumper.android.core + +import android.annotation.SuppressLint +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import android.widget.Toast +import com.dumper.android.BuildConfig +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.topjohnwu.superuser.Shell +import kotlin.system.exitProcess + +@SuppressLint("CustomSplashScreen") +class SplashActivity : Activity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + Shell.getShell { + if (it.isRoot) { + val intent = Intent(this@SplashActivity, MainActivity::class.java) + startActivity(intent) + finish() + } else { + MaterialAlertDialogBuilder(this@SplashActivity) + .setTitle("Error") + .setMessage("You need to be root to use this app") + .setPositiveButton("Exit") { _, _ -> exitProcess(0) } + .show() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/dumper/android/dumper/Dumper.kt b/app/src/main/java/com/dumper/android/dumper/Dumper.kt new file mode 100644 index 0000000..b55714b --- /dev/null +++ b/app/src/main/java/com/dumper/android/dumper/Dumper.kt @@ -0,0 +1,160 @@ +package com.dumper.android.dumper + +import androidx.core.text.isDigitsOnly +import com.dumper.android.utils.DEFAULT_DIR +import com.dumper.android.utils.toHex +import com.dumper.android.utils.toMB +import java.io.File +import java.io.FileNotFoundException +import java.io.RandomAccessFile +import java.nio.ByteBuffer + +class Dumper(private val pkg: String) { + private val mem = Memory(pkg) + var file: String = "" + + /** + * Dump the memory to a file + * + * @param autoFix if `true` the dumped file will be fixed after dumping + * @param flagCheck if `true` the dumped file will be checked for flags/ + * @return log of the dump + */ + fun dumpFile(autoFix: Boolean, flagCheck: Boolean): String { + val log = StringBuilder() + try { + mem.pid = getProcessID() ?: throw Exception("Process not found!\ndid you already run it?") + + log.appendLine("PID : ${mem.pid}") + log.appendLine("FILE : $file") + + val map = parseMap(flagCheck) + map.forEach { + if (it == 0L) { + log.append("[ERROR] Failed to get memory map of $pkg\n") + return@forEach + } + } + + mem.sAddress = map.first() + mem.eAddress = map.last() + mem.size = mem.eAddress - mem.sAddress + + log.appendLine("Start Address : ${mem.sAddress.toHex()}") + log.appendLine("End Address : ${mem.eAddress.toHex()}") + log.appendLine("Size Memory : ${mem.size.toHex()}") + + if (mem.sAddress > 1L && mem.eAddress > 1L) { + val path = File("$DEFAULT_DIR/$pkg") + if (!path.exists()) path.mkdirs() + + val pathOut = File("${path.absolutePath}/${mem.sAddress.toHex()}-$file") + val outputStream = pathOut.outputStream() + + val inputAccess = RandomAccessFile("/proc/${mem.pid}/mem", "r") + inputAccess.channel.let { + // Check if mem.size under 500MB + if (mem.size < 500L.toMB()) { + val buffer = ByteBuffer.allocate(mem.size.toInt()) + it.read(buffer, mem.sAddress) + outputStream.write(buffer.array()) + it.close() + } else { + throw Exception("Size of memory is too big") + } + } + + outputStream.flush() + inputAccess.close() + outputStream.close() + + if (!file.contains(".dat") && autoFix) { + log.appendLine("Fixing...") + val is32bit = mem.sAddress.toHex().length == 8 + val fixer = Fixer.fixDump(pathOut, mem.sAddress.toHex(), is32bit) + // Check output fixer and error fixer + if (fixer[0].isNotEmpty()) { + log.appendLine("Fixer output : \n${fixer[0].joinToString("\n")}") + } + if (fixer[1].isNotEmpty()) { + log.appendLine("Fixer error : \n${fixer[1].joinToString("\n")}") + } + } + log.appendLine("Dump Success") + log.appendLine("Output: ${pathOut.parent}") + } + } catch (e: Exception) { + log.appendLine("[ERROR] ${e.message}") + e.printStackTrace() + } + return log.toString() + } + + /** + * Parsing the memory map + * + * @throws FileNotFoundException if required file is not found in memory map + */ + private fun parseMap(checkFlag: Boolean): LongArray { + val files = File("/proc/${mem.pid}/maps") + if (files.exists()) { + val lines = files.readLines() + + val lineStart = lines.find { + val map = MapLinux(it) + if (file.contains(".dat")) { + map.getPath().contains(file) + } else { + if (checkFlag) + map.getPerms().contains("r-xp") && map.getPath().contains(file) + else { + map.getPath().contains(file) + } + } + } ?: throw Exception("Unable find baseAddress of $file") + + val mapStart = MapLinux(lineStart) + + val lineEnd = lines.findLast { + val map = MapLinux(it) + mapStart.getInode() == map.getInode() + } ?: throw Exception("Unable find endAddress of $file") + + val mapEnd = MapLinux(lineEnd) + return longArrayOf(mapStart.getStartAddress(), mapEnd.getEndAddress()) + } else { + throw Exception("Failed To Open : ${files.path}") + } + } + + /** + * Get the process ID + * + * @throws Exception if dir "/proc" is empty + * @throws FileNotFoundException if "/proc" failed to open + */ + private fun getProcessID(): Int? { + val proc = File("/proc") + if (proc.exists()) { + val dPID = proc.listFiles() + if (dPID.isNullOrEmpty()) { + throw Exception("Unable to get process list id") + } + for (line in dPID) { + if (line.name.isDigitsOnly()) { + val cmdline = File("${line.path}/cmdline") + if (cmdline.exists()) { + val textCmd = cmdline.readText() + if (textCmd.contains(pkg)) { + return line.name.toInt() + } + } + } + } + } else { + throw FileNotFoundException("Failed To Open : ${proc.path}") + } + return null + } +} + diff --git a/app/src/main/java/com/dumper/android/dumper/Fixer.kt b/app/src/main/java/com/dumper/android/dumper/Fixer.kt new file mode 100644 index 0000000..85c2d5f --- /dev/null +++ b/app/src/main/java/com/dumper/android/dumper/Fixer.kt @@ -0,0 +1,68 @@ +package com.dumper.android.dumper + +import android.content.Context +import android.system.Os.chmod +import com.dumper.android.BuildConfig +import com.topjohnwu.superuser.Shell +import java.io.File + +object Fixer { + + /** + * Extract SoFixer into filesDir and + * set permissions to 777 so the file can be executed + */ + fun extractLibs(ctx: Context) { + val libs = ctx.assets.list("SoFixer") + libs?.forEach { lib -> + ctx.assets.open("SoFixer/$lib").use { input -> + File(ctx.filesDir, lib).outputStream().use { output -> + input.copyTo(output) + Shell.cmd("chmod 777 ${ctx.filesDir.absolutePath}/$lib").exec() + } + } + } + } + + /** + * Run SoFixer + * @param dumpFile the file to dump + * @param startAddress the start address of the dump + * @param is32 if the dump is 32 bit or 64 bit + * @return List of strings containing the results of the SoFixer + */ + fun fixDump( + dumpFile: File, + startAddress: String, is32: Boolean + ): Array> { + val outList = mutableListOf() + val errList = mutableListOf() + val fixerPath = File("/data/data/${BuildConfig.APPLICATION_ID}/files", if (is32) "SoFixer32" else "SoFixer64").absolutePath + ProcessBuilder( + listOf( + fixerPath, + "-s", + dumpFile.path, + "-o", + "${dumpFile.parent}/${dumpFile.nameWithoutExtension}_fix.${dumpFile.extension}", + "-m", + "0x$startAddress" + ) + ) + .redirectErrorStream(true) + .start().let { proc -> + proc.waitFor() + proc.inputStream.bufferedReader().use { buff -> + buff.forEachLine { + outList.add(it) + } + } + proc.errorStream.bufferedReader().use { buff -> + buff.forEachLine { + errList.add(it) + } + } + } + return arrayOf(outList, errList) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/dumper/android/dumper/Memory.kt b/app/src/main/java/com/dumper/android/dumper/Memory.kt new file mode 100644 index 0000000..9fb8ac1 --- /dev/null +++ b/app/src/main/java/com/dumper/android/dumper/Memory.kt @@ -0,0 +1,79 @@ +package com.dumper.android.dumper + +class MapLinux(str: String) { + + private var address = "" + private var perms = "" + private var offset = "" + private var dev = "" + private var inode = "" + private var path = "" + + init { + val strp = str.replace("\\s+".toRegex(), " ").split(" ") + strp.forEachIndexed { index, s -> + when (index) { + 0 -> address = s + 1 -> perms = s + 2 -> offset = s + 3 -> dev = s + 4 -> inode = s + 5 -> path = s + } + } + } + + fun getStartAddress(): Long { + if (address.isEmpty()) return 0L + return address.split("-")[0].toLong(16) + } + + fun getEndAddress(): Long { + if (address.isEmpty()) return 0L + return address.split("-")[1].toLong(16) + } + + fun getPerms(): String { + return perms + } + + fun getOffset(): Long { + return offset.toLong(16) + } + + fun getDev(): String { + return dev + } + + fun getInode(): Int { + return inode.toInt() + } + + fun getPath(): String { + return path + } + + fun getSize(): Long { + return getEndAddress() - getStartAddress() + } + + fun isValid(): Boolean { + return getStartAddress() != 0L && getEndAddress() != 0L + } + + override fun toString(): String { + return "MapLinux(address='$address', perms='$perms', offset='$offset', dev='$dev', inode='$inode', path='$path')" + } +} + +data class Memory(val pkg: String) { + var pid: Int = 0 + var sAddress: Long = 0L + var eAddress: Long = 0L + var size: Long = 0L + var perms: String = "" + var offset: Long = 0L + var device: String = "" + var inode: Long = 0L + var path: String = "" +} \ No newline at end of file diff --git a/app/src/main/java/com/dumper/android/dumper/process/Process.kt b/app/src/main/java/com/dumper/android/dumper/process/Process.kt new file mode 100644 index 0000000..3f89d52 --- /dev/null +++ b/app/src/main/java/com/dumper/android/dumper/process/Process.kt @@ -0,0 +1,33 @@ +package com.dumper.android.dumper.process + +import android.app.ActivityManager +import android.content.Context +import android.content.Context.ACTIVITY_SERVICE +import android.content.pm.ApplicationInfo +import com.dumper.android.BuildConfig +import java.util.ArrayList + +class Process(private val ctx: Context) { + fun getAllProcess(): ArrayList { + val finalAppsBundle = ArrayList() + val activityManager = ctx.getSystemService(ACTIVITY_SERVICE) as ActivityManager + val processInfo = activityManager.runningAppProcesses + + processInfo.forEach { + try { + val apps = + ctx.packageManager.getApplicationInfo(it.processName.substringBefore(":"), 0) + if (!apps.isInvalid() && apps.packageName != BuildConfig.APPLICATION_ID) { + val data = ProcessData( + it.processName, + ctx.packageManager.getApplicationLabel(apps).toString() + ) + finalAppsBundle.add(data) + } + } catch (_: Exception) { } + } + return finalAppsBundle + } + + private fun ApplicationInfo.isInvalid() = (flags and ApplicationInfo.FLAG_STOPPED != 0) || (flags and ApplicationInfo.FLAG_SYSTEM != 0) +} \ No newline at end of file diff --git a/app/src/main/java/com/dumper/android/dumper/process/ProcessData.kt b/app/src/main/java/com/dumper/android/dumper/process/ProcessData.kt new file mode 100644 index 0000000..bdd9b84 --- /dev/null +++ b/app/src/main/java/com/dumper/android/dumper/process/ProcessData.kt @@ -0,0 +1,7 @@ +package com.dumper.android.dumper.process + +import kotlinx.parcelize.Parcelize +import android.os.Parcelable + +@Parcelize +data class ProcessData(val processName: String, val appName: String): Parcelable \ No newline at end of file diff --git a/app/src/main/java/com/dumper/android/messager/MSGConnection.kt b/app/src/main/java/com/dumper/android/messager/MSGConnection.kt new file mode 100644 index 0000000..abe3e89 --- /dev/null +++ b/app/src/main/java/com/dumper/android/messager/MSGConnection.kt @@ -0,0 +1,19 @@ +package com.dumper.android.messager + +import android.content.ComponentName +import android.content.ServiceConnection +import android.os.IBinder +import android.os.Messenger +import com.dumper.android.core.MainActivity + +class MSGConnection(private val activity: MainActivity) : ServiceConnection { + override fun onServiceConnected(name: ComponentName, service: IBinder) { + activity.console.appendInfo("RootService: Successfully connected") + activity.mainVm.remoteMessenger = Messenger(service) + } + + override fun onServiceDisconnected(name: ComponentName) { + activity.console.appendInfo("RootService: Service disconnected") + activity.mainVm.remoteMessenger = null + } +} \ No newline at end of file diff --git a/app/src/main/java/com/dumper/android/messager/MSGReceiver.kt b/app/src/main/java/com/dumper/android/messager/MSGReceiver.kt new file mode 100644 index 0000000..c1f1334 --- /dev/null +++ b/app/src/main/java/com/dumper/android/messager/MSGReceiver.kt @@ -0,0 +1,32 @@ +package com.dumper.android.messager + + +import android.os.* +import android.widget.Toast +import com.dumper.android.core.MainActivity +import com.dumper.android.core.RootServices +import com.dumper.android.dumper.process.ProcessData +import com.dumper.android.ui.MemoryFragment + +class MSGReceiver(private val activity: MainActivity) : Handler.Callback { + override fun handleMessage(message: Message): Boolean { + message.data.classLoader = activity.classLoader + + when (message.what) { + RootServices.MSG_GET_PROCESS_LIST -> { + message.data.getParcelableArrayList(RootServices.LIST_ALL_PROCESS) + ?.let { + MemoryFragment.instance.showProcess(it) + } + } + RootServices.MSG_DUMP_PROCESS -> { + message.data.getString(RootServices.DUMP_LOG)?.let { + activity.console.append(it) + activity.console.appendLine("==========================") + Toast.makeText(activity, "Dump Complete!", Toast.LENGTH_SHORT).show() + } + } + } + return false + } +} \ No newline at end of file diff --git a/app/src/main/java/com/dumper/android/ui/ConsoleFragment.kt b/app/src/main/java/com/dumper/android/ui/ConsoleFragment.kt new file mode 100644 index 0000000..97a80c2 --- /dev/null +++ b/app/src/main/java/com/dumper/android/ui/ConsoleFragment.kt @@ -0,0 +1,45 @@ +package com.dumper.android.ui + +import android.annotation.SuppressLint +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.viewModelScope +import com.dumper.android.databinding.FragmentConsoleBinding +import com.dumper.android.ui.viewmodel.ConsoleViewModel +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +class ConsoleFragment : Fragment() { + companion object { + val instance by lazy { ConsoleFragment() } + } + + private lateinit var consoleBind: FragmentConsoleBinding + private val vm: ConsoleViewModel by activityViewModels() + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + consoleBind = FragmentConsoleBinding.inflate(layoutInflater, container, false) + return consoleBind.root + } + + @SuppressLint("SetTextI18n") + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + vm.console.observe(viewLifecycleOwner) { + consoleBind.console.text = "$it\n" + vm.viewModelScope.launch { + delay(10) + consoleBind.scrollView.fullScroll(View.FOCUS_DOWN) + } + } + } +} diff --git a/app/src/main/java/com/dumper/android/ui/MemoryFragment.kt b/app/src/main/java/com/dumper/android/ui/MemoryFragment.kt new file mode 100644 index 0000000..f87a651 --- /dev/null +++ b/app/src/main/java/com/dumper/android/ui/MemoryFragment.kt @@ -0,0 +1,91 @@ +package com.dumper.android.ui + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.fragment.app.viewModels +import com.dumper.android.core.MainActivity +import com.dumper.android.databinding.FragmentMemoryBinding +import com.dumper.android.dumper.process.ProcessData +import com.dumper.android.ui.viewmodel.ConsoleViewModel +import com.dumper.android.ui.viewmodel.MemoryViewModel +import com.google.android.material.dialog.MaterialAlertDialogBuilder + +class MemoryFragment : Fragment() { + companion object { + val instance by lazy { MemoryFragment() } + } + + private val vm: MemoryViewModel by viewModels() + private val consoles: ConsoleViewModel by activityViewModels() + + private lateinit var memBinding: FragmentMemoryBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + memBinding = FragmentMemoryBinding.inflate(inflater, container, false) + return memBinding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + memBinding.apply { + vm.selectedApps.observe(viewLifecycleOwner) { + processText.editText?.setText(it) + } + + dumpButton.setOnClickListener { + val process = processText.editText!!.text.toString() + if (process.isNotBlank()) { + consoles.appendLine("==========================\nProcess : $process") + + val listDump = mutableListOf(libName.editText!!.text.toString()) + if (metadata.isChecked) + listDump.add("global-metadata.dat") + + getMainActivity().sendRequestDump( + process, + listDump.toTypedArray(), + autoFix.isChecked, + flagCheck.isChecked + ) + } else { + consoles.appendError("Process name is empty") + } + } + + selectApps.setOnClickListener { + getMainActivity().sendRequestAllProcess() + } + } + } + + fun showProcess(list: ArrayList) { + list.sortBy { lists -> lists.appName } + + val appNames = list.map { processData -> + val processName = processData.processName + if (processName.contains(":")) + "${processData.appName} (${processName.substringAfter(":")})" + else + processData.appName + } + + MaterialAlertDialogBuilder(requireContext()) + .setTitle("Select process") + .setSingleChoiceItems(appNames.toTypedArray(), -1) { dialog, which -> + vm.selectedApps.value = list[which].processName + dialog.dismiss() + } + .show() + + } + + private fun getMainActivity() = requireActivity() as MainActivity +} \ No newline at end of file diff --git a/app/src/main/java/com/dumper/android/ui/viewmodel/ConsoleViewModel.kt b/app/src/main/java/com/dumper/android/ui/viewmodel/ConsoleViewModel.kt new file mode 100644 index 0000000..c83f1a6 --- /dev/null +++ b/app/src/main/java/com/dumper/android/ui/viewmodel/ConsoleViewModel.kt @@ -0,0 +1,41 @@ +package com.dumper.android.ui.viewmodel + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel + +class ConsoleViewModel : ViewModel() { + val console = MutableLiveData("") + + fun append(text: String) { + console.value = console.value + text + } + + fun clear() { + console.value = "" + } + + fun appendLine(text: String) { + append(text + "\n") + } + + fun appendLine() { + append("\n") + } + + fun appendError(text: String) { + appendLine("[ERROR] $text") + } + + fun appendWarning(text: String) { + appendLine("[WARNING] $text") + } + + fun appendInfo(text: String) { + appendLine("[INFO] $text") + } + + fun appendSuccess(text: String) { + appendLine("[SUCCESS] $text") + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/dumper/android/ui/viewmodel/MainViewModel.kt b/app/src/main/java/com/dumper/android/ui/viewmodel/MainViewModel.kt new file mode 100644 index 0000000..240daa0 --- /dev/null +++ b/app/src/main/java/com/dumper/android/ui/viewmodel/MainViewModel.kt @@ -0,0 +1,12 @@ +package com.dumper.android.ui.viewmodel + +import android.os.Messenger +import androidx.lifecycle.ViewModel +import com.dumper.android.messager.MSGConnection + +class MainViewModel : ViewModel() { + + var remoteMessenger: Messenger? = null + lateinit var receiver : Messenger + lateinit var dumperConnection : MSGConnection +} \ No newline at end of file diff --git a/app/src/main/java/com/dumper/android/ui/viewmodel/MemoryViewModel.kt b/app/src/main/java/com/dumper/android/ui/viewmodel/MemoryViewModel.kt new file mode 100644 index 0000000..af5e1d9 --- /dev/null +++ b/app/src/main/java/com/dumper/android/ui/viewmodel/MemoryViewModel.kt @@ -0,0 +1,14 @@ +package com.dumper.android.ui.viewmodel + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.dumper.android.dumper.process.ProcessData + +class MemoryViewModel: ViewModel() { + + val allApps by lazy { + MutableLiveData>() + } + + val selectedApps by lazy { MutableLiveData() } +} \ No newline at end of file diff --git a/app/src/main/java/com/dumper/android/utils/Utils.kt b/app/src/main/java/com/dumper/android/utils/Utils.kt new file mode 100644 index 0000000..63a8d5b --- /dev/null +++ b/app/src/main/java/com/dumper/android/utils/Utils.kt @@ -0,0 +1,12 @@ +package com.dumper.android.utils + +const val TAG = "PADumper" +const val DEFAULT_DIR = "/sdcard/PADumper" + +fun Long.toHex(): String { + return this.toString(16) +} + +fun Long.toMB(): Long { + return this * 1024 * 1024 +} \ No newline at end of file diff --git a/app/src/main/res/anim/fade_in.xml b/app/src/main/res/anim/fade_in.xml new file mode 100644 index 0000000..64a46de --- /dev/null +++ b/app/src/main/res/anim/fade_in.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/fade_out.xml b/app/src/main/res/anim/fade_out.xml new file mode 100644 index 0000000..afab199 --- /dev/null +++ b/app/src/main/res/anim/fade_out.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/app/src/main/res/drawable-hdpi/outline_handyman_black_18.png b/app/src/main/res/drawable-hdpi/outline_handyman_black_18.png new file mode 100644 index 0000000..ac5d490 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/outline_handyman_black_18.png differ diff --git a/app/src/main/res/drawable-hdpi/outline_memory_black_18.png b/app/src/main/res/drawable-hdpi/outline_memory_black_18.png new file mode 100644 index 0000000..7d647e7 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/outline_memory_black_18.png differ diff --git a/app/src/main/res/drawable-mdpi/outline_handyman_black_18.png b/app/src/main/res/drawable-mdpi/outline_handyman_black_18.png new file mode 100644 index 0000000..cc99817 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/outline_handyman_black_18.png differ diff --git a/app/src/main/res/drawable-mdpi/outline_memory_black_18.png b/app/src/main/res/drawable-mdpi/outline_memory_black_18.png new file mode 100644 index 0000000..56686b1 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/outline_memory_black_18.png differ diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-xhdpi/outline_handyman_black_18.png b/app/src/main/res/drawable-xhdpi/outline_handyman_black_18.png new file mode 100644 index 0000000..a7a573f Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/outline_handyman_black_18.png differ diff --git a/app/src/main/res/drawable-xhdpi/outline_memory_black_18.png b/app/src/main/res/drawable-xhdpi/outline_memory_black_18.png new file mode 100644 index 0000000..94fd4ae Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/outline_memory_black_18.png differ diff --git a/app/src/main/res/drawable-xxhdpi/outline_handyman_black_18.png b/app/src/main/res/drawable-xxhdpi/outline_handyman_black_18.png new file mode 100644 index 0000000..cd95169 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/outline_handyman_black_18.png differ diff --git a/app/src/main/res/drawable-xxhdpi/outline_memory_black_18.png b/app/src/main/res/drawable-xxhdpi/outline_memory_black_18.png new file mode 100644 index 0000000..2d1919b Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/outline_memory_black_18.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/outline_handyman_black_18.png b/app/src/main/res/drawable-xxxhdpi/outline_handyman_black_18.png new file mode 100644 index 0000000..2cd4ff5 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/outline_handyman_black_18.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/outline_memory_black_18.png b/app/src/main/res/drawable-xxxhdpi/outline_memory_black_18.png new file mode 100644 index 0000000..caef96c Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/outline_memory_black_18.png differ diff --git a/app/src/main/res/drawable/github.png b/app/src/main/res/drawable/github.png new file mode 100644 index 0000000..628da97 Binary files /dev/null and b/app/src/main/res/drawable/github.png differ diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..b1f124d --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_console.xml b/app/src/main/res/layout/fragment_console.xml new file mode 100644 index 0000000..97daf4f --- /dev/null +++ b/app/src/main/res/layout/fragment_console.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_memory.xml b/app/src/main/res/layout/fragment_memory.xml new file mode 100644 index 0000000..d605460 --- /dev/null +++ b/app/src/main/res/layout/fragment_memory.xml @@ -0,0 +1,88 @@ + + + + + + + + + +