zoukankan      html  css  js  c++  java
  • linux ptrace学习

         ptrace提供了一种使父进程得以监视和控制其它进程的方式,它还能够改变子进程中的寄存器和内核映像,因而可以实现断点调试和系统调用的跟踪。学习linux的ptrace是为学习android adbi框架和古河的libinject做基础。

      ptrace有四个参数:long ptrace(enum __ptrace_request request,pid_t pid,void *addr,void *data);第一个参数是重点,可设如下值:

    PTRACE_ME    ptrace(PTRACE_ME,0 ,0 ,0);本进程被其父进程所跟踪
    
    PTRACE_ATTACH  ptrace(PTRACE_ATTACH,pid);跟踪指定pid 进程
    PTRACE_PEEKTEXT  ptrace(PTRACE_PEEKTEXT, pid, addr, data);从内存地址中读取一个字节,内存地址由addr给出 PTRACE_PEEKDATA  ptrace(PTRACE_PEEKDATA, pid, addr, data);从内存地址中读取一个字节,内存地址由addr给出 PTRACE_PEEKUSER  ptrace(PTRACE_PEEKUSR, pid, addr, data)从USER区域中读取一个字节,偏移量为addr PTRACE_POKETEXT  ptrace(PTRACE_POKETEXT, pid, addr, data);往内存地址中写入一个字节。内存地址由addr给出 PTRACE_POKEDATA  ptrace(PTRACE_POKEDATA, pid, addr, data);往内存地址中写入一个字节。内存地址由addr给出 PTRACE_POKEUSER  ptrace(PTRACE_POKEUSR, pid, addr, data);往USER区域中写入一个字节。偏移量为addr PTRACE_GETREGS   ptrace(PTRACE_GETREGS, pid, 0, data);读取寄存器 PTRACE_GETFPREGS  ptrace(PTRACE_GETFPREGS, pid, 0, data);读取浮点寄存器   PTRACE_SETREGS   ptrace(PTRACE_SETREGS, pid, 0, data);设置寄存器 PTRACE_SETFPREGS  ptrace(PTRACE_SETREGS, pid, 0, data);设置浮点寄存器 PTRACE_CONT   ptrace(PTRACE_CONT, pid, 0, signal);继续执行,signal为0则忽略引起调试进程中止的信号,若不为0则继续处理信号signal PTRACE_SYSCALL  ptrace(PTRACE_SYS, pid, 0, signal);内核在子进程做出系统调用或者准备退出的时候暂停它;包含2个步骤:继续执行+系统调用是停止 PTRACE_SINGLESTEP  ptrace(PTRACE_KILL, pid, 0, signle);设置单步执行标志,单步执行一条指令 PTRACE_DETACH  ptrace(PTRACE_DETACH,pid);结束跟踪

    PTRACE_KILL  ptrace(PTRACE_KILL,pid);杀掉子进程,使它退出
    
    
    上面就是ptrace函数的用法,大体可以分为2类:控制流程——singleStep、cont...;获取或设置内容——getRegs、setRegs...。内容的获取和设置就不多说,拿张图来看下如何控制子进程执行:
      
    1.子进程被ptrace
    2.父进程调用ptrace带参数PTRACE_SYSCALL,让子进程在进入和退出系统调用时暂停;调用完ptrace后,wait触发
    3.子进程要调用syscall(为何syscall?调用系统函数)了,此时因为父进程之前调用了
    PTRACE_SYSCALL,内核会暂停子进程并给父进程发信号
    4.父进程得到信号后结束wait函数,去调用自己的函数去处理;处理完后调用带PTRACE_SYSCALL参数的ptrace函数,并wait
    5.内核在父进程的ptrace函数后,让子进程继续执行;
    6.子进程如愿的完成系统调用,但在退出系统调用前。因为父进程调用了PTRACE_SYSCALL,所以内核又暂停子进程并发信号给父进程
    7.父进程得到信号从wait中退出,去执行自己的函数;
    处理完后调用带PTRACE_SYSCALL参数的ptrace函数,并wait
    8.内核在父进程的ptrace函数后,让子进程继续执行;子进程退出系统调用继续执行直到下一个系统调用
    ......
    通过上面这么啰嗦的步骤,我想你一定get到ptrace是如何控制代码的执行了。当然上面仅仅是PTRACE_SYSCALL,但方法是相通的。ok,本来接下去应该是要实践下了。但是鉴于网上代码太多(直接看参考资料),本人又玩不出新花样,就此略过。这里解释下关于参考资料中乌云的那篇文章的几个知识点:
    1.getSysCallNo
    ARM架构上,所有的系统调用都是通过SWI(Dos下int指令类似)来实现的。并且在ARM 架构中有两个SWI指令,分别针对EABI和OABI:
    OABI:old abi
    mov   r0,#34       //设置子功能号位34
    SWI   12            //调用12号软中断    
    
    EABI:extend abi
    mov  r0,#12       //  ;调用12号软中断
    mov r1,#34        // ;设置子功能号位34
    SWI  
    
    SWI{cond}      immed_24     // ;immed_24为软中断号(服务类型)
    //    1110 1111 0000 0000 -- SWI 0
      而为什么是获取(regs->ARM_pc - 4)地址的内容呢?先解释下pc的概念,pc是取指令的地址对于普通架构pc=当前执行指令地址+1*指令长度;而对于armv7的三级(取值、译码、执行)流水线来说pc=当前指令地址+8(2*4指令长度,arm指令32位,thunb指令16位)。而发生ptrace时,SWI指令是处于译码,故SWI指令的地址为PC-4(不同之处请一起探讨)。
    2.libinject
    libinject中利用ptrace加载自定义so去执行自定义函数,其中获取系统函数地址涉及到/proc/pid/maps(可以看Linux Tips
    )知识且运用了linux中类似list_entry技术:
    "因为libc.so在内存中的地址是随机的,所以我们需要先获取目标进程的libc.so的加载地址,再获取自己进程的libc.so的加载地址和sleep()在内存中的地址。然后我们就能计算出sleep()函数在目标进程中的地址了。”

    ptrace android源码位于/bionic/libc/bionic/ptrace.cpp

    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);
    
      long result = __ptrace(req, pid, addr, data);
      if (is_peek && result == 0) {
        return peek_result;
      }
      return result;
    }

       看出实际是调用_ptrace来实现的,位于/bionic/libc/arch-arm/syscalls/__ptrace.S

    ENTRY(__ptrace)
        mov     ip, r7
        ldr     r7, =__NR_ptrace
        swi     #0
        mov     r7, ip
        cmn     r0, #(MAX_ERRNO + 1)
        bxls    lr
        neg     r0, r0
        b       __set_errno_internal
    END(__ptrace)

       直接使用SWI调用系统函数ptrace,而_NR_ptrace是个宏定义为系统的调用号(参考android调用号和libc)。下篇以本文知识点开启adbi之旅。

    参考资料:

      1 玩转ptrace

      2 安卓动态调试七种武器之离别钩 – Hooking(上)

         3 为何ARM7中PC=PC+8

         4 ptrace 跟踪多线程程序

  • 相关阅读:
    JAVA中handleEvent和action的区别
    Hessian的使用以及理解
    Java基础中的RMI介绍与使用
    Callable与Runable接口 submit与execute区别
    XXL-JOB原理--定时任务框架简介(一)
    11.并发包阻塞队列之LinkedBlockingQueue
    并发队列ConcurrentLinkedQueue和阻塞队列LinkedBlockingQueue用法
    正确实现用spring扫描自定义的annotation
    自贡进入“刷脸卡”时代 人脸识别支付“黑科技”现身自流井老街
    谷歌最新研究:量子计算机能在8小时内破解2048位RSA加密
  • 原文地址:https://www.cnblogs.com/vendanner/p/5002702.html
Copyright © 2011-2022 走看看