如何绕过IsDebuggerPresent的反调试

  • A+
所属分类:逆向工程

在某爱论坛上看到有个师傅写了个Crackme

关于如何绕过IsDebuggerPresent的反调试的,闲来无事复现调试一下

先上原文链接:https://www.52pojie.cn/thread-1432590-1-1.html


反调试

什么是反调试技术

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

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

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

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

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

反调试常见手段

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

  • 0环,内核级调试

  • 3环,用户应用层调试

之前写对抗沙盒的时候:判断父进程是否是explorer.exe,不是则退出,似乎也可以作为一种简单的反调试手段,之前没怎么了解过反调试,最多听海哥说过可以检查句柄表,今天就学习一下,先看看windows的反调试API,0环反调试等以后知识储备够了再学习

IsDebuggerPresent

https://docs.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-isdebuggerpresent

确定调用过程是否正在由用户模式调试器调试。

CheckRemoteDebuggerPresent

https://docs.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-checkremotedebuggerpresent

确定是否正在调试指定的进程。

开始调试

打开就是一个人畜无害的样子

如何绕过IsDebuggerPresent的反调试

查壳

如何绕过IsDebuggerPresent的反调试

64位,MFC做的,c++写的,没壳


ASLR

什么是ASLR

维基百科:在计算机科学中,地址空间配置随机加载(英语:Address space layout randomization,缩写ASLR,又称地址空间配置随机化地址空间布局随机化)是一种防范内存损坏漏洞被利用的计算机安全技术。ASLR通过随机放置进程关键数据区域的地址空间来防止攻击者能可靠地跳转到内存的特定位置来利用函数。现代操作系统一般都加设这一机制,以防范恶意程序对已知地址进行Return-to-libc攻击。

总的来说就是将内存地址虚拟化,我们看到的内存地址并不是真正的内存地址偏移

ASLR的作用

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

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

ASLR的体现

用x64 debug打开程序

如何绕过IsDebuggerPresent的反调试

到达系统断点,我们需要让他到达oep,即程序入口点

ALT+F9

如何绕过IsDebuggerPresent的反调试

这里地址是7FF6E.....

再看真实的入口点:

如何绕过IsDebuggerPresent的反调试

明显不一样

用MFC编译出的64位程序默认是开启ASLR的

关闭ASLR

如何绕过IsDebuggerPresent的反调试

找到可选pe头的DllCharacteristics属性,取消DYNAMIC_BASE

如何绕过IsDebuggerPresent的反调试

回到真正的内存偏移

如何绕过IsDebuggerPresent的反调试

关于DllCharacteristics可以参考:

https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_optional_header64

x64反调试

F9让程序运行,但是一运行程序就会直接结束,不会弹出窗口

如何绕过IsDebuggerPresent的反调试

 

 做到这里不禁让我想到直接写反杀箱的时候一样,一运行就挂

大概代码是这样:

if (explorer_id == parent_id){CeatRemoThread(explorer_id);}else{exit(1);}


只不过他这里是其他判断,比如是否被调试,是就直接exit,不是则执行下面的

于是对ExitProcess下断点

bp ExitProcess

如何绕过IsDebuggerPresent的反调试

下断点后直接F9运行到断点处

观察此时的堆栈

如何绕过IsDebuggerPresent的反调试

这里又返回到crakeme,猜想是否是判断是否在调试之后又回到原本的函数

选中这一行按回车,跟进反汇编

如何绕过IsDebuggerPresent的反调试

看到使用了IsDebuggerPresent来反调试

IDA Pro x64反调试

进入ida后,按G,并输入刚刚反汇编开始的地址

如何绕过IsDebuggerPresent的反调试

跳转后

如何绕过IsDebuggerPresent的反调试

选择startaddress

如何绕过IsDebuggerPresent的反调试

F5进入伪代码

如何绕过IsDebuggerPresent的反调试

 

这里很明确了,就是这个在反调试

IDA pro 反反调试处理

可以直接在函数头部就直接ret,让他不走IsDebuggerPresent

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

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

如何绕过IsDebuggerPresent的反调试

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

Edit→ Patch Program → Apply patches to input file

如何绕过IsDebuggerPresent的反调试

OK即可

如何绕过IsDebuggerPresent的反调试

 

验证反反调试处理

如何绕过IsDebuggerPresent的反调试

 

正式Crack


先随便输入一个数看看

如何绕过IsDebuggerPresent的反调试

本来这里可以搜索字符串,但我发现定位有些问题

换一种思路,定位API,以前写win32程序的时候,要想在dialog中输出一段字符串,用SetWindowText,这里可以用这个api定位

bp SetWindowTextW

如何绕过IsDebuggerPresent的反调试

回车,断点就设置好了

如何绕过IsDebuggerPresent的反调试

然后再点确定

如何绕过IsDebuggerPresent的反调试

观察此时堆栈,出现了100和密码错误,并且有个返回函数

如何绕过IsDebuggerPresent的反调试

选中返回函数那一行,回车

找到附近的"密码正确"

如何绕过IsDebuggerPresent的反调试

IDA Pro分析

跳转到刚刚"密码正确的地址"

如何绕过IsDebuggerPresent的反调试

  选中函数头部F5,进入伪代码

如何绕过IsDebuggerPresent的反调试

得到:

如何绕过IsDebuggerPresent的反调试

说实话,这个伪代码不是很能直接看得懂,看了下原作者的,他调试的是Debug版的,更这个release版的还是有差别的,感觉release版ida很多都识别不了了

附上作者关于密码的源代码

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 - 233CString sToken = _T(""); //用来接收每次分割的字符串int i = 0; // substring index to extractwhile (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);}

}


可以看到跟ida生成的伪代码差距还是比较大,但还是不影响用源码分析一波算法

1.通过GetTickCount64获取自系统启动以来经过的毫秒数,变量t1

GetTickCount64:https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-gettickcount64

2.获取输入的密码长度,如果长度小于15,或大于25,就赋值flag=false,然后SetWindowText"密码错误",并且可以看到这个字符串是由encodeCString加密了的,所以如果一开始如果想直接找字符串,可能就无法准确定位

3.AfxExtractSubString:https://docs.microsoft.com/en-us/cpp/mfc/reference/cstring-formatting-and-message-box-display?view=msvc-160

这个API可用于从给定的源字符串中提取子字符串,通过这个api的返回值可以判断有几个"-",如果是4段密码,且以“-”分割,就可以进入比较字符串环节

4.CompareNoCase:https://docs.microsoft.com/en-us/windows/win32/api/chstring/nf-chstring-chstring-comparenocase

该函数这个函数使用lstrcmpi函数对一个CString和另一个CString进行比较

返回值为:

由参数lpsz指定这个用于比较的string。如果两个对象完全一致则返回0,如果小于lpsz,则返回-1,否则返回1.

这里不等于-1就行,也就是不小于

5.MakeReverse:https://docs.microsoft.com/en-us/windows/win32/api/chstring/nf-chstring-chstring-makereverse

功能大概就是反转字符串,所以四个数为610,520,666,233

6.最后有一个计算时间差

所以总结一下就是:长度满足15<=长度<=25,以“-”分割密码且4个子密码,在分隔符之间数字依次大于等于610,520,666,233 就能密码正确

比如这样

如何绕过IsDebuggerPresent的反调试

 但是这个小程序我还是发现不少bug

比如:

如何绕过IsDebuggerPresent的反调试

 还有这样写的话程序会直接崩掉

如何绕过IsDebuggerPresent的反调试

 

后记

作为学习反反调试初级,重要的是使用x64 debug和ida pro分析的过程,这个还是很有帮助的。

脑海中又浮现了海哥的话:"没有好的正向基础就不会有好的逆向基础。"


如何绕过IsDebuggerPresent的反调试


推荐阅读:


本月报名可以参加抽奖送暗夜精灵6Pro笔记本电脑的优惠活动


如何绕过IsDebuggerPresent的反调试


点赞,转发,在看


投稿作者:Buffer

如何绕过IsDebuggerPresent的反调试

本文始发于微信公众号(HACK学习呀):如何绕过IsDebuggerPresent的反调试

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: