• 手机版
    手机扫一扫访问 讯岚社区手机版
  • 关注讯岚微博
    扫一扫关注 讯岚官方微博
论坛聚焦

来源:HackerNews 面向所有尚处于支持状态的Pixel设备,谷歌于今天发布了2019年7月的Android安全

DRx寄存器的使用

[复制链接]
CareyElion实名认证 发表于 2019-7-6 12:58:12 | 显示全部楼层 |阅读模式 打印 上一主题 下一主题
本帖最后由 CareyElion 于 2019-7-6 13:00 编辑

调试寄存器(DRx)理论与实践 By Hume/冷雨飘心 前言+牢骚:
生活的苦痛就象烈火,时时煎熬着伤痕累累疲惫不堪的那颗心。我拼力挣扎,然而却无济于事…… 太残酷了….上帝也在苦笑。        
                                       题记 很多人问Drx调试寄存器的用法,网上实际上有很多资料,但是很多人还是不肯去翻,于是我来写点东西,算是给对DRx尚有疑惑的解答吧。不要在QQ上问我任何问题,如果有错误,请来信指明:Humewen@21cn.com
1.一点理论 ===== 本文假设你已经知道调试器的使用方法以及bpx,bpm,int 3,异常都分别是什么,本文不是一篇超级扫盲教程。 Intel 80386以上的CPU提供了调试寄存器以用于软件调试。386和486包括6个调试寄存器:Dr0,Dr1,Dr2,Dr3,Dr6和Dr7。这些寄存器全是32位,如下图所示:
   |---------------|
----------------| Dr0|                 用于一般断点的线性地址                     
  |---------------|----------------| Dr1|                 用于一般断点的线性地址                     
  |---------------|----------------| Dr2|                 用于一般断点的线性地址                     
  |---------------|----------------| Dr3|                 用于一般断点的线性地址                     
  |---------------|----------------| Dr4|                     保留                              
     |---------------|----------------| Dr5|                     保留                                 
   |---------------|----------------| Dr6|                              |BBB                     BBB B |  
  |                              |TSD                      3 2 1 0 |    |---------------
|----------------
|
Dr7|RWE LEN   ...    RWE LEN    |  G               GLGLGLGLGL |    | 3   3    ...        0    0     |  D               E E 3 3 2 21 100 |
   |---------------|----------------| 31                            15                              
  0 Dr0~3用于设置硬件断点,即在调试器中经常使用的bpm断点,由于只有4个断点寄存器,所以最多只能设置4个bpm断点。Dr7是一些控制位,用于控制断点的方式,Dr6用于显示是哪些引起断点的原因,如果是Dr0~3或单步(EFLAGS的TF)或由于GD置位时访问调试寄存器引起1号调试陷阱的话,则相应设置对应的位。下面对Dr6和Dr7的对应位做一些详细介绍: 调试控制寄存器Dr7:
========== 位0 L0和位1 G0:用于控制Dr0是全局断点还是局部断点,如果G0置位则是全局断点,L0置位则是局部断点。 G1L1~G3L3用于控制D1~Dr3,其功能同上。 LEN0:占两个位,开始于位15,用于控制Dr0的断点长度,可能取值: 00  1字节 01  2字节 10  保留 11  4字节 RWE0:从第17位开始,占两个位,控制Dr0的断点是读、写还是执行断点或是I/O端口断点: 00  只执行 01 写入数据断点 10 I/O端口断点(只用于pentium+,需设置CR4的DE位) 11 读或写数据断点 RWE1~3,LEN1~3分别用于控制Dr1~3的断点方式,含义如上。 还有一个GD位:用于保护DRx,如果GD位为1,则对Drx的任何访问都会导致进入1号调试陷阱。即IDT的对应入口,这样可以保证调试器在必要的时候完全控制Drx。 调试状态寄存器Dr6: ========= 该寄存器用于表示进入陷阱1的原因,各个位的含义如下: B0~B3,如果其中任何一个位置位,则表示是相应的Dr0~3断点引发的调试陷阱。但还需注意的是,有时候不管GiLi如何设置,只要是遇到Drx指定的断点,总会设置Bi,如果看到多个Bi置位,则可以通过GiLi的情况判断究竟是哪个Dr寄存器引发的调试陷阱。 BD置位表示是GD位置位情况下访问调试寄存器引发的陷阱。 BT置位表示是因为TS置位即任务切换时TSS中TS位置1时切到第二个任务时第一条指令时引发的。 BS置位表示是单步中断引发的断点。。。。即EFLAGS的TF置位时引发的调试陷阱。 注意I/O端口断点是586+以上CPU才有的功能,受CR4的DE位的控制,DE为1才有效。(DE是CR4的第3位)。
2、一点常识 ====== 如果你使用调试器的话,一定清楚bpx断点,bpx实际上就通过在代码中插入int 3(0xCC或0xCD03),将引发int 3中断。具体的intel IA-32保护模式异常机制并不是我三言两语能解释清楚的,如必要请参照相关资料。 Bpm和BPIO断点是利用CPU的硬件调试器设置的断点。 有些调试功能只在586+以上CPU才能使用,为增强兼容性用CPUID测试。(测试方法见我后续文章。) 关于调试API,是Windows提供给开发者的调试原API。具体实现涉及windows的内部机制,不是简单的int x就能解释清楚的,有兴趣者可参阅相关资料,或等我有时间胡说一通。
3、一点实践 设置BPM断点很简单,只要相应设置Drx即可,产生的异常是STATUS_SINGLE_STEP,只要用调试API或SEH或VEH处理一下即可。下面是9X下测试BPM断点的一个例子: A::-> ----------- COMMENT/* 仅工作于9X下,BPM产生的异常类型是SINGLE_STEP 可用来对付调试器,反跟踪,至于效果,试试就知道了 这里采用SEH来设置9x下的Drx寄存器 还可采用GetThreadContext、SetThreadContext设置Drx的值 NT类系统由于安全机制,无法使用该办法 需使用Debug技术 */ include c:\hd\hhd.h include c:\hd\drx.h ASSUME FS:NOTHING ;~~~~~~~~~~~~~~~~~~~ .CODE _StArT:   
      int 3         SLDT    cx         JCXZ    isNT         JMP     @F      isNT:      
   MsgBox  CTEXT("NT Series Not Work! only 9X!")         JMP     _XXX_      @@:         
call    instSEH      XH01       PROC    C  pExcept,pFrame,pContext,pDispatch    Not minimal form         ASSUME  ESITR EXCEPTION_RECORD,EDITR CONTEXT         MOV     ESI,pExcept         MOV     EDI,pContext         MOV     EAX,1         TEST    [ESI].ExceptionFlags,7
        JNZ     @@Not_handled         cmp     [esi].ExceptionCode,STATUS_ILLEGAL_INSTRUCTION         jz      illegal_instr         cmp     [esi].ExceptionCode,STATUS_SINGLE_STEP         JZ      BPM0_ISOK         jmp     @@Not_handled       BPM0_ISOK:   
     MOV     [EDI].regEip,OFFSET MSGbpmOK         JMP     SEHexit         //Set the Dr0 bpm Global BreakPoint       illegal_instr:   
     MOV     [EDI].ContextFlags,CONTEXT_DEBUG_REGISTERS or CONTEXT_FULL         MOV     [EDI].iDr7,M_INSTR0 or M_GDR0 or M_BYTE0         MOV     [EDI].iDr0,OFFSET bpm01         //ByPASS the INVALID INSTRS         ADD     [EDI].regEip,2                  SEHexit:      
  DEC     EAX       @@Not_handled:         ret      XH01       ENDP      instSEH:         LEA     eax,[esp-4]         XCHG    eax,fs:[0]         push    eax         NOP         DB 0Fh,0Bh              INVALID INSTRS ON ALL PLATFORMS =UD2      bpm01:               
      SIMPLE ANTI DEBUGER         NOP         jmp     bpm01      _XXX_:   
     POP     fs:[0]         pop     EAX    invoke ExitProcess,0      MSGbpmOK:   
      MsgBox  CTEXT("Hello BPM01 TEST SUC,Prepare TO EXIT")         JMP     _XXX_ END _StArT WINNT下设置断点并非如此简单,由于严格的检查机制,原来的SEH的方法或简单的GetThreadContext方法已经失效。那么要我们写驱动吗?大可不必,因为WIndows还提供了调试API,使用调试API可以为目标程序设置断点: WINNT下设置断点并非如此简单,由于严格的检查机制,原来的SEH的方法或简单的SetThreadContext方法已经失效。那么要我们写驱动吗?大可不必,因为WIndows还提供了调试API,使用调试API可以为目标程序设置断点。使用调试程序设置断点仍然使用GetThreadContext和SetThreadContext两个API。 在看下一个例子之前,还是让我们看看著名的IczeLion的教程中利用调试API检测程序执行了多少指令这一例子,这个例子在9X下运行正常,但在2K/Xp下却显示程序初始化失败。究其原因在于在2K/Xp下程序收到第一个断点异常时被调试程序的主线程内容并未准备好,是无效的!很奇怪但确实如此,这时根本不能通过CONTEXT设置断点或单步等。解决方案之一是Elicz提出来的,就是先在调试程序收到的第一个断点中断中给NtContinue设一bpm断点,截获其第一个参数,这个参数就是指向即将初始化为主线程有效CONTEXT内容的指针,通过在其内设置一个bpm断点,就可以中断在我们想要中断的任何位置。 下面是改造过的计算程序执行指令条数的例子,测试结果显示,一个很简单的程序, include c:\hd\hhd.h .DATA? hInstance dd ?
;;----------------------------------------- .CODE _StArT:  
       nop         nop mov hInstance,$invoke(GetModuleHandle,0) MsgBox  CTEXT("Hello World") invoke ExitProcess,0 END _StArT 执行的内核代码+程序代码19196764条,如果只想获得程序执行的程序代码,还是有办法的,自己去想啦。 本文不是讲述调试API的使用,使用见IczeLion的教程: ; include c:\hd\hhd.h include c:\hd\drx.h BREAK_RVA EQU   401000H         断点。。。。 ;~~~~~~~~~~~~~~~~~~~ .DATA dwSScnt dd 0 sinstr  dd 0         pi      PROCESS_INFORMATION <> sif     STARTUPINFO  Dev     DEBUG_EVENT <> Rs      CONTEXT  buf     db 256 dup(?) dwBuf   dd 0 dwBytes dd 0 ;;----------------------------------------- .CODE WipeContextBPdr0        Proc         invoke GetThreadContext,pi.hThread,addr Rs         mov        Rs.iDr0,0         mov        Rs.iDr7,0         invoke SetThreadContext,pi.hThread,addr Rs         ret WipeContextBPdr0        Endp ;----------------------------------------- _StArT:         SLDT    CX         JCXZ    @F         MsgBox  CTEXT("9x Not supported")         JMP     pExit         @@:         invoke GetStartupInfo,addr sif         SUB     EAX,EAX invoke CreateProcess,0,CTEXT("target01.exe"),EAX,EAX,EAX,\                 DEBUG_PROCESS or DEBUG_ONLY_THIS_PROCESS,EAX,EAX,addr sif,addr pi         JEAXZ   crp_fail             //some basic text macro              //why so many ppl use long names,aren't they tired ?             wmDevent    EQU Dev.dwDebugEventCode             excCode     EQU Dev.u.Exception.pExceptionRecord.ExceptionCode             excAddr     EQU Dev.u.Exception.pExceptionRecord.ExceptionAddress             pDllName    EQU Dev.u.LoadDll.lpImageName             lpBase      EQU Dev.u.LoadDll.lpBaseOfDll             //         .WHILE TRUE             invoke WaitForDebugEvent,addr Dev,INFINITE             mov                      .IF wmDevent==EXIT_PROCESS_DEBUG_EVENT                 MsgBox CTEXT("target Exit...")                 .break             .ELSEIF wmDevent==LOAD_DLL_DEBUG_EVENT                 //to see how many DLLS were LOADED                 invoke wsprintf,addr buf,CTEXT("DLL BASE =: %08X"),lpBase                                                        invoke MessageBox,0,addr buf,0,0                 JMP  DBG_con             .ELSEIF wmDevent==EXCEPTION_DEBUG_EVENT                 .if  excCode==STATUS_BREAKPOINT                      invoke GetThreadContext,pi.hThread,addr Rs                      invoke GetProcAddress,$invoke(GetModuleHandle,CTEXT("NTDLL.DLL")),CTEXT("NtContinue")                      JEAXZ      end_debug                      mov        Rs.iDr0,EAX             设置NtContinue的断点                      mov        Rs.iDr7,M_LDR0 or M_INSTR0                      invoke SetThreadContext,pi.hThread,addr Rs                      JMP  DBG_con                 .elseif excCode==STATUS_SINGLE_STEP                      INC        dwSScnt                      .if        dwSScnt==1                         //清除NtContinue的bpm断点                                                   invoke WipeContextBPdr0                          MOV    EDX,Rs.regEsp                          ADD    EDX,4           //获取NtContinue的第一个参数pContext                          //读入pContext值,因为是两个进程要用ReadProcessMemory                          invoke ReadProcessMemory,pi.hProcess,EDX,addr dwBuf,sizeof DWORD,addr dwBytes                          //读入真正的CONTEXT值                          invoke ReadProcessMemory,pi.hProcess,dwBuf,addr Rs,sizeof CONTEXT,addr dwBytes                          mov    Rs.iDr0,BREAK_RVA                          mov    Rs.iDr7,M_LDR0 or M_INSTR0                          invoke WriteProcessMemory,pi.hProcess,dwBuf,addr Rs,sizeof CONTEXT,addr dwBytes                          JMP    DBG_con                      .elseif    dwSScnt==2                          inc    sinstr                          invoke WipeContextBPdr0        //清除断点                          invoke wsprintf,addr buf,CTEXT("Break Address: %08X"),excAddr                          MsgBox addr buf                      .endif                      comment $                      //ICZELION's Example,uncomment this                      inc        sinstr                      invoke GetThreadContext,pi.hThread,addr Rs                      invoke wsprintf,addr buf,CTEXT("XINSTR ADDR %08X"),Rs.regEip                      MsgBox     addr buf                      or         Rs.regFlag,100h                      invoke SetThreadContext,pi.hThread,offset Rs                      $                      JMP  DBG_con                 .endif                  .ENDIF                DBG_no:     //continue Debug events without handling             invoke ContinueDebugEvent,Dev.dwProcessId,Dev.dwThreadId,DBG_EXCEPTION_NOT_HANDLED             .continue       DBG_con:             invoke ContinueDebugEvent,Dev.dwProcessId,Dev.dwThreadId,DBG_CONTINUE         .ENDW                  invoke wsprintf,addr buf,CTEXT("total : %08d Instructions"),sinstr         MsgBox  addr buf     end_debug:         invoke CloseHandle,pi.hProcess         invoke CloseHandle,pi.hThread     pExit: invoke ExitProcess,0     crp_fail:         MsgBox  CTEXT("Create Process Failed!")         jmp     pExit END _StArT 只要能使用Drx的断点功能就可以配合SEH、调试API进行一些反跟踪等,具体怎么用,取决于你自己了。 〔完〕 ================================================================================================= 一些常量定义(FOR MASM) ; for DR6 M_HIT0                         EQU 1 M_HIT1                         EQU 2 M_HIT2                         EQU 4 M_HIT3                         EQU 8 M_BD                         EQU 2000H         DRX access M_BS                         EQU 4000H         single step M_BT                         EQU 8000H         task switch ;----------------------------------------- ; for DR7 M_LDR0                         EQU 01           Li mask for Dr7 M_LDR1                         EQU M_LDR0    SHL 02 M_LDR2                         EQU M_LDR1    SHL 02 M_LDR3                         EQU M_LDR2    SHL 02 M_LDRALL EQU M_LDR0 or M_LDR1 or M_LDR2 or M_LDR3 M_GDR0                         EQU 02           Gi mask for Dr7 M_GDR1                         EQU M_GDR0    SHL 02 M_GDR2                         EQU M_GDR1    SHL 02 M_GDR3                         EQU M_GDR2    SHL 02 M_GDRALL EQU M_GDR0 or M_GDR1 or M_GDR2 or M_GDR3 ;----------------------------------------- M_LE                         EQU 01      SHL 08   局部断点精确相符 M_GE                         EQU M_LE     SHL 01   全局断点精确相符 ;DRX access M_GD                         EQU M_BD              drx保护位置一即使在ring0也产生int 1 ;instruction fetch M_INSTR0                        EQU 00 M_INSTR1                        EQU 00 M_INSTR2                        EQU 00 M_INSTR3                        EQU 00 ;memory write M_WRITE0                        EQU 01 SHL 16 M_WRITE1                        EQU M_WRITE0 SHL 04 M_WRITE2                        EQU M_WRITE1 SHL 04 M_WRITE3                        EQU M_WRITE2 SHL 04 ;port watches: 586+ only and CR4 bit DE (=04  bit 3 in termiology) must be 1 ;M_DE EQU 04 M_DE EQU 08                 M_PORT0                         EQU 02 SHL 16 M_PORT1                         EQU M_PORT0  SHL 04 M_PORT2                         EQU M_PORT1  SHL 04 M_PORT3                         EQU M_PORT2  SHL 04 ;memory access Read or Write! M_RW0                         EQU 03 SHL 16 M_RW1                         EQU M_RW0    SHL 04 M_RW2                         EQU M_RW1    SHL 04 M_RW3                         EQU M_RW2    SHL 04 M_RWALL EQU M_RW0 or M_RW1 or M_RW2 or M_RW3 ;lengths M_BYTE0                         EQU 00           use byte for instruction by default always M_BYTE1                         EQU 00            for data BreakPoints,these exist M_BYTE2                         EQU 00 M_BYTE3                         EQU 00 M_WORD0                         EQU 01 SHL 18 M_WORD1                         EQU M_WORD0  SHL 04 M_WORD2                         EQU M_WORD1  SHL 04 M_WORD3                         EQU M_WORD2  SHL 04 M_DWORD0                        EQU 03 SHL 18 M_DWORD1                        EQU M_DWORD0 SHL 04 M_DWORD2                        EQU M_DWORD1 SHL 04 M_DWORD3                        EQU M_DWORD2 SHL 04
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

周一至周日:09:00-22:00 400-888-4568

扫码讯岚官方微博

Powered by 讯岚社区 X3.4© 2017-2019 xkmchenmu Blog Inc.