From add882a6e6e331f6ce885a1fc8a8829bfa346731 Mon Sep 17 00:00:00 2001 From: dqzg12300 Date: Sat, 8 Apr 2023 22:46:21 +0800 Subject: [PATCH] =?UTF-8?q?11=E7=AB=A0=E5=86=85=E5=AE=B9=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter-11/README.md | 350 +++++++++++++++++++++++++-- chapter-11/images/ida_attach_err.png | Bin 0 -> 16881 bytes 2 files changed, 329 insertions(+), 21 deletions(-) create mode 100644 chapter-11/images/ida_attach_err.png diff --git a/chapter-11/README.md b/chapter-11/README.md index 69293d6..0629e72 100644 --- a/chapter-11/README.md +++ b/chapter-11/README.md @@ -137,7 +137,7 @@ ps -e|grep 7525 root 7525 7523 10803524 33392 0 0 S android_server64 ``` -​ 除了`status`文件外,`wchan`文件同样可以用来检测。下面是调试附加前,和附加后的对比。 +​ 除了`status`文件外,`/proc//wchan`文件同样可以用来检测。下面是调试附加前,和附加后的对比。 ``` // 附加前 @@ -147,29 +147,337 @@ SyS_epoll_wait ptrace_stop ``` +​ 文件`/proc//stat`也可以用来检测,当进程被中断等待时,内容将会由`S`变成`t`。对比如下。 + +``` +// 附加前 + S 1027 1027 0 0 -1 1077952832 29093 4835 0 0 81 9 0 0 20 0 19 0 424763 15088168960 24716 18446744073709551615 1 1 0 0 0 0 4612 1 1073775864 0 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +// 附加后 +t 1027 1027 0 0 -1 1077952832 29405 4835 0 0 81 9 0 0 20 0 19 0 424763 15088168960 24987 18446744073709551615 1 1 0 0 0 0 4612 1 1073775864 0 0 0 17 1 0 0 0 0 0 0 0 0 0 0 0 0 0 +``` + ### 11.1.2 根据ptrace的特性检测 -​ +​ 由于动态调试基本都是依赖`ptrace`对进程追踪,那么可以通过了解`ptrace`的使用特性,来针对性的检查自身是否被调试了。由于`ptrace`附加进程时,目标进程同时只能被一个进程附加,第二次附加就会失败,那么通过对自身进行`ptrace`处理,如果发现对自己进行附加失败,说明已经被调试了。同时对自身附加后,也能阻止其他进程再对其进行附加调试。下面看实现代码。 -## 11.2 常见反调试绕过方案 - - - -## 11.3 系统层面如何解决反调试 - - - -## 11.4 集成反调试功能 - - - -## 11.5 Android下的硬件调试 - -### 11.5.1 什么是硬件调试 - -### 11.5.2 开启Android的硬件调试 - -### 11.5.3 硬件调试测试 +```c++ +extern "C" JNIEXPORT jstring JNICALL +Java_cn_mik_nativedemo_MainActivity_stringFromJNI( + JNIEnv* env, + jobject /* this */) { + std::string hello = "Hello from C++"; + prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0); + pid_t pid = getpid(); + int ret=ptrace(PTRACE_TRACEME,pid, 0, 0); + // 检测是否正在被调试 + if (ret < 0) { + ALOGD("I'm being debugged! %d\n",ret); + } else { + ALOGD("Not being debugged %d\n",ret); + } + return env->NewStringUTF(hello.c_str()); +} +``` + +​ 在`AOSP12`中,为了增强`Android`系统的安全性,`Google`限制了应用程序使用`ptrace`对自身进行调试。在当前进程中调用`ptrace(PTRACE_TRACEME)`函数将始终返回-1。但是我们可以创建一个子进程,来进行测试。下面是调整后的代码。 + +```c++ +extern "C" JNIEXPORT jstring JNICALL +Java_cn_mik_nativedemo_MainActivity_stringFromJNI( + JNIEnv* env, + jobject /* this */) { + std::string hello = "Hello from C++"; + pid_t mypid = getpid(); + pid_t pid = fork(); + if (pid == -1) { + perror("fork"); + exit(1); + } else if (pid == 0 ) { + // 这里是子进程的代码 + ALOGD("I'm child process, my PID is %d\n", getpid()); + int ret=ptrace(PTRACE_TRACEME,0, 0, 0); + // 检测是否正在被调试 + if (ret < 0) { + ALOGD("I'm being debugged! %d\n",ret); + } else { + ALOGD("Not being debugged %d\n",ret); + sleep(30); + } + } else { + // 这里是父进程的代码 + ALOGD("I'm parent process, my PID is %d and my child's PID is %d\n", mypid, pid); + } + return env->NewStringUTF(hello.c_str()); +} +``` + +​ 然后使用`ida`尝试对子进程进行调试,发现无法正常附加该进程了,错误如下。 + +![image-20230405162058014](.\images\ida_attach_err.png) + +### 11.1.3 其他检测方式 + +​ 除了以上这两种比较常见的检测方式外,还有很多种方式进行检测,这些检测大多都是围绕着调试过程会产生的特征来进行检测,在真实的保护场景下,开发者会结合多种方案检测来防止被攻击者动态调试。以下是其他检测方案的介绍。 + +* `Android`本身提供的`api`判断是否被调试中,`android.os.Debug.isDebuggerConnected()`,这样的检测方法非常容易被`Hook`修改替换。 +* 调试器默认端口检测,例如`ida`默认使用的`23946`,以及调试进程名检测,例如前文中看到的`android_server`进程名称,这种检测方式同样很容易被处理,攻击者会修改默认端口,以及进程名称。 +* 运行效率检测,在函数执行过程计算执行消耗的时间,正常情况下执行效率是非常快的,如果时间较长,说明很有可能被人单步调试中。这种方式属于后知后觉,并不能根本性的阻止对方调试。 +* 断点指令检测,调试器在调试时,会在`so`的代码部分插入`breakpoint`指令,可以通过获取目标`so`的可执行部分,搜索其中是否存在断点的指令。 +* `ro.debuggable`是一个系统级属性,当在调试模式时,该值为1,否则是0,所以有时也会被拿来检测是否被调试中。 + +​ 除了一些常规的检测反调试,还有一些措施是针对反反调试的,例如通常情况下,检测`/proc//status`中的`TracerPid`来判断是否被调试了,而开发者同时也知道,攻击者会选择将`status`文件重定向,或者采取其他方式,让`TracerPid`固定返回0,而这种情况,可以先检测,是否有攻击者将`status`文件进行的特殊出合理,例如先对自己的进程使用`ptrace`,然后检测`status`中的`TracerPid`是否有变更,如果结果为0,说明是被攻击者使用某种手段篡改了该值。 + +​ 由于大多数情况下,反调试手段会被攻击者使用各种`Hook`的方式进行替换处理,所以有些开发者会采用非常规的手段来获取,用来判断是否为调试状态的信息。例如内联汇编通过`svc`来执行对应的系统调用。 + +## 11.2 系统层面的反调试 + +​ 了解常见的反调试检测后,就可以对症进行修改,这些修改并不会完美解决反调试的所有问题,主要是处理掉一些常规的检测办法。来尽量减少分析成本。下面开始简单的对几种检测方式进行修改处理。 + +​ 然后修改属性`ro.debuggable`的值,让其固定显示为0,修改文件`build/make/core/main.mk`,修改代码如下。 + +``` +# ADDITIONAL_SYSTEM_PROPERTIES += ro.debuggable=1 +ADDITIONAL_SYSTEM_PROPERTIES += ro.debuggable=0 +``` + +​ 函数`__android_log_is_debuggable`是`AOSP`中用来快速获取`ro.debuggable`属性的,将该函数默认返回值修改为1。修改如下。 + +```c++ +int __android_log_is_debuggable() { + return 1; +// static int is_debuggable = [] { +// char value[PROP_VALUE_MAX] = {}; +// return __system_property_get("ro.debuggable", value) > 0 && !strcmp(value, "1"); +// }(); +// +// return is_debuggable; +} +``` + +​ 除此之外,还有多个针对文件检测的处理,修改文件`android-kernel/private/msm-google/fs/proc/array.c`,修改如下。 + +```c++ +static inline void task_state(struct seq_file *m, struct pid_namespace *ns, + struct pid *pid, struct task_struct *p) +{ + struct user_namespace *user_ns = seq_user_ns(m); + struct group_info *group_info; + int g, umask; + struct task_struct *tracer; + const struct cred *cred; + pid_t ppid, tpid = 0, tgid, ngid; + unsigned int max_fds = 0; + + rcu_read_lock(); + ppid = pid_alive(p) ? + task_tgid_nr_ns(rcu_dereference(p->real_parent), ns) : 0; + + tracer = ptrace_parent(p); + if (tracer) + tpid = task_pid_nr_ns(tracer, ns); + // 固定tpid为0 + tpid=0; + ... +} +``` + +​ 在这里的`tpid`就是前文中`status`中的`TracerPid`。被调试时,该值将是调试进程`id`,但是考虑到刚刚说的反反调试检测的情况,不能直接固定将文件中的调试特征去掉,而是添加控制,当我们需要调试时,才让其调试的特征不要被检测。这里可以通过应用层和内核层交互,传递参数过来,当该参数的值为1时,就修改其过滤掉调试特征。这里就不详细展开了,继续看下一个特征的修改。 + +​ 同样是在这个文件中,修改函数`get_task_state`,这里同样可以优化成,由值来控制是否使用新的数组,修改内容如下。 + +```c++ +static const char * const task_state_array[] = { + "R (running)", /* 0 */ + "S (sleeping)", /* 1 */ + "D (disk sleep)", /* 2 */ + "T (stopped)", /* 4 */ + "t (tracing stop)", /* 8 */ + "X (dead)", /* 16 */ + "Z (zombie)", /* 32 */ +}; +// 将上面的数组拷贝一个,将T (stopped) 和t (tracing stop)都修改为S (sleeping) +static const char * const task_state_array_no_debug[] = { + "R (running)", /* 0 */ + "S (sleeping)", /* 1 */ + "D (disk sleep)", /* 2 */ + "S (sleeping)", /* 4 */ + "S (sleeping)" , /* 8 */ + "X (dead)", /* 16 */ + "Z (zombie)", /* 32 */ +}; + +static inline const char *get_task_state(struct task_struct *tsk) +{ + unsigned int state = (tsk->state | tsk->exit_state) & TASK_REPORT; + + /* + * Parked tasks do not run; they sit in __kthread_parkme(). + * Without this check, we would report them as running, which is + * clearly wrong, so we report them as sleeping instead. + */ + if (tsk->state == TASK_PARKED) + state = TASK_INTERRUPTIBLE; + // 修改使用新定义的数组 + BUILD_BUG_ON(1 + ilog2(TASK_REPORT) != ARRAY_SIZE(task_state_array_no_debug)-1); + // 使用新定义的数组 + return task_state_array_no_debug[fls(state)]; +} +``` + +​ 最后处理`wchan`的对应代码,修改文件`android-kernel/private/msm-google/fs/proc/base.c`,相关修改如下。 + +```c++ +static int proc_pid_wchan(struct seq_file *m, struct pid_namespace *ns, + struct pid *pid, struct task_struct *task) +{ + unsigned long wchan; + char symname[KSYM_NAME_LEN]; + + wchan = get_wchan(task); + + if (wchan && ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS) + && !lookup_symbol_name(wchan, symname)) + seq_printf(m, "%s", symname); + else{ + // add + if (strstr(symname,"trace")){ + seq_printf(m, "%s", "SyS_epoll_wait"); + } + // addend + seq_putc(m, '0'); + } + return 0; +} +``` + +## 11.3 Android下的硬件断点 + +​ 在调试中,可以通过对程序下不同类型的断点,来辅助分析代码,其中最常见的就是软件断点,软件断点是通过将原有的指令进行替换,在`ARM64`架构中,软件断点通常是通过将原有的指令替换为`BRK`指令`(opcode为0xD4200000)`来实现的。当程序执行到该指令时,处理器会触发一个异常`(trap exception)`,从而停止程序的运行。 + +​ 在软件断点的基础上添加条件判断,就是一个条件断点了,只有满足指定条件才会触发该软件断点。 + +​ 内存断点,是通过修改指定内存的访问属性,让其触发异常,来实现中断的效果。在程序运行时,将要监视的内存地址标记为不可访问,当程序尝试访问该地址时,会触发一个异常,并且操作系统会中断该进程的执行。然后,调试器会根据异常信息来确定是哪个内存地址引起了中断,并且可以进行相应的处理和调试工作。内存断点的实现与硬件断点或软件断点不同,它需要操作系统提供的支持才能实现。由于内存断点的实现需要修改系统的内存映射表等底层数据结构,因此可能会影响程序的性能和稳定性。应该谨慎地选择要监视的内存地址,并避免过多地使用内存断点。 + +### 11.3.1 什么是硬件断点 + +​ 硬件断点是通过CPU内置的调试功能实现的。当程序执行到设置了硬件断点的地址时,CPU会发出一个异常信号,从而让操作系统停止当前进程的执行。然后,操作系统将控制权转移给调试器,并通知调试器哪个线程触发了异常,以便调试器可以进行相应的调试工作。 + +​ 在ARM架构下,硬件断点主要有两种类型:执行断点和数据断点。执行断点可以用于监视代码执行,当程序尝试执行指定的指令时触发中断;数据断点则可以用于监视内存读写操作,当程序尝试访问指定的内存地址时触发中断。执行断点和数据断点都由CPU硬件实现,因此响应速度很快,但数量有限。 + +### 11.3.2 开启Android的硬件调试 + +​ 在开始硬件断点的使用前,首先要进行环境的准备,下面的测试案例将使用`22.0`版本`ndk`中的`gdb`来调试。然后检查当前内核编译选项中是否开启了硬件断点支持。下面是查询过程。 + +```bash +adb shell + +zcat /proc/config.gz |grep -i BREAKPOINT + +// 显示内容如下 +CONFIG_HAVE_HW_BREAKPOINT=y +``` + +​ 如果你的结果显示为`n`,则说明需要在内核中修改配置,不要直接去修改`defconfig`配置,而是使用命令生成`.config`文件,然后修改`.config`文件,再由该文件生成对应的`defconfig`,再将其覆盖原文件,最后重新编译。具体的操作过程如下。 + +``` +cd /root/android_src/android-kernel/private/msm-google + +// b1c1_defconfig是当前设备使用的对应配置,b1c1表示的是pixel3和pixel3 XL的代号 +// 第一步会在当前目录生成.config文件 +make ARCH=arm64 b1c1_defconfig + +// 使用图形界面来开启配置,修改完成后记得保存 +make ARCH=arm64 menuconfig + +// 如果不想在图形界面编辑,可以直接修改.config文件 +vim .config + +// 添加选项 +CONFIG_HAVE_HW_BREAKPOINT=y +CONFIG_HAVE_ARCH_TRACEHOOK=y + +// 选项添加完成后,保存配置,将会生成新的b1c1_defconfig文件 +make ARCH=arm64 savedefconfig + +// 替换原文件 +cp defconfig arch/arm64/configs/b1c1_defconfig + +cd /root/android_src/android-kernel + +// 重新编译 +./build/build.sh +``` + +​ 编译完成,重新刷入手机后,再次查询配置就能看到该选项被开启了。 + +​ `GEF(GDB Enhanced Features)`是一个用于`GDB`调试器的`Python`扩展框架,提供了一些额外的功能,使得在调试过程中更加便捷和高效。`GEF`具有丰富的命令行界面和可扩展性,可以通过编写`Python`脚本来自定义其功能。以下是`GEF`的特点: + +1. 命令行界面友好:`GEF`提供了易于使用的命令行界面,支持自动补全、历史记录和语法高亮等功能,使得调试过程更加简单和快速。 +2. 调试功能强大:`GEF`提供了一系列额外的调试功能,如内存断点、硬件断点、`ASM`混淆解析等,可以显著提高调试效率。 +3. 可扩展性好:`GEF`基于`Python`开发,支持编写自定义脚本来扩展其功能,用户可以根据自己的需求进行定制化。 +4. 平台支持广泛:`GEF`支持多种操作系统和处理器架构,如`Linux、macOS、Windows`,以及`ARM、x86`等常见的处理器架构。 +5. 社区活跃:`GEF`是一个开源项目,拥有庞大的用户群体和贡献者团队,开发进程活跃,问题能够及时得到解决。 + +​ 安装`GEF`非常简单,一句命令即可完成安装。 + +``` +bash -c "$(curl -fsSL https://gef.blah.cat/sh)" +``` + +### 11.3.3 硬件断点测试 + +​ 环境准备就绪后,开发一个简单的应用作为被硬件断点的目标,声明两个全局变量,分别为`int`类型和`char`数组,然后分别对两个变量进行访问和写入。下面是样例代码。 + +```c++ +int test1=1024; +char test2[100]; + +extern "C" JNIEXPORT jstring JNICALL +Java_cn_mik_nativedemo_MainActivity_stringFromJNI( + JNIEnv* env, + jobject /* this */) { + std::string hello = "Hello from C++"; + memset(test2,0,100); + strcpy(test2,"demo"); + ALOGD("test1",test1); + ALOGD("test2",test2); + return env->NewStringUTF(hello.c_str()); +} +``` + +​ 安装该测试样例后,接着将`ndk`中的`gdbserver`传入手机中。命令如下。 + +``` +adb push '/home/king/Android/Sdk/ndk/23.1.7779620/prebuilt/android-arm64/gdbserver/gdbserver' /data/local/tmp/ + +adb shell + +su + +cd /datalocal/tmp + +chmod +x ./gdbserver + +// 在手机上打开测试应用后,查看该应用的pid +ps -e|grep nativedemo + +// 设置 +./gdbserver :1234 --attach 5991 +``` + +​ 接下来使用`gdb`连接上手机中的`gdbserver`,这里需要注意,使用的`gdb`和`gdbserver`的版本需要对应,否则就会导致连接错误的问题。下面是连接的相关操作。 + +``` +// 将gdbserver监听的端口转发到本地 +adb forward tcp:1234 tcp:1234 + +gdb + +// 连接监听的端口 +target remote :1234 + +``` diff --git a/chapter-11/images/ida_attach_err.png b/chapter-11/images/ida_attach_err.png new file mode 100644 index 0000000000000000000000000000000000000000..e68aab4e940ffae35f471d350c2bedbc30e4113f GIT binary patch literal 16881 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 CqjcK< literal 0 HcmV?d00001