首页
社区
课程
招聘
[原创]【Fireyer】一款Android平台环境检测应用
发表于: 2024-5-24 20:26 16486

[原创]【Fireyer】一款Android平台环境检测应用

2024-5-24 20:26
16486


项目已开源:

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

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

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

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

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

我们自测的方法:

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

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

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

优势:

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

对抗:

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

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

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

普通的检测方法:

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

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

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

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

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

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

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

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

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

1、校验Application完整性。

2、检测permission

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

4、检测meta-data

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

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

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

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

检测的方式:

Java层检测:

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

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

应对方案:

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

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

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

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

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

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

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

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);
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);
__attribute__((__naked__)) int svc_openat() {
  __asm__ volatile("mov x15, x8\n"
    "ldr x8, =0x38\n"
    "svc #0\n"
    "mov x8, x15\n"
    "bx lr"
  );
}
__attribute__((__naked__)) int svc_openat() {
  __asm__ volatile("mov x15, x8\n"
    "ldr x8, =0x38\n"
    "svc #0\n"
    "mov x8, x15\n"
    "bx lr"
  );
}
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 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);
    }
}
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);
    }
}
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);
}
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
// 正常类
android.view.IWindowSession$Stub$Proxy
// 代理后的类
android.view.IWindowSession$Stub$Proxy$Proxy
/**
 * /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);
    }
}
/**
 * /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);
    }
}
Object obj = ReflectUtils.getStaticFieldValue("android.app.ActivityManager", "IActivityManagerSingleton");
Object inst = ReflectUtils.getFieldValue(obj, "mInstance");
if (Proxy.isProxyClass(inst.getClass())) {
    // TODO
}
Object obj = ReflectUtils.getStaticFieldValue("android.app.ActivityManager", "IActivityManagerSingleton");
Object inst = ReflectUtils.getFieldValue(obj, "mInstance");
if (Proxy.isProxyClass(inst.getClass())) {
    // TODO
}
PackageInfo pi = getContext().getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_SIGNING_CERTIFICATES);
pi.signingInfo;// TODO
PackageInfo pi = getContext().getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_SIGNING_CERTIFICATES);
pi.signingInfo;// TODO
PackageInfo pi = getContext().getPackageManager().getPackageArchiveInfo(mPackageInfo.applicationInfo.sourceDir, PackageManager.GET_SIGNING_CERTIFICATES);
pi.signingInfo;// TODO
PackageInfo pi = getContext().getPackageManager().getPackageArchiveInfo(mPackageInfo.applicationInfo.sourceDir, PackageManager.GET_SIGNING_CERTIFICATES);
pi.signingInfo;// TODO
// 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];
        }
    };
}
// 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];
        }
    };

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 1
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//