当前位置: 首页 > news >正文

Android app反编译 攻与防

     大概是2020年的时候,有一次,我们的竞争同行有另外一家公司要用我们的安卓软件app,拉了个群,告知他用一个软件多少钱,然后在群里发了一个我打包的apk包。结果就没有下文了。又过了一个月。我同事在那个要买我们apk的人的朋友圈,发现他拍的宣传照片和我们的app界面非常相似。来问我了。我那时候觉得我apk加固的还可以。代码也都做了混淆。应该不会被破解了。排除了这可能,后来公司领导还专门因为这个事开了会。讨论了下,都怀疑是有内鬼,把apk的解密卖了出去。然后这个事就不了了之了。

   直到2024年,他们的设备过保了。我们公司又中了他们之前安装的设备的维保项目,然后,我忽然想起来之前发生过的这个事情来。就和那边同时远程解决售后问题的时候。才发现我打包的apk被人套了个壳从新打包了。名字和图标都被改了。但是进去以后的界面和功能都没有变。很明显我的android apk包被人破解了。重新打了包。并且商用了。整个西藏所有的网点都用了这个apk。当时整个人瞬间感觉都不好了。那天心情一直很低落。证明我的加固根本就没有起大的作用。我发现这个事的时候就给老板发了消息。老板没有回我信息。我就胡思乱想是不是老板会质疑我的能力。心里也挺痛恨那个破解我apk并且商用的公司和人。这就像我的孩子换了个衣服让人给拐走了。是我做好安全防护。等上班以后。我就开始研究他是怎么给我破解的。自己把自己做的老版本给破解了。非常简单10分钟都不到就从新打包运行了。

先简单介绍下我的app。第一个界面一个激活界面。激活界面需要我们授权,授权通过激活界面以后。会直接跳入设置界面就行相应的配置。激活后的设备。下次进入软件会直接进到设置界面。他反编译从新打包的我的软件版本是1.9版本。android1.9版本的app下的build.gradle配置如下:

apply plugin: 'com.android.application'
android {compileSdkVersion 26defaultConfig {applicationId ""minSdkVersion 17targetSdkVersion 26versionCode 1versionName "1.7"testInstrumentationRunner "androidx.support.test.runner.AndroidJUnitRunner"//加载红外的cpp文件externalNativeBuild {cmake {cppFlags ""}}//abiFilter 'armeabi'ndk {abiFilters 'armeabi'}// Enabling multidex support.multiDexEnabled true}//签名配置。 配置名release{ //配置内容 }signingConfigs {release {try {storeFile file("plbs.jks")storePassword KEYSTORE_PASSWORDkeyAlias "plbs-android"keyPassword KEY_PASSWORD}catch (ex) {throw new InvalidUserDataException("You should define KEYSTORE_PASSWORD and KEY_PASSWORD in gradle.properties.")}}}buildTypes {debug {//允许debugdebuggable truesigningConfig signingConfigs.release  //在buildTypes中指定release时的signingConfigs对应的配置名}release {signingConfig signingConfigs.release  //在buildTypes中指定release时的signingConfigs对应的配置名zipAlignEnabled true    //Zipalign优化shrinkResources true  // 移除无用的resource文件minifyEnabled true//是否混淆debuggable trueproguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'}}externalNativeBuild {cmake {path "CMakeLists.txt"}}//gradle3.01 从新的打包android.applicationVariants.all { variant ->variant.outputs.all {outputFileName = "重庆金融数据播放${defaultConfig.versionName}_${releaseTime()}.apk"}}
}
def releaseTime() {return new Date().format("yyyy-MM-dd", TimeZone.getTimeZone("UTC"))
}
dependencies {implementation 'com.android.support.constraint:constraint-layout:1.0.2'implementation fileTree(include: ['*.jar'], dir: 'libs')testImplementation 'junit:junit:4.12'debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.3'releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.3'implementation 'com.android.support:appcompat-v7:26.1.0'implementation 'com.jiechic.library:xUtils:2.6.14'implementation 'com.squareup.picasso:picasso:2.5.2'implementation 'com.squareup.okhttp3:okhttp:3.7.0'implementation 'com.google.code.gson:gson:2.8.0'implementation 'com.squareup.okio:okio:1.13.0'implementation 'org.greenrobot:eventbus:3.0.0'implementation 'com.github.bumptech.glide:glide:3.7.0'implementation 'org.java-websocket:Java-WebSocket:1.4.0'//  字体fontimplementation 'uk.co.chrisjenx:calligraphy:2.2.0'//粒子动画implementation 'com.plattysoft.leonids:LeonidsLib:1.3.2'//加载dll 动态文件implementation files('libs/jna-3.1.0.jar')implementation files('libs/fastjson-1.2.12.jar')implementation files('libs/xstream-1.4.7.jar')implementation files('libs/zxing.jar')implementation files('libs/sun.misc.BASE64Decoder.jar')implementation 'com.android.support:multidex:1.0.1'//webview加载网页太慢用 第三方的webview加载网页implementation 'ren.yale.android:cachewebviewlib:2.1.8'}

反编译攻:先演示下是如何通过反编译从新打包1.9版本的:

首先从这个网址下载apktool工具,链接如下:iBotPeaches / Apktool / Downloads — Bitbucket

我下载的是apktool2.4的版本的工具,有需要的自己下载相应的版本。

1.9版本的第一个需要激活的界面如下图:名字叫LoginActivity

通过第一个界面我们授权激活以后就跳转第二个设置界面:名字叫CCBSetActivity。如下图:

将我的1.9版本的apk包放到apktool工具所在的文件夹目录下,如下图:

 

在此目录下打开cmd命令。

在cmd中输入以下命令:

java -jar apktool.jar d -f 1.9.apk -o out

命令的目的是将1.9.apk反编译到out目录下: 如下图结束运行后,可以看到在目录下多了个out文件夹。

 打开out就可以看到反编译后的文件。

在清单文件里就可以修改入口了。

清单文件和android打包前的目录是完全一致的。如下:

<?xml version="1.0" encoding="utf-8" standalone="no"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.meiaomei.absadplayerrotation"><uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/><uses-permission android:name="android.permission.READ_PHONE_STATE"/><uses-permission android:name="android.permission.READ_CONTACTS"/><uses-permission android:name="android.permission.INTERNET"/><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/><uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/><uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/><uses-permission android:name="android.permission.WAKE_LOCK"/><uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/><uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/><uses-permission android:name="android.permission.CAMERA"/><uses-permission android:name="android.permission.GET_TASKS"/><uses-permission android:name="android.permission.DISABLE_KEYGUARD"/><application android:allowBackup="true" android:debuggable="true" android:icon="@mipmap/icon" android:label="@string/app_name" android:name="com.meiaomei.absadplayerrotation.AbsPlbsApplication" android:supportsRtl="true" android:theme="@android:style/Theme.NoTitleBar.Fullscreen"><activity android:configChanges="keyboardHidden|orientation|screenSize" android:launchMode="singleTask" android:name="com.meiaomei.absadplayerrotation.activity.CCBSetActivity" android:screenOrientation="unspecified" android:windowSoftInputMode="stateHidden"/><activity android:configChanges="keyboardHidden|orientation|screenSize" android:launchMode="singleTask" android:name="com.meiaomei.absadplayerrotation.activity.LoginActivity"><intent-filter><action android:name="android.intent.action.MAIN"/><category android:name="android.intent.category.LAUNCHER"/></intent-filter></activity><receiver </application>
</manifest>

可以看到入口文件是LoginActivity。将入口文件的LoginActivity修改为配置界面的CCBSetActivity。这样就可以跳过激活界面直接到配置界面。在这个反编译的文件里也可以修改xml布局文件。我们简单修改下CCBSetActivity的布局文件将系统设置4个字改为 我被狗币反编译了。如果你想修改app的图标还有app的名字在这里都可以改。改完从新打包即可。那个公司的人肯定也是这么反编译我的包的。然后换名字从新打包。

 修改完清单文件AndroidManifest.xml的入口直接为系统设置界面CCBSetActivity。并且将设置界面的系统设置4个字改为 我被狗币反编译了。

然后我们从新打包这个修改过的out文件:

cmd里执行以下命令:

java -jar apktool.jar b -o 1.9change.apk out

 将out重新打包为 1.9change.apk。

cmd执行完成后。我们可以看到已经在这个文件目录下有了一个叫1.9change.apk的包了。

 

这个重新打包的包拖动到模拟器里安装的时候会显示安装失败。因为这个从新打包的包没有签名。就是平常我们在as里面点击这个Generate Signed Bundle or APK按钮。然后输入app的别名账号密码等的这个按钮,输入完成就打包了。

因为缺少这个签名。我们可以新建一个AS项目。然后快速的打包。会生成一个.jks文件。将这个生产的.jks文件拷贝至这个目录下。用这个.jks去替换原来这个apk的签名文件。

此时在cmd里在次执行:

jarsigner -verbose -keystore aqy.jks -storepass youpassword -signedjar 1.9change_signed.apk 1.9change.apk key0

这里的youpassword就是你给新项目打包的密码:替换成自己的即可。这个命令就是用aqy.jks的签名文件去给这个1.9change.apk签名生成一个1.9change_signed.apk的签名的apk包。敲完回车就会看到在重新签名。

签名成功以后。会在该目录下生成一个叫做 1.9change_signed.apk的包。

把这个包拖动到夜神模拟器安装,会显示安装成功。至此完美的绕过了我设置的激活界面。打开以后看下。我在out文件里修改设置界面的布局也生效了。如下图:

我能确定他就是通过这个方法反编译了我的1.9版本。

反编译防

事后我也在思考。我的失误就是我把激活界面放在首页以后在别的界面没有做校验。在首页激活软件以后。应该在每一个界面都去问下软件。是否是激活的?如果不是,就不让他继续使用软件。我的两个界面没有任何联系。导致他直接跳过了我的激活界面。把程序的入口修改了。

因为我们的软件是对接的第三方的接口,接口是对方给的,接口也没有关于安全方面的校验。没办法通过后台去做是否激活的校验。并且软件是运行在内网中的。不涉及到互联网,安装后我们也没法去控制。导致别的公司破解以后就可以直接接入运行。

我后来的修改就是每个界面加了是否激活的校验,如果没有激活就强制退出软件。

在修改的过程中,我发现android的res目录下的valuse下的文件很不安全。被对方反编译以后。这个目录下的所有string都暴漏了。一些重要的数据最好都不要存在这里,如果万不得已非要存,最后采用密文存储。不要用明文。并且把名字都修改下。

之前我把激活界面的3DES对称加密的秘钥都存这里,如下图:(真是够胆大的哈)

    <string name="KEY">npiTUAL6InCrYPLA++dbtlQfnqCNoVG5</string><string name="IV">SqHzP3eZlXE+</string>

现在的修改是将他们都隐藏起来。把名字修改成很普通的。很容易混淆视听的名字。

然后将3DES的秘钥字符串用AES的公钥将加密一下。用的时候用私钥先进行解密。然后再去使用。如图所示,涉及到非常重要的数据。从命名到数据都尽量的隐藏起来。让破解的人不知道你这个是做什么的。这样就不好去修改和破解。

   <string name="t1">3C8659595676946B94D7F7C7F9A27141FC84A96049349B9116DC2E3BBCADD379E9C9063D1E7DC4013DF0BD5A135B895F3477A00E6B01C7D92D9A04BDC7D4B8623A162C69915AAFAA1CA2F5464EE7C95383174A1450AD265874B37B99812404D9947DEBB57FCA249D5F96E8ED4A5D45A25F7FB84F704D9144280C93638B96F67FB59C2AEB5DD94268613F8508E32423ACB7001E1D2994C799F9B80C8EA7BF229F2F7CA0FB2301580D31A8046F87589279E4191DAE0446B3367E06D064157E4109A2DB0CE0A9DC92422905519327525E343B07D4BBE2F81328127F39B51E067A6D612F7AD703FF75E5DECA1C50B80D79052CA7687CA491FDB288269DFC5BAAB876</string><string name="t2">264578595676946B94D7F7C7F9A27141FC84A96049349B9116DC2E3BBCADD379E9C9063D1E7DC4013DF0BD5A135B895F3477A00E6B01C7D92D9A04BDC7D4B8623A162C69915AAFAA1CA2F5464EE7C95383174A1450AD265874B37B99812404D9947DEBB57FCA249D5F96E8ED4A5D45A25F7FB84F704D9144280C93638B96F67FB59C2AEB5DD94268613F8508E32423ACB7001E1D2994C799F9B80C8EA7BF229F2F7CA0FB2301580D31A8046F87589279E4191DAE0446B3367E06D064157E4109A2DB0CE0A9DC92422905519327525E343B07D4BBE2F81328127F39B51E067A6D612F7AD703FF75E5DECA1C50B80D79052CA7687CA491FDB288269DFC5BAAB876</string>

加密key 和iv的时候可以用以下的RSA加密工具类:


import android.os.Environment;
import java.io.File;
import java.io.IOException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.Cipher;
import Decoder.BASE64Decoder;
import Decoder.BASE64Encoder;
/*** RSA 工具类(生成/保存密钥对、加密、解密)*/
public class RSAUtils {public static void main() {// 随机生成一对密钥(包含公钥和私钥)KeyPair keyPair = null;try {keyPair = RSAUtils.generateKeyPair();// 获取 公钥 和 私钥PublicKey pubKey = keyPair.getPublic();PrivateKey priKey = keyPair.getPrivate();// 保存 公钥 和 私钥RSAUtils.saveKeyForEncodedBase64(pubKey, new File(Environment.getExternalStorageDirectory() + "/"+"pub1.txt"));RSAUtils.saveKeyForEncodedBase64(priKey, new File(Environment.getExternalStorageDirectory() + "/"+"pri1.txt"));} catch (Exception e) {e.printStackTrace();}}/*** 算法名称*/private static final String ALGORITHM = "RSA";/*** 密钥长度*/private static final int KEY_SIZE = 2048;/*** 加密, 返回加密后的数据*/public static byte[] clientEncrypt(byte[] plainData, File pubFile) {// 读取公钥文件, 创建公钥对象PublicKey pubKey = null;byte[] cipher = {};try {pubKey = getPublicKey(IOUtils.readFile(pubFile));// 用公钥加密数据cipher = RSAUtils.encrypt(plainData, pubKey);} catch (Exception e) {e.printStackTrace();}return cipher;}/*** 解密, 返回解密后的数据*/public static byte[] serverDecrypt(byte[] cipherData, File priFile) {// 读取私钥文件, 创建私钥对象PrivateKey priKey = null;byte[] plainData = {};try {priKey = getPrivateKey(IOUtils.readFile(priFile));// 用私钥解密数据plainData = RSAUtils.decrypt(cipherData, priKey);} catch (Exception e) {e.printStackTrace();}return plainData;}/*** 随机生成密钥对(包含公钥和私钥)*/public static KeyPair generateKeyPair() throws Exception {// 获取指定算法的密钥对生成器KeyPairGenerator gen = KeyPairGenerator.getInstance(ALGORITHM);// 初始化密钥对生成器(指定密钥长度, 使用默认的安全随机数源)gen.initialize(KEY_SIZE);// 随机生成一对密钥(包含公钥和私钥)return gen.generateKeyPair();}/*** 将 公钥/私钥 编码后以 Base64 的格式保存到指定文件*/public static void saveKeyForEncodedBase64(Key key, File keyFile) throws IOException {// 获取密钥编码后的格式byte[] encBytes = key.getEncoded();// 转换为 Base64 文本String encBase64 = new BASE64Encoder().encode(encBytes);// 保存到文件IOUtils.writeFile(encBase64, keyFile);}/*** 根据公钥的 Base64 文本创建公钥对象*/public static PublicKey getPublicKey(String pubKeyBase64) throws Exception {// 把 公钥的Base64文本 转换为已编码的 公钥bytesbyte[] encPubKey = new BASE64Decoder().decodeBuffer(pubKeyBase64);// 创建 已编码的公钥规格X509EncodedKeySpec encPubKeySpec = new X509EncodedKeySpec(encPubKey);// 获取指定算法的密钥工厂, 根据 已编码的公钥规格, 生成公钥对象return KeyFactory.getInstance(ALGORITHM).generatePublic(encPubKeySpec);}/*** 根据私钥的 Base64 文本创建私钥对象*/public static PrivateKey getPrivateKey(String priKeyBase64) throws Exception {// 把 私钥的Base64文本 转换为已编码的 私钥bytesbyte[] encPriKey = new BASE64Decoder().decodeBuffer(priKeyBase64);// 创建 已编码的私钥规格PKCS8EncodedKeySpec encPriKeySpec = new PKCS8EncodedKeySpec(encPriKey);// 获取指定算法的密钥工厂, 根据 已编码的私钥规格, 生成私钥对象return KeyFactory.getInstance(ALGORITHM).generatePrivate(encPriKeySpec);}/*** 公钥加密数据*/public static byte[] encrypt(byte[] plainData, PublicKey pubKey) throws Exception {// 获取指定算法的密码器Cipher cipher = Cipher.getInstance(ALGORITHM);// 初始化密码器(公钥加密模型)cipher.init(Cipher.ENCRYPT_MODE, pubKey);// 加密数据, 返回加密后的密文return cipher.doFinal(plainData);}/*** 私钥解密数据*/public static byte[] decrypt(byte[] cipherData, PrivateKey priKey) throws Exception {// 获取指定算法的密码器Cipher cipher = Cipher.getInstance(ALGORITHM);// 初始化密码器(私钥解密模型)cipher.init(Cipher.DECRYPT_MODE, priKey);// 解密数据, 返回解密后的明文return cipher.doFinal(cipherData);}
}
import java.io.*;
/*** IO 工具类, 读写文件*/
public class IOUtils {public static void writeFile(String data, File file) throws IOException {OutputStream out = null;try {out = new FileOutputStream(file);out.write(data.getBytes());out.flush();} finally {close(out);}}public static String readFile(File file) throws IOException {InputStream in = null;ByteArrayOutputStream out = null;try {in = new FileInputStream(file);out = new ByteArrayOutputStream();byte[] buf = new byte[1024];int len = -1;while ((len = in.read(buf)) != -1) {out.write(buf, 0, len);}out.flush();byte[] data = out.toByteArray();return new String(data);} finally {close(in);close(out);}}public static void close(Closeable c) {if (c != null) {try {c.close();} catch (IOException e) {// nothing}}}
}

在任意一个activity的界面里,生成公钥和私钥,用公钥去加密需要保护的数据:如下图:

需要注意的是公钥加密完后的byte[]数组,转换为string的时候,不可以用new String(byte[]a)直接去转,加密返回的byte[]中存在负值,例如-116 。负数在Ascii码中是没有对应的值。所以直接用你new String()去转byte的时候,采用文件默认字符集UTF-8,处理时遇到解析不了的值,会用 ‘\uFFFD’代替,显示为 ‘�’。解密的时候,再次采用string.getbytes()将� 转换回byte的话 ,由于找不到对应的Ascii值。会造成数据丢失。导致解密以后的数据和加密的数据对不上。所以采用HexUtils转换一下。解密的时候也用这个工具类将string转为byte。最后再去new String()。就会拿到原来加密前的字符串。然后我们将这个加密后的秘钥放在string里。这样就算破解了也不能马上就看到这个秘钥。

    private void aa() {RSAUtils.main();//随机生成公钥和私钥File pubFile = new File(Environment.getExternalStorageDirectory() + "/"+"pub1.txt");byte keyByteEncy[]=clientEncrypt("npiTUAL6InCrYPLA++dbtlQfnqCNoVG5".getBytes(),pubFile);File priFile = new File(Environment.getExternalStorageDirectory() + "/"+"pri1.txt");String s=HexUtils.bytesToHexString(keyByteEncy);Log.e(TAG, "aa1: "+s );byte keyDecry[]= serverDecrypt(HexUtils.hexStringToBytes(s),priFile);Log.e(TAG, "aa2: "+new String(keyDecry));}//2024-12-18 15:39:16.206 4688-4688/com.hyw.safetyofficertiku E/SecureLife: aa1: 71CD5EE6506C035F749E356495E2C0847F1A38488C45569B24F74414C8EEB566DEF81B6B40F44BDBC5E3D1F5F82365FF84C931E1E34B5D77AA66062B4775D90343018F04931A3A92C401176041E2183EABABAA8DCAE0036E5028119905D44284A3E963DCB3AD4B88702C1396FC85808D5503A947F6EDB0C541BA05E8C26E28094DC56F9BE12E7856D12AC680DFC93B82BA1972CF24AE48097563334186D993508EC24F5A1C334DCF177935D825F257779487740BCDFFF2C84D81CD9C9EDC91D9E87EE25A9CE4DEDB46158E57C428C95EDE70DF372035782B3F45CA3AF962937EEA4967A0EDE6D860176DB737014DB00CCAA9DDFF5A8F902CEBA33FA49032EF70
//2024-12-18 15:39:16.214 4688-4688/com.hyw.safetyofficertiku E/SecureLife: aa2: npiTUAL6InCrYPLA++dbtlQfnqCNoVG5

此处附上HeXUtils类。


public class HexUtils {private static final char[] DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};public static String bytesToHexString(byte[] data) {String string = "";try {char[] chars = new char[data.length << 1];//十六进制数一个四位,byte一个八位for (int i = 0, j = 0; i < data.length; i++) {chars[j++] = DIGITS[(data[i] >> 4) & 0x0F];chars[j++] = DIGITS[data[i] & 0x0F];}string = new String(chars);} catch (Exception e) {e.printStackTrace();}return string;}public static byte[] hexStringToBytes(String data) {//两个四位十六进制字符合成一个八位bytebyte[] bytes = new byte[data.length() / 2];char[] chars = data.toCharArray();for (int i = 0; i < bytes.length; i++) {bytes[i] = (byte) ((hexCharToByte(chars[i * 2]) << 4) | hexCharToByte(chars[i * 2 + 1]));}return bytes;}private static byte hexCharToByte(char c) {return (byte) "0123456789ABCDEF".indexOf(c);}
}

 

以上都是我们的代码被反编译以后从新打包我们做出的一些防护措施。

如何防止反编译后从新打包:

 我也拿我的另外一个程序做了反编译的测试,发现在用apk工具从新打包的时候会报错。这个程序的build.gradle文件是这样配置的。如图:

apply plugin: 'com.android.application'
//使用greendao
apply plugin: 'org.greenrobot.greendao'
android {compileSdkVersion 30defaultConfig {applicationId ""minSdkVersion 22targetSdkVersion 30versionCode 6versionName "6.0"testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"multiDexEnabled true}compileOptions {sourceCompatibility JavaVersion.VERSION_1_8targetCompatibility JavaVersion.VERSION_1_8}aaptOptions {noCompress "pdf"  //表示不让aapt压缩的文件后缀 (不压缩assets下的文件)}buildTypes {release {minifyEnabled truezipAlignEnabled true    //Zipalign优化shrinkResources true  // 移除无用的resource文件proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'}}greendao {//版本号,升级时可配置schemaVersion 1//这里可以指定编译成功后(DaoMaster、DaoSession、DAOS类)的文件目录,可以不指定daoPackage '.greendao'targetGenDir 'src/main/java'}lintOptions {checkReleaseBuilds false}//gradle3.01 从新的打包android.applicationVariants.all { variant ->variant.outputs.all {outputFileName = "唐朝${defaultConfig.versionName}.apk"}}
}
def releaseTime() {return new Date().format("yyyy-MM-dd", TimeZone.getTimeZone("UTC"))
}
dependencies {implementation fileTree(include: ['*.jar'], dir: 'libs')//  实现沉浸式状态栏implementation 'com.gyf.barlibrary:barlibrary:2.3.0'implementation'androidx.appcompat:appcompat:1.0.0'implementation'androidx.recyclerview:recyclerview:1.0.0'implementation'androidx.constraintlayout:constraintlayout:1.1.2'implementation 'com.android.support.constraint:constraint-layout:1.1.3'testImplementation 'junit:junit:4.12'androidTestImplementation 'com.android.support.test:runner:1.0.2'androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'implementation files('libs/java_json-1.2.75.jar')implementation files('libs/zxing.jar')implementation files('libs/sun.misc.BASE64Decoder.jar')implementation 'com.github.barteksc:android-pdf-viewer:2.8.2'implementation 'commons-io:commons-io:2.8.0'//数据库加密类implementation 'net.zetetic:android-database-sqlcipher:4.2.0'implementation 'org.greenrobot:eventbus:3.0.0'//greendao依赖api 'org.greenrobot:greendao:3.2.2'implementation 'com.google.code.gson:gson:2.8.0'implementation 'androidx.multidex:multidex:2.0.0'
}

目标版本都是android sdk版本为30。引入了androidx的包去替换了原来的 support依赖。

继续使用前面的方法去破解先将包解压到out文件夹下。然后修改入口。从新打包。打包的时候会报错。如下代码:

F:\android反编译\apktool2.4>java -jar apktool.jar d -f androidx.apk -o out
I: Using Apktool 2.2.4 on androidx.apk
I: Loading resource table...
I: Decoding AndroidManifest.xml with resources...
I: Loading resource table from file: C:\Users\hyw\AppData\Local\apktool\framework\1.apk
I: Regular manifest package...
I: Decoding file-resources...
I: Decoding values XMLs...
I: Baksmaling classes.dex...
I: Copying assets and libs...
I: Copying unknown files...
I: Copying original files...F:\android反编译\apktool2.4>java -jar apktool.jar b -o androidx_change.apk out
I: Using Apktool 2.2.4
I: Checking whether sources has changed...
I: Smaling smali folder into classes.dex...
I: Checking whether resources has changed...
I: Building resources...
W: F:\android反编译\apktool2.4\out\AndroidManifest.xml:1: error: No resource identifier found for attribute 'compileSdkVersion' in package 'android'
W:
W: F:\android反编译\apktool2.4\out\AndroidManifest.xml:1: error: No resource identifier found for attribute 'compileSdkVersionCodename' in package 'android'
W:
W: F:\android反编译\apktool2.4\out\AndroidManifest.xml:14: error: No resource identifier found for attribute 'appComponentFactory' in package 'android'
W:
Exception in thread "main" brut.androlib.AndrolibException: brut.androlib.AndrolibException: brut.common.BrutException: could not exec (exit code = 1): [C:\Users\hyw\AppData\Local\Temp\brut_util_Jar_5866709263106215219.tmp, p, --forced-package-id, 127, --min-sdk-version, 22, --target-sdk-version, 30, --version-code, 6, --version-name, 6.0, --no-version-vectors, -F, C:\Users\hyw\AppData\Local\Temp\APKTOOL333571415111046532.tmp, -0, arsc, -0, db, -0, txt, -0, arsc, -I, C:\Users\hyw\AppData\Local\apktool\framework\1.apk, -S, F:\android反编译\apktool2.4\out\res, -M, F:\android反编译\apktool2.4\out\AndroidManifest.xml]at brut.androlib.Androlib.buildResourcesFull(Androlib.java:496)at brut.androlib.Androlib.buildResources(Androlib.java:430)at brut.androlib.Androlib.build(Androlib.java:329)at brut.androlib.Androlib.build(Androlib.java:267)at brut.apktool.Main.cmdBuild(Main.java:230)at brut.apktool.Main.main(Main.java:83)
Caused by: brut.androlib.AndrolibException: brut.common.BrutException: could not exec (exit code = 1): [C:\Users\hyw\AppData\Local\Temp\brut_util_Jar_5866709263106215219.tmp, p, --forced-package-id, 127, --min-sdk-version, 22, --target-sdk-version, 30, --version-code, 6, --version-name, 6.0, --no-version-vectors, -F, C:\Users\hyw\AppData\Local\Temp\APKTOOL333571415111046532.tmp, -0, arsc, -0, db, -0, txt, -0, arsc, -I, C:\Users\hyw\AppData\Local\apktool\framework\1.apk, -S, F:\android反编译\apktool2.4\out\res, -M, F:\android反编译\apktool2.4\out\AndroidManifest.xml]at brut.androlib.res.AndrolibResources.aaptPackage(AndrolibResources.java:441)at brut.androlib.Androlib.buildResourcesFull(Androlib.java:482)... 5 more
Caused by: brut.common.BrutException: could not exec (exit code = 1): [C:\Users\hyw\AppData\Local\Temp\brut_util_Jar_5866709263106215219.tmp, p, --forced-package-id, 127, --min-sdk-version, 22, --target-sdk-version, 30, --version-code, 6, --version-name, 6.0, --no-version-vectors, -F, C:\Users\hyw\AppData\Local\Temp\APKTOOL333571415111046532.tmp, -0, arsc, -0, db, -0, txt, -0, arsc, -I, C:\Users\hyw\AppData\Local\apktool\framework\1.apk, -S, F:\android反编译\apktool2.4\out\res, -M, F:\android反编译\apktool2.4\out\AndroidManifest.xml]at brut.util.OS.exec(OS.java:95)at brut.androlib.res.AndrolibResources.aaptPackage(AndrolibResources.java:435)... 6 more

 反编译从新打包的时候会报错。显示android版本相关的一些信息。我怀疑是apktool工具版本太低。将apktool工具升级到最新的版本2.10。然后去打包。还是会报这个错误。我是不是可以理解为升级到androdx以后。安全性得到了提高呢。也许还是我1.9版本一样。我以为防的还可以结果被轻松绕过了入口。如果有朋友重新打包的时候解决了这个报错。

麻烦也和我分享下。不知攻,焉能防。

总结:重要的数据,采用明文存的尽量都改为密文存储。还有google更新的时候,尽量都跟着最新版本的走。不要偷懒。sdk版本越高,安全性越好。

被人反编译apk的感受还是很不好的。尤其被人商用了。搞了好多钱。让人内心沮丧,心情低落。幸好我去找老板的时候,老板也没说我啥,我义愤填膺的说要去起诉他们。至少给他们一个警告。让他们停止侵害。老板说你怎么证明他们用的咱们的。我说反编译完了对代码行数 。再说我们还有软著。奈何老板不想搞事。我只能把漏洞堵上。然后再写一篇文章记录下。

相关文章:

Android app反编译 攻与防

大概是2020年的时候&#xff0c;有一次&#xff0c;我们的竞争同行有另外一家公司要用我们的安卓软件app,拉了个群&#xff0c;告知他用一个软件多少钱&#xff0c;然后在群里发了一个我打包的apk包。结果就没有下文了。又过了一个月。我同事在那个要买我们apk的人的朋友圈&…...

ElasticSearch 简介

一、什么是 ElastcSearch&#xff1f; ElasticSearch 是基于 Lucene 的 Restful 的分布式实时全文搜索引擎。 1.1 ElasticSearh 的基本术语概念 index 索引 索引类似与 mysql 中的数据库&#xff0c;ES 中的索引是存储数据的地方&#xff0c;包含了一堆有相似结构的文档数据…...

Kerberos实验

kdc&#xff1a;192.168.72.163 客户端&#xff08;机器账户win10&#xff09;&#xff1a;192.168.72.159 用户&#xff1a;administrator 抓包&#xff1a;开机登录win10&#xff0c;使用administrator域用户凭据登录。 生成 Kerberos 解密文件 抓取 krbtgt 用户和 win1…...

Android之RecyclerView显示数据列表和网格

一、RecyclerView的优势 RecyclerView 的最大优势在于&#xff0c;它对大型列表来说非常高效&#xff1a; 默认情况下&#xff0c;RecyclerView 仅会处理或绘制当前显示在屏幕上的项。例如&#xff0c;如果您的列表包含一千个元素&#xff0c;但只有 10 个元素可见&#xff0…...

docker mysql挂载

在提供的 docker run 命令中&#xff0c;已经挂载了三个卷到 MySQL 容器中&#xff1a;日志目录、数据目录和配置目录。然而&#xff0c;还没有挂载一个包含 cube_admin.sql 文件的目录。要将 SQL 文件放入容器中并在 MySQL 中执行它&#xff0c;可以按照以下步骤操作&#xff…...

顺序表-递增有序表合并

两个递增有序表合并操作 题目&#xff1a; 将两个递增有序的顺序表 A 和 B 合并成一个新的递增有序顺序表 C。 思路&#xff1a; 使用三个索引 i, j, k 分别遍历顺序表 A, B 和合并后的顺序表 C。比较 A 和 B 当前索引指向的元素&#xff0c;将较小的元素放入 C 中&#xf…...

【Qt】qt安装

在工作一年之后&#xff0c;还是想做一个Qt的教程&#xff0c;遥想研一刚刚接触Qt&#xff0c;从0到1学习&#xff0c;没有什么参考书籍&#xff0c;网上的资料也不多&#xff0c;幸好Qt官方文档写得好&#xff0c;加上自己肯研究&#xff0c;才堪堪入门。 现在我想自己写一个…...

CXF WebService SpringBoot 添加拦截器,处理响应报文格式

描述 XFIRE升级CXF框架&#xff0c;但是对接的系统不做调整&#xff0c;这时候就要保证参数报文和响应报文和以前是一致的。但是不同的框架有不同的规则&#xff0c;想要将报文调整的一致&#xff0c;就需要用到拦截器拦截报文&#xff0c;自定义解析处理。 CXF框架本身就是支…...

vue iframe进行父子页面通信并切换URL

使用通义千问提问后得到一个很好的示例。 需求是2个项目需要使用同一个面包屑进行跳转&#xff0c;其中一个是iframe所在的项目&#xff0c;另一个需要通过地址访问。通过 window.parent.postMessage &#xff0c;帮助 <iframe> 内嵌入的子页面和其父页面之间进行跨域通…...

基于Spring Boot的摄影师分享交流社区

一、系统背景与目的 随着摄影技术的不断发展和摄影爱好者群体的日益扩大&#xff0c;摄影师们需要一个能够展示自己作品、分享摄影心得、交流摄影技巧的平台。基于Spring Boot的摄影师分享交流社区应运而生&#xff0c;它旨在满足摄影师们的这些需求&#xff0c;促进摄影文化的…...

Web 毕设篇-适合小白、初级入门练手的 Spring Boot Web 毕业设计项目:电影院后台管理系统(前后端源码 + 数据库 sql 脚本)

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 项目介绍 2.0 用户登录功能 3.0 用户管理功能 4.0 影院管理功能 5.0 电影管理功能 6.0 影厅管理功能 7.0 电影排片管理功能 8.0 用户评论管理功能 9.0 用户购票功…...

Linux(网络协议和管理)

后面也会持续更新&#xff0c;学到新东西会在其中补充。 建议按顺序食用&#xff0c;欢迎批评或者交流&#xff01; 缺什么东西欢迎评论&#xff01;我都会及时修改的&#xff01; 在这里真的很感谢这位老师的教学视频让迷茫的我找到了很好的学习视频 王晓春老师的个人空间…...

C++ 入门第 20 天:STL 容器之无序集合与无序多重集合

往期回顾&#xff1a; C 入门17&#xff1a;STL 容器之映射&#xff08;map&#xff09;与多重映射&#xff08;multimap&#xff09;_-CSDN博客 C 入门18&#xff1a;STL 容器之栈&#xff08;stack&#xff09;与队列&#xff08;queue&#xff09;-CSDN博客 C 入门19&#x…...

devops-部署Harbor实现私有Docker镜像仓库

文章目录 概述下载配置安装安装后生成的文件使用和维护Harbor参考资料 概述 Harbor是一个开源注册中心&#xff0c;它使用策略和基于角色的访问控制来保护工件&#xff0c;确保镜像被扫描并且没有漏洞&#xff0c;并将镜像签名为可信的。Harbor是CNCF的一个毕业项目&#xff0…...

rebase ‘A‘ onto ‘master‘ 和 merge ‘master‘ into ‘A‘有什么区别

在Git版本控制系统中&#xff0c;rebase 和 merge 是两种不同的操作&#xff0c;用于合并分支。rebase A onto master 和 merge master into A 虽然最终目的都是将两个分支的更改合并在一起&#xff0c;但它们在处理方式和结果上有所不同。 rebase ‘A’ onto ‘master’ 含义…...

Vulhub:Jackson[漏洞复现]

CVE-2017-7525(Jackson反序列化) 启动漏洞环境 docker-compose up -d 阅读vulhub给出的漏洞文档 cat README.zh-cn.md # Jackson-databind 反序列化漏洞&#xff08;CVE-2017-7525&#xff09; Jackson-databind 支持 [Polymorphic Deserialization](https://github.com/Fas…...

strongswan构建测试环境

make-testing脚本文件负责构建strongswan的虚拟化测试系统。位于目录strongswan-5.9.14/testing/&#xff0c;需要以管理员身份运行make-testing。生成测试用到的虚拟客户机镜像&#xff0c;KVM虚拟机和虚拟网络的配置文件位于目录:config/kvm。 ~/strongswan-5.9.14/testing$…...

前端:金额高精度处理

Decimal 是什么 想必大家在用js 处理 数字的 加减乘除的时候&#xff0c;或许都有遇到过 精度不够 的问题&#xff0c;还有那些经典的面试题 0.20.1 ! 0.3&#xff0c; 至于原因&#xff0c;那就是 js 计算底层用的是 IEEE 754 &#xff0c;精度上有限制&#xff0c; 那么Deci…...

面试题整理3----nc命令的常见用法

面试题整理3----nc命令的常见用法 1. NC是什么2. NC的常用参数2.1 开启指定端口TCP监听(-l小写的L)2.2 测试端口是否能访问(-v)2.3 开启指定端口UDP监听(-u)2.4 端口扫描(-z)2.5 指定超时时间(-w)2.6 指定本地端口号连接(-p)2.7 指定的命令(-e) 1. NC是什么 nc&#xff08;Net…...

Trimble天宝三维激光扫描仪在建筑工程竣工测量中的应用【沪敖3D】

竣工测量是建筑项目竣工阶段的一个至关重要的环节&#xff0c;它为建筑工程的质量验收和成果核查提供了核心的参考依据。传统的竣工测量方法&#xff0c;如全站仪测量&#xff0c;主要依赖于现场人工操作&#xff0c;存在一些明显的局限性&#xff0c;例如作业时间长、工作量大…...

浅谈 React Hooks

React Hooks 是 React 16.8 引入的一组 API&#xff0c;用于在函数组件中使用 state 和其他 React 特性&#xff08;例如生命周期方法、context 等&#xff09;。Hooks 通过简洁的函数接口&#xff0c;解决了状态与 UI 的高度解耦&#xff0c;通过函数式编程范式实现更灵活 Rea…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)

文章目录 1.什么是Redis&#xff1f;2.为什么要使用redis作为mysql的缓存&#xff1f;3.什么是缓存雪崩、缓存穿透、缓存击穿&#xff1f;3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...

零基础设计模式——行为型模式 - 责任链模式

第四部分&#xff1a;行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习&#xff01;行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想&#xff1a;使多个对象都有机会处…...

蓝桥杯3498 01串的熵

问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798&#xff0c; 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...

基于SpringBoot在线拍卖系统的设计和实现

摘 要 随着社会的发展&#xff0c;社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 在线拍卖系统&#xff0c;主要的模块包括管理员&#xff1b;首页、个人中心、用户管理、商品类型管理、拍卖商品管理、历史竞拍管理、竞拍订单…...

CSS3相关知识点

CSS3相关知识点 CSS3私有前缀私有前缀私有前缀存在的意义常见浏览器的私有前缀 CSS3基本语法CSS3 新增长度单位CSS3 新增颜色设置方式CSS3 新增选择器CSS3 新增盒模型相关属性box-sizing 怪异盒模型resize调整盒子大小box-shadow 盒子阴影opacity 不透明度 CSS3 新增背景属性ba…...

Appium下载安装配置保姆教程(图文详解)

目录 一、Appium软件介绍 1.特点 2.工作原理 3.应用场景 二、环境准备 安装 Node.js 安装 Appium 安装 JDK 安装 Android SDK 安装Python及依赖包 三、安装教程 1.Node.js安装 1.1.下载Node 1.2.安装程序 1.3.配置npm仓储和缓存 1.4. 配置环境 1.5.测试Node.j…...

13.10 LangGraph多轮对话系统实战:Ollama私有部署+情感识别优化全解析

LangGraph多轮对话系统实战:Ollama私有部署+情感识别优化全解析 LanguageMentor 对话式训练系统架构与实现 关键词:多轮对话系统设计、场景化提示工程、情感识别优化、LangGraph 状态管理、Ollama 私有化部署 1. 对话训练系统技术架构 采用四层架构实现高扩展性的对话训练…...

【Qt】控件 QWidget

控件 QWidget 一. 控件概述二. QWidget 的核心属性可用状态&#xff1a;enabled几何&#xff1a;geometrywindows frame 窗口框架的影响 窗口标题&#xff1a;windowTitle窗口图标&#xff1a;windowIconqrc 机制 窗口不透明度&#xff1a;windowOpacity光标&#xff1a;cursor…...

Qt/C++学习系列之列表使用记录

Qt/C学习系列之列表使用记录 前言列表的初始化界面初始化设置名称获取简单设置 单元格存储总结 前言 列表的使用主要基于QTableWidget控件&#xff0c;同步使用QTableWidgetItem进行单元格的设置&#xff0c;最后可以使用QAxObject进行单元格的数据读出将数据进行存储。接下来…...