windbg入门初探心得之--ZwContinue之后给启动新启动的线程下断点绕过反调试技术

最近在用wgdbg逆向MFC程序,windbg打开的程序一开始进去的时候是会停在ntdll.dll的0xcc也就是int 3处。
在这里插入图片描述
之后一边查看栈一边step in和step out,希望能够结束程序的初始化流程,尽快到达程序入口点,当然对于有经验的师傅来说直接使用bp $exentry 就能够直接下断在程序入口点。但对于我这种小白来说,喜欢一步步调试,那么我们继续走起。
很快啊,我们看到程序进到了一个函数:
KiUserApcDispacher,在这个函数里面,我们又进入了,ZwContinue,step into 看看:
在这里插入图片描述
之后又从ZwContinue跳到了KiFastSystemCall
在这里插入图片描述
进入到这个系统调用之后,后面就是内核态的代码了,windbg就不能访问,我们就只能等到内核态的代码结束返回到用户态的时候才能继续单步调试,但问题是我继续按下step into之后后面的程序系统调用完就一直跑下去了,我的调试器根本就没办法跟进上去,这是为什么呢?
后来我查到了这个帖子,并在black binary师傅的指导下了解到,windows x86程序下软件断点到本质是在下断点处到内存里写入0xcc,而step into单步调试到本质则是通过设置cpu的flag 寄存器里面陷阱标志T位来实现的。
程序在进入到ZwContinue这个函数之后会通过系统调用启动一个新的线程并清空我们flag寄存器里面的陷阱标志T位,导致接下来我们的程序就不能单步调试了,相当于go继续执行。

在那个帖子里面,我们可以了解到ZwContinue这个函数的作用是启动新线程,其中第一个参数IN PCONTEXT ThreadContext是一个结构体,表示的是新线程的信息,而在这个结构体里面,有个成员叫做eip会表示新线程的起始地址,我们只要使用命令bp address给他下断就可以了。
那么这个结构体的情况如何呢?我们就要去ntddk.h头文件里面去找了:

typedef struct _CONTEXT {
  ULONG              ContextFlags;
  ULONG              Dr0;
  ULONG              Dr1;
  ULONG              Dr2;
  ULONG              Dr3;
  ULONG              Dr6;
  ULONG              Dr7;
  FLOATING_SAVE_AREA FloatSave;
  ULONG              SegGs;
  ULONG              SegFs;
  ULONG              SegEs;
  ULONG              SegDs;
  ULONG              Edi;
  ULONG              Esi;
  ULONG              Ebx;
  ULONG              Edx;
  ULONG              Ecx;
  ULONG              Eax;
  ULONG              Ebp;
  ULONG              Eip;
  ULONG              SegCs;
  ULONG              EFlags;
  ULONG              Esp;
  ULONG              SegSs;
  UCHAR              ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];
} CONTEXT;

这个结构体里面的ULONG都是4字节的,而里面的FLOATING_SAVE_AREA的数据就要在winsdk的nti386.h查了:

typedef struct _FLOATING_SAVE_AREA {
    ULONG   ControlWord;
    ULONG   StatusWord;
    ULONG   TagWord;
    ULONG   ErrorOffset;
    ULONG   ErrorSelector;
    ULONG   DataOffset;
    ULONG   DataSelector;
    UCHAR   RegisterArea[SIZE_OF_80387_REGISTERS];
    ULONG   Cr0NpxState;
} FLOATING_SAVE_AREA;

这里的SIZE_OF_80387_REGISTERS是80大小。
而我们恰好在这个图里面call zwcontinue之前有push edi,这个edi自然就是我们这个结构体的地址:
在这里插入图片描述
我们使用命令dd edi看一下edi指向的结构体的内容:
在这里插入图片描述
可以看到edi开始的00010017对应的是结构体里面的ContextFlags。再根据这个图简单算一下,edi+0xb8就是结构成员的ULONG Eip;的值。
可以看到dd edi+0xb8的结果是7c8106f5
那么我们只要在这个地址下断点就好了。
一开始我用的是命令bp edi+b8下断点后来发现这实际上是下断点到栈上面去了,这相当于在栈上面插入0xcc。
这就导致了我后来的这个报错:

(720.474): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00456ada ebx=7ffd5000 ecx=020fa685 edx=00000082 esi=00c5f76a edi=00c5f6ee
eip=7c8106cc esp=0012fffc ebp=00000280 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000 efl=00010202
kernel32!CreateThread+0x5:
7c8106cc ff751c push dword ptr [ebp+1Ch] ss:0023:0000029c=???

这个报错在还没到达断点0x7c8106f5的时候就发生了。
在这里插入图片描述

正确的下断点方法应该是断在栈上面的这个指针指向的地址,也就是使用命令bp 7c8106f5即可。
之后就可以愉快的继续从断点单步了:
在这里插入图片描述

这是一个普通pe程序的初始化过程,可能很多师傅调试的时候直接从入口点开始调,就没注意这些细节,但事实上这些细节可以拿来做反调试,比如你在程序段里面手动调用ZwContinue那么就能够清空你的陷阱信息,让你不能单步调试,这也就是为什么那个帖子的名字叫做"绕过ZwContinue"。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值