GKLBB

当你经历了暴风雨,你也就成为了暴风雨

导航

应用安全 --- apk加固 之 已安装应用检查

// 这个代码的主要作用是:

// 应用互斥检查机制:

// 检查设备上是否安装了包名为"com.gklbb"的应用
// 如果检测到该应用已安装,立即关闭当前应用
// 如果未安装,应用正常运行
// 可能的使用场景:

// 防止重复安装:

// 防止用户同时安装同一应用的不同版本
// 确保只有一个版本在设备上运行
// 许可证检查:

// "com.gklbb"可能是一个许可证验证应用
// 只有安装了特定的许可证应用,当前应用才能运行
// 应用冲突避免:

// 避免与特定应用产生功能冲突
// 确保应用生态的稳定性
// 反调试/反分析:

// 检测是否安装了特定的调试工具或分析工具
// 如果检测到,立即退出以防止被分析


public class MainActivity extends AppCompatActivity {
   
    // 在MainActivity的onCreate或onResume方法中调用此方法
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
       
        // 调用检查方法
        isInstalled();
    }
   
    // 或者在onResume中调用
    @Override
    protected void onResume() {
        super.onResume();
        isInstalled();
    }
   
    /**
     * 检查特定应用是否已安装的方法
     * 如果目标应用已安装,则关闭当前应用
     */
    public void isInstalled() {
        try {
            // 获取PackageManager实例
            PackageManager packageManager = getPackageManager();
           
            // 尝试获取包名为"com.gklbb"的应用信息
            // 第二个参数1表示GET_ACTIVITIES标志
            PackageInfo packageInfo = packageManager.getPackageInfo("com.gklbb", 1);
           
            // 如果能成功获取到包信息(没有抛出异常),说明应用已安装
            // 立即关闭当前应用
            finish();
           
        } catch (Exception e) {
            // 如果抛出异常(通常是NameNotFoundException),说明应用未安装
            // 什么都不做,让应用继续运行
            return;
        }
    }
}

 

 

 

package com.iran.SmaliHelper; // 定义包名,这是代码的命名空间

// 导入Android系统相关的类库
import android.app.Application; // 导入应用程序基类
import android.content.Context; // 导入上下文类,用于访问系统服务
import android.content.pm.PackageInfo; // 导入包信息类,包含应用的详细信息
import android.content.pm.PackageManager; // 导入包管理器类,用于管理应用包
import android.content.pm.Signature; // 导入签名类,用于应用签名验证
import android.util.Base64; // 导入Base64编码解码工具
import java.io.ByteArrayInputStream; // 导入字节数组输入流
import java.io.DataInputStream; // 导入数据输入流
import java.lang.reflect.Field; // 导入反射字段类,用于访问类的字段
import java.lang.reflect.InvocationHandler; // 导入调用处理器接口,用于动态代理
import java.lang.reflect.Method; // 导入反射方法类,用于调用方法
import java.lang.reflect.Proxy; // 导入代理类,用于创建动态代理对象
import java.util.ArrayList; // 导入数组列表类

// 定义一个最终类,继承Application并实现InvocationHandler接口
// 这个类的作用是拦截和修改系统的包管理器调用
public final class RemoveSoftware extends Application implements InvocationHandler {
    
    private String appClassName = "com.GKLBB "; // 存储应用类名
    private String data = ""; // 存储签名数据的Base64编码字符串
    private Object sPackageManager; // 保存原始的系统包管理器对象
    private byte[][] sign; // 存储伪造的签名数据
    private String type = "2"; // 工作模式:1=修改版本号,2=隐藏应用,3=伪造签名

    // 构造函数,调用父类构造函数
    public RemoveSoftware() {
        super(); // 调用Application的构造函数
    }

    @Override
    // 重写attachBaseContext方法,这是应用启动时最早调用的方法之一
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base); // 调用父类方法
        
        try {
            // 通过反射获取ActivityThread类(Android系统的主线程类)
            Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
            
            // 获取当前ActivityThread实例的方法
            Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
            // 调用静态方法获取当前ActivityThread实例
            Object currentActivityThread = currentActivityThreadMethod.invoke(null);
            
            // 获取ActivityThread类中的sPackageManager字段(系统包管理器)
            Field sPackageManagerField = activityThreadClass.getDeclaredField("sPackageManager");
            sPackageManagerField.setAccessible(true); // 设置为可访问(绕过private限制)
            this.sPackageManager = sPackageManagerField.get(currentActivityThread); // 保存原始包管理器
            
            // 获取IPackageManager接口类(包管理器的接口)
            Class<?> iPackageManagerInterface = Class.forName("android.content.pm.IPackageManager");
            // 创建动态代理对象,用我们的类作为调用处理器
            Object proxy = Proxy.newProxyInstance(
                iPackageManagerInterface.getClassLoader(), // 使用接口的类加载器
                new Class[]{iPackageManagerInterface}, // 要代理的接口
                this // 使用当前对象作为调用处理器
            );
            
            // 用我们的代理对象替换ActivityThread中的原始包管理器
            sPackageManagerField.set(currentActivityThread, proxy);
            
            // 同时替换PackageManager中的mPM字段
            PackageManager pm = base.getPackageManager(); // 获取包管理器实例
            Field mPmField = pm.getClass().getDeclaredField("mPM"); // 获取内部的mPM字段
            mPmField.setAccessible(true); // 设置为可访问
            mPmField.set(pm, proxy); // 用代理对象替换
            
        } catch (Exception e) {
            e.printStackTrace(); // 如果出错就打印错误信息
        }
    }

    // 反射工具方法:通过类名和对象获取指定字段的值
    public Object getFieldObject(String className, Object obj, String fieldName) {
        try {
            Class<?> objClass = Class.forName(className); // 根据类名获取Class对象
            Field field = objClass.getDeclaredField(fieldName); // 获取指定名称的字段
            field.setAccessible(true); // 设置字段为可访问(绕过private等限制)
            return field.get(obj); // 从对象中获取字段值并返回
        } catch (Exception e) {
            e.printStackTrace(); // 出错时打印错误信息
            return null; // 返回null表示获取失败
        }
    }

    // 反射工具方法:获取类的静态字段值
    public Object getStaticFieldObject(String className, String fieldName) {
        try {
            Class<?> objClass = Class.forName(className); // 根据类名获取Class对象
            Field field = objClass.getDeclaredField(fieldName); // 获取指定名称的字段
            field.setAccessible(true); // 设置字段为可访问
            return field.get(null); // 获取静态字段值(传入null因为是静态字段)
        } catch (Exception e) {
            e.printStackTrace(); // 出错时打印错误信息
            return null; // 返回null表示获取失败
        }
    }

    @Override
    // 动态代理的核心方法:拦截所有对包管理器的调用
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName(); // 获取被调用的方法名
        
        // 模式1:修改版本号模式
        if ("1".equals(type)) {
            // 如果调用的是getPackageInfo方法,且查询的是当前应用
            if ("getPackageInfo".equals(methodName) && 
                getPackageName().equals(args[0])) {
                // 先调用原始方法获取真实的包信息
                PackageInfo info = (PackageInfo) method.invoke(sPackageManager, args);
                info.versionCode = Integer.MAX_VALUE; // 把版本号改为最大值(防检测)
                return info; // 返回修改后的包信息
            }
        }
        
        // 模式2:隐藏应用模式
        else if ("2".equals(type)) {
            // 如果调用getInstalledPackages(获取已安装应用列表)
            if ("getInstalledPackages".equals(methodName)) {
                return new ArrayList<>(); // 返回空列表,让系统以为没有安装任何应用
            }
            
            // 对于查询当前应用的各种方法,都把包名替换成假的
            if (isCurrentPackageQuery(methodName, args)) {
                args[0] = "ccc"; // 把真实包名替换为"ccc",让查询失败
            }
        }
        
        // 模式3:签名伪造模式
        else if ("3".equals(type)) {
            initializeSignatures(); // 初始化伪造的签名数据
            
            // 如果调用getPackageInfo方法
            if ("getPackageInfo".equals(methodName)) {
                String pkgName = (String) args[0]; // 获取要查询的包名
                Integer flag = (Integer) args[1]; // 获取查询标志
                
                // 如果请求签名信息(flag包含0x40)且查询的是当前应用
                if ((flag & 0x40) != 0 && getPackageName().equals(pkgName)) {
                    // 先获取真实的包信息
                    PackageInfo info = (PackageInfo) method.invoke(sPackageManager, args);
                    
                    // 用伪造的签名替换真实签名
                    info.signatures = new Signature[sign.length];
                    for (int i = 0; i < info.signatures.length; i++) {
                        info.signatures[i] = new Signature(sign[i]); // 创建伪造的签名对象
                    }
                    return info; // 返回包含伪造签名的包信息
                }
            }
        }
        
        // 如果不需要特殊处理,就调用原始的包管理器方法
        return method.invoke(sPackageManager, args);
    }
    
    // 判断是否是查询当前应用的方法调用
    private boolean isCurrentPackageQuery(String methodName, Object[] args) {
        if (args == null || args.length == 0) return false; // 如果没有参数就返回false
        
        // 定义需要拦截的方法名列表(这些都是查询应用信息的方法)
        String[] targetMethods = {
            "getPackageInfo", "getApplicationInfo", "getPackageUid", 
            "getPackageGids", "getApplicationEnabledSetting", "getText",
            "getResourcesForApplication", "getLaunchIntentForPackage",
            "getInstallerPackageName"
        };
        
        // 遍历目标方法列表
        for (String targetMethod : targetMethods) {
            // 如果方法名匹配且第一个参数是当前应用的包名
            if (targetMethod.equals(methodName) && 
                getPackageName().equals(args[0])) {
                return true; // 返回true表示需要拦截
            }
        }
        
        // 特殊处理获取应用图标和横幅的方法
        if (("getApplicationBanner".equals(methodName) || 
             "getApplicationIcon".equals(methodName)) &&
            args[0] instanceof String && // 确保第一个参数是字符串
            getPackageName().equals(args[0])) { // 且是当前应用包名
            return true; // 返回true表示需要拦截
        }
        
        return false; // 其他情况返回false,不需要拦截
    }
    
    // 初始化伪造的签名数据
    private void initializeSignatures() {
        if (sign != null) return; // 如果已经初始化过就直接返回
        
        try {
            // 创建数据输入流,从Base64解码的data字符串中读取签名数据
            DataInputStream is = new DataInputStream(
                new ByteArrayInputStream(Base64.decode(data, 0)) // 将Base64字符串解码为字节数组
            );
            
            int signCount = is.read() & 0xff; // 读取签名数量(一个字节)
            sign = new byte[signCount][]; // 创建二维字节数组存储签名
            
            // 循环读取每个签名的数据
            for (int i = 0; i < sign.length; i++) {
                int length = is.readInt(); // 读取当前签名的长度(4个字节)
                sign[i] = new byte[length]; // 创建对应长度的字节数组
                is.readFully(sign[i]); // 读取完整的签名数据
            }
        } catch (Exception e) {
            e.printStackTrace(); // 如果出错就打印错误信息
        }
    }
}
}

"偷梁换柱"的魔术
想象一下,Android系统就像一个大公司:


原本的情况:
IPackageManager = 公司的"包裹管理部门"接口规范
真正的包管理器 = 负责处理所有App信息查询的"真实员工"
ActivityThread = 公司的"总调度中心",所有请求都通过它
这段代码做了什么:
找到真实员工


Class<?> iPackageManagerInterface = Class.forName("android.content.pm.IPackageManager");
先获取"包裹管理部门"的工作规范(接口定义)
制造一个"替身"


Object proxy = Proxy.newProxyInstance(...)
创建一个假员工(代理对象)
这个假员工看起来和真员工一模一样(实现相同接口)
但实际上所有工作都会转给我们的处理器(this)
偷梁换柱


sPackageManagerField.set(currentActivityThread, proxy);
把公司总调度中心里的"真员工"换成我们的"假员工"
从此以后,所有对包管理器的请求都会先经过我们
🔄 工作流程:
App请求查询包信息

总调度中心(ActivityThread)

我们的假员工(proxy)

我们的处理器(invoke方法) ← 在这里可以篡改数据!

真正的员工(原始PackageManager)

返回结果给App
💡 核心原理:
动态代理模式:创建一个中间人,拦截所有方法调用
反射机制:在运行时修改系统内部的对象引用
接口统一性:代理对象和原对象实现相同接口,外界无法察觉
这样,当任何App(包括系统)想查询包信息时,都会先经过我们的"假员工",我们就可以在invoke方法里随意修改返回的数据,

 

 

举个例子:

假设设备上有两个应用:

  • 应用A:就是集成了这段 RemoveSoftware 代码的应用。

  • 应用B:是一个安全检查应用,它想扫描设备上安装了哪些应用。

  • 场景1(应用A未启动):

    • 应用B 调用 getInstalledPackages()。这个调用由系统服务处理,返回真实的、完整的应用列表,其中包括应用A。代码未生效。

  • 场景2(应用A已启动):

    • 应用A 的进程启动,attachBaseContext 被执行,成功Hook了它自己进程内的包管理器。

    • 现在,如果应用A 自己 调用 getInstalledPackages(),这个调用会被自己的 invoke 方法拦截,并返回一个空列表 new ArrayList<>()

    • 但是,应用B 调用 getInstalledPackages() 仍然不受影响,会拿到完整列表。代码只对应用A自身生效。

结论

这段代码是一种 “自欺欺人” 式的隐藏技术。它只能在一个已经运行的应用程序进程内部欺骗自己,让自己无法通过标准API看到真实的应用信息。它无法在全局范围内隐藏应用,也无法阻止其他应用程序或系统服务发现它。

要实现真正的全局隐藏,需要更高层次的权限和技术,例如修改系统框架(需要刷入自定义ROM)、使用Root权限直接修改系统服务的数据、或者利用Android系统本身的漏洞(通常会被在新版本中修复)。这段代码不属于上述任何一种情况。

 

 

posted on 2025-09-05 10:52  GKLBB  阅读(14)  评论(0)    收藏  举报