首页 > 其他分享 >【Fireyer】一款Android平台环境检测应用

【Fireyer】一款Android平台环境检测应用

时间:2024-05-24 20:51:09浏览次数:22  
标签:Fireyer java int 检测 Android 一款 public android

Fireyer 是为了校验我们的虚拟化环境构建是否存在缺陷,可以保障我们的每次更新的产品质量,提升开发效率。

项目已开源:

☞ Github:https://www.github.com/iofomo/fireyer ☜ 

如果您也喜欢 Fireyer,别忘了给我们点个星。

1. 说明

fire + eyer = Fireyer(火眼),Fireyer项目是我们在做虚拟化沙箱产品过程中的内部副产品。目的是为了校验我们的虚拟化环境构建是否存在漏洞,在内部作为我们产品的黑白检测工具应用,可以保障我们的每次更新的产品质量,提升开发效率。对于开发沙箱,虚拟化等相关场景产品的伙伴也可以提升开发效率,快速验证功能稳定性。Fireyer的检测项还在不断完善中,后续会持续同步更新。

由于我们的虚拟化产品是普通主流机型,因此Fireyer主要用于在正常系统环境下,检测应用被重打包(或重签名),容器环境(免安装加载运行),虚拟机(将Android系统变成普通应用)的通用个人手机场景。Fireyer当前并不适用于定制ROM,或刷入Magisk,或ROOT的环境检测(当然由于技术的相关性,其中某些检测项可能生效,但并非针对性用例),但也在我们后续的迭代计划中。

2. 如何使用

Fireyer项目的主要目的是为了提升我们产品的稳定性,并非为了应用的强对抗,只是为了保证正常的应用行为运行稳定。

我们自测的方法:

  1. 在正常的应用环境中,点击单元测试【原始环境】Fireyer会将运行完成的用例数据格式化保存在系统的剪切板中备用。
  2. 在虚拟的测试环境中,点击单元测试【虚拟环境】Fireyer会从系统的剪切板中获取测试数据,然后与当前运行用例结果进行对比,最终得到测试验证的目的。

3. 系统调用实现

为了可以实现对inlinegot表的拦截检测,我们需要实现一些基本函数的系统调用,如:

int open(const char *pathname, int flags, ...);
int close(int fd);
int stat(const char* path, struct stat* buf);
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
ssize_t readlink(const char *path, char *buf, size_t bufsiz);

系统调用的方式如何实现呢,有个简单的办法就是将手机里面的libc.so库导出来(这里导出的64位的库),然后用ida打开,查看对应函数的实现,如open的实现如下:

这样我们得到openat在64位系统上的系统调用的实现方式:

__attribute__((__naked__)) int svc_openat() { 
  __asm__ volatile("mov x15, x8\n" 
    "ldr x8, =0x38\n"
    "svc #0\n"
    "mov x8, x15\n"
    "bx lr"
  );
}

优势:

通过自实现系统调用函数,可以在关键的地方和正常的函数调用进行对比,从而达到识别的目的,不管是基于got表还是inline的拦截。

对抗:

如何对抗该检测,则可以使用应用级trace拦截。

4. 代理拦截和检测

拦截是利用JavaProxy模块完成的,如:

package java.lang.reflect;

public class Proxy implements java.io.Serializable {
       public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h);
}

代理后,原对象实例被更换为代理后的对象,当应用使用调用接口方法后,即可回调。

普通的检测方法:

package java.lang.reflect;

public class Proxy implements java.io.Serializable {
    public static boolean isProxyClass(Class<?> cl) {
        return Proxy.class.isAssignableFrom(cl) && proxyClassCache.containsValue(cl);
    }
}

通常对方会自己调用native方法实现创建代理对象,而不使用Proxy类,如:

package java.lang.reflect;

public class Proxy implements java.io.Serializable {
       private static native Class<?> generateProxy(String name, Class<?>[] interfaces,
                                                 ClassLoader loader, Method[] methods,
                                                 Class<?>[][] exceptions);
}

那我们依然可以通过对比该对象的类名进行识别,如:

// 正常类
android.view.IWindowSession$Stub$Proxy
// 代理后的类
android.view.IWindowSession$Stub$Proxy$Proxy

5. Binder拦截和检测

很多时候我们与Service的通信可能被劫持,而拦截Binder通信最简单的方法就是接口代理。由于Android服务的Binder通信框架的数据解析和序列化都是基于接口:

/**
 * /frameworks/base/core/java/android/app/IActivityManager.aidl
 */
interface IActivityManager {
  // ...
}

/**
 * /frameworks/base/core/java/android/content/pm/IPackageManager.aidl
 */
interface IPackageManager {
  // ...
}

public interface Parcelable {
       public interface Creator<T> {
        public T createFromParcel(Parcel source);
        public T[] newArray(int size);
    }
}

1、我们可以获取对应服务的Binder对象,检测是否已经被代理。

Object obj = ReflectUtils.getStaticFieldValue("android.app.ActivityManager", "IActivityManagerSingleton");
Object inst = ReflectUtils.getFieldValue(obj, "mInstance");
if (Proxy.isProxyClass(inst.getClass())) {
    // TODO
}

2、可能面临基于底层Binder拦截的方案,如之前分享的开源项目:【Android】深入Binder底层拦截

则整个解析不经过Java层,上层无法检测,但是底层解析有个很大的弊端就是对于复杂的Binder通信,如参数或返回值为BundleIntentApplicationInfoPackageInfo时,解析逻辑非常复杂,要做到兼容性好,通常会调用上层的代码进行解析。

6. 完整性检测

6.1 签名校验

1、通过系统的PackageManagerService提供的返回值(太简单,非小白略过)。

PackageInfo pi = getContext().getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_SIGNING_CERTIFICATES);
pi.signingInfo;// TODO

2、通过解析本地文件。(太简单,非小白略过)。

PackageInfo pi = getContext().getPackageManager().getPackageArchiveInfo(mPackageInfo.applicationInfo.sourceDir, PackageManager.GET_SIGNING_CERTIFICATES);
pi.signingInfo;// TODO

以上两种方法都可以通过接口代理方式替换SigningInfo.CREATOR,来完成PackageInfo.signingInfo的拦截和伪装。

// source code
public final class SigningInfo implements Parcelable {

    public static final @android.annotation.NonNull Parcelable.Creator<SigningInfo> CREATOR =
            new Parcelable.Creator<SigningInfo>() {
        @Override
        public SigningInfo createFromParcel(Parcel source) {
            return new SigningInfo(source);
        }

        @Override
        public SigningInfo[] newArray(int size) {
            return new SigningInfo[size];
        }
    };
}

6.2 属性检测

1、校验Application完整性。

<application
    android:theme="@ref/0x7f120289" ----------------------------------------- 是否被替换
    android:label="@ref/0x7f0d0001" ----------------------------------------- 是否被替换
    android:icon="@ref/0x7f0d0001" ------------------------------------------ 是否被替换
    android:name="com.demo.app.Application" --------------------------------- 是否被替换
    android:persistent="false"
    android:allowBackup="false"
    android:debuggable="false" ---------------------------------------------- 是否被开启
    android:hardwareAccelerated="true"
    android:largeHeap="true"
    android:supportsRtl="false"
    android:extractNativeLibs="true"
    android:usesCleartextTraffic="true"
    android:networkSecurityConfig="@ref/0x7f150051"
    android:appComponentFactory="androidx.core.app.CoreComponentFactory" ---- 是否替换
    android:requestLegacyExternalStorage="true"
    android:allowNativeHeapPointerTagging="false"
    android:preserveLegacyExternalStorage="true"
    >
</application>

2、检测permission

3、检测四大组件:activityactivity-aliasserviceproviderreceiver

4、检测meta-data

7. 运行环境

7.1 检测隐藏API权限

很多应用篡改目的是为了完成某些功能,时常涉及隐藏接口的调用(从9.0后),会将一些模块的保护权限解除,因此我们需要对一些常用的模块做检测。

if (classFind("android.app.ActivityThread")) break;
// /libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
if (classFind("dalvik.system.DexPathList")) break;
// /frameworks/base/core/java/android/app/LoadedApk.java
if (classFind("android.app.LoadedApk")) break;
// /frameworks/base/core/java/android/app/IActivityManager.aidl
if (classFind("android.app.IActivityManager")) break;
// /frameworks/base/core/java/android/content/pm/IPackageManager.aidl
if (classFind("android.content.pm.IPackageManager")) break;

通过一些类的反射访问(该类在Android开发者网站上说明,源码有@hide标注),可以确认当前运行环境的隐藏API是否已经被解除。该方案很难被修复,如果完全无感知需要虚拟化框架在调用时设置隐藏API策略,提前缓存好目标classmethodfield,然后再恢复,但如此则虚拟化环境内存消耗和初始化性能则会受到很大影响。

7.2 检测目录

通过系统调用实现查看当前私有目录下是否存在未知文件和目录,某些虚拟化环境会在应用目录提前存放了一些数据文件。

7.3 检测调用栈

在某些关键函数回调中进行调用栈的检测。

  1. 如:AppComopentFactory的初始化回调。
  2. 如:Application的初始化回调。
  3. 如:ActivityThread$Hcallback回调。

检测的方式:

  1. 直接上层的Thread.dumpStack获取。虚拟化环境可以通过对native的函数拦截伪装。
  2. 通过低层libunwind库获取对应的函数名和库信息。虚拟化环境可以通过对getcontext的拦截进行伪装。

7.4 检测线程

Java层检测:

public static void getAllThreadsInfo() {
    Map<Thread, StackTraceElement[]> allThreads = Thread.getAllStackTraces();
    for (Map.Entry<Thread, StackTraceElement[]> entry : allThreads.entrySet()) {
        Thread thread = entry.getKey();
        StackTraceElement[] stackTrace = entry.getValue();
        // Got thread id and names
    }
}

但某些实现会拦截native层函数调用进行伪装,因此我们需要遍历线程目录(使用自实现的系统调用函数访问)

void getAllThreadsInfo() {
    char threadName[128];
    DIR* taskDir = opendir("/proc/self/task");
    if (taskDir != nullptr) {
        struct dirent* entry;
        while ((entry = svc_readdir(taskDir)) != nullptr) {
            if (entry->d_type == DT_DIR && strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0) {
                pid_t threadId = atoi(entry->d_name);
                if (pthread_getname_np(pthread_t(threadId), threadName, sizeof(threadName)) == 0) {
                             // Got thread id and names
                }
            }
        }
        closedir(taskDir);
    }
}

7.5 C进程检测

增加采用C程序命令的方式采集信息。如:

  1. ls ${dir}
  2. cat ${file}
  3. 自己实现c程序对主进程进行信息采集。

应对方案:

  1. 拦截进程execve函数,对调用c程序命令的参数进行修正。
  2. 拦截进程execve函数,对即将fork的子进程,向子进程的envp环境变量注入预加载库,从而实现对C程序内部函数调用的拦截。

7.6 maps检测

maps检测实现,使用系统调用函数对/proc/self/maps中的内容进行校验。

  1. 校验maps是否有第三方库的加载痕迹。
  2. 校验base.apk路径是否合法。
  3. 校验dex库是否被篡改。

该检测可以被Trace方案拦截,并映射至修正的新的maps文件,达到虚拟化伪装的目的。

7.7 注入库检测

当前进程可能被加载了执行代码(如:dexlib),因此我们通过查找本进程的maps进行识别(使用自实现的系统调用函数访问)。

int fd = svc_open("proc/self/maps", "r");
if (0 <= fd) {
  char buffer[1024];
  svc_read(fd, buffer, sizeof(buffer);// 这里循环读取并检测,是否包含非安装目录库(如:/data/user)
  svc_close(fd);
}

而对方可能会直接采用内存方式加载dexapk,如:

/**
 * /libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
 **/
public final class DexPathList {
       public static Element[] makeInMemoryDexElements(ByteBuffer[] dexFiles,
            List<IOException> suppressedExceptions) {
        Element[] elements = new Element[dexFiles.length];
        int elementPos = 0;
        for (ByteBuffer buf : dexFiles) {
            try {
                DexFile dex = new DexFile(new ByteBuffer[] { buf }, /* classLoader */ null,
                        /* dexElements */ null);
                elements[elementPos++] = new Element(dex);
            } catch (IOException suppressed) {
                System.logE("Unable to load dex file: " + buf, suppressed);
                suppressedExceptions.add(suppressed);
            }
        }
        if (elementPos != elements.length) {
            elements = Arrays.copyOf(elements, elementPos);
        }
        return elements;
    }
}

同样也会通过先在将lib库加载到内存,然后通过从内存加载lib的方式实现,这样在maps中就不会留下的文件目录痕迹。

FILE* tempFile = tmpfile();
// TODO read lib file to tempFile
const char* tempFileName = fileno(tempFile);
void* libHandle = dlopen(tempFileName, RTLD_NOW);
if (libHandle != nullptr) {
    // ...
    dlclose(libHandle);
}
unlink(tempFileName);

以上情况,我们需要对maps中的地址区间的内容进行进一步的识别。

7.8 Trace检测

Trace检测实现,当前使用系统调用函数对/proc/self/status中的TracerPid:字段进行简单校验。后面会有单独的文章分享如何构建Trace进程互相检测实现。

标签:Fireyer,java,int,检测,Android,一款,public,android
From: https://www.cnblogs.com/iofomo/p/18211659

相关文章

  • Android Studio 常用快捷键(超实用!!!)
    AndroidStudio常用快捷键(超实用!!!) 快捷键又称为“热键”,多个按键的组合可以实现某些快速操作,例如Window中最常用的Ctrl+C和Ctrl+V,熟练使用快捷键可以大大提高开发效率并可以减少某些错误的发生。AndroidStudio也默认提供了众多快捷键方式供开发者调用,推荐使用AndroidStudio......
  • android中怎么将一个aar打包进另一个aar
    怎么将一个aar打包进另一个aar方法一、使用fat-aar插件,不过由于fat-aar插件较长时间未更新,导致无法支持最新的Android版本(已不推荐使用)第一步:在你的工程根目录下的build.gradle文件中添加以下代码:buildscript{​ repositories{  google()  mavenCentral()  jce......
  • Android 背景资源自定义 ShapeDrawble
    前言制作随时可以编辑的背景图,不需要向UI要背景图。【规则的】ShapeDrawable 是绘制形状的Drawable,定义了基本的几何图形,如(矩形,圆形,线条等)ShapeDrawable 根元素是 <shape/>效果图分别四种形状情况:ectangle(矩形)、oval(椭圆,包括圆)、line(线段)、ring(环形)......
  • NRF52833是一款通用多协议SoC蓝牙芯片内置M4内核超低功耗
    NRF52833是一款通用多协议SoC,具有蓝牙测向无线电,可在-40°C至105°C的扩展温度范围内工作。它是业界领先的nRF52系列的第5款产品,围绕带FPU的64MHzArmCortex-M4构建,具有512KB闪存和128KBRAM内存,可用于高价值应用。nRF52833可在105°C运行,加上大量内存和动态多......
  • 无线网卡有几种接口?怎么给电脑选择一款合适的无线网卡?
    前言这篇文章一共有两个问题:无线网卡有几种接口怎么给电脑选择一款合适的无线网卡目测这一期的文章很长很长,但不水。想要给笔记本或台式机升级无线网卡的小伙伴看过来了!最近有小伙伴问:华硕r555笔记本能不能升级无线网卡,毕竟原机自带的网卡是百兆的,速度太慢了。想当......
  • Android JNI/NDK环境的配置与Demo编译
    一、背景​JNI(JavaNativeInterface)和NDK(NativeDevelopmentKit)在Android开发中扮演着重要的角色。JNI,即Java本地接口,是Java平台的一部分,它允许Java代码与其他语言写的代码进行交互。通过JNI,Java代码可以调用本地应用程序或库中的代码,也可以被本地代码调用。这主要使得......
  • PhiData 一款开发AI搜索、agents智能体和工作流应用的AI框架
    引言在人工智能领域,构建一个能够理解并响应用户需求的智能助手是一项挑战性的任务。PhiData作为一个开源框架,为开发者提供了构建具有长期记忆、丰富知识和强大工具的AI助手的可能性。本文将介绍PhiData的核心优势、应用示例以及如何使用PhiData来构建自己的AI助手。PhiData的设......
  • Android11快速编译并替换framework.jar
    Android11快速编译并替换framework.jar在Android11之前修改了framework相关代码,只需makeframework就可以编译出framework.jar。在Android11,这个编译命令不起作用了,根据framework/base/目录下Android.bp中的提示:java_library{name:"framework-minus-apex",defaults:......
  • 同样的APP为何在Android 8以后网络感觉变卡?
    前言 在无线网络技术不断发展的今天,Wi-Fi已经成为了我们日常生活中不可或缺的一部分。无论是家庭娱乐、办公还是在线游戏,Wi-Fi都在提供着便捷的互联网接入服务。然而,在安卓8.1后,为了进一步延长安卓设备的待机时间。原生安卓(AOSP等)默认开启了全局省电(低功耗模式),该模式下W......
  • 基于ADB Shell 实现的 Android TV、电视盒子万能遥控器 — ADB Remote ATV
    ADBRemoteATVAndroidTV的遥控器,基于ADBShell命令ADBRemoteATV是一个AndroidTV的遥控器,基于ADBShell命令,泛用性更高。下面的shell命令,是软件的基本原理,通过shell命令可模拟物理遥控器的基本按键,此外还可以快捷启动指定APP、借助手机软键盘输入中/英字符等。......