diff --git b/.gitignore a/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ a/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git b/.idea/.gitignore a/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ a/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git b/.idea/compiler.xml a/.idea/compiler.xml new file mode 100644 index 0000000..61a9130 --- /dev/null +++ a/.idea/compiler.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="CompilerConfiguration"> + <bytecodeTargetLevel target="1.8" /> + </component> +</project> \ No newline at end of file diff --git b/.idea/gradle.xml a/.idea/gradle.xml new file mode 100644 index 0000000..23a89bb --- /dev/null +++ a/.idea/gradle.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="GradleMigrationSettings" migrationVersion="1" /> + <component name="GradleSettings"> + <option name="linkedExternalProjectsSettings"> + <GradleProjectSettings> + <option name="testRunner" value="PLATFORM" /> + <option name="distributionType" value="DEFAULT_WRAPPED" /> + <option name="externalProjectPath" value="$PROJECT_DIR$" /> + <option name="gradleJvm" value="1.8" /> + <option name="modules"> + <set> + <option value="$PROJECT_DIR$" /> + <option value="$PROJECT_DIR$/app" /> + </set> + </option> + <option name="resolveModulePerSourceSet" value="false" /> + <option name="useQualifiedModuleNames" value="true" /> + </GradleProjectSettings> + </option> + </component> +</project> \ No newline at end of file diff --git b/.idea/jarRepositories.xml a/.idea/jarRepositories.xml new file mode 100644 index 0000000..a5f05cd --- /dev/null +++ a/.idea/jarRepositories.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="RemoteRepositoriesConfiguration"> + <remote-repository> + <option name="id" value="central" /> + <option name="name" value="Maven Central repository" /> + <option name="url" value="https://repo1.maven.org/maven2" /> + </remote-repository> + <remote-repository> + <option name="id" value="jboss.community" /> + <option name="name" value="JBoss Community repository" /> + <option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" /> + </remote-repository> + <remote-repository> + <option name="id" value="BintrayJCenter" /> + <option name="name" value="BintrayJCenter" /> + <option name="url" value="https://jcenter.bintray.com/" /> + </remote-repository> + <remote-repository> + <option name="id" value="Google" /> + <option name="name" value="Google" /> + <option name="url" value="https://dl.google.com/dl/android/maven2/" /> + </remote-repository> + </component> +</project> \ No newline at end of file diff --git b/.idea/misc.xml a/.idea/misc.xml new file mode 100644 index 0000000..d5d35ec --- /dev/null +++ a/.idea/misc.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK"> + <output url="file://$PROJECT_DIR$/build/classes" /> + </component> + <component name="ProjectType"> + <option name="id" value="Android" /> + </component> +</project> \ No newline at end of file diff --git b/.idea/vcs.xml a/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ a/.idea/vcs.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="VcsDirectoryMappings"> + <mapping directory="$PROJECT_DIR$" vcs="Git" /> + </component> +</project> \ No newline at end of file diff --git b/app/.gitignore a/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ a/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git b/app/build.gradle a/app/build.gradle new file mode 100644 index 0000000..5098667 --- /dev/null +++ a/app/build.gradle @@ -0,0 +1,37 @@ +plugins { + id 'com.android.application' +} + +android { + compileSdkVersion 30 + + defaultConfig { + applicationId "com.fear1ess.reyunadihookplugin" + minSdkVersion 21 + targetSdkVersion 30 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + compileOnly 'de.robv.android.xposed:api:82' + implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'com.google.android.material:material:1.2.1' + testImplementation 'junit:junit:4.+' + androidTestImplementation 'androidx.test.ext:junit:1.1.2' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' +} \ No newline at end of file diff --git b/app/proguard-rules.pro a/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ a/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 b/app/src/androidTest/java/com/fear1ess/reyunaditool/ExampleInstrumentedTest.java a/app/src/androidTest/java/com/fear1ess/reyunaditool/ExampleInstrumentedTest.java new file mode 100644 index 0000000..55cbadc --- /dev/null +++ a/app/src/androidTest/java/com/fear1ess/reyunaditool/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.fear1ess.reyunaditool; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("com.fear1ess.reyunadihookplugin", appContext.getPackageName()); + } +} \ No newline at end of file diff --git b/app/src/main/AndroidManifest.xml a/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..68e8dea --- /dev/null +++ a/app/src/main/AndroidManifest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.fear1ess.reyunaditool"> + + <application + android:allowBackup="true" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:roundIcon="@mipmap/ic_launcher_round" + android:supportsRtl="true" + android:theme="@style/Theme.ReyunAdiHookPlugin"> + <meta-data + android:name="xposedmodule" + android:value="true" /> + <meta-data + android:name="xposedminversion" + android:value="40" /> + <meta-data + android:name="xposeddescription" + android:value="ReyunAdi hook广告sdk接口插件" /> + </application> + +</manifest> \ No newline at end of file diff --git b/app/src/main/aidl/com/fear1ess/reyunaditool/IDoCommandService.aidl a/app/src/main/aidl/com/fear1ess/reyunaditool/IDoCommandService.aidl new file mode 100644 index 0000000..559fc79 --- /dev/null +++ a/app/src/main/aidl/com/fear1ess/reyunaditool/IDoCommandService.aidl @@ -0,0 +1,17 @@ +// IHandleDataService.aidl +package com.fear1ess.reyunaditool; + +// Declare any non-default types here with import statements + +interface IDoCommandService { + /** + * Demonstrates some basic types that you can use as parameters + * and return values in AIDL. + */ + void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, + double aDouble, String aString); + + // void handleData(int cmd, in Map args); + + String doCommand(int cmd, String args); +} \ No newline at end of file diff --git b/app/src/main/assets/xposed_init a/app/src/main/assets/xposed_init new file mode 100644 index 0000000..798cb5c --- /dev/null +++ a/app/src/main/assets/xposed_init @@ -0,0 +1 @@ +com.fear1ess.reyunaditool.HookEntry \ No newline at end of file diff --git b/app/src/main/java/com/fear1ess/reyunaditool/AdsSdkExistsFlag.java a/app/src/main/java/com/fear1ess/reyunaditool/AdsSdkExistsFlag.java new file mode 100644 index 0000000..264674b --- /dev/null +++ a/app/src/main/java/com/fear1ess/reyunaditool/AdsSdkExistsFlag.java @@ -0,0 +1,10 @@ +package com.fear1ess.reyunaditool; + +public class AdsSdkExistsFlag { + public final static int ADMOB = 1; + public final static int UNITY = 2; + public final static int VUNGLE = 4; + public final static int FACEBOOK = 8; + public final static int IRONSOURCE = 16; + public final static int PANGLEMODULE = 32; +} diff --git b/app/src/main/java/com/fear1ess/reyunaditool/ExecuteCmdUtils.java a/app/src/main/java/com/fear1ess/reyunaditool/ExecuteCmdUtils.java new file mode 100644 index 0000000..cb4bc44 --- /dev/null +++ a/app/src/main/java/com/fear1ess/reyunaditool/ExecuteCmdUtils.java @@ -0,0 +1,82 @@ +package com.fear1ess.reyunaditool; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.util.Log; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +public class ExecuteCmdUtils { + public static String TAG = "reyunaditool_log"; + public static int executeCmd(String cmd){ + Process p = null; + int exitCode = 0; + try { + p = Runtime.getRuntime().exec("su -c " + cmd); + exitCode = p.waitFor(); + if(exitCode != 0){ + byte[] b = new byte[1024]; + InputStream is = p.getErrorStream(); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + int len = 0; + while((len = is.read(b)) != -1){ + bos.write(b,0,len); + bos.flush(); + } + String cmdErrMsg = new String(bos.toByteArray()); + bos.close(); + is.close(); + Log.e(TAG, cmdErrMsg + " when execute cmd: " + cmd); + } + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + return exitCode; + } + + public static int installApp(String downloadPath){ + String cmd = "pm install " + downloadPath; + Log.d(TAG, "installApp " + downloadPath); + return executeCmd(cmd); + } + + public static int finishApp(String packageName){ + String cmd = "am force-stop " + packageName; + Log.d(TAG, "finishApp " + packageName); + return executeCmd(cmd); + } + + public static int uninstallApp(String packageName){ + String cmd = "pm uninstall " + packageName; + Log.d(TAG, "uninstallapp " + packageName); + return executeCmd(cmd); + } + + public static int deletePkg(String downloadPath){ + if(!downloadPath.startsWith("/sdcard/reyundownload")){ + Log.e(TAG, "delete path is not reyundownload!!!" ); + return -1; + } + String cmd = "rm -f " + downloadPath; + return executeCmd(cmd); + } + + public static int startApp(Context appContext, String packageName) { + PackageManager pm = appContext.getPackageManager(); + Intent intent = pm.getLaunchIntentForPackage(packageName); + if(intent == null) return -1; + ComponentName cn = intent.getComponent(); + String pkgName = cn.getPackageName(); + String className = cn.getClassName(); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + appContext.startActivity(intent); + return 0; + // Log.d(TAG, "startApp " + packageName); + // String cmd = "am start -n " + pkgName + "/" + className; + // return executeCmd(cmd); + } +} diff --git b/app/src/main/java/com/fear1ess/reyunaditool/HookEntry.java a/app/src/main/java/com/fear1ess/reyunaditool/HookEntry.java new file mode 100644 index 0000000..0aa4014 --- /dev/null +++ a/app/src/main/java/com/fear1ess/reyunaditool/HookEntry.java @@ -0,0 +1,140 @@ +package com.fear1ess.reyunaditool; + +import android.app.Activity; +import android.app.Application; +import android.app.Service; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.Looper; +import android.os.RemoteException; +import android.util.Log; + +import com.fear1ess.reyunaditool.adsfinder.AdmobFinder; +import com.fear1ess.reyunaditool.adsfinder.Finder; +import com.fear1ess.reyunaditool.adsfinder.FinderUtils; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import de.robv.android.xposed.IXposedHookLoadPackage; +import de.robv.android.xposed.XC_MethodHook; +import de.robv.android.xposed.XC_MethodReplacement; +import de.robv.android.xposed.XposedBridge; +import de.robv.android.xposed.XposedHelpers; +import de.robv.android.xposed.callbacks.XC_LoadPackage; + +import static com.fear1ess.reyunaditool.pangle.pangleModule.hook_AppID; +import static com.fear1ess.reyunaditool.pangle.pangleModule.hook_SlotID; +import static com.fear1ess.reyunaditool.pangle.pangleModule.hook_doFinal; + +public class HookEntry implements IXposedHookLoadPackage { + public Context cxt = null; + public static ClassLoader cl = null; + public static ArrayList<String> admobDataList = new ArrayList<>(); + public static IDoCommandService hdService = null; + public static ServiceConnection conn = null; + public static String processName = null; + public static String TAG = "adihookplugin_log"; + public static String[] notNeedAppList = {"android", "system_server", "org.meowcat.edxposed.manager"}; + + @Override + public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable { + processName = (String)Class.forName("android.app.ActivityThread").getDeclaredMethod("currentProcessName").invoke(null); + + Log.d(TAG, "handleLoadPackage processName :" + processName); + + if((lpparam.appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) return; + + + for(String item : notNeedAppList) { + if(item.contains(processName)) return; + } +// MLog.d(lpparam.packageName); +// if(lpparam.packageName.contains("com.union_test.internationad")){ +// MLog.d(lpparam.packageName+"进入了"); +//// hook_AppID(lpparam); +//// hook_SlotID(lpparam); +// hook_doFinal(lpparam); +// } + cl = lpparam.classLoader; + XposedHelpers.findAndHookMethod("android.view.ContextThemeWrapper", cl, "attachBaseContext", + Context.class, new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + super.beforeHookedMethod(param); + Log.d(TAG, "enter app attach...."); + cxt = (Context) param.args[0]; + + if(processName.equals("com.topjohnwu.magisk")){ + Log.d(TAG, "open app"); + ExecuteCmdUtils.startApp(cxt, "com.fear1ess.reyunaditool"); + return; + } + + if(lpparam.processName.contains("com.fear1ess.reyunaditool")) return; + + Log.d(TAG, "start hook " + processName); + + Log.d(TAG, "start bindservice..."); + bindAdiToolService(); + Log.d("reyunadihookplugin_adsfinder", "beforeHookedMethod: "+lpparam.packageName); + FinderUtils.doWork(cl,null); + } + }); + + + } + + + + public void bindAdiToolService(){ + Intent intent = new Intent(); + intent.setComponent(new ComponentName("com.fear1ess.reyunaditool", + "com.fear1ess.reyunaditool.DoCommandService")); + + conn = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + hdService = IDoCommandService.Stub.asInterface(service); + + new Thread(){ + @Override + public void run() { + try { + String curPkgName = hdService.doCommand(OperateCmd.QUERY_CURRENT_PKGNAME, null); + // if(!processName.equals(curPkgName)) return; + } catch (RemoteException e) { + e.printStackTrace(); + } + FinderUtils.notifyServiceBound(hdService); + } + }.start(); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + + } + }; + + boolean res = cxt.bindService(intent, conn, Service.BIND_AUTO_CREATE); + if(res == false) Log.d(TAG, "bindAdiToolService failed!"); + if(res == true) Log.d(TAG, "bindAdiToolService success!"); + } +} diff --git b/app/src/main/java/com/fear1ess/reyunaditool/HttpParser.java a/app/src/main/java/com/fear1ess/reyunaditool/HttpParser.java new file mode 100644 index 0000000..de3ff1c --- /dev/null +++ a/app/src/main/java/com/fear1ess/reyunaditool/HttpParser.java @@ -0,0 +1,123 @@ +// by fear1ess 2020/12/25 + +package com.fear1ess.reyunaditool; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + + +public class HttpParser { + private String method; + private String path; + private Map<String, String> urlParams = new HashMap<>(); + private Map<String, String> headers = new HashMap<>(); + private Map<String, String> formBodyParams = null; + private Map<String, Object> jsonBodyParams = null; + private JSONObject jsonBody = null; + + public String getMethod(){ + return method; + } + + public String getPath() { + return path; + } + + public String getUrlParam(String key){ + return urlParams.get(key); + } + + public String getHeader(String key) { + return headers.get(key); + } + + public String getFormBodyParam(String key) { + return formBodyParams.get(key); + } + + public Object getjsonObjectParam(String key) { + return jsonBodyParams.get(key); + } + + public JSONObject getJsonBody(){ + return jsonBody; + } + + public HttpParser(byte[] payload){ + doParse(new String(payload)); + } + + public HttpParser(String payload){ + doParse(payload); + } + + private boolean doParse(String payload) { + String httpStr = new String(payload); + if (!httpStr.startsWith("GET") && !httpStr.startsWith("POST")) return false; + try { + String[] items = httpStr.split("\r\n\r\n"); + String urlAndHeaderItem = items[0]; + String[] items_1 = urlAndHeaderItem.split("\r\n", 2); + String urls = items_1[0]; + String[] items_2 = urls.split(" "); + method = items_2[0]; + String[] items_3 = items_2[1].split("\\?"); + path = items_3[0]; + if (items_3.length > 1) { + String[] urlParamStrs = items_3[1].split("&"); + for (String item : urlParamStrs) { + String[] paramItem = item.split("="); + String key = URLDecoder.decode(paramItem[0], "utf-8"); + String value = URLDecoder.decode(paramItem[1], "utf-8"); + urlParams.put(key, value); + } + } + if(items_1.length > 1){ + String headerStr = items_1[1]; + String[] headerStrs = headerStr.split("\r\n"); + headers = new HashMap<>(); + for (String item : headerStrs) { + String[] headerItem = item.split(": "); + String key = URLDecoder.decode(headerItem[0], "utf-8"); + String value = URLDecoder.decode(headerItem[1], "utf-8"); + headers.put(key, value); + } + } + + if (items.length > 1 && httpStr.startsWith("POST")) { + String body = items[1]; + if(headers.get("Content-Type").contains("application/x-www-form-urlencoded")){ + formBodyParams = new HashMap<>(); + String[] urlParamStrs = body.split("&"); + for (String item : urlParamStrs) { + String[] paramItem = item.split("="); + String key = URLDecoder.decode(paramItem[0], "utf-8"); + String value = URLDecoder.decode(paramItem[1], "utf-8"); + formBodyParams.put(key, value); + } + } + + if(headers.get("Content-Type").contains("application/json")){ + jsonBodyParams = new HashMap<>(); + JSONObject jo = new JSONObject(body); + jsonBody = jo; + Iterator iterator = jo.keys(); + while(iterator.hasNext()){ + String key = (String) iterator.next(); + jsonBodyParams.put(key, jo.get(key)); + } + } + } + + } catch (UnsupportedEncodingException | JSONException e) { + e.printStackTrace(); + } + return true; + } +} diff --git b/app/src/main/java/com/fear1ess/reyunaditool/MLog.java a/app/src/main/java/com/fear1ess/reyunaditool/MLog.java new file mode 100644 index 0000000..fc9f31e --- /dev/null +++ a/app/src/main/java/com/fear1ess/reyunaditool/MLog.java @@ -0,0 +1,15 @@ +package com.fear1ess.reyunaditool; + +import android.util.Log; + +public class MLog { + private static final String TAG = "ReYunADI"; + private static boolean is_open=true; + public static void d(Object object){ +// if(is_open)return; + synchronized (object){ + + Log.d(TAG, ""+object); + } + } +} diff --git b/app/src/main/java/com/fear1ess/reyunaditool/OperateCmd.java a/app/src/main/java/com/fear1ess/reyunaditool/OperateCmd.java new file mode 100644 index 0000000..e34fc54 --- /dev/null +++ a/app/src/main/java/com/fear1ess/reyunaditool/OperateCmd.java @@ -0,0 +1,8 @@ +package com.fear1ess.reyunaditool; + +public class OperateCmd { + public final static int SHUTDOWN_APP = 0; + public final static int UPLOAD_ADSDK_DATA = 1; + public final static int QUERY_CURRENT_PKGNAME = 2; + public final static int UPLOAD_ADSDK_EXISTS_STATE = 3; +} diff --git b/app/src/main/java/com/fear1ess/reyunaditool/RyTag.java a/app/src/main/java/com/fear1ess/reyunaditool/RyTag.java new file mode 100644 index 0000000..8caf625 --- /dev/null +++ a/app/src/main/java/com/fear1ess/reyunaditool/RyTag.java @@ -0,0 +1,5 @@ +package com.fear1ess.reyunaditool; + +public class RyTag { + public final static String TAG = "reyunadihookplugin_log"; +} diff --git b/app/src/main/java/com/fear1ess/reyunaditool/adsfinder/AdmobFinder.java a/app/src/main/java/com/fear1ess/reyunaditool/adsfinder/AdmobFinder.java new file mode 100644 index 0000000..2fbeb63 --- /dev/null +++ a/app/src/main/java/com/fear1ess/reyunaditool/adsfinder/AdmobFinder.java @@ -0,0 +1,85 @@ +package com.fear1ess.reyunaditool.adsfinder; + +import android.content.Context; +import android.util.Log; + +import com.fear1ess.reyunaditool.HookEntry; +import com.fear1ess.reyunaditool.HttpParser; +import com.fear1ess.reyunaditool.IDoCommandService; +import com.fear1ess.reyunaditool.OperateCmd; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import de.robv.android.xposed.XC_MethodHook; +import de.robv.android.xposed.XposedBridge; +import de.robv.android.xposed.XposedHelpers; + +public class AdmobFinder extends Finder { + public AdmobFinder(String adsClassName, String adsName, ClassLoader cl, IDoCommandService service) { + super(adsClassName, adsName, cl, service); + } + + public class AdmobApiHook extends AdsApiHook{ + + private AdmobApiHook(String s, String dataName, int posInArgs){ + super(s, dataName, posInArgs); + } + + public AdmobApiHook(String s, int posInArgs){ + this(s, "data", posInArgs); + } + + public Map buildAdsData(String adUnitId){ + String[] vals = adUnitId.split("/"); + if(vals.length != 2) return null; + Map<String,String> map = new HashMap<>(); + map.put("client", vals[0]); + map.put("slotname", vals[1]); + map.put("adType", this.mAdType); + return map; + } + } + + public void hookAdsApi() { + registerSSLHook(new SSLOutputStreamHookedCallback() { + @Override + public void onSSLOutputStreamHooked(HttpParser hp) { + if(!hp.getMethod().equals("GET") || !hp.getPath().contains("/mads/gma")) return; + String slotname = hp.getUrlParam("slotname"); + String client = hp.getUrlParam("client"); + Map<String,String> map = new HashMap<>(); + map.put("client", client); + map.put("slotname", slotname); + uploadAdsData(map); + } + }); + + + + XposedHelpers.findAndHookMethod("com.google.android.gms.ads.BaseAdView", mAppClassLoader, "setAdUnitId", + String.class, new AdmobApiHook("bannerAd", 0)); + + XposedHelpers.findAndHookConstructor("com.google.android.gms.ads.AdLoader$Builder", mAppClassLoader, + Context.class, String.class, new AdmobApiHook("nativeAd", 1)); + + + XposedHelpers.findAndHookConstructor("com.google.android.gms.ads.rewarded.RewardedAd", mAppClassLoader, + Context.class, String.class, new AdmobApiHook("rewardedAd", 1)); + + try { + + XposedBridge.hookAllMethods(mAppClassLoader.loadClass("com.google.android.gms.ads.reward.RewardedVideoAd"), "loadAd", + new AdmobApiHook("rewardedAd(legacy)", 0)); + + XposedBridge.hookAllMethods(mAppClassLoader.loadClass("import com.google.android.gms.ads.appopen.AppOpenAd"), "load", + new AdmobApiHook("openAd", 1)); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + + } + +} diff --git b/app/src/main/java/com/fear1ess/reyunaditool/adsfinder/DefaultFinder.java a/app/src/main/java/com/fear1ess/reyunaditool/adsfinder/DefaultFinder.java new file mode 100644 index 0000000..af530bf --- /dev/null +++ a/app/src/main/java/com/fear1ess/reyunaditool/adsfinder/DefaultFinder.java @@ -0,0 +1,14 @@ +package com.fear1ess.reyunaditool.adsfinder; + +import com.fear1ess.reyunaditool.IDoCommandService; + +public class DefaultFinder extends Finder { + public DefaultFinder(String adsName, String adsClassName, ClassLoader cl, IDoCommandService service) { + super(adsName, adsClassName, cl, service); + } + + @Override + public void hookAdsApi() { + return; + } +} diff --git b/app/src/main/java/com/fear1ess/reyunaditool/adsfinder/FacebookFinder.java a/app/src/main/java/com/fear1ess/reyunaditool/adsfinder/FacebookFinder.java new file mode 100644 index 0000000..36a6bb3 --- /dev/null +++ a/app/src/main/java/com/fear1ess/reyunaditool/adsfinder/FacebookFinder.java @@ -0,0 +1,63 @@ +package com.fear1ess.reyunaditool.adsfinder; + +import android.util.Log; + +import com.fear1ess.reyunaditool.HttpParser; +import com.fear1ess.reyunaditool.IDoCommandService; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.HashMap; +import java.util.Map; + +public class FacebookFinder extends Finder { + + public FacebookFinder(String adsName, String adsClassName, ClassLoader cl, IDoCommandService service) { + super(adsName, adsClassName, cl, service); + } + + @Override + public void hookAdsApi() { + registerSSLHook(new SSLOutputStreamHookedCallback() { + @Override + public void onSSLOutputStreamHooked(HttpParser hp) { + if(!hp.getMethod().equals("POST")) return; + if(!hp.getPath().contains("network_ads_common") && (!hp.getPath().contains("/adnw_sync"))) return; + Map<String, String> map = new HashMap<>(); + // PLACEMENT_ID, IDFA, M_BANNER_KEY, PLACEMENT_TYPE, APPNAME, AFP, ASHAS + if(hp.getPath().contains("network_ads_common")){ + String placementId = hp.getFormBodyParam("PLACEMENT_ID"); + String idfa = hp.getFormBodyParam("IDFA"); + String afp = hp.getFormBodyParam("AFP"); + String ashas = hp.getFormBodyParam("ASHAS"); + String mBannerKey = hp.getFormBodyParam("M_BANNER_KEY"); + String placementType = hp.getFormBodyParam("PLACEMENT_TYPE"); + map.put("PLACEMENT_ID", placementId); + map.put("IDFA", idfa); + map.put("AFP", afp); + map.put("ASHAS", ashas); + map.put("PLACEMENT_TYPE", placementType); + map.put("M_BANNER_KEY", mBannerKey); + }else{ + String payload = (String) hp.getFormBodyParam("payload"); + try { + JSONObject jo = new JSONObject(payload); + JSONObject context = jo.getJSONObject("context"); + String placementId = context.getString("PLACEMENT_ID"); + String idfa = context.getString("IDFA"); + String afp = context.getString("AFP"); + String ashas = context.getString("ASHAS"); + map.put("PLACEMENT_ID", placementId); + map.put("IDFA", idfa); + map.put("AFP", afp); + map.put("ASHAS", ashas); + } catch (JSONException e) { + e.printStackTrace(); + } + } + uploadAdsData(map); + } + }); + } +} diff --git b/app/src/main/java/com/fear1ess/reyunaditool/adsfinder/Finder.java a/app/src/main/java/com/fear1ess/reyunaditool/adsfinder/Finder.java new file mode 100644 index 0000000..2c30c3f --- /dev/null +++ a/app/src/main/java/com/fear1ess/reyunaditool/adsfinder/Finder.java @@ -0,0 +1,273 @@ +package com.fear1ess.reyunaditool.adsfinder; + +import android.graphics.Path; +import android.os.Handler; +import android.os.RemoteException; +import android.util.Log; + +import com.fear1ess.reyunaditool.AdsSdkExistsFlag; +import com.fear1ess.reyunaditool.HookEntry; +import com.fear1ess.reyunaditool.HttpParser; +import com.fear1ess.reyunaditool.IDoCommandService; +import com.fear1ess.reyunaditool.MLog; +import com.fear1ess.reyunaditool.OperateCmd; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import de.robv.android.xposed.XC_MethodHook; +import de.robv.android.xposed.XposedHelpers; + +public abstract class Finder { + public static SSLOutputStreamHook sslHook = new SSLOutputStreamHook(); + public static Map<String, Map<String, String>> adsMap = new HashMap<>(); + protected String mAdsClassName; + protected String mAdsName; + protected ClassLoader mAppClassLoader; + protected IDoCommandService mDoCommandService; + protected List<Map> mRemainAdsDataList = new ArrayList<>(); + protected volatile boolean isServiceBound = false; + public static String TAG = "reyunadihookplugin_adsfinder"; + private static int adsSdkState = 0; + private static int adsDataState = 0; + + public Finder(String adsName,String adsClassName,ClassLoader cl,IDoCommandService service){ + mAdsClassName = adsClassName; + mAdsName = adsName; + mAppClassLoader = cl; + mDoCommandService = service; + } + + static { + hookSSLOutputStream(sslHook); + } + + public boolean isAdsExisted(){ + Class<?> cls = null; + try{ + Log.d(TAG, "start find " + mAdsClassName); + cls = mAppClassLoader.loadClass(mAdsClassName); + } catch (ClassNotFoundException e) { + Log.d(TAG, "not find ads: " + mAdsName); + } finally { + if(cls == null) return false; + Log.d(TAG, "find ads: " + mAdsName); + updateAdsSdkState(mAdsName); + return true; + } + } + + public static int getAdsSdkState(){ + return adsSdkState; + } + + + public static int getAdsDataState() { + return adsDataState; + } + + public static void updateAdsSdkState(String adsName){ + switch (adsName){ + case "admob": + adsSdkState |= AdsSdkExistsFlag.ADMOB; + break; + case "unity": + adsSdkState |= AdsSdkExistsFlag.UNITY; + break; + case "vungle": + adsSdkState |= AdsSdkExistsFlag.VUNGLE; + break; + case "facebook": + adsSdkState |= AdsSdkExistsFlag.FACEBOOK; + case "ironsource": + adsSdkState |= AdsSdkExistsFlag.IRONSOURCE; + case "pangle": + adsSdkState |= AdsSdkExistsFlag.PANGLEMODULE; + default: break; + } + } + + public static void updateAdsDataState(String adsName){ + switch (adsName){ + case "admob": + adsDataState |= AdsSdkExistsFlag.ADMOB; + break; + case "unity": + adsDataState |= AdsSdkExistsFlag.UNITY; + break; + case "vungle": + adsDataState |= AdsSdkExistsFlag.VUNGLE; + break; + case "facebook": + adsDataState |= AdsSdkExistsFlag.FACEBOOK; + break; + case "ironsource": + adsSdkState |= AdsSdkExistsFlag.IRONSOURCE; + case "pangle": + adsDataState |= AdsSdkExistsFlag.PANGLEMODULE; + default: break; + } + } + + public void startWork(){ + if(!isAdsExisted()) return; + hookAdsApi(); + } + + public void registerSSLHook(SSLOutputStreamHookedCallback cb){ + sslHook.registerSSLOutputStreamCallback(cb); + } + + public void hookAdsApi(){ + return; + } + + + public void uploadRemainAdsData(IDoCommandService service){ + isServiceBound = true; + mDoCommandService = service; + for(Map map : mRemainAdsDataList){ + uploadAdsData(map); + } + } + + public void uploadAdsData(Map<String,String> map){ + //ad map to global ads map + adsMap.put(mAdsName, map); + + + if(isServiceBound == false){ + mRemainAdsDataList.add(map); + Log.d(TAG, "uploadAdsData failed, service is not bound" ); + return; + } + + try { + JSONObject adsDataJson = new JSONObject(); + for(Map.Entry<String,String> entry : map.entrySet()){ + adsDataJson.put(entry.getKey(),entry.getValue()); + } + JSONObject jo2 = new JSONObject(); + jo2.put(mAdsName, adsDataJson); + JSONObject jo3 = new JSONObject(); + jo2.put("package_name", HookEntry.processName); + jo3.put("data",jo2); + String uploadData = jo3.toString(); + Log.d(TAG, "uploadAdsData: " + uploadData); + MLog.d("uploadAdsData: " + uploadData); + String res = mDoCommandService.doCommand(OperateCmd.UPLOAD_ADSDK_DATA, uploadData); + + updateAdsDataState(mAdsName); + FinderUtils.uploadAdsSdkExistsState(mDoCommandService, adsSdkState, adsDataState); + if(res.equals("success")){ + Log.d(TAG, "uploadAdsData success"); + } + } catch (RemoteException | JSONException e) { + e.printStackTrace(); + } + } + + + public static void hookSSLOutputStream(XC_MethodHook hook){ + XposedHelpers.findAndHookMethod("com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLOutputStream", HookEntry.cl, "write", + byte[].class, int.class, int.class, hook); + } + + public static class SSLOutputStreamHook extends XC_MethodHook { + private List<SSLOutputStreamHookedCallback> mCallbacks = new ArrayList<>(); + + public void registerSSLOutputStreamCallback(SSLOutputStreamHookedCallback cb){ + mCallbacks.add(cb); + } + + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + super.beforeHookedMethod(param); + byte[] data = (byte[]) param.args[0]; + int pos = (int) param.args[1]; + int len = (int) param.args[2]; + String payload = new String(data, pos, len); + Log.d(TAG, "SSLOutputSteam data: " + payload); + HttpParser hp = new HttpParser(payload); + for(SSLOutputStreamHookedCallback cb : mCallbacks){ + cb.onSSLOutputStreamHooked(hp); + } + } + } JSONObject jo = new JSONObject(Finder.adsMap); + + public interface SSLOutputStreamHookedCallback { + void onSSLOutputStreamHooked(HttpParser hp); + } + + public class AdsApiHook extends XC_MethodHook { + public String mAdType; + public String mDataName; + public int mPos; + + public AdsApiHook(String s, String dataName, int posInArgs) { + super(); + mAdType = s; + mDataName = dataName; + mPos = posInArgs; + } + + public Map buildAdsData(String data) { + Map<String, String> map = new HashMap<>(); + map.put(mDataName, data); + map.put("adType", this.mAdType); + return map; + } + + public void handleAdUnitId(String adUnitId) { + if (adUnitId == null) return; + Map map = buildAdsData(adUnitId); + if (map == null) return; + uploadAdsData(map); + } + + + @Override + protected void beforeHookedMethod(XC_MethodHook.MethodHookParam param) throws Throwable { + super.beforeHookedMethod(param); + String data = (String) param.args[mPos]; + Log.d(TAG, "find " + mAdsName + " " + mAdType + ", " + mDataName + ": " + data); + handleAdUnitId(data); + } + } + + public String matchQueryValue(String str, String key) { + String patternStr = key + "=((.*&)|(.* ))"; + String matchStr = match(str, patternStr); + if(matchStr == null) return null; + String value = matchStr.replace(key + "=",""). + replace("&","").replace(" ",""); + Log.d(TAG, "matchQueryValue key: " + key + ", value: " + value); + return value; + } + + public String matchJsonBodyValue(String str, String key) { + String patternStr = "\"" + key + "\":" + "((.*,)|(.*\\}))"; + String matchStr = match(str, patternStr); + if(matchStr == null) return null; + String value = matchStr.replace("\"" + key + "\":", "") + .replace(",", "").replace("}",""); + Log.d(TAG, "matchJsonBodyValue key: " + key + ", value: " + value); + return value; + } + + public String match(String str, String patternStr) { + Pattern pattern = Pattern.compile(patternStr); + Matcher matcher = pattern.matcher(str); + if(!matcher.find()) return null; + String value = matcher.group(0); + return value; + } +} diff --git b/app/src/main/java/com/fear1ess/reyunaditool/adsfinder/FinderUtils.java a/app/src/main/java/com/fear1ess/reyunaditool/adsfinder/FinderUtils.java new file mode 100644 index 0000000..f8530d7 --- /dev/null +++ a/app/src/main/java/com/fear1ess/reyunaditool/adsfinder/FinderUtils.java @@ -0,0 +1,146 @@ +package com.fear1ess.reyunaditool.adsfinder; + +import android.net.VpnManager; +import android.net.VpnService; +import android.os.RemoteException; +import android.util.Log; + +import com.fear1ess.reyunaditool.HookEntry; +import com.fear1ess.reyunaditool.IDoCommandService; +import com.fear1ess.reyunaditool.MLog; +import com.fear1ess.reyunaditool.OperateCmd; +import com.fear1ess.reyunaditool.RyTag; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import static com.fear1ess.reyunaditool.adsfinder.Finder.TAG; + +public class FinderUtils { + public static Map<String,String> adsInfoMap = new HashMap<>(); + public static List<Finder> finderArray = new ArrayList<>(); + + static{ + adsInfoMap.put("admob", "com.google.android.gms.ads.MobileAds"); + adsInfoMap.put("unity", "com.unity3d.ads.UnityAds"); + adsInfoMap.put("vungle", "com.vungle.warren.Vungle"); + adsInfoMap.put("facebook", "com.facebook.ads.AudienceNetworkAds"); + adsInfoMap.put("ironsource", "com.ironsource.mediationsdk.IronSource"); + adsInfoMap.put("pangle", "com.bytedance.sdk.openadsdk.TTAdConfig"); //海外穿山甲 + } + + public static Finder createFinder(String adsName, String adsClsName, ClassLoader cl, IDoCommandService service){ + switch(adsName){ + case "admob": + return new AdmobFinder(adsName, adsClsName, cl, service); + case "unity": + return new UnityFinder(adsName, adsClsName, cl, service); + case "vungle": + return new VungleFinder(adsName, adsClsName, cl, service); + case "facebook": + return new FacebookFinder(adsName, adsClsName, cl, service); + case "ironsource": + return new IronSourceFinder(adsName, adsClsName, cl, service); + case "pangle": + MLog.d("createFinder: PangLeFinder"); + return new PangLeFinder(adsName, adsClsName, cl, service); + default: + return new DefaultFinder(adsName, adsClsName, cl, service); + } + } + + public static void uploadAdsSdkExistsState(IDoCommandService service, int sdkState, int dataState){ + if(service == null){ + Log.d(RyTag.TAG, "uploadAdsData failed, service is not bound" ); + return; + } + try { + JSONObject jo = new JSONObject(); + jo.put("ads_sdk_state", sdkState); + jo.put("ads_data_state", dataState); + jo.put("package_name", HookEntry.processName); + JSONObject jo2 = new JSONObject(); + jo2.put("data", jo); + String uploadData = jo2.toString(); + Log.d(RyTag.TAG, "uploadAdsExistsStateData: " + uploadData); + String res = service.doCommand(OperateCmd.UPLOAD_ADSDK_EXISTS_STATE, uploadData); + if(res.equals("success")){ + Log.d(RyTag.TAG, "uploadAdsExistsStateData success"); + } + } catch (JSONException | RemoteException e) { + e.printStackTrace(); + } + } + + public static void notifyServiceBound(IDoCommandService service){ + uploadAdsSdkExistsState(service, Finder.getAdsSdkState(), Finder.getAdsDataState()); + for(Finder finder : finderArray){ + finder.uploadRemainAdsData(service); + } + + //do upload... + ExecutorService es = Executors.newSingleThreadExecutor(); + es.execute(new UploadDataProceduce(service)); + es.shutdown(); + } + + public static void doWork(ClassLoader cl, IDoCommandService service){ + for(Map.Entry<String,String> entry : adsInfoMap.entrySet()){ + Finder finder = createFinder(entry.getKey(), entry.getValue(), cl, service); + finderArray.add(finder); + finder.startWork(); + } + + } + + public static class UploadDataProceduce implements Runnable { + + IDoCommandService hdService; + + public UploadDataProceduce(IDoCommandService service){ + hdService = service; + } + + @Override + public void run(){ + + // wait for some time + try { + Thread.sleep(50*1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + String curPkg = null; + try { + curPkg = hdService.doCommand(OperateCmd.QUERY_CURRENT_PKGNAME, null); + Log.d(RyTag.TAG, "currentPkg: " + curPkg); + Log.d(RyTag.TAG, "processName: " + HookEntry.processName); + // if(!HookEntry.processName.equals(curPkg)) return; + } catch (RemoteException e) { + e.printStackTrace(); + } + + //upload ads data + try { + JSONObject jo = new JSONObject(Finder.adsMap); + jo.put("app_id", HookEntry.processName); + String data = jo.toString(); + Log.d(RyTag.TAG, "upload ads data: " + data); + MLog.d( "upload ads data: " + data); + if(!HookEntry.processName.equals(curPkg)) return; + hdService.doCommand(OperateCmd.UPLOAD_ADSDK_DATA, data); + // hdService.doCommand(OperateCmd.SHUTDOWN_APP, null); + } catch (JSONException | RemoteException e) { + e.printStackTrace(); + } + } + } +} diff --git b/app/src/main/java/com/fear1ess/reyunaditool/adsfinder/IronSourceFinder.java a/app/src/main/java/com/fear1ess/reyunaditool/adsfinder/IronSourceFinder.java new file mode 100644 index 0000000..d3ffeb8 --- /dev/null +++ a/app/src/main/java/com/fear1ess/reyunaditool/adsfinder/IronSourceFinder.java @@ -0,0 +1,30 @@ +package com.fear1ess.reyunaditool.adsfinder; + +import com.fear1ess.reyunaditool.HttpParser; +import com.fear1ess.reyunaditool.IDoCommandService; + +import java.util.HashMap; +import java.util.Map; + +public class IronSourceFinder extends Finder { + public IronSourceFinder(String adsClassName, String adsName, ClassLoader cl, IDoCommandService service) { + super(adsClassName, adsName, cl, service); + } + + @Override + public void hookAdsApi() { + registerSSLHook(new SSLOutputStreamHookedCallback() { + @Override + public void onSSLOutputStreamHooked(HttpParser hp) { + if(!hp.getMethod().equals("GET")) return; + if(!hp.getPath().contains("/gateway/sdk/request")) return; + String applicationKey = hp.getUrlParam("applicationKey"); + if(applicationKey != null) { + Map<String,String> map = new HashMap<>(); + map.put("applicationKey", applicationKey); + uploadAdsData(map); + } + } + }); + } +} diff --git b/app/src/main/java/com/fear1ess/reyunaditool/adsfinder/PangLeFinder.java a/app/src/main/java/com/fear1ess/reyunaditool/adsfinder/PangLeFinder.java new file mode 100644 index 0000000..b92820e --- /dev/null +++ a/app/src/main/java/com/fear1ess/reyunaditool/adsfinder/PangLeFinder.java @@ -0,0 +1,227 @@ +package com.fear1ess.reyunaditool.adsfinder; + +import android.os.RemoteException; +import android.text.TextUtils; +import android.util.Log; + +import com.fear1ess.reyunaditool.HookEntry; +import com.fear1ess.reyunaditool.IDoCommandService; +import com.fear1ess.reyunaditool.MLog; +import com.fear1ess.reyunaditool.OperateCmd; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.HashMap; +import java.util.Map; + +import de.robv.android.xposed.XC_MethodHook; +import de.robv.android.xposed.XposedHelpers; +import de.robv.android.xposed.callbacks.XC_LoadPackage; + +public class PangLeFinder extends Finder { + private static String m_appID=""; + private static String m_temp=""; + private static boolean m_flag=false; + public PangLeFinder(String adsName, String adsClassName, ClassLoader cl, IDoCommandService service) { + super(adsName, adsClassName, cl, service); + } + public void hookAdsApi() { + hook_doFinal(mAppClassLoader); + hook_JSON_Put(mAppClassLoader); + } + public void hook_doFinal(ClassLoader classLoader){ + XposedHelpers.findAndHookMethod("javax.crypto.Cipher", + classLoader, "doFinal",byte[].class, + new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + super.beforeHookedMethod(param); + try{ + String content= new String((byte[]) param.args[0]); + try{ + JSONObject jsonObject=new JSONObject(content); + JSONArray adslots= (JSONArray) jsonObject.get("adslots"); +// JSONObject item= (JSONObject) adslots.get(0); + if( adslots!=null){ +// MLog.d("打印原内容:"+content); + if((m_temp.hashCode()==content.hashCode())){ + return; + } + m_temp=content; + m_flag=true; + } + }catch (Exception e){ + + } +// if(content.contains("\"adslots\":[{\"id\":\"")){ +// if((m_temp.hashCode()==content.hashCode())){ +// return; +// } +// m_temp=content; +// m_flag=true; +//// JSONObject jsonObject=new JSONObject(content); +//// String ad_sdk_version=(String) jsonObject.get("ad_sdk_version"); +//// JSONObject app= (JSONObject) jsonObject.get("app"); +//// String appid= (String) app.get("appid"); +//// String package_name= (String) app.get("package_name"); +//// String version_code= (String) app.get("version_code"); +//// String version= (String) app.get("version"); +//// +//// JSONArray adslots= (JSONArray) jsonObject.get("adslots"); +////// MLog.d("adslots-> "+adslots.toString()); +//// JSONArray my_Adslots=new JSONArray(); +//// for (int i = 0; i < adslots.length(); i++) { +//// JSONObject item= (JSONObject) adslots.get(i); +//// JSONObject object= new JSONObject(); +//// object.put("id",item.get("id")); +//// object.put("adtype",item.get("adtype")); +//// my_Adslots.put(object); +//// } +//// +//// Map<String,String> map = new HashMap<>(); +//// map.put("appid", appid); +//// map.put("ad_sdk_version", ad_sdk_version); +//// map.put("package_name", package_name); +//// map.put("version_code", version_code); +//// map.put("version", version); +//// map.put("adslots", my_Adslots.toString()); +//// MLog.d("穿山甲上传数据: "+map.toString()); +//// uploadAdsData(map); +// } + }catch (Exception e){ + MLog.d(e.toString()); + } + + + } + } + ); + } + + public void hook_JSON_Put(ClassLoader classLoader){ + XposedHelpers.findAndHookMethod(JSONObject.class, "put", + String.class, int.class, + new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + super.beforeHookedMethod(param); + String key= (String) param.args[0]; + + if(key.equals("cypher")){ + if(!m_flag){ + return; + } + m_flag=false; +// MLog.d("key= "+param.args[0]+" "+param.args[1]); + JSONObject jsonObject=new JSONObject(m_temp); + String ad_sdk_version=(String) jsonObject.get("ad_sdk_version"); + JSONObject app= (JSONObject) jsonObject.get("app"); + String appid= (String) app.get("appid"); + String package_name= (String) app.get("package_name"); + String version_code= (String) app.get("version_code"); + String version= (String) app.get("version"); + + JSONArray adslots= (JSONArray) jsonObject.get("adslots"); +// MLog.d("adslots-> "+adslots.toString()); + JSONArray my_Adslots=new JSONArray(); + for (int i = 0; i < adslots.length(); i++) { + JSONObject item= (JSONObject) adslots.get(i); + JSONObject object= new JSONObject(); + object.put("id",item.get("id")); + object.put("adtype",item.get("adtype")); + my_Adslots.put(object); + } + + Map<String,String> map = new HashMap<>(); + map.put("appid", appid); + map.put("ad_sdk_version", ad_sdk_version); + map.put("package_name", package_name); + map.put("version_code", version_code); + map.put("version", version); + map.put("adslots", my_Adslots.toString()); + map.put("cypher", String.valueOf(param.args[1])); + MLog.d("穿山甲上传数据: "+map.toString()); + uploadAdsData(map); + + } + } + }); + + } + + + public void uploadAdsData(Map<String,String> map){ + //ad map to global ads map + adsMap.put(mAdsName, map); + + + if(isServiceBound == false){ + mRemainAdsDataList.add(map); + Log.d(TAG, "uploadAdsData failed, service is not bound" ); + return; + } + + try { + JSONObject adsDataJson = new JSONObject(); + for(Map.Entry<String,String> entry : map.entrySet()){ + adsDataJson.put(entry.getKey(),entry.getValue()); + } + JSONObject jo2 = new JSONObject(); + jo2.put(mAdsName, adsDataJson); +// JSONObject jo3 = new JSONObject(); + jo2.put("app_id", HookEntry.processName+System.currentTimeMillis()); +// jo3.put("data",jo2); + String uploadData = jo2.toString(); + Log.d(TAG, "uploadAdsData: " + uploadData); + MLog.d("uploadAdsData: " + uploadData); + String res = mDoCommandService.doCommand(OperateCmd.UPLOAD_ADSDK_DATA, uploadData); + + updateAdsDataState(mAdsName); + FinderUtils.uploadAdsSdkExistsState(mDoCommandService, 32, 32); + if(res.equals("success")){ + Log.d(TAG, "uploadAdsData success"); + } + } catch (RemoteException | JSONException e) { + e.printStackTrace(); + } + } + + + private void hook_AppID(ClassLoader classLoader){ + XposedHelpers.findAndHookMethod("com.bytedance.sdk.openadsdk.TTAdConfig", + classLoader, "setAppId",String.class, + new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + super.beforeHookedMethod(param); + m_appID= (String) param.args[0]; + MLog.d( "beforeHookedMethod: 穿山甲APPID: "+m_appID); + } + } + ); + } + + + private void hook_SlotID(ClassLoader classLoader){ + XposedHelpers.findAndHookMethod("com.bytedance.sdk.openadsdk.AdSlot$Builder", + classLoader, "setCodeId",String.class, + new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + super.beforeHookedMethod(param); + String slotID= (String) param.args[0]; + Map<String,String> map = new HashMap<>(); + if(TextUtils.isEmpty(m_appID)){ + m_appID="error"; + } + map.put("appID", m_appID); + map.put("slotID", slotID); + MLog.d( "beforeHookedMethod: 上传的数据: "+map.toString()); + uploadAdsData(map); + } + } + ); + } +} diff --git b/app/src/main/java/com/fear1ess/reyunaditool/adsfinder/UnityFinder.java a/app/src/main/java/com/fear1ess/reyunaditool/adsfinder/UnityFinder.java new file mode 100644 index 0000000..59048e4 --- /dev/null +++ a/app/src/main/java/com/fear1ess/reyunaditool/adsfinder/UnityFinder.java @@ -0,0 +1,121 @@ +package com.fear1ess.reyunaditool.adsfinder; + +import android.util.Log; + +import com.fear1ess.reyunaditool.HttpParser; +import com.fear1ess.reyunaditool.IDoCommandService; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import de.robv.android.xposed.XC_MethodHook; +import de.robv.android.xposed.XposedBridge; +import de.robv.android.xposed.XposedHelpers; + +public class UnityFinder extends Finder { + public UnityFinder(String adsName, String adsClassName, ClassLoader cl, IDoCommandService service) { + super(adsName, adsClassName, cl, service); + } + + @Override + public void hookAdsApi() { + registerSSLHook(new SSLOutputStreamHookedCallback() { + @Override + public void onSSLOutputStreamHooked(HttpParser hp) { + if(!hp.getMethod().equals("POST") || !hp.getPath().contains("/v6/games")) return; + Log.d(TAG, "onSSLOutputStreamHooked: unity0"); + String unityGameId = match(hp.getPath(), "v6/games/.*/requests").replace("v6/games/","") + .replace("/requests",""); + if(unityGameId == null) return; + Log.d(TAG, "onSSLOutputStreamHooked: unity1"); + JSONObject jo = hp.getJsonBody(); + String gameSessionId = null; + try { + gameSessionId = jo.getString("gameSessionId"); + Log.d(TAG, "onSSLOutputStreamHooked: unity2"); + String projectId = jo.getString("projectId"); + String token = jo.getString("token"); + // if(gameSessionId == null || projectId == null || token == null) return; + Map<String, String> map = new HashMap<>(); + map.put("gameSessionId", gameSessionId); + map.put("token", token); + map.put("projectId", projectId); + map.put("unityGameId", unityGameId); + uploadAdsData(map); + } catch (JSONException e) { + e.printStackTrace(); + } + } + }); + + /* + try { + XposedBridge.hookAllMethods(mAppClassLoader.loadClass("com.unity3d.ads.UnityAds"), "initialize", + new AdsApiHook("AD", "unityGameId", 1)); + + XposedHelpers.findAndHookMethod("com.unity3d.services.core.request.WebRequest", mAppClassLoader, + "makeRequest", new UnityNetWorkHook("AD", "unityParam", 0)); + + XposedHelpers.findAndHookMethod("java.net.SocketOutputStream", mAppClassLoader, "write", + byte[].class, int.class, int.class, new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + super.beforeHookedMethod(param); + Log.d(TAG, "socketos hook success!!!!"); + // String data = new String((byte[])param.args[0]); + // Log.d(TAG, "SocketOutput data: " + data); + } + }); + + XposedHelpers.findAndHookMethod("sun.nio.cs.StreamEncoder", mAppClassLoader, "write", + String.class, int.class, int.class, new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + super.beforeHookedMethod(param); + Log.d(TAG, "streamencoder hook success!!!!"); + // String data = new String((byte[])param.args[0]); + Log.d(TAG, "streamencoder data: " + param.args[0]); + } + }); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + }*/ + } + + public class UnityNetWorkHook extends AdsApiHook{ + + public UnityNetWorkHook(String s, String dataName, int posInArgs) { + super(s, dataName, posInArgs); + } + + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + Object o = param.thisObject; + URL url = (URL) XposedHelpers.getObjectField(o, "_url"); + String urlStr = url.toString(); + Log.d(TAG, "urlStr: " + urlStr); + Pattern pattern = Pattern.compile("v6/games/.*/requests"); + Matcher matcher = pattern.matcher(urlStr); + if(!matcher.find()) return; + Log.d(TAG, "1"); + String body = (String) XposedHelpers.getObjectField(o, "_body"); + JSONObject jo = new JSONObject(body); + Log.d(TAG, "2"); + if(!jo.has("gameSessionId") || !jo.has("token") || !jo.has("projectId")) return; + Log.d(TAG, "3"); + Map<String,String> map = new HashMap<>(); + map.put("gameSessionId", jo.getString("gameSessionId")); + map.put("token", jo.getString("token")); + map.put("projectId", jo.getString("projectId")); + String unityGameId = matcher.group(0).replace("v6/games/","").replace("/requests",""); + map.put("unityGameId", unityGameId); + uploadAdsData(map); + } + } +} diff --git b/app/src/main/java/com/fear1ess/reyunaditool/adsfinder/VungleFinder.java a/app/src/main/java/com/fear1ess/reyunaditool/adsfinder/VungleFinder.java new file mode 100644 index 0000000..3e41d28 --- /dev/null +++ a/app/src/main/java/com/fear1ess/reyunaditool/adsfinder/VungleFinder.java @@ -0,0 +1,67 @@ +package com.fear1ess.reyunaditool.adsfinder; + +import android.util.Log; + +import com.fear1ess.reyunaditool.HttpParser; +import com.fear1ess.reyunaditool.IDoCommandService; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.HashMap; +import java.util.Map; + +import de.robv.android.xposed.XC_MethodHook; +import de.robv.android.xposed.XposedBridge; +import de.robv.android.xposed.XposedHelpers; + +public class VungleFinder extends Finder{ + public VungleFinder(String adsName, String adsClassName, ClassLoader cl, IDoCommandService service) { + super(adsName, adsClassName, cl, service); + } + + @Override + public void hookAdsApi() { + registerSSLHook(new SSLOutputStreamHookedCallback() { + @Override + public void onSSLOutputStreamHooked(HttpParser hp) { + if(!hp.getMethod().equals("POST") || !hp.getPath().contains("api/v5/ads")) return; + JSONObject jo = hp.getJsonBody(); + try { + JSONObject app = jo.getJSONObject("app"); + String id = app.getString("id"); + JSONObject request = jo.getJSONObject("request"); + JSONArray pArr = request.getJSONArray("placements"); + String placement = null; + for(int i = 0;i < pArr.length(); ++i){ + placement = pArr.getString(i); + } + Map<String, String> map = new HashMap<>(); + map.put("id", id); + map.put("placement", placement); + uploadAdsData(map); + } catch (JSONException e) { + e.printStackTrace(); + } + } + }); + /* + try { + XposedBridge.hookAllMethods(mAppClassLoader.loadClass("com.vungle.warren.network.VungleApiImpl"), "createNewPostCall", + new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + super.beforeHookedMethod(param); + Log.d(TAG, "vungle ads ua: " + param.args[0]); + Log.d(TAG, "vungle ads path: " + param.args[1]); + Log.d(TAG, "vungle ads body: " + XposedHelpers.callMethod(param.args[2],"toString")); + } + }); + + + } catch (ClassNotFoundException e) { + e.printStackTrace(); + }*/ + } +} diff --git b/app/src/main/java/com/fear1ess/reyunaditool/pangle/pangleModule.java a/app/src/main/java/com/fear1ess/reyunaditool/pangle/pangleModule.java new file mode 100644 index 0000000..8dc4952 --- /dev/null +++ a/app/src/main/java/com/fear1ess/reyunaditool/pangle/pangleModule.java @@ -0,0 +1,110 @@ +package com.fear1ess.reyunaditool.pangle; + +import android.text.TextUtils; +import android.util.Log; + +import com.fear1ess.reyunaditool.MLog; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.util.HashMap; +import java.util.Map; + +import de.robv.android.xposed.XC_MethodHook; +import de.robv.android.xposed.XposedHelpers; +import de.robv.android.xposed.callbacks.XC_LoadPackage; + +import static com.fear1ess.reyunaditool.adsfinder.Finder.TAG; +//--------------------------------------------------测试代码 +public class pangleModule { + private static String m_appID=""; + public static void hook_AppID(XC_LoadPackage.LoadPackageParam lpparam){ + + XposedHelpers.findAndHookMethod("com.bytedance.sdk.openadsdk.TTAdConfig", + lpparam.classLoader, "setAppId",String.class, + new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + super.beforeHookedMethod(param); + m_appID= (String) param.args[0]; + MLog.d( "测试:beforeHookedMethod: "+m_appID); + } + } + ); + } + + + public static void hook_SlotID(XC_LoadPackage.LoadPackageParam lpparam){ + XposedHelpers.findAndHookMethod("com.bytedance.sdk.openadsdk.AdSlot$Builder", + lpparam.classLoader, "setCodeId",String.class, + new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + super.beforeHookedMethod(param); + String slotID= (String) param.args[0]; + Map<String,String> map = new HashMap<>(); + if(TextUtils.isEmpty(m_appID)){ + m_appID=lpparam.packageName; + } + map.put("appID", m_appID); + map.put("slotID", slotID); + MLog.d("测试:beforeHookedMethod: "+map.toString()); + } + } + ); + } + + + public static void hook_doFinal(XC_LoadPackage.LoadPackageParam lpparam){ + XposedHelpers.findAndHookMethod("javax.crypto.Cipher", + lpparam.classLoader, "doFinal",byte[].class, + new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + super.beforeHookedMethod(param); + try{ + String content= new String((byte[]) param.args[0]); + if(content.contains("\"adslots\":[{\"id\":\"")){ +// MLog.d(content); + JSONObject jsonObject=new JSONObject(content); + String ad_sdk_version=(String) jsonObject.get("ad_sdk_version"); + JSONObject app= (JSONObject) jsonObject.get("app"); + String appid= (String) app.get("appid"); + String package_name= (String) app.get("package_name"); + String version_code= (String) app.get("version_code"); + String version= (String) app.get("version"); + + JSONArray adslots= (JSONArray) jsonObject.get("adslots"); +// MLog.d("adslots-> "+adslots.toString()); + JSONArray my_Adslots=new JSONArray(); + for (int i = 0; i < adslots.length(); i++) { + JSONObject item= (JSONObject) adslots.get(i); + JSONObject object= new JSONObject(); + object.put("id",item.get("id")); + object.put("adtype",item.get("adtype")); + my_Adslots.put(object); + } + + Map<String,String> map = new HashMap<>(); + if(TextUtils.isEmpty(m_appID)){ + m_appID=lpparam.packageName; + } + map.put("appID", appid); + map.put("ad_sdk_version", ad_sdk_version); + map.put("package_name", package_name); + map.put("version_code", version_code); + map.put("version", version); + map.put("slotID", my_Adslots.toString()); + MLog.d("测试:beforeHookedMethod: "+map.toString()); + } + }catch (Exception e){ + MLog.d(e.toString()); + } + + + } + } + ); + } +} diff --git b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt" + android:width="108dp" + android:height="108dp" + android:viewportWidth="108" + android:viewportHeight="108"> + <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z"> + <aapt:attr name="android:fillColor"> + <gradient + android:endX="85.84757" + android:endY="92.4963" + android:startX="42.9492" + android:startY="49.59793" + android:type="linear"> + <item + android:color="#44000000" + android:offset="0.0" /> + <item + android:color="#00000000" + android:offset="1.0" /> + </gradient> + </aapt:attr> + </path> + <path + android:fillColor="#FFFFFF" + android:fillType="nonZero" + android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z" + android:strokeWidth="1" + android:strokeColor="#00000000" /> +</vector> \ No newline at end of file diff --git b/app/src/main/res/drawable/ic_launcher_background.xml a/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ a/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="108dp" + android:height="108dp" + android:viewportWidth="108" + android:viewportHeight="108"> + <path + android:fillColor="#3DDC84" + android:pathData="M0,0h108v108h-108z" /> + <path + android:fillColor="#00000000" + android:pathData="M9,0L9,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M19,0L19,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M29,0L29,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M39,0L39,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M49,0L49,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M59,0L59,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M69,0L69,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M79,0L79,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M89,0L89,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M99,0L99,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,9L108,9" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,19L108,19" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,29L108,29" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,39L108,39" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,49L108,49" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,59L108,59" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,69L108,69" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,79L108,79" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,89L108,89" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,99L108,99" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M19,29L89,29" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M19,39L89,39" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M19,49L89,49" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M19,59L89,59" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M19,69L89,69" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M19,79L89,79" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M29,19L29,89" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M39,19L39,89" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M49,19L49,89" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M59,19L59,89" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M69,19L69,89" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M79,19L79,89" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> +</vector> diff --git b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@drawable/ic_launcher_background" /> + <foreground android:drawable="@drawable/ic_launcher_foreground" /> +</adaptive-icon> \ No newline at end of file diff --git b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@drawable/ic_launcher_background" /> + <foreground android:drawable="@drawable/ic_launcher_foreground" /> +</adaptive-icon> \ No newline at end of file diff --git b/app/src/main/res/mipmap-hdpi/ic_launcher.png a/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..a571e60 Binary files /dev/null and a/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..61da551 Binary files /dev/null and a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git b/app/src/main/res/mipmap-mdpi/ic_launcher.png a/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..c41dd28 Binary files /dev/null and a/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..db5080a Binary files /dev/null and a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git b/app/src/main/res/mipmap-xhdpi/ic_launcher.png a/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..6dba46d Binary files /dev/null and a/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..da31a87 Binary files /dev/null and a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..15ac681 Binary files /dev/null and a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..b216f2d Binary files /dev/null and a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..f25a419 Binary files /dev/null and a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..e96783c Binary files /dev/null and a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git b/app/src/main/res/values-night/themes.xml a/app/src/main/res/values-night/themes.xml new file mode 100644 index 0000000..5c04abb --- /dev/null +++ a/app/src/main/res/values-night/themes.xml @@ -0,0 +1,16 @@ +<resources xmlns:tools="http://schemas.android.com/tools"> + <!-- Base application theme. --> + <style name="Theme.ReyunAdiHookPlugin" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> + <!-- Primary brand color. --> + <item name="colorPrimary">@color/purple_200</item> + <item name="colorPrimaryVariant">@color/purple_700</item> + <item name="colorOnPrimary">@color/black</item> + <!-- Secondary brand color. --> + <item name="colorSecondary">@color/teal_200</item> + <item name="colorSecondaryVariant">@color/teal_200</item> + <item name="colorOnSecondary">@color/black</item> + <!-- Status bar color. --> + <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item> + <!-- Customize your theme here. --> + </style> +</resources> \ No newline at end of file diff --git b/app/src/main/res/values/colors.xml a/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..f8c6127 --- /dev/null +++ a/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <color name="purple_200">#FFBB86FC</color> + <color name="purple_500">#FF6200EE</color> + <color name="purple_700">#FF3700B3</color> + <color name="teal_200">#FF03DAC5</color> + <color name="teal_700">#FF018786</color> + <color name="black">#FF000000</color> + <color name="white">#FFFFFFFF</color> +</resources> \ No newline at end of file diff --git b/app/src/main/res/values/strings.xml a/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..059a5de --- /dev/null +++ a/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ +<resources> + <string name="app_name">ReyunAdiHookPlugin</string> +</resources> \ No newline at end of file diff --git b/app/src/main/res/values/themes.xml a/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..622a250 --- /dev/null +++ a/app/src/main/res/values/themes.xml @@ -0,0 +1,16 @@ +<resources xmlns:tools="http://schemas.android.com/tools"> + <!-- Base application theme. --> + <style name="Theme.ReyunAdiHookPlugin" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> + <!-- Primary brand color. --> + <item name="colorPrimary">@color/purple_500</item> + <item name="colorPrimaryVariant">@color/purple_700</item> + <item name="colorOnPrimary">@color/white</item> + <!-- Secondary brand color. --> + <item name="colorSecondary">@color/teal_200</item> + <item name="colorSecondaryVariant">@color/teal_700</item> + <item name="colorOnSecondary">@color/black</item> + <!-- Status bar color. --> + <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item> + <!-- Customize your theme here. --> + </style> +</resources> \ No newline at end of file diff --git b/app/src/test/java/com/fear1ess/reyunaditool/ExampleUnitTest.java a/app/src/test/java/com/fear1ess/reyunaditool/ExampleUnitTest.java new file mode 100644 index 0000000..2c8dca2 --- /dev/null +++ a/app/src/test/java/com/fear1ess/reyunaditool/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.fear1ess.reyunaditool; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git b/build.gradle a/build.gradle new file mode 100644 index 0000000..38aef93 --- /dev/null +++ a/build.gradle @@ -0,0 +1,24 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.6.0' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} \ No newline at end of file diff --git b/gradle.properties a/gradle.properties new file mode 100644 index 0000000..52f5917 --- /dev/null +++ a/gradle.properties @@ -0,0 +1,19 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app"s APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true \ No newline at end of file diff --git b/gradle/wrapper/gradle-wrapper.jar a/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..f6b961f Binary files /dev/null and a/gradle/wrapper/gradle-wrapper.jar differ diff --git b/gradle/wrapper/gradle-wrapper.properties a/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..8bb258a --- /dev/null +++ a/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Thu Jul 08 14:50:33 CST 2021 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-bin.zip diff --git b/gradlew a/gradlew new file mode 100644 index 0000000..cccdd3d --- /dev/null +++ a/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git b/gradlew.bat a/gradlew.bat new file mode 100644 index 0000000..f955316 --- /dev/null +++ a/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git b/settings.gradle a/settings.gradle new file mode 100644 index 0000000..7b353a4 --- /dev/null +++ a/settings.gradle @@ -0,0 +1,2 @@ +include ':app' +rootProject.name = "ReyunAdiHookPlugin" \ No newline at end of file