学习目标:了解一些app的防护措施,能对自己写的app做一些简单防护

反编译一个app时搜索不到关键字

  • HTML5的App
  • 字符串被加密了
  • 反射调用相关类
  • 动态加载dex
  • 热修复

简单的字符串加密的实现

dex的字符串加密可以用代码自动实现,需要使用到dexlib2这个库

若是不加任何防护,在登陆操作时,我们将密码发送给服务器,攻击者再抓包后直接反编译然后搜索password便可以定位到关键代码

package com.example.encrypt;

import android.os.Build;
import android.os.Bundle;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

import java.security.MessageDigest;
import android.util.Base64;
import android.util.Log;

import java.util.HashMap;

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;
});
String password = "a12345678";
try{
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(password.getBytes());
byte[] digest = md5.digest();
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("password", Base64.encodeToString(digest, 0));
Log.d("hyq", hashMap.toString());
}catch (Exception e){
e.printStackTrace();
}
}
}

反编译后直接查找,轻轻松松就找到了

我么便可以使用一些简单的方式,将password字段进行加密

先将password进行一下base64编码得到cGFzc3dvcmQ=,接下来我们使用这个字段代替password

package com.example.encrypt;

import android.os.Bundle;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

import java.security.MessageDigest;
import android.util.Base64;
import android.util.Log;

import java.util.HashMap;

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;
});
String password = "a12345678";
try{
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(password.getBytes());
byte[] digest = md5.digest();
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put(decrypt("cGFzc3dvcmQ="), Base64.encodeToString(digest, 0));
Log.d("hyq", hashMap.toString());
}catch (Exception e){
e.printStackTrace();
}

}
private String decrypt(String str){
return new String(Base64.decode(str, 0));
}
}

这样就不能直接搜到了

注意下细节:System.out.println(Base64.decode(str, 0));和 System.out.println(Base64.decode(str, 0).toString());输出的是哈希值不是字节数组的内容

使用反射进一步加密

package com.example.encrypt;

import android.os.Bundle;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

import java.lang.reflect.Method;
import java.security.MessageDigest;
import android.util.Base64;
import android.util.Log;

import java.util.HashMap;
import java.util.Objects;

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;
});
String password = "12345678";
try{
// MessageDigest md5 = MessageDigest.getInstance("MD5");
Class<?> a = Class.forName(d("amF2YS5zZWN1cml0eS5NZXNzYWdlRGlnZXN0"));
Method b = a.getMethod(d("Z2V0SW5zdGFuY2U="), String.class);
Object c = b.invoke(null, d("TUQ1"));
// md5.update(password.getBytes());
Method e = a.getMethod(d("dXBkYXRl"), byte[].class);
e.invoke(c, password.getBytes());
// byte[] digest = md5.digest();
Method f= a.getMethod(d("ZGlnZXN0"));
byte[] cipherBytes = (byte[]) f.invoke(c);

HashMap<String, String> hashMap = new HashMap<>();
hashMap.put(d("cGFzc3dvcmQ="), Base64.encodeToString(cipherBytes,0));
Log.d("hyq", hashMap.toString());
}catch (Exception e){
e.printStackTrace();
}

}
private String d(String str){
return new String(Base64.decode(str, 0));
}
}

反编译出来长这样

package com.example.fctf;

import android.os.Bundle;
import android.util.Base64;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

import com.example.fctf.databinding.ActivityMainBinding;

import org.w3c.dom.Text;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.security.MessageDigest;
import java.security.spec.KeySpec;
import java.util.Objects;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;

import okio.ByteString;

public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;

private int num = 1;
private static String key0 = "82305002";
private static String iv0 = "82505002";
public final int getNum(){
return this.num;
}
public void setNum(int i){
this.num = i;
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
final TextView textView = binding.textView;
Button button = binding.button;
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
MainActivity.a(MainActivity.this, textView, button);
} catch (IllegalBlockSizeException e) {
throw new RuntimeException(e);
} catch (BadPaddingException e) {
throw new RuntimeException(e);
}
}
});
try {
System.out.println(decrypt("whyysqwmstoryhzcontinues"));
} catch (IllegalBlockSizeException e) {
throw new RuntimeException(e);
} catch (BadPaddingException e) {
throw new RuntimeException(e);
}
}
public static final void a(MainActivity this$0, TextView tv, Button button) throws IllegalBlockSizeException, BadPaddingException {
MainActivity mainActivity = this$0;
tv.setText(String.valueOf(this$0.num));
if(this$0.check() == 20220422){
Toast.makeText(mainActivity, "Congratuations!!!", Toast.LENGTH_SHORT).show();
tv.setText("flag{" + this$0.decrypt("whyysqwmstoryhzcontinues") + "}");
}
}
public final int check(){
int i = this.num + 1;
this.num = i;
return i;
}
public final String decrypt(String str) throws IllegalBlockSizeException, BadPaddingException {
Object ddd = null;
byte[] bytes =null;
try {
Class<?> a = Class.forName(d("amF2YS5zZWN1cml0eS5NZXNzYWdlRGlnZXN0"));
Method b = a.getMethod(d("Z2V0SW5zdGFuY2U="), String.class);
Object c = b.invoke(null, d("TUQ1"));
Method e = a.getMethod(d("dXBkYXRl"), byte[].class);
e.invoke(c, key0.getBytes());
Method f = a.getMethod(d("ZGlnZXN0"));
byte[] k = (byte[]) f.invoke(c);


//反射des
// //DESKeySpec dsk = new DESKeySpec(k);
// Class<?> DESKeySpec = Class.forName("java.security.spec.KeySpec");
// Constructor<?> dESKeySpecConstructor = DESKeySpec.getConstructor(byte[].class);
// Object dsk = dESKeySpecConstructor.newInstance(k);
// //SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
// Class<?> SecretKeyFactory = Class.forName("javax.crypto.SecretKeyFactory");
// Method getInstance = SecretKeyFactory.getMethod("getInstance", String.class);
// Object keyFactory = getInstance.invoke(null, "DES");
// SecretKey key = keyFactory.generateSecret(dsk);
// Method generateSecret = SecretKeyFactory.getMethod();
// IvParameterSpec iv = new IvParameterSpec(iv0.getBytes());
// des = Cipher.getInstance("DES/CBC/PKCS5Padding");
// des.init(Cipher.ENCRYPT_MODE, key, iv);
// bytes = des.doFinal(str.getBytes());
Class<?> aa= Class.forName(d("amF2YXguY3J5cHRvLnNwZWMuREVTS2V5U3BlYw=="));
Constructor<?> desKeySpecConstructor = aa.getConstructor(byte[].class);
Object dsk = desKeySpecConstructor.newInstance(k);

// 获取 SecretKeyFactory 实例
Class<?> bb = Class.forName(d("amF2YXguY3J5cHRvLlNlY3JldEtleUZhY3Rvcnk="));
Method cc = bb.getMethod(d("Z2V0SW5zdGFuY2U="), String.class);
Object keyFactory = cc.invoke(null, "DES");

// 生成 SecretKey
Method dd = bb.getMethod(d("Z2VuZXJhdGVTZWNyZXQ="), KeySpec.class);
Object key = dd.invoke(keyFactory, dsk);

// 创建 IvParameterSpec 实例
Class<?> ee = Class.forName(d("amF2YXguY3J5cHRvLnNwZWMuSXZQYXJhbWV0ZXJTcGVj"));
Constructor<?> ff = ee.getConstructor(byte[].class);
Object iv =ff.newInstance(iv0.getBytes());

// 获取 Cipher 实例
Class<?> gg = Class.forName(d("amF2YXguY3J5cHRvLkNpcGhlcg=="));
Method hh = gg.getMethod(d("Z2V0SW5zdGFuY2U="), String.class);
ddd = hh.invoke(null, d("REVTL0NCQy9QS0NTNVBhZGRpbmc="));

// 初始化 Cipher
Method ii = gg.getMethod(d("aW5pdA=="), int.class, java.security.Key.class, java.security.spec.AlgorithmParameterSpec.class);
ii.invoke(ddd, Cipher.ENCRYPT_MODE, key, iv);

// 执行加密操作
Method jj = gg.getMethod(d("ZG9GaW5hbA=="), byte[].class);
bytes = (byte[]) jj.invoke(ddd, str.getBytes());
// MessageDigest md5 = MessageDigest.getInstance("MD5");
// md5.update("a12345678".getBytes());
// byte[] bytes=md5.digest();
// ByteString byteString = ByteString.of(bytes);
// String key = byteString.hex();
// System.out.println(key);
} catch (Exception e) {
e.printStackTrace();
}
ByteString byteString = ByteString.of(bytes);
return byteString.hex();
}

private String d(String str){
return new String(Base64.decode(str, 0));
}

}