吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

搜索
查看: 2198|回复: 42
上一主题 下一主题
收起左侧

[调试逆向] 【原创】反调试实战系列一 x64dbg+IDA 过IsDebuggerPresent

  [复制链接]
跳转到指定楼层
楼主
lyl610abc 发表于 2021-5-3 21:02 回帖奖励
本帖最后由 lyl610abc 于 2021-5-4 11:37 编辑

前言

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

这次分析的程序为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

到了这里其实基本上就已经水落石出了,已经有人解出了密码,详见置顶楼


总结

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

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

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

  • 反调试的手段可以大致归纳为:检测、干扰、权限清零 三种
  • 分析程序时,可以采取x64dbg搭配IDA Pro 动静结合的方式进行分析
  • ASLR可以使用PE工具关闭,之后调试起来更方便
  • 反调试的手段五花八门,重点在于如何定位,一般都是以某个相关函数作为突破口进行分析

此次的实战确实很简单,重点在于分享反调试相关的思路和x64dbg配合IDA Pro分析的操作,大佬勿喷

帖子中可能会有不妥之处,欢迎大家指出

附件

CrackMe下载地址

CrackMe.zip


CrackMe源代码

(因为MFC项目还挺大的,这里截取出关键的代码)


反调试代码

unsigned int __stdcall ThreadFun(PVOID pM)  //反调试线程
{
    if (IsDebuggerPresent()) {          //使用IsDebuggerPresent判断是否被调试
        exit(0);                        //被调试则退出
        return 0;
    }

    Sleep(100);                         //休眠,防止线程创建过多,导致资源占用
    HANDLE handle = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, NULL, 0, NULL);  //继续创建反调试线程

    return 0;
}
HANDLE handle = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, NULL, 0, NULL);  //创建线程

密码验证相关代码

void encodeCString(CString str) {                   //简单的字符串加密函数
    for (int i=0;i<str.GetLength();i++)
    {
        str.SetAt(i, -str[i]);                      //简单的加密
    }
}

CString correctStr = L"密码正确";
CString errorStr = L"密码错误";
CString debugStr = L"检测到被调试";
void CMFCApplication2Dlg::OnBnClickedButton1()      //按钮"确定"的响应事件
{

    // TODO: 在此添加控件通知处理程序代码
    //获取到edit1的内容 然后给edit2赋值
        CString str;
        edit1.GetWindowTextW(str);                  //获取输入的密码

        WCHAR out[1024];
        CString strList[4];
        CString correctList[4];                     //用来存放正确的密码,后面拿来比较
        BOOL flag = TRUE;                           //标志,用来标记密码是否正确
        correctList[0] = "016";
        correctList[1] = "025";
        correctList[2] = "666";
        correctList[3] = "332";

        encodeCString(correctStr);                  //简单的加密
        encodeCString(errorStr);                    //简单的加密
        encodeCString(debugStr);                    //简单的加密

        long t1 = GetTickCount64();                 //获取开始时间

        if (str.GetLength() > 25 || str.GetLength() < 15) {       //字符串长度判断
            flag = FALSE;
            encodeCString(errorStr);
            GetDlgItem(IDC_STATIC)->SetWindowTextW(errorStr);
        }
        else {
            //password
            //610 - 520 - 666 - 233
            CString sToken = _T("");                            //用来接收每次分割的字符串
            int i = 0; // substring index to extract
            while (AfxExtractSubString(sToken, str, i, '-'))    //以-进行分割
            {
                //.. 
                //work with sToken
                //..

                strList[i] = sToken.Trim();                     //字符串去空格
                i++;
                if (i > 4) {                                 //如果分割大于4,则不满足条件
                    flag = FALSE;
                    encodeCString(errorStr);
                    GetDlgItem(IDC_STATIC)->SetWindowTextW(errorStr);
                    break;
                }
            }
            if (i != 4) {                                       //如果分割不等于4,不满足条件
                flag = FALSE;
                encodeCString(errorStr);
                GetDlgItem(IDC_STATIC)->SetWindowTextW(errorStr);
            }
            else {
                for (i = 0; i < 4; i++) {
                    //比较字符串
                    if(strList[i].CompareNoCase(correctList[i].MakeReverse())==-1){ //注意这里的MakeReverse()
                        flag = FALSE;
                        encodeCString(errorStr);
                        GetDlgItem(IDC_STATIC)->SetWindowTextW(errorStr);
                        break;
                    }
                }
            }

        }
        //判断标记
        if (flag) {
            encodeCString(correctStr);
            GetDlgItem(IDC_STATIC)->SetWindowTextW(correctStr);
        }

        Sleep(500);

        long t2 = GetTickCount64();             //获取结束时间
        if (t2 - t1 >= 560) {                    //如果时间差大于等于560则超时,是被调试的情况
            encodeCString(debugStr);
            GetDlgItem(IDC_STATIC)->SetWindowTextW(debugStr);
        }

}

留一份副本在论坛里,土豪专享( •̀ .̫ •́ )✧


CrackMe.zip

67.28 KB, 下载次数: 9, 下载积分: 吾爱币 -1 CB

土豪专享,已更新

免费评分

参与人数 23吾爱币 +43 热心值 +21 收起 理由
MasterW + 1 + 1 用心讨论,共获提升!
kwq1995 + 1 + 1 我很赞同!
ming1332236 + 1 + 1 我很赞同!
nmy124 + 1 + 1 谢谢@Thanks!
莫问刀 + 2 + 1 大佬v5
一口猪头肉 + 1 + 1 我很赞同!
零夜夜 + 1 热心回复!
fengbolee + 2 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
shiina0831 + 1 + 1 谢谢@Thanks!
18077484116 + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
国际豆哥 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
这个游戏不太行 + 1 + 1 认真的高质量帖子!共同提升!
爱你小吉君 + 1 热心回复!
阿锋01 + 1 + 1 谢谢@Thanks!
涛之雨 + 16 + 1 老咕咕咕怪了
PrincessSnow + 1 + 1 我很赞同!
游叶子明 + 1 + 1 热心回复!
领悟者的涂鸦笔 + 1 一看就会一做就废
正己 + 3 + 1 前排出售楼主大佬的丝袜
zhuzhuxia111 + 1 + 1 我很赞同!
qaz003 + 1 + 1 难得有空收藏好慢慢看
qianshang666 + 1 + 1 我很赞同!
Ps出来的小赵 + 3 + 1 前排出售楼主大佬的丝袜

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

来自 13#
ZJevon 发表于 2021-5-4 10:14
不知道是我理解有问题还是题目本身问题,只要满足  15<=长度=<25 ,4个 - 分隔密码,在分隔符之间数字依次大于610,520,666,233 就能密码正确

免费评分

参与人数 2吾爱币 +2 热心值 +2 收起 理由
fengbolee + 1 + 1 用心讨论,共获提升!
lyl610abc + 1 + 1 正解

查看全部评分

来自 31#
 楼主| lyl610abc 发表于 2021-5-5 11:56 |楼主
fxinyi 发表于 2021-5-5 08:35
先感谢大佬分享经验,能者为师,请教一下, 也许是用的版本不同, 我用的是PE 3.01 去掉ASLR以后, 重新加载虽然 ...

第一个问题:为什么没有直接跳到一堆JMP的地方?
我解说的版本是Debug版本,后来改成了Release版本,所以反汇编代码会有所差异,但思路没有问题,不影响学习
第二个问题:下断找不到EXITPROCESS
下断要区分大小写,API函数为ExitProcess
第三个问题:直接OD修改可以吗?
可以通过OD直接修改
第四个问题:OD隐藏就可以过掉
因为这里演示的反调试手段十分常见,OD隐藏就可以过掉,毕竟是系列开篇,先拿个简单的演示
推荐
这个游戏不太行 发表于 2021-5-3 23:58
[ 不懂就问 ]
打开/调试CrackMe.exe的时候报错
请问这种情况如何解决(网上说是软件从Visual Studio做出来但没有Release的结果...mfc140ud.dll是VS内部调试工具)
我应该补哪个运行库?

批注 2021-05-03 224944.png (16.8 KB, 下载次数: 1)

错误界面

错误界面

批注 2021-05-03 225115.png (10.09 KB, 下载次数: 0)

批注 2021-05-03 225115.png

批注 2021-05-03 233141.png (36.1 KB, 下载次数: 0)

已安装的运行库

已安装的运行库

免费评分

参与人数 2吾爱币 +3 热心值 +2 收起 理由
fengbolee + 1 用心讨论,共获提升!
lyl610abc + 3 + 1 已经修改为Release版本,感谢指正

查看全部评分

推荐
zhangderek123 发表于 2021-5-4 12:32
大佬,更新保护模式!!!
沙发
浅╮念 发表于 2021-5-3 22:37
太牛了  压根看不懂
3#
qaz003 发表于 2021-5-3 22:50
谢谢分享。。。
难得有空收藏好慢慢看
4#
lock6969 发表于 2021-5-3 23:13
非常不错的讲解,学到了很多!
5#
正己 发表于 2021-5-3 23:30
大佬好可怕
7#
fat_mantou 发表于 2021-5-4 01:55
想请教下目前是否有彻底不会被破解的加壳方式,有没有可能设置一个壳,然后对壳的关键部分和机器的部分识别码做哈希作为内核信息的加密密钥,密钥正确才能运行,这样来防止破解
8#
Pwqi 发表于 2021-5-4 05:02
非常不错的讲解,学到了很多!
9#
tan567421 发表于 2021-5-4 07:38
非常不错。值得学习。。收藏了
10#
muzb 发表于 2021-5-4 08:07
谢谢分享
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2021-5-10 00:01

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表