commit 2575c8fda3a504fd1abd50b06ea9fe73070f2239 Author: Administrator Date: Wed Jul 30 16:45:15 2025 +0800 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f0c5069 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +*.iml +.gradle +/.idea +/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 a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..e97d721 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,43 @@ +plugins { + alias(libs.plugins.android.application) +} + +android { + namespace 'com.bjlx.armcloudaf' + compileSdk 36 + + defaultConfig { + applicationId "com.bjlx.armcloudaf" + minSdk 26 + targetSdk 36 + 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_11 + targetCompatibility JavaVersion.VERSION_11 + } +} + +dependencies { + + implementation libs.appcompat + implementation libs.material + implementation libs.activity + implementation libs.constraintlayout + testImplementation libs.junit + androidTestImplementation libs.ext.junit + androidTestImplementation libs.espresso.core + implementation("net.armcloud.xscore:xscore:1.0.0") + implementation("org.lsposed.hiddenapibypass:hiddenapibypass:4.3") + implementation 'com.alibaba:fastjson:1.2.68' +} \ No newline at end of file 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/release/ArmCloudAF_lo.apk b/app/release/ArmCloudAF_lo.apk new file mode 100644 index 0000000..137f41c Binary files /dev/null and b/app/release/ArmCloudAF_lo.apk differ diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json new file mode 100644 index 0000000..ae47b4f --- /dev/null +++ b/app/release/output-metadata.json @@ -0,0 +1,37 @@ +{ + "version": 3, + "artifactType": { + "type": "APK", + "kind": "Directory" + }, + "applicationId": "com.bjlx.armcloudaf", + "variantName": "release", + "elements": [ + { + "type": "SINGLE", + "filters": [], + "attributes": [], + "versionCode": 1, + "versionName": "1.0", + "outputFile": "app-release.apk" + } + ], + "elementType": "File", + "baselineProfiles": [ + { + "minApi": 28, + "maxApi": 30, + "baselineProfiles": [ + "baselineProfiles/1/app-release.dm" + ] + }, + { + "minApi": 31, + "maxApi": 2147483647, + "baselineProfiles": [ + "baselineProfiles/0/app-release.dm" + ] + } + ], + "minSdkVersionForDexing": 26 +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/bjlx/armcloudaf/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/bjlx/armcloudaf/ExampleInstrumentedTest.java new file mode 100644 index 0000000..bbc7fb6 --- /dev/null +++ b/app/src/androidTest/java/com/bjlx/armcloudaf/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.bjlx.armcloudaf; + +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 Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("com.bjlx.armcloudaf", appContext.getPackageName()); + } +} \ 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..66cbbf6 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/androidx/app/Encode.java b/app/src/main/java/androidx/app/Encode.java new file mode 100644 index 0000000..0e54566 --- /dev/null +++ b/app/src/main/java/androidx/app/Encode.java @@ -0,0 +1,32 @@ +package androidx.app; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class Encode { + public static String md5(byte[] data) { + try { + // 创建 MessageDigest 实例,指定算法为 MD5 + MessageDigest messageDigest = MessageDigest.getInstance("MD5"); + // 更新 MessageDigest 以传入要加密的数据 + messageDigest.update(data); + // 获取加密后的字节数组 + byte[] digest = messageDigest.digest(); + // 将字节数组转换为十六进制字符串 + StringBuilder hexString = new StringBuilder(); + for (byte b : digest) { + // 将每个字节转换为两位十六进制 + String hex = Integer.toHexString(0xFF & b); + if (hex.length() == 1) { + hexString.append("0"); // 添加前导零 + } + hexString.append(hex); + } + + return hexString.toString(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/app/src/main/java/androidx/app/Entry.java b/app/src/main/java/androidx/app/Entry.java new file mode 100644 index 0000000..2821500 --- /dev/null +++ b/app/src/main/java/androidx/app/Entry.java @@ -0,0 +1,514 @@ +package androidx.app; + + +import android.app.Application; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.Signature; +import android.net.Uri; +import android.os.Process; +import android.text.TextUtils; +import android.util.Base64; +import android.util.Log; + +import androidx.core.content.ContextCompat; + +import com.alibaba.fastjson.JSON; +import com.android.core.XC_MethodHook; +import com.android.core.XSHelpers; +import com.bjlx.armcloudaf.ModifyData; +import com.bjlx.armcloudaf.MyLogger; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.ByteArrayInputStream; +import java.net.URLConnection; +import java.nio.charset.StandardCharsets; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.crypto.Cipher; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; + +import java.io.*; +import java.lang.reflect.*; +import java.net.HttpURLConnection; +import java.net.ProtocolException; +import java.net.URL; +import java.security.Permission; +import java.util.List; + + +public class Entry { + public static final String TAG = "HKAPP(Appsflyer)"; + private static final ExecutorService executor = Executors.newFixedThreadPool(5); + private static final Map tmpMap=new HashMap<>(); + + + /* public static void systemMain(ClassLoader classLoader, String pkg, String processName) { + Log.e(TAG,"systemMain pkg="+pkg); + doJavaHook(pkg, classLoader); + } +*/ + + public static void appMain(ClassLoader loader, Context context, String appClass, String pkg, String process) { + Log.d(TAG, "appClass=" + appClass + ", filesDir=" + context.getFilesDir() + ", pid=" + Process.myPid() + ", process=" + process); + if(process.contains("sandboxed_process")){ + return; + } + + try { + doJavaHook(pkg, loader); + }catch (Exception ex){ + Log.d(TAG, Log.getStackTraceString(ex)); + } + } + + static String currentPackageName; + static String currentAppVersion; + + private static void doJavaHook(String pkg, ClassLoader loader) { + + try { + initData(); + /* final AppsFlyerCollector collector = new AppsFlyerCollector(context); + currentPackageName = context.getPackageName(); + currentAppVersion = context.getPackageManager().getPackageInfo(currentPackageName, 0).versionName; + Log.d(TAG,"currentPackageName="+currentPackageName+" currentAppVersion="+currentAppVersion); +*/ + /* XSHelpers.findAndHookConstructor(JSONObject.class, Map.class, new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + super.beforeHookedMethod(param); + Map indata=(Map) param.args[0]; + if (indata.containsKey("xdp")){ + Log.i(TAG,"JSONObject map 找到xdp 修改前: xdp="+indata.get("xdp")); + indata.put("xdp",900); + } + } + + @Override + protected void afterHookedMethod(MethodHookParam param) throws Throwable { + Log.i(TAG, "#------- JSONObject Found -------#"); + Log.i(TAG, "修改前:"+((JSONObject)param.thisObject).toString()); + JSONObject jsonObject=(JSONObject)param.thisObject; + if (jsonObject.has("xdp")){ + Log.i(TAG,"hook 找到xdp"); + jsonObject.put("xdp","800"); + } + if (jsonObject.has("ydp")){ + Log.i(TAG,"hook 找到ydp"); + } + if (jsonObject.has("x_px")){ + Log.i(TAG,"hook 找到x_px"); + } + if (jsonObject.has("y_px")){ + Log.i(TAG,"hook 找到y_px"); + } + if (jsonObject.has("d_dpi")){ + Log.i(TAG,"hook 找到d_dpi"); + } + if (jsonObject.has("size")){ + Log.i(TAG,"hook 找到size"); + } + if (jsonObject.has("arch")){ + Log.i(TAG,"hook 找到arch"); + } + if (jsonObject.has("cpu_abi")){ + Log.i(TAG,"hook 找到cpu_abi"); + } + if (jsonObject.has("cpu_abi2")){ + Log.i(TAG,"hook 找到cpu_abi2"); + } + if (jsonObject.has("btl")){ + Log.i(TAG,"hook 找到btl"); + } + if (jsonObject.has("btch")){ + Log.i(TAG,"hook 找到btch"); + } + if (jsonObject.has("disk")){ + Log.i(TAG,"hook 找到disk"); + } + if (jsonObject.has("sdk")){ + Log.i(TAG,"hook 找到sdk"); + } + if (jsonObject.has("network")){ + Log.i(TAG,"hook 找到network"); + } + if (jsonObject.has("api_ver")){ + Log.i(TAG,"hook 找到api_ver"); + } + if (jsonObject.has("api_ver_name")){ + Log.i(TAG,"hook 找到api_ver_name"); + } + if (jsonObject.has("carrier")){ + Log.i(TAG,"hook 找到carrier"); + } + if (jsonObject.has("product")){ + Log.i(TAG,"hook 找到product"); + } + if (jsonObject.has("last_boot_time")){ + Log.i(TAG,"hook 找到last_boot_time"); + } + if (jsonObject.has("initiating_package")){ + Log.i(TAG,"hook 找到initiating_package"); + } + if (jsonObject.has("advertiserId")){ + Log.i(TAG,"hook 找到advertiserId"); + } + logLong(TAG, "修改后:"+((JSONObject)param.thisObject).toString()); + //collector.checkAndParse(param); + } + });*/ + + XSHelpers.findAndHookMethod(URL.class, "openConnection", new XC_MethodHook() { + @Override + protected void afterHookedMethod(MethodHookParam param) throws Throwable { + java.net.URL url = (java.net.URL) param.thisObject; + URLConnection origConn = (URLConnection) param.getResult(); + String urlStr = url.toString(); + Log.d(TAG, "afterHookedMethod: "+urlStr); + if (urlStr.contains("appsflyer") ) { + //Log.i(TAG, "#------- Found AF -------#"); + MyLogger.i(TAG, "openConnection urlStr="+urlStr); + param.setResult(new XHttpURLConnection(url, (HttpURLConnection) origConn, new HttpRequestListener() { + @Override + public void onAfterRequest(XHttpURLConnection conn, byte[] b, int off, int len) { + try { + byte[] bytes = new byte[len-8-16]; + System.arraycopy(b, off, bytes, 0, len-8-16); + String md5 = Encode.md5(bytes); + String afdata = tmpMap.get(md5); + Uri uri=Uri.parse(urlStr); + String app_id = uri.getQueryParameter("app_id"); + String buildnumber=uri.getQueryParameter("buildnumber"); +// if (urlStr.contains("conversions") || urlStr.contains("gcd") || urlStr.contains("inapps")){ +// +// } + logLong(TAG,"发送数据:afdata="+afdata); + MyLogger.e(TAG,"发送数据:afdata="+afdata); + MyLogger.e(TAG,"发送数据:app_id="+app_id); + } + catch (Throwable e) { + Log.e(TAG,"发送数据异常:"+e.getMessage()); + e.printStackTrace(); + } + } + })); + } + } + }); + + XSHelpers.findAndHookMethod(Cipher.class, "doFinal", byte[].class, new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + super.beforeHookedMethod(param); + + byte[] bytes = (byte[]) param.args[0]; + String dataStr=new String(bytes); + if (!TextUtils.isEmpty(dataStr)){ + com.alibaba.fastjson.JSONObject jsonObj=convertToJson(dataStr); + if (jsonObj!=null&&jsonObj.containsKey("appsflyerKey")&&jsonObj.containsKey("af_timestamp")){ + logLong(TAG,"doFinal 修改前="+jsonObj.toJSONString()); + jsonObj=ModifyData.modifyData(jsonObj); + logLong(TAG,"doFinal 修改后="+jsonObj.toJSONString()); + param.args[0]=jsonObj.toJSONString().getBytes(); + } + + } + + } + + @Override + protected void afterHookedMethod(MethodHookParam param) throws Throwable { + + byte[] bytes = (byte[]) param.args[0]; + String dataStr=new String(bytes); + //Log.e(AFHookTools,"Cipher doFinal data="+dataStr); + if (!TextUtils.isEmpty(dataStr)){ + com.alibaba.fastjson.JSONObject jsonObj=convertToJson(dataStr); + if (jsonObj!=null&&jsonObj.containsKey("appsflyerKey")&&jsonObj.containsKey("af_timestamp")){ + byte[] result = (byte[])param.getResult(); + String md5 = Encode.md5(result); + //Log.e(AFHookTools,"md5="+md5+" encodeData="+dataStr); + tmpMap.put(md5,dataStr); + } + } + } + }); + + + + + } catch (Throwable e) { + e.printStackTrace(); + } + + } + + private static void broadcast(Context context, JSONObject collectedData) + { + new Thread(() -> { + try { + Intent intent = new Intent("com.titan.HOOK_BROADCAST"); + intent.setClassName("com.titan.taskpolling", "com.titan.taskpolling.HookBroadcastReceiver"); + + intent.putExtra("extractType", "AF"); + intent.putExtra("packageName", context.getPackageName()); + intent.putExtra("collectedData", collectedData.toString()); + context.sendBroadcast(intent); + } catch (Throwable e) { + e.printStackTrace(); + } + }).start(); + } + + public static byte[] decrypt(byte[] data, String key) throws Exception { + final byte[] salt = new byte[]{0, 0, 0, 0, 0, 0, 0, 0}; + if (data != null && data.length > 24) { + byte[] iv = new byte[16]; + System.arraycopy(data, data.length - 8 - 16, iv, 0, 16); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + SecretKeySpec seckey = new SecretKeySpec(SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1").generateSecret(new PBEKeySpec(key.toCharArray(), salt, 10000, 0x80)).getEncoded(), "AES"); + cipher.init(Cipher.DECRYPT_MODE, seckey, new IvParameterSpec(iv)); + return cipher.doFinal(data, 0, data.length - 8 - 16); + } + return null; + } + + public static class HttpRequestListener extends Object { + public XHttpURLConnection Owner; + public void onBeforeRequest(XHttpURLConnection conn, byte[] b, int off, int len) { } + public void onAfterRequest(XHttpURLConnection conn, byte[] b, int off, int len) { } + } + public final static class XOutputStream extends OutputStream { + private HttpRequestListener _listener; + public OutputStream Out; + + protected XOutputStream(HttpRequestListener listener){ super(); _listener = listener; } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + if (_listener != null) { _listener.onBeforeRequest(_listener.Owner, b, off, len); } + Out.write(b, off, len); + if (_listener != null) { _listener.onAfterRequest(_listener.Owner, b, off, len); } + } + + @Override + public void write(int b) throws IOException { Out.write(b); } + @Override + public void write(byte[] b) throws IOException { Out.write(b); } + @Override + public void flush() throws IOException { Out.flush(); } + @Override + public void close() throws IOException { Out.close(); } + @Override + public int hashCode() { return Out.hashCode(); } + @Override + public boolean equals(Object obj) {return Out.equals(obj); } + @Override + public String toString() { return Out.toString(); } + } + public final static class XHttpURLConnection extends HttpURLConnection { + private final URL _orgUrl; + private final HttpURLConnection _orgConn; + private final HttpRequestListener _listener; + private XOutputStream _outStream; + + protected XHttpURLConnection(URL url, HttpURLConnection conn, HttpRequestListener listener) { + super(url); + _orgUrl = url; + _orgConn = conn; + _listener = listener; + _listener.Owner = this; + _outStream = new XOutputStream(_listener); + } + + @Override + public OutputStream getOutputStream() throws IOException { + _outStream.Out = _orgConn.getOutputStream(); + return _outStream; + } + @Override + public void connect() throws IOException { + if (_listener != null){ _listener.onBeforeRequest(this, new byte[0], 0, 0); } + _orgConn.connect(); + if (_listener != null){ _listener.onAfterRequest(this, new byte[0], 0, 0); } + } + @Override + public String getHeaderFieldKey(int n) { return _orgConn.getHeaderFieldKey(n); } + @Override + public void setFixedLengthStreamingMode(int contentLength) { _orgConn.setFixedLengthStreamingMode(contentLength); } + @Override + public void setFixedLengthStreamingMode(long contentLength) { _orgConn.setFixedLengthStreamingMode(contentLength); } + @Override + public void setChunkedStreamingMode(int chunklen) { _orgConn.setChunkedStreamingMode(chunklen); } + @Override + public String getHeaderField(int n) { return _orgConn.getHeaderField(n); } + @Override + public void setInstanceFollowRedirects(boolean followRedirects) { _orgConn.setInstanceFollowRedirects(followRedirects); } + @Override + public boolean getInstanceFollowRedirects() { return _orgConn.getInstanceFollowRedirects(); } + @Override + public void setRequestMethod(String method) throws ProtocolException { _orgConn.setRequestMethod(method); } + @Override + public String getRequestMethod() { return _orgConn.getRequestMethod(); } + @Override + public int getResponseCode() throws IOException { return _orgConn.getResponseCode(); } + @Override + public String getResponseMessage() throws IOException { return _orgConn.getResponseMessage(); } + @Override + public long getHeaderFieldDate(String name, long Default) { return _orgConn.getHeaderFieldDate(name, Default); } + @Override + public Permission getPermission() throws IOException { return _orgConn.getPermission(); } + @Override + public InputStream getErrorStream() { return _orgConn.getErrorStream(); } + @Override + public void setConnectTimeout(int timeout) { _orgConn.setConnectTimeout(timeout); } + @Override + public int getConnectTimeout() { return _orgConn.getConnectTimeout(); } + @Override + public void setReadTimeout(int timeout) { _orgConn.setReadTimeout(timeout); } + @Override + public int getReadTimeout() { return _orgConn.getReadTimeout(); } + @Override + public URL getURL() { return _orgConn.getURL(); } + @Override + public int getContentLength() { return _orgConn.getContentLength(); } + @Override + public long getContentLengthLong() { return _orgConn.getContentLengthLong(); } + @Override + public String getContentType() { return _orgConn.getContentType(); } + @Override + public String getContentEncoding() { return _orgConn.getContentEncoding(); } + @Override + public long getExpiration() { return _orgConn.getExpiration(); } + @Override + public long getDate() { return _orgConn.getDate(); } + @Override + public long getLastModified() { return _orgConn.getLastModified(); } + @Override + public String getHeaderField(String name) { return _orgConn.getHeaderField(name); } + @Override + public Map> getHeaderFields() { return _orgConn.getHeaderFields(); } + @Override + public int getHeaderFieldInt(String name, int Default) { return _orgConn.getHeaderFieldInt(name, Default); } + @Override + public long getHeaderFieldLong(String name, long Default) { return _orgConn.getHeaderFieldLong(name, Default); } + @Override + public Object getContent() throws IOException { return _orgConn.getContent(); } + @Override + public Object getContent(Class[] classes) throws IOException { return _orgConn.getContent(classes); } + @Override + public InputStream getInputStream() throws IOException { return _orgConn.getInputStream(); } + @Override + public String toString() { return _orgConn.toString(); } + @Override + public void setDoInput(boolean doinput) { _orgConn.setDoInput(doinput); } + @Override + public boolean getDoInput() { return _orgConn.getDoInput(); } + @Override + public void setDoOutput(boolean dooutput) { _orgConn.setDoOutput(dooutput); } + @Override + public boolean getDoOutput() { return _orgConn.getDoOutput(); } + @Override + public void setAllowUserInteraction(boolean allowuserinteraction) { _orgConn.setAllowUserInteraction(allowuserinteraction); } + @Override + public boolean getAllowUserInteraction() { return _orgConn.getAllowUserInteraction(); } + @Override + public void setUseCaches(boolean usecaches) { _orgConn.setUseCaches(usecaches); } + @Override + public boolean getUseCaches() { return _orgConn.getUseCaches(); } + @Override + public void setIfModifiedSince(long ifmodifiedsince) { _orgConn.setIfModifiedSince(ifmodifiedsince); } + @Override + public long getIfModifiedSince() { return _orgConn.getIfModifiedSince(); } + @Override + public boolean getDefaultUseCaches() { return _orgConn.getDefaultUseCaches(); } + @Override + public void setDefaultUseCaches(boolean defaultusecaches) { _orgConn.setDefaultUseCaches(defaultusecaches); } + @Override + public void setRequestProperty(String key, String value) { _orgConn.setRequestProperty(key, value); } + @Override + public void addRequestProperty(String key, String value) { _orgConn.addRequestProperty(key, value); } + @Override + public String getRequestProperty(String key) { return _orgConn.getRequestProperty(key); } + @Override + public Map> getRequestProperties() { return _orgConn.getRequestProperties(); } + @Override + public void disconnect() { _orgConn.disconnect(); } + @Override + public int hashCode() { return _orgConn.hashCode(); } + @Override + public boolean equals(Object obj) { return _orgConn.equals(obj); } + @Override + public boolean usingProxy() { return _orgConn.usingProxy(); } + } + public static com.alibaba.fastjson.JSONObject convertToJson(String str) + { + try { + + return JSON.parseObject(str); + } catch (Exception e) { + } + return null; + } + public static void initData(){ + /*XSHelpers.findAndHookMethod(Application.class, "attach", + Context.class, new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + super.beforeHookedMethod(param); + Log.e(TAG,"Application attach call============"); + SocketClient socketClient=new SocketClient(); + socketClient.connect(new SocketClient.MessageListener() { + @Override + public void onMessage(String message) { + Log.e(TAG,"socket 收到消息:"+message); + } + }); + } + });*/ + XSHelpers.findAndHookMethod(Application.class, "onCreate", new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + super.beforeHookedMethod(param); + Application application=(Application) param.thisObject; + String dataDir=application.getDataDir().getAbsolutePath(); + MyLogger.init(application.getDataDir()); + Log.e(TAG,"Application onCreate call dataDir="+dataDir); + ModifyData.initNewData(application.getDataDir()); + } + }); + } + public static void logLong(String tag, String message) { + int maxLogSize = 4000; + if (message == null) return; + + int length = message.length(); + int chunkCount = (int) Math.ceil((double) length / maxLogSize); + + for (int i = 0; i < chunkCount; i++) { + int start = i * maxLogSize; + int end = Math.min(start + maxLogSize, length); + Log.e(tag, "参数:"+message.substring(start, end) + " _" + i); + } + } +} + diff --git a/app/src/main/java/androidx/app/HttpClient.java b/app/src/main/java/androidx/app/HttpClient.java new file mode 100644 index 0000000..d1a2b57 --- /dev/null +++ b/app/src/main/java/androidx/app/HttpClient.java @@ -0,0 +1,122 @@ +package androidx.app; + +import android.util.Log; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; + +import org.json.JSONException; +import org.json.JSONObject; + +/* loaded from: classes5.dex */ +public class HttpClient { + private static final String TAG = "HttpClient"; + + public interface IDownloadListener { + void onError(Exception exc); + + void onFinished(String str); + + void onProgress(float f); + } + + public static void requestPost(String urlString, JSONObject params) throws IOException { + URL url = new URL(urlString); + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setRequestMethod("POST"); + con.setRequestProperty("Content-Type", "application/json"); + con.setDoOutput(true); + con.setDoInput(true); + con.setConnectTimeout(30000); + con.setReadTimeout(30000); + OutputStream os = con.getOutputStream(); + try { + byte[] input = params.toString().getBytes("utf-8"); + os.write(input, 0, input.length); + if (os != null) { + os.close(); + } + BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream(), "utf-8")); + try { + StringBuilder response = new StringBuilder(); + while (true) { + String responseLine = br.readLine(); + if (responseLine != null) { + response.append(responseLine.trim()); + } else { + br.close(); + Log.i(TAG, "send finish"); + return; + } + } + } catch (Throwable th) { + try { + br.close(); + } catch (Throwable th2) { + th.addSuppressed(th2); + } + throw th; + } + } catch (Throwable th3) { + if (os != null) { + try { + os.close(); + } catch (Throwable th4) { + th3.addSuppressed(th4); + } + } + throw th3; + } + } + + public static JSONObject requestGet(String urlString) throws IOException, JSONException { + URL url = new URL(urlString); + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setRequestMethod("GET"); + con.setRequestProperty("Content-Type", "application/json"); + con.setDoOutput(true); + con.setDoInput(true); + con.setConnectTimeout(30000); + con.setReadTimeout(30000); + BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream(), "utf-8")); + try { + StringBuilder response = new StringBuilder(); + while (true) { + String responseLine = br.readLine(); + if (responseLine != null) { + response.append(responseLine.trim()); + } else { + JSONObject jSONObject = new JSONObject(response.toString()); + br.close(); + return jSONObject; + } + } + } catch (Throwable th) { + try { + br.close(); + } catch (Throwable th2) { + th.addSuppressed(th2); + } + throw th; + } + } + + public static JSONObject convertToJson(String str) { + try { + return new JSONObject(str); + } catch (JSONException e) { + return null; + } + } + + + + +} diff --git a/app/src/main/java/androidx/app/SocketClient.java b/app/src/main/java/androidx/app/SocketClient.java new file mode 100644 index 0000000..84854fc --- /dev/null +++ b/app/src/main/java/androidx/app/SocketClient.java @@ -0,0 +1,112 @@ +package androidx.app; + +import android.util.Log; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.net.Socket; + +public class SocketClient { + + private String serverIp = "127.0.0.1"; + private int port = 8888; + + private Socket socket; + private PrintWriter writer; + private boolean connected = false; + private MessageListener messageListener; + + public interface MessageListener { + void onMessage(String message); + } + + public SocketClient() { + } + + public SocketClient(String serverIp, int port) { + this.serverIp = serverIp; + this.port = port; + } + + public void connect(MessageListener listener) { + this.messageListener = listener; + + new Thread(() -> { + try { + socket = new Socket(serverIp, port); + writer = new PrintWriter(socket.getOutputStream(), true); + connected = true; + Log.d("SocketClient", "已连接到服务器"); + if (messageListener != null) { + messageListener.onMessage("已连接到服务器"); + } + startReceiving(); + } catch (Exception e) { + Log.e("SocketClient", "连接失败", e); + if (messageListener != null) { + messageListener.onMessage("连接失败: " + e.getMessage()); + } + } + }).start(); + } + + private void startReceiving() { + new Thread(() -> { + try { + BufferedReader reader = new BufferedReader( + new InputStreamReader(socket.getInputStream())); + + while (connected) { + String message = reader.readLine(); + if (message == null) break; + if (messageListener != null) { + messageListener.onMessage(message); + } + } + } catch (Exception e) { + Log.e("SocketClient", "接收消息失败", e); + if (messageListener != null) { + messageListener.onMessage("接收失败: " + e.getMessage()); + } + } finally { + disconnect(); + } + }).start(); + } + + public void sendMessage(String message) { + if (!connected) return; + + new Thread(() -> { + try { + if (writer != null) { + writer.println(message); + Log.d("SocketClient", "已发送消息: " + message); + } + } catch (Exception e) { + Log.e("SocketClient", "发送消息失败", e); + if (messageListener != null) { + messageListener.onMessage("发送失败: " + e.getMessage()); + } + } + }).start(); + } + + public void disconnect() { + connected = false; + try { + if (writer != null) writer.close(); + if (socket != null) socket.close(); + } catch (Exception e) { + Log.e("SocketClient", "断开连接错误", e); + } + if (messageListener != null) { + messageListener.onMessage("已断开连接"); + } + } + + public boolean isConnected() { + return connected; + } +} diff --git a/app/src/main/java/com/bjlx/armcloudaf/AESPatchHelper.java b/app/src/main/java/com/bjlx/armcloudaf/AESPatchHelper.java new file mode 100644 index 0000000..eb30207 --- /dev/null +++ b/app/src/main/java/com/bjlx/armcloudaf/AESPatchHelper.java @@ -0,0 +1,80 @@ +package com.bjlx.armcloudaf; + +import java.io.ByteArrayOutputStream; +import java.util.Arrays; + +import javax.crypto.Cipher; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; + +public class AESPatchHelper { + private static final byte[] SALT = new byte[8]; // 全零 salt + private static final int PADDING_LEN = 8; + private static final int IV_LEN = 16; + + public static byte[] extractIV(byte[] data) { + int ivPos = data.length - PADDING_LEN - IV_LEN; + return Arrays.copyOfRange(data, ivPos, ivPos + IV_LEN); + } + + public static byte[] extractPadding(byte[] data) { + int padPos = data.length - PADDING_LEN - IV_LEN - PADDING_LEN; + if (padPos < 0) padPos = data.length - PADDING_LEN - IV_LEN; + return Arrays.copyOfRange(data, data.length - PADDING_LEN - IV_LEN, data.length - IV_LEN); + } + + public static byte[] extractCiphertext(byte[] data) { + int ctLen = data.length - PADDING_LEN - IV_LEN; + return Arrays.copyOfRange(data, 0, ctLen); + } + + private static SecretKeySpec deriveKey(String password) throws Exception { + return new SecretKeySpec( + SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1") + .generateSecret(new PBEKeySpec(password.toCharArray(), SALT, 10000, 128)) + .getEncoded(), + "AES" + ); + } + + public static byte[] decryptRaw(byte[] ciphertext, byte[] iv, String password) throws Exception { + SecretKeySpec key = deriveKey(password); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv)); + return cipher.doFinal(ciphertext); + } + + public static byte[] encryptRaw(byte[] plaintext, byte[] iv, String password) throws Exception { + SecretKeySpec key = deriveKey(password); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv)); + return cipher.doFinal(plaintext); + } + + /** + * 入口函数: + * 提取 data 中 IV + padding + 密文 → 解密 → 加密 → 再拼装成结构化数据 + */ + public static byte[] patchAndReEncrypt(byte[] data, String password) throws Exception { + if (data == null || data.length <= (PADDING_LEN + IV_LEN)) return null; + + byte[] iv = extractIV(data); + byte[] padding = extractPadding(data); + byte[] ciphertext = extractCiphertext(data); + // 解密 + byte[] plaintext = decryptRaw(ciphertext, iv, password); + // 可在此修改 plaintext(如篡改某些字段) + // 再加密 + byte[] newCiphertext = encryptRaw(plaintext, iv, password); + + // 拼装为结构:[newCiphertext] + [padding] + [iv] + ByteArrayOutputStream out = new ByteArrayOutputStream(); + out.write(newCiphertext); + out.write(padding); + out.write(iv); + return out.toByteArray(); + } +} + diff --git a/app/src/main/java/com/bjlx/armcloudaf/App.java b/app/src/main/java/com/bjlx/armcloudaf/App.java new file mode 100644 index 0000000..5facd5e --- /dev/null +++ b/app/src/main/java/com/bjlx/armcloudaf/App.java @@ -0,0 +1,24 @@ +package com.bjlx.armcloudaf; + +import android.app.Application; + +public class App extends Application { + @Override + public void onCreate() { + super.onCreate(); + test2(); + } + + private void test2(){ + ClipboardUtils.pasteText(this); + /* ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); + + if (clipboard.hasPrimaryClip()) { + ClipData clipData = clipboard.getPrimaryClip(); + if (clipData != null && clipData.getItemCount() > 0) { + CharSequence text = clipData.getItemAt(0).coerceToText(this); + Log.d("Clipboard", "剪贴板内容是:" + text); + } + }*/ + } +} diff --git a/app/src/main/java/com/bjlx/armcloudaf/ClipboardUtils.java b/app/src/main/java/com/bjlx/armcloudaf/ClipboardUtils.java new file mode 100644 index 0000000..ee8dd57 --- /dev/null +++ b/app/src/main/java/com/bjlx/armcloudaf/ClipboardUtils.java @@ -0,0 +1,66 @@ +package com.bjlx.armcloudaf; + +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.text.TextUtils; +import android.util.Log; + +public class ClipboardUtils { + private static final String TAG = "ClipboardUtils"; + + public static String pasteText(Context context) { + try { + // 获取剪贴板服务 + ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); + + if (clipboard == null) { + Log.w(TAG, "Clipboard service not available"); + return null; + } + + // 检查剪贴板是否有内容 + if (!clipboard.hasPrimaryClip()) { + Log.d(TAG, "Clipboard is empty"); + return null; + } + + // 获取剪贴板内容 + ClipData clipData = clipboard.getPrimaryClip(); + if (clipData == null) { + Log.w(TAG, "ClipData is null"); + return null; + } + + // 检查项目数量 + if (clipData.getItemCount() == 0) { + Log.d(TAG, "ClipData has no items"); + return null; + } + + // 获取第一项内容 + ClipData.Item item = clipData.getItemAt(0); + + // 尝试获取文本内容 + CharSequence text = item.getText(); + if (text != null) { + String result = text.toString(); + if (!TextUtils.isEmpty(result)) { + Log.d(TAG, "Successfully pasted text: " + result.substring(0, Math.min(20, result.length())) + "..."); + return result; + } + } + + Log.d(TAG, "Clipboard item has no text content"); + return null; + + } catch (SecurityException e) { + Log.e(TAG, "Security exception when accessing clipboard", e); + return null; + } catch (Exception e) { + Log.e(TAG, "Error pasting text from clipboard", e); + return null; + } + } +} + diff --git a/app/src/main/java/com/bjlx/armcloudaf/MainActivity.java b/app/src/main/java/com/bjlx/armcloudaf/MainActivity.java new file mode 100644 index 0000000..de1bef7 --- /dev/null +++ b/app/src/main/java/com/bjlx/armcloudaf/MainActivity.java @@ -0,0 +1,81 @@ +package com.bjlx.armcloudaf; + +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.util.Log; +import android.view.View; + +import androidx.activity.EdgeToEdge; +import androidx.app.Entry; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.graphics.Insets; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; + +import java.util.Arrays; + +public class MainActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + EdgeToEdge.enable(this); + setContentView(R.layout.activity_main); + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { + Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); + v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); + return insets; + }); + Entry.appMain(getClassLoader(), this, "", getPackageName(), ""); + test2(); + findViewById(R.id.btn_test).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + test2(); + } + }); + } + private void test2(){ + ClipboardUtils.pasteText(this); + /* ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); + + if (clipboard.hasPrimaryClip()) { + ClipData clipData = clipboard.getPrimaryClip(); + if (clipData != null && clipData.getItemCount() > 0) { + CharSequence text = clipData.getItemAt(0).coerceToText(this); + Log.d("Clipboard", "剪贴板内容是:" + text); + } + }*/ + } + private void test(){ + Uri uri = Uri.parse("content://com.android.grape.deviceinfo.provider/device_info"); + Uri path=Uri.withAppendedPath( + uri, + "device/d1b3e7f8c9a04b8e9f2c1d5e6f7a8b9c" + ); + //new String[]{"data"} + Cursor cursor = getContentResolver().query(path, null, null, null, null); + if (cursor != null) { + while (cursor.moveToNext()) { + // 获取字段名(列名)数组 + String[] columnNames = cursor.getColumnNames(); + + // 遍历每一列,按名字读取 + for (String columnName : columnNames) { + int columnIndex = cursor.getColumnIndex(columnName); + String columnVale=cursor.getString(columnIndex); + Log.d("Cursor", columnName + ": " + columnVale); + } + + Log.d("Cursor", "——— 一行结束 ———"); + } + cursor.close(); + }else { + Log.e("Cursor","cursor ======== is null"); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bjlx/armcloudaf/ModifyData.java b/app/src/main/java/com/bjlx/armcloudaf/ModifyData.java new file mode 100644 index 0000000..66112c7 --- /dev/null +++ b/app/src/main/java/com/bjlx/armcloudaf/ModifyData.java @@ -0,0 +1,143 @@ +package com.bjlx.armcloudaf; + +import android.os.Build; +import android.util.Base64; +import android.util.Log; + +import androidx.annotation.RequiresApi; +import androidx.app.Entry; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; + +public class ModifyData { + private static JSONObject newData; + private final static String fileName="/device.txt"; + public static JSONObject modifyData(JSONObject jsonObj){ + if (newData == null) return jsonObj; + if (jsonObj.containsKey("deviceData")){ + com.alibaba.fastjson.JSONObject deviceData =jsonObj.getJSONObject("deviceData"); + if (deviceData.containsKey("sensors")&&newData.containsKey("sensors")){ + deviceData.put("sensors",newData.getJSONArray("sensors")); + } + if (deviceData.containsKey("arch")&&newData.containsKey("arch")){ + deviceData.put("arch",newData.getString("arch")); + } + if (deviceData.containsKey("cpu_abi")&&newData.containsKey("cpu_abi")){ + deviceData.put("cpu_abi",newData.getString("cpu_abi")); + } + if (deviceData.containsKey("cpu_abi2")&&newData.containsKey("cpu_abi2")){ + deviceData.put("cpu_abi2",newData.getString("cpu_abi2")); + } + if (deviceData.containsKey("cpu_abi2")&&newData.containsKey("cpu_abi2")){ + deviceData.put("cpu_abi2",newData.getString("cpu_abi2")); + } + if (deviceData!=null&&deviceData.containsKey("dim")){ + com.alibaba.fastjson.JSONObject dim =deviceData.getJSONObject("dim"); + if (dim!=null){ + if (dim.containsKey("xdp")&&newData.containsKey("xdp")){ + dim.put("xdp",newData.getString("xdp")); + } + if (dim.containsKey("ydp")&&newData.containsKey("ydp")){ + dim.put("ydp",newData.getString("ydp")); + } + if (dim.containsKey("x_px")&&newData.containsKey("x_px")){ + dim.put("x_px",newData.getString("x_px")); + } + if (dim.containsKey("y_px")&&newData.containsKey("y_px")){ + dim.put("y_px",newData.getString("y_px")); + } + if (dim.containsKey("d_dpi")&&newData.containsKey("d_dpi")){ + dim.put("d_dpi",newData.getString("d_dpi")); + } + if (dim.containsKey("size")&&newData.containsKey("size")){ + dim.put("size",newData.getString("size")); + } + } + deviceData.put("dim",dim); + } + if (deviceData.containsKey("btch")&&newData.containsKey("btch")){ + deviceData.put("btch",newData.getString("btch")); + } + if (deviceData.containsKey("btl")&&newData.containsKey("btl")){ + deviceData.put("btl",newData.getString("btl")); + } + jsonObj.put("deviceData",deviceData); + } + if (jsonObj.containsKey("disk")&&newData.containsKey("disk")){ + jsonObj.put("disk",newData.getString("disk")); + } + if (jsonObj.containsKey("sdk")&&newData.containsKey("sdk")){ + jsonObj.put("sdk",newData.getString("sdk")); + } + if (jsonObj.containsKey("network")&&newData.containsKey("network")){ + jsonObj.put("network",newData.getString("network")); + } + if (jsonObj.containsKey("referrers")){ + JSONArray referrers =jsonObj.getJSONArray("referrers"); + JSONArray newRefs=new JSONArray(); + if (referrers!=null){ + for (int i = 0; i < referrers.size(); i++) { + JSONObject objitem = referrers.getJSONObject(i); + if (objitem.containsKey("api_ver")&&newData.containsKey("api_ver")){ + objitem.put("api_ver",newData.getIntValue("api_ver")); + } + if (objitem.containsKey("api_ver_name")&&newData.containsKey("api_ver_name")){ + objitem.put("api_ver_name",newData.getString("api_ver_name")); + } + newRefs.add(i,objitem); + } + jsonObj.put("referrers",newRefs); + } + } + + if (jsonObj.containsKey("carrier")&&newData.containsKey("carrier")){ + jsonObj.put("carrier",newData.getString("carrier")); + } + if (jsonObj.containsKey("product")&&newData.containsKey("product")){ + jsonObj.put("product",newData.getString("product")); + } + if (jsonObj.containsKey("last_boot_time")&&newData.containsKey("last_boot_time")){ + jsonObj.put("last_boot_time",newData.getLong("last_boot_time")); + } + if (jsonObj.containsKey("advertiserId")&&newData.containsKey("advertiserId")){ + jsonObj.put("advertiserId",newData.getString("advertiserId")); + } + if (jsonObj.containsKey("install_source_info")&&newData.containsKey("install_source_info")){ + JSONObject install_source_info=jsonObj.getJSONObject("install_source_info"); + if (install_source_info!=null){ + install_source_info.put("initiating_package",newData.getString("install_source_info")); + install_source_info.put("installing_package",newData.getString("install_source_info")); + } + + jsonObj.put("install_source_info",install_source_info); + } + return jsonObj; + } + public static void initNewData(File fileDir){ + + try { + byte[] content = readFile(new File(fileDir,fileName)); + byte[] msgByte=Base64.decode(content,Base64.DEFAULT); + Log.e(Entry.TAG,"initNewData msgByte="+new String(msgByte)); + newData=JSONObject.parseObject(new String(msgByte)); + Log.e(Entry.TAG,"数据初始化完成=========="); + } catch (Exception e) { + Log.d(Entry.TAG, "initNewData: "+e.getMessage()); + } + } + public static byte[] readFile(File file) { + try { + return Files.readAllBytes(file.toPath()); + } catch (IOException e) { + Log.e(Entry.TAG,"读文件异常:"+e.toString()); + e.printStackTrace(); + return null; + } + } +} diff --git a/app/src/main/java/com/bjlx/armcloudaf/MyLogger.java b/app/src/main/java/com/bjlx/armcloudaf/MyLogger.java new file mode 100644 index 0000000..b7bfebb --- /dev/null +++ b/app/src/main/java/com/bjlx/armcloudaf/MyLogger.java @@ -0,0 +1,51 @@ +package com.bjlx.armcloudaf; +import android.util.Log; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; + +public class MyLogger { + private static final String LOG_TAG = "MyLogger"; + private static final String LOG_FILE_NAME = "/log.txt"; + private static File logFile; + + public static void init(File filesDir) { + logFile = new File(filesDir, LOG_FILE_NAME); + } + + private static void writeToFile(String level, String tag, String msg) { + if (logFile == null){ + Log.e(LOG_TAG, "日志文件未初始化"); + return; + } + try (FileWriter writer = new FileWriter(logFile, true)) { + String logLine = String.format("%s/%s: %s\n", level, tag, msg); + writer.write(logLine); + writer.flush(); + Log.d(LOG_TAG, "写入日志成功"); + } catch (IOException e) { + Log.e(LOG_TAG, "写入日志失败: " + e.getMessage()); + } + } + + public static void d(String tag, String msg) { + Log.d(tag, msg); + writeToFile("D", tag, msg); + } + + public static void i(String tag, String msg) { + Log.i(tag, msg); + writeToFile("I", tag, msg); + } + + public static void w(String tag, String msg) { + Log.w(tag, msg); + writeToFile("W", tag, msg); + } + + public static void e(String tag, String msg) { + Log.e(tag, msg); + writeToFile("E", tag, msg); + } +} 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/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file 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..0eaa5f5 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,24 @@ + + + + +