Modify ptrace to bypass anti-debugging (Android10)

1. Introduction to ptrace in Android 10

 
   1. Source code location tracking

      In Android10, the ptrace function definition file path is:

 bionic/libc/include/sys/ptrace.h

    The function prototype is defined as follows:

long ptrace(int __request, ...);

        The file path of the ptrace function is:

 bionic/libc/bionic/ptrace.cpp

          

    The function implementation code is as follows:

long ptrace(int req, ...) {

  bool is_peek = (req == PTRACE_PEEKUSR || req == PTRACE_PEEKTEXT || req == PTRACE_PEEKDATA);  long peek_result;
  va_list args;  va_start(args, req);  pid_t pid = va_arg(args, pid_t);  void* addr = va_arg(args, void*);  void* data;  if (is_peek) {
   
       data = &peek_result;  } else {
   
       data = va_arg(args, void*);  }  va_end(args);  //最终是调用__ptrace函数  long result = __ptrace(req, pid, addr, data);  if (is_peek && result == 0) {
   
       return peek_result;  }  return result;}

    From the ptrace implementation code, we can see that the __ptrace function is finally called. After code tracing, the __ptrace function is implemented through the assembly of different platforms, and finally calls the __NR_ptrace system call.

   Android arm platform __ptrace implementation code file path:

bionic/libc/arch-arm/syscalls/__ptrace.S

 

The implementation code is as follows:

#include <private/bionic_asm.h>
ENTRY(__ptrace)    mov     ip, r7    .cfi_register r7, ip    ldr     r7, =__NR_ptrace    swi     #0    mov     r7, ip    .cfi_restore r7    cmn     r0, #(MAX_ERRNO + 1)    bxls    lr    neg     r0, r0    b       __set_errno_internalEND(__ptrace)

      

      Android arm64 platform __ptrace implementation code file path:

               

 bionic/libc/arch-arm64/syscalls/__ptrace.S

               

      The implementation code is as follows:

#include <private/bionic_asm.h>
ENTRY(__ptrace)    mov     x8, __NR_ptrace    svc     #0
    cmn     x0, #(MAX_ERRNO + 1)    cneg    x0, x0, hi    b.hi    __set_errno_internal
    retEND(__ptrace).hidden __ptrace

2. Function  

     ptrace can allow a process to monitor and control the execution of another process, and modify the memory and registers of the monitored process. It is mainly used for debugger breakpoint debugging, system call tracking, etc. For example, the strace tool provided by the Android system can track all system calls executed by the app, as shown below:

image

   In Android app protection, ptrace is widely used for anti-debugging. A process can only be ptrace once. If the ptrace method is called first, it can prevent others from debugging our program. This is the legendary preemptive pit. For example, call the following code in JNI_onLoad to occupy the hole first:

 

    if (ptrace(PTRACE_TRACEME, 0, 1, 0) < 0) {
   
           LOGD("I am ptraced");        exit(0);    }else{
   
           LOGD("ptrace success");    }

   You can also use multi-process, ptrace parent process to detect whether it is debugged, the code referenced online is as follows:​​​​​​​

static void anti_ptrace(){
   
       pid_t child;    child = fork();    if (child)    {
   
           //等待子进程退出        wait(NULL);    }    else    {
   
           // 获取父进程的pid        pid_t parent = getppid();        LOGD("ptrace attach start");        // ptrace附加父进程        if (ptrace(PTRACE_ATTACH, parent, 0, 0) < 0)        {
   
               LOGD("ptrace attach failure");            //attach父进程失败,说明被调试了,进入死循环,也可以考虑用杀进程的方式杀掉父进程            //kill(parent,SIGKILL);            while(1);        }        LOGD("ptrace attach success");        //说明没有被调试,附加成功        // 释放附加的进程        ptrace(PTRACE_DETACH, parent, 0, 0);        // 结束当前子进程        exit(0);    }}

Two, source code modification

      

     For the above anti-debugging methods, we can change the ptrace logic in the source code to the following:

long ptrace(int req, ...) {

  bool is_peek = (req == PTRACE_PEEKUSR || req == PTRACE_PEEKTEXT || req == PTRACE_PEEKDATA);  long peek_result;
  va_list args;  va_start(args, req);  pid_t pid = va_arg(args, pid_t);  void* addr = va_arg(args, void*);  void* data;  if (is_peek) {
   
       data = &peek_result;  } else {
   
       data = va_arg(args, void*);  }  va_end(args);

  ///ADD START  int caller_uid=getuid();    //int caller_pid=getpid();  //caller_uid>10000说明是普通App调用  if(caller_uid>10000)  {
   
             if(req ==PTRACE_TRACEME)     {
   
           //自己ptrace自己,直接返回成功        return 0;     }   if(req==PTRACE_ATTACH)   {
   
          int caller_ppid=getppid();     if(caller_ppid==pid)     {
   
           //如果是子进程ptrace父进程,直接返回成功        return 0;     }         //TODO 也可以通过读取/proc/$pid/status获取进程的uid,如果uid和当前调用ptrace的uid一致,也直接返回成功            }  }  ///ADD END  long result = __ptrace(req, pid, addr, data);  if (is_peek && result == 0) {
   
       return peek_result;  }  return result;}

   After completing the modification, execute the following command to compile the flash test:​​​​​​​

source build/envsetup.shbreakfast oneplus3brunch  oneplus3

Three, expansion

    Since the ptrace function is easy to be hijacked, many apps use the ptrace function internally and will not directly call ptrace. Instead, use syscall internally or use inline assembly to implement the function of calling ptrace. Therefore, the kernel hook ptrace module can be developed to take into account various ptrace calls. About kernel system call interception, you can refer to this article: https://www.anquanke.com/post/id/85375.

 

Follow the WeChat public account to get more related articles:

image

Guess you like

Origin blog.csdn.net/u011426115/article/details/113194084