反调试实战系列一 x64dbg+IDA 过 IsDebuggerPresent

前言

挺久之前就想开一个反调试系列的坑,而且正好可以用这个实战系列来巩固所学

这次分析的程序为 64 位,用到的调试工具有:IDA Pro (64-bit) 和 x64dbg

这次的 CrackMe 并不难,有兴趣的可以尝试先自己动手破解看看


反调试介绍

在实战之前,先大致介绍一下反调试

什么是反调试

反调试技术,顾名思义就是用来防止被调试的一种技术

简单的反调试往往是识别是否被调试,如果是则退出程序,封禁账号等等        (检测)

再复杂些可以在反汇编代码中插入花指令,使调试器的反汇编引擎无法正确解析反汇编指令(干扰)

门槛较高的反调试则可以是从驱动层将调试权限清零,使得调试器失效等等        (权限清零)

反调试的手段可以大致归纳为:检测、干扰、权限清零 三种


反调试的常见手段

反调试手段层出不穷,可以分为两类:

  • Ring0(内核层反调试)
  • Ring3(应用层反调试)

Ring3

Windows API 中提供了两个用于检测是否被调试的函数:

  • IsDebuggerPresent
  • CheckRemoteDebuggerPresent

Windows API 作用
IsDebuggerPresent 确定调用进程是否由用户模式调试器进行调试
CheckRemoteDebuggerPresent 确定是否正在调试指定的进程

除了使用上述两个 Windows API 外 还有不少在 Ring3 下的反调试手段:

检测 PEB(Process Environment Block)进程环境块 中的 BeingDebuggedProcessHeap 等

除了检测 PEB 外,还可以检测 软件断点、硬件断点代码校验等等

除了检测,之前提到的插入花指令进行干扰也属于 Ring3 中的反调试手段

在 Ring3 下的反调试手段 五花八门,这里仅列举出了一小部分


Ring0

在 Ring0 下的反调试保护,TenProtect 不可谓不强,在先前的【原创】TP 驱动保护分析系列一 定位 TenProtect 保护中已经提及

这里限于篇幅原因不再赘述


反调试实战

前面稍微补充了一点关于反调试的知识,接下来正式进入实战环节

要调试的程序

为了更好地起到学习作用,这次要调试的程序是我自己写的一个小 demo,是一个 MFC 程序(练习了一波 MFC)

image-20210503173142596

界面比较简陋,毕竟是个小 demo,不要介意

关于这个 demo 的代码之后也会放在后面的附件里,有需要的可以自行取用 (* ̄3 ̄)╭


实战流程

查壳

首先使用 PE 工具:DIE(Detect It Easy) 查壳

image-20210503182249040

可以看到,程序并没有加壳,并且是 64 位程序、用 MFC 编写


关闭 ASLR

使用 MFC 编译出的 64 位程序默认是开启 ASLR 的,不利于调试,需要先关闭


什么是 ASLR

ASLR 全称 Address Space Layout Randomization,又称地址空间配置随机化地址空间布局随机化


ASLR 的作用

地址空间配置随机加载利用随机方式配置数据地址空间,使某些敏感数据配置到一个恶意程序无法事先获知的地址,令攻击者难以进行攻击

粗俗地说,就是使得每次调试工具(如 OD、x64dbg 等)加载程序后,地址是随机动态分配的,无法使用固定的地址进行定位


ASLR 的体现

上面纯粹的说明可能不是很直观,接下来使用 x64dbg 载入程序

image-20210503184317064

可以看到,x64dbg 默认是中断在了系统断点,我们需要它运行到 OEP(程序入口点)

使用快捷键:ALT+F9 运行到 OEP(程序没有加壳,所以可以运行到 OEP),或者 调试→运行到用户代码


到达OEP


得到了:

image-20210503185325655

可以看到此时的 EntryPoint 为:

 复制代码 隐藏代码
00007FF6950D14F1 | E9 CADE0000              | jmp <crackme.wWinMainCRTStartup>              |

记录下此时的地址为:00007FF6950D14F1

如果学习过 PE 就会知道 EntryPoint 的地址 = EntryPoint + ImageBase

(不了解这个知识点的可以回顾:PE 文件笔记五 PE 文件头之扩展 PE 头)

image-20210503185622558

从前面的 DIE 工具的查看中可以得到:

正常的 EntryPoint 的地址 = EntryPoint + ImageBase  = 0x114F1 + 0x140000000 = 0x1400114F1

但是此时的地址很明显不等于 0x1400114F1,这就是 ASLR 的体现


使用 PE 工具关闭 ASLR

知道了 ASLR 会干扰调试,于是要使用 PE 工具关闭 ASLR

关闭ASLR

ASLR 由 扩展 PE 头中的 DllCharacteristics 决定,关于 DllCharacteristics 可参考:DllCharacteristics


验证 ASLR 的关闭

关闭完 ASLR,再使用 x64dbg 载入程序,查看此时的 OEP:

image-20210503191219952

可以发现,此时的 EntryPoint 地址就和前面计算出来的地址一致,为:0x1400114F1


x64dbg 定位反调试

载入程序以后,要先让程序跑起来再设置相关的 API 断点,于是先运行

使用快捷键:F9 使得程序运行起来

但是当运行 F9 后,会发现程序直接退出了(不使用调试工具时程序是可以正常运行的);这也就是本帖的关键了:反调试

通过上面的操作,可以推测出:程序检测当前是否正在被调试,如是是则直接退出程序

推断出大致的流程后,可以写出伪代码:

 复制代码 隐藏代码
void AntiDebug(){                //反调试函数
    bool IsBeingDebugged=checkIsDebug();//通过某种方式判断当前程序是否正在被调试
    if (IsBeingDebugged){        //如果正在被调试
        exit();                                //退出程序
    }
}

可以得出调用情况为:AntiDebug→exit

于是可以从 exit (退出程序) 入手,开始定位

退出程序一般会使用到 ExitProcess () 这个 Windows API,于是对这个函数下断点

image-20210503192924459

在底下的命令行输入:

 复制代码 隐藏代码
bp ExitProcess

然后可以得到:

image-20210503193044899


确定设置完断点后,按 F9 让程序运行起来,然后断点断下:

image-20210503193240187


注意堆栈中调用情况:

image-20210503193317550

找到调用的该程序的函数,可以怀疑这个函数相当于 AntiDebug 函数(用来反调试)


image-20210503193514900

选中这一行,然后回车,查看其对应的反汇编


得到:

image-20210503193606227

不难发现在调用 exit 函数的前面调用了 IsDebuggerPresent 来检测是否被调试


IDA Pro 分析反调试

为了更清晰地分析代码,使用 x64dbg 定位到了关键函数后,可以搭配 IDA Pro 进行分析

使用 IDA Pro 载入程序后,按 G 键弹出:

image-20210503194315804


这里要跳转的地址为前面得到的地址:这里填 14001A1D8

image-20210503194407263


跳转后得到:

image-20210503194442007


选中函数的头部,按快捷键:F5 查看其对应的伪代码:

image-20210503194618513


得到:

image-20210503194802393


即:

 复制代码 隐藏代码
__int64 StartAddress_0()
{
  __int64 *v0; // rdi
  __int64 i; // rcx
  __int64 v3; // [rsp+0h] [rbp-30h] BYREF

  v0 = &v3;
  for ( i = 70i64; i; --i )
  {
    *(_DWORD *)v0 = -858993460;
    v0 = (__int64 *)((char *)v0 + 4);
  }
  sub_140011C12(&unk_14004201F);
  if ( IsDebuggerPresent() )                                                                //通过IsDebuggerPresent判断是否被调试
    exit(0);                                                                                                //如果检测到被调试则退出程序
  Sleep(0x64u);                                                                                                //为防止线程占用过高,使用Sleep
  beginthreadex(0i64, 0, StartAddress, 0i64, 0, 0i64);                //启动检测线程
  return 0i64;
}

到这里其实就已经十分清晰了,接下来开始处理反调试


IDA Pro 处理反调试

分析出了该函数就是个反调试函数,于是可以直接在函数头部使其返回,让反调试函数无效

这里要用到 IDA Pro 的 KeyPatch 功能:

选中函数的头部,然后右键 → Key Patch → Patch:

keyPatch


Patch 完结果如下:

image-20210503200226681


接下来要将 Patch 完的结果导出到文件:

Edit→ Patch Program → Apply patches to input file

image-20210503200440658


image-20210503200549048

确定导出即可(导出的时候,记得要先关闭 x64dbg,不然程序被占用会无法导出)

image-20210503200642669


验证反调试的处理

此时再使用 x64dbg 载入程序 并让程序运行起来,可以发现此时就可以正常运行了:

image-20210503201037198


x64 定位 Crack 相关函数

摆脱了反调试的干扰后,终于可以正式开始 Crack 了

一般来说,对于没有加壳的程序直接搜索字符串即可,但这里字符串是被加密的,于是不能通过字符串直接定位

换个角度,可以通过对相关的 API 函数下断进行定位

这里可以发现当输入了错误的密码后,等待 Crack 中变成了密码错误

image-20210503201507775


这很显然是对标签文本的修改,可以尝试对 SetWindowTextW 下断点:

image-20210503201638038

 复制代码 隐藏代码
bp SetWindowTextW

image-20210503201719965


设置完断点后,再点击确定来触发断点:

image-20210503201900612


观察此时的堆栈情况:

image-20210503202152598

可以看到,堆栈中有输入的密码:610 和 要设置的文本:密码错误

于是可以以此为突破口,继续分析


选中 L"密码错误" 上面的函数,按回车查看其对应的反汇编

image-20210503202426839


image-20210503202501821


翻看附近的代码,可以看到在代码下方不远处可以找到:

image-20210503203628172

这里就算定位到了关键的函数处,接下来使用 IDA Pro 进行分析


IDA Pro 分析 Crack 相关函数

在 IDA Pro 中 按 G 弹出跳转地址窗口,然后填入要跳转的地址:140019272

image-20210503203721623


得到:

image-20210503203853910


接下来故技重施,选中函数的头部,然后按 F5 查看伪代码:

image-20210503204015648


得到:

image-20210503204046921

到了这里其实基本上就已经水落石出了,为了留点悬念,接下来的步骤等有人 Crack 出来后再放出 ( ̄︶ ̄*))

Crack 出来的要求:逆推出正确的密码(该密码唯一)


总结

该篇是反调试实战这个系列的开篇之作,此次实战只涉及了一个最简单的反调试:IsDebuggerPresent

后续还会不断更新其它反调试实战内容(应该会吧?咕咕咕 (づ ̄ 3 ̄) づ),希望大家多多支持 (。・∀・)ノ゙

稍微总结一下此篇的内容:

  • 反调试的手段可以大致归纳为:检测、干扰、权限清零 三种
  • 分析程序时,可以采取 x64dbg 搭配 IDA Pro 动静结合的方式进行分析
  • ASLR 可以使用 PE 工具关闭,之后调试起来更方便
  • 反调试的手段五花八门,重点在于如何定位,一般都是以某个相关函数作为突破口进行分析
posted @ 2022-07-20 19:38  CharyGao  阅读(3831)  评论(0编辑  收藏  举报