Android 反调试技巧之Self-Debuging/proc 文件系统检测、调试断点探测

本文涉及的产品
阿里云百炼推荐规格 ADB PostgreSQL,4核16GB 100GB 1个月
简介: 本文讲的是Android 反调试技巧之Self-Debuging/proc 文件系统检测、调试断点探测,首先,我们来看看Bluebox Security(一家移动数据保护的公司)所描述的反调试方法。gDvm是一个类型为DvmGlobals的全局变量,用来收集当前进程所有虚拟机相关的信息
本文讲的是 Android 反调试技巧之Self-Debuging/proc 文件系统检测、调试断点探测

Android 反调试技巧之Self-Debuging/proc 文件系统检测、调试断点探测

首先,我们来看看Bluebox Security(一家移动数据保护的公司)所描述的反调试方法。gDvm是一个类型为DvmGlobals的全局变量,用来收集当前进程所有虚拟机相关的信息,其中,它的成员变量vmList指向的就是当前进程中的Dalvik虚拟机实例,即一个JavaVMExt对象。以后每当需要访问当前进程中的Dalvik虚拟机实例时,就可以通过全局变量gDvm的成员变量vmList来获得,避免了在函数之间传递该Dalvik虚拟机实例。

gDvm的存在使得直接访问JDWP相关数据变得很容易。例如,gDvm。jdwpState指向包含全局调试数据和函数指针的结构。操作数据会导致JDWP线程发生故障或崩溃。下图就是Bluebox Security的具体方法:

JNIEXPORT jboolean JNICALL Java_com_example_disable(JNIENV* env, jobject dontuse ){

  // gDvm==struct DvmGlobals

  gDvm.jdwpState = NULL;

  return JNI_TRUE; 

}

刚开始,反调试实现起来并不容易,因为没有指向重要数据结构的全局符号。虽然我们有一个指向主结构体的JdwpState,但是 gJdwpState只是一个本地符号,所以链接器不会解决不了这个问题。

不过,libart.so会将JDWP相关类的一些vtables导出为全局符号。但是到目前,我们还搞不清楚其中的原因,以及这是否正常,但是这个方法却给了我们修改JDWP线程提供了一些很好的提示。这其中就包括JdwpSocketState和JdwpAdbState这两个分别通过网络套接字和adb端处理的JDWP连接:

Android 反调试技巧之Self-Debuging/proc 文件系统检测、调试断点探测

我们可以以各种方式覆盖这个指针,简单的归零它们不是一个好主意,因为这会让整个过程崩溃。于是,我们找到的一个使用“JdwpAdbState :: Shutdown()”的地址来覆盖“jdwpAdbState :: ProcessIncoming()”地址的好方法,这个方法看起来如下:

#include <jni.h>
#include <string>
#include <android/log.h>
#include <dlfcn.h>
#include <sys/mman.h>
#include <jdwp/jdwp.h>#define log(FMT, ...) __android_log_print(ANDROID_LOG_VERBOSE, "JDWPFun", FMT, ##__VA_ARGS__)// Vtable structure. Just to make messing around with it more intuitivestruct VT_JdwpAdbState {
    unsigned long x;
    unsigned long y;
    void * JdwpSocketState_destructor;
    void * _JdwpSocketState_destructor;
    void * Accept;
    void * showmanyc;
    void * ShutDown;
    void * ProcessIncoming;
};extern "C"JNIEXPORT void JNICALL Java_sg_vantagepoint_jdwptest_MainActivity_JDWPfun(
        JNIEnv *env,
        jobject /* this */) {    void* lib = dlopen("libart.so", RTLD_NOW);    if (lib == NULL) {
        log("Error loading libart.so");
        dlerror();
    }else{        struct VT_JdwpAdbState *vtable = ( struct VT_JdwpAdbState *)dlsym(lib, "_ZTVN3art4JDWP12JdwpAdbStateE");        if (vtable == 0) {
            log("Couldn't resolve symbol '_ZTVN3art4JDWP12JdwpAdbStateE'.n");
        }else {            log("Vtable for JdwpAdbState at: %08xn", vtable);            // Let the fun begin!            unsigned long pagesize = sysconf(_SC_PAGE_SIZE);
            unsigned long page = (unsigned long)vtable & ~(pagesize-1);            mprotect((void *)page, pagesize, PROT_READ | PROT_WRITE);            vtable->ProcessIncoming = vtable->ShutDown;            // Reset permissions & flush cache            mprotect((void *)page, pagesize, PROT_READ);        }
    }
}

一旦此功能运行,任何连接Java的调试器都将断开连接,任何进一步的连接尝试都将失败。令人惊讶的是,目前使用这个方法可以进行反调试了,并没有在日志中进行任何解释:

Pyramidal Neuron:~ berndt$ adb jdwp2926Pyramidal Neuron:~ berndt$ adb forward tcp:7777 jdwp:2926Pyramidal Neuron:~ berndt$ jdb -attach localhost:7777java.io.IOException: handshake failed - connection prematurally closed    at com.sun.tools.jdi.SocketTransportService.handshake(SocketTransportService.java:136)    at com.sun.tools.jdi.SocketTransportService.attach(SocketTransportService.java:232)    at com.sun.tools.jdi.GenericAttachingConnector.attach(GenericAttachingConnector.java:116)    at com.sun.tools.jdi.SocketAttachingConnector.attach(SocketAttachingConnector.java:90)    at com.sun.tools.example.debug.tty.VMConnection.attachTarget(VMConnection.java:519)    at com.sun.tools.example.debug.tty.VMConnection.open(VMConnection.java:328)    at com.sun.tools.example.debug.tty.Env.init(Env.java:63)    at com.sun.tools.example.debug.tty.TTY.main(TTY.java:1066)

这个方法相当隐秘的,即通过欺骗和隐藏实现,不过我们只尝试了进行ADB端口连接,大家在使用这个方法时可能需要修补JdwpSocketState,以防止Java调试。

但是有一个问题:Android是建立在Linux上的,因此继承了ptrace系统调用。

什么是ptrace系统调用?

ptrace 系统调从名字上看是用于进程跟踪的,它提供了父进程可以观察和控制其子进程执行的能力,并允许父进程检查和替换子进程的内核镜像(包括寄存器)的值。其基 本原理是: 当使用了ptrace跟踪后,所有发送给被跟踪的子进程的信号(除了SIGKILL),都会被转发给父进程,而子进程则会被阻塞,这时子进程的状态就会被 系统标注为TASK_TRACED。而父进程收到信号后,就可以对停止下来的子进程进行检查和修改,然后让子进程继续运行。    

其原型为:    

#include <sys/ptrace.h>
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data)

ptrace有四个参数: 

1.enum __ptrace_request request:指示了ptrace要执行的命令。
2.pid_t pid: 指示ptrace要跟踪的进程。
3.void *addr: 指示要监控的内存地址。
4.void *data: 存放读取出的或者要写入的数据。

PTRACE_TRACEME反调试

ptrace是如此的强大,以至于有很多大家所常用的工具都基于ptrace来实现,如gdb,strace,jtrace和Frida。其中一些工具甚至提供了虚拟机自省技术,为与java虚拟机进行交互提供了便利的后门。 

许多过去的Linux反调试技巧,例如监控proc文件系统和检测内存中的断点,一直都是Android上常用的的方法。通常在恶意软件使用的另一种技术是自我调试。该方法利用了一个事实,即只有一个调试器可以随时附加到进程。我们来看一下这个工作原理。

在Linux上,ptrace()系统调用用于观察和控制另一个进程(“tracee”)的执行,并检查和更改跟踪的内存和寄存器。它是实现断点调试和系统调用跟踪的主要手段,使用ptrace系统调用进行反调试的最明显的方法是对内存的分配和回收,然后调用ptrace(parent_pid)附加到父进程:

void anti_debug() {

    child_pid = fork();

    if (child_pid == 0)
    {
        int ppid = getppid();
        int status;

        if (ptrace(PTRACE_ATTACH, ppid, NULL, NULL) == 0)
        {
            waitpid(ppid, &status, 0);

            ptrace(PTRACE_CONT, ppid, NULL, NULL);

            while (waitpid(ppid, &status, 0)) {

                if (WIFSTOPPED(status)) {
                    ptrace(PTRACE_CONT, ppid, NULL, NULL);
                } else {
                    // Process has exited for some reason
                    _exit(0);
                }
            }
        }
    }
}

如果按照上述方式来实现,子进程将继续跟踪父进程,直到父进程退出,从而导致将调试器附加到父进程时失败。我们可以通过将代码编译成JNI函数并将其打包到我们在设备上运行的应用程序来验证。

假设一切顺利,我们现在就要尝试调试应用程序的逆向工程了。如下图所示,ps不是返回一个进程,而是返回相同命令行的两个进程:

root@android:/ # ps | grep -i anti
u0_a151   18190 201   1535844 54908 ffffffff b6e0f124 S sg.vantagepoint.antidebug
u0_a151   18224 18190 1495180 35824 c019a3ac b6e0ee5c S sg.vantagepoint.antidebug

Google 这么多年来,已经把 Android 做成了本质上无法分支(fork)的软件,开源只是名义上的,让我们来尝试使用gdbserver附加到父进程来进行验证:

root@android:/ # ./gdbserver --attach localhost:12345 18190
warning: process 18190 is already traced by process 18224
Cannot attach to lwp 18190: Operation not permitted (1)
Exiting

如上图所示,仍然需要进行反向工程:

root@android:/ # kill -9 18224

现在让我们再试一次尝试附加gdbserver:

root@android:/ # ./gdbserver --attach localhost:12345 18190  Attached; pid = 18190
Listening on port 12345

ptrace调用通常常见的方法包括:

分别跟踪彼此的多个进程
跟踪运行过程,监视子进程
监视/ proc文件系统中的值,例如/ proc / pid / status中的TracerPID。

大家来看一下我们对上述方法的简单改进,在最初的fork()之后,我们在父进程中启动一个额外的线程来连续监视子进程的状态。根据应用程序是否已内置在调试或发布模式(根据Manifest中的android:可调试标志),子进程将以下列方式之一运行:

1.在释放模式下,调用ptrace失败,子进程立即退出分段错误(退出代码11)。

2.在调试模式下,调用ptrace工作,子进程预计会无限运行下去。因此,对waitpid(child_pid)的调用不应该返回,如果有的话,会阻碍了整个进程组的运行。

完整的JNI实现如下,通过添加JNIEXPORT(…)_ antidebug()作为本机方法,可以在自己的项目中自由使用它。

#include <jni.h>
#include <string>
#include <unistd.h>
#include <sys/ptrace.h>
#include <sys/wait.h>

static int child_pid;

void *monitor_pid(void *) {

    int status;

    waitpid(child_pid, &status, 0);

    /* Child status should never change. */

    _exit(0); // Commit seppuku

}

void anti_debug() {

    child_pid = fork();

    if (child_pid == 0)
    {
        int ppid = getppid();
        int status;

        if (ptrace(PTRACE_ATTACH, ppid, NULL, NULL) == 0)
        {
            waitpid(ppid, &status, 0);

            ptrace(PTRACE_CONT, ppid, NULL, NULL);

            while (waitpid(ppid, &status, 0)) {

                if (WIFSTOPPED(status)) {
                    ptrace(PTRACE_CONT, ppid, NULL, NULL);
                } else {
                    // Process has exited
                    _exit(0);
                }
            }
        }

    } else {
        pthread_t t;

        /* Start the monitoring thread */

        pthread_create(&t, NULL, monitor_pid, (void *)NULL);
    }
}
extern "C"

JNIEXPORT void JNICALL
Java_sg_vantagepoint_antidebug_MainActivity_antidebug(
        JNIEnv *env,
        jobject /* this */) {

        anti_debug();
}

另外,我们将其打包成一个Android应用程序,看看它是否有效。就像上文一样,运行应用程序的调试版本时会显示两个进程:

root@android:/ # ps | grep -i anti-debug
u0_a152   20267 201   1552508 56796 ffffffff b6e0f124 S sg.vantagepoint.anti-debug
u0_a152   20301 20267 1495192 33980 c019a3ac b6e0ee5c S sg.vantagepoint.anti-debug

但是,如果我们现在终止子进程,父进程也退出:

root@android:/ # kill -9 20301                               
root@android:/ # ./gdbserver --attach localhost:12345 20267   
gdbserver: unable to open /proc file '/proc/20267/status'
Cannot attach to lwp 20267: No such file or directory (2)
Exiting

为了绕过这个进程,我们有必要稍微修改一下应用程序的进程,最简单的方法是使用NOP将调用修改为_exit,或者在libc.so中hook函数_exit。

如何防范这种反调试

预防这种反调试其实有很多种方法,比如我们可以修补应用程序漏洞,防止vtable被篡改。如果你不能及时修复,那以后还会再次受到这种攻击。另外就是在其他情况下,使用Xposed或Frida修改内核模块可能更合适,我们给大家介绍2种方法:

1.修补反调试功能。通过简单地用NOP指令覆盖来禁用不需要的行为。请注意,如果反调试机制已经经过深加工了,则可能需要更复杂的修补程序。

2.使用Frida或Xposed hook本地API,如ptrace()和fork(),或使用内核模块hook相关的系统调用。




原文发布时间为:2017年4月14日
本文作者:xiaohui 
本文来自云栖社区合作伙伴嘶吼,了解相关信息可以关注嘶吼网站。
相关实践学习
阿里云百炼xAnalyticDB PostgreSQL构建AIGC应用
通过该实验体验在阿里云百炼中构建企业专属知识库构建及应用全流程。同时体验使用ADB-PG向量检索引擎提供专属安全存储,保障企业数据隐私安全。
AnalyticDB PostgreSQL 企业智能数据中台:一站式管理数据服务资产
企业在数据仓库之上可构建丰富的数据服务用以支持数据应用及业务场景;ADB PG推出全新企业智能数据平台,用以帮助用户一站式的管理企业数据服务资产,包括创建, 管理,探索, 监控等; 助力企业在现有平台之上快速构建起数据服务资产体系
目录
相关文章
|
7月前
|
存储 Java Linux
Android Mstar增加IR 自定义遥控头码完整调试过程
Android Mstar增加IR 自定义遥控头码完整调试过程
140 1
|
4月前
|
Shell Linux 开发工具
"开发者的救星:揭秘如何用adb神器征服Android设备,开启高效调试之旅!"
【8月更文挑战第20天】Android Debug Bridge (adb) 是 Android 开发者必备工具,用于实现计算机与 Android 设备间通讯,执行调试及命令操作。adb 提供了丰富的命令行接口,覆盖从基础设备管理到复杂系统操作的需求。本文详细介绍 adb 的安装配置流程,并列举实用命令示例,包括设备连接管理、应用安装调试、文件系统访问等基础功能,以及端口转发、日志查看等高级技巧。此外,还提供了常见问题的故障排除指南,帮助开发者快速解决问题。掌握 adb 将极大提升 Android 开发效率,助力项目顺利推进。
114 0
|
25天前
|
前端开发 数据处理 Android开发
Flutter前端开发中的调试技巧与工具使用方法,涵盖调试的重要性、基本技巧如打印日志与断点调试、常用调试工具如Android Studio/VS Code调试器和Flutter Inspector的介绍
本文深入探讨了Flutter前端开发中的调试技巧与工具使用方法,涵盖调试的重要性、基本技巧如打印日志与断点调试、常用调试工具如Android Studio/VS Code调试器和Flutter Inspector的介绍,以及具体操作步骤、常见问题解决、高级调试技巧、团队协作中的调试应用和未来发展趋势,旨在帮助开发者提高调试效率,提升应用质量。
44 8
|
7月前
|
Android开发
如何在Android真机上检测是否有Google Map add-on
如何在Android真机上检测是否有Google Map add-on
77 3
|
2月前
|
设计模式 Java Android开发
安卓应用开发中的内存泄漏检测与修复
【9月更文挑战第30天】在安卓应用开发过程中,内存泄漏是一个常见而又棘手的问题。它不仅会导致应用运行缓慢,还可能引发应用崩溃,严重影响用户体验。本文将深入探讨如何检测和修复内存泄漏,以提升应用性能和稳定性。我们将通过一个具体的代码示例,展示如何使用Android Studio的Memory Profiler工具来定位内存泄漏,并介绍几种常见的内存泄漏场景及其解决方案。无论你是初学者还是有经验的开发者,这篇文章都将为你提供实用的技巧和方法,帮助你打造更优质的安卓应用。
|
4月前
|
Ubuntu Android开发
安卓系统调试与优化:(一)bootchart 的配置和使用
本文介绍了如何在安卓系统中配置和使用bootchart工具来分析系统启动时间,包括安装工具、设备端启用bootchart、PC端解析数据及分析结果的详细步骤。
222 0
安卓系统调试与优化:(一)bootchart 的配置和使用
|
5月前
|
监控 Java Android开发
探究Android应用开发中的内存泄漏检测与修复
在移动应用的开发过程中,优化用户体验和提升性能是至关重要的。对于Android平台而言,内存泄漏是一个常见且棘手的问题,它可能导致应用运行缓慢甚至崩溃。本文将深入探讨如何有效识别和解决内存泄漏问题,通过具体案例分析,揭示内存泄漏的成因,并提出相应的检测工具和方法。我们还将讨论一些最佳实践,帮助开发者预防内存泄漏,确保应用稳定高效地运行。
|
6月前
|
安全 API Android开发
Android打开USB调试命令
【6月更文挑战第20天】
208 1
|
7月前
|
存储 定位技术 开发工具
Android 开发前的设计,Android之内存泄漏调试学习与总结
Android 开发前的设计,Android之内存泄漏调试学习与总结
|
7月前
|
Android开发
android检测网络连接是否存在(一)
android检测网络连接是否存在(一)
54 2