00:02
今天呢,咱们来讲的是这个反调试啊,就是我们在这个写代码的过程中啊,有可能会有一些东西呢,不是特别愿意让别人去调试我们的代码,比如说我们使用一些调试器进行附加,那么这种情况下呢,我们就写一些这个反调试的东西啊,然后来进行一些处理,那首先呢,我们来这个。嗯,我们来创建一个这个空项目啊。呃,比如说我现在啊,添加一个新建项,然后呢,就比如说我写一个这个点CP文件啊。好,然后呢,我现在呢,就给包含一下我们需要用到的一些东西啊,比如说我们包含一个I。然后包含一个windows.h。
01:02
好,接着呢,写个数函数啊,然后呢,我们今天呢,就来讲一些这个比较简单的东西,比较基础的,然后之后呢,因为这个会讲很多节啊,所以说呢,这节课是其中的第一节,呃,然后我们现在呢,就来给他这个写一下,首先我们最最简单的方式就是什么?就是使用一个API啊,这个API叫什么东西啊叫。啊,这个API呢,就是专门用来检检查什么,检查我们当前的进程有没有被调试的啊,我们首先F1啊看一下它的说明,确定是否正常用户模式的一个调试器调试这个进程啊然后它呢是没有参数的啊,它的返回值呢,如果说啊,当前是在调试器的上下文中就返回一个非零的值,如果说它是在未在调试器的中运行之返回一个这个零值,但是因为它的返回值呢,实际上是一个倍值,也就是说它其实只有触和false的两种状态,那这个非零值呢,就是什么意思呢?就是这个true啊,True返回true就是在调试器中啊,如果返回false呢,False就是不在调试器中啊啊然后呢,上面呢,你可以看到它还有一个函数也有类似的功能啊,就是这个啊,这个这个也是来检查的,但是它呢是干嘛用的呢?这个刚才我们这个呢,它是用来检查我们当前进程的,这个是什么,这个是可以检查其他进程的,因为你可以看到它有两个参数,第一个参数呢是可以确定目标。
02:35
这个什么呀,目标的句柄,然后第二个是啊返回确定是不是被调试啊,这个呢,它是使用第二个参数作为返回值来进行返回的,而它的返真正的返回值呢,则要是确定它的函数有没有调用成功啊,是这样的,那一会儿呢,这两个函数呢,我们都来给它简单使用一下啊,另外呢,这两个都包含在我们windows.h里,所以说呢,不需要包含什么特殊的头,那如果说我直接在这儿啊来写一个这样的函数的话,那它只能在我这个直接运行的时候呢,才能去检查它,那我现在呢干嘛呢,我给他写一个线程啊,让我这个线程一直监视我这个,我这个进程有没有被调试,那它呢,这个参数呢,我们前面讲线程的时候也讲过,我们只有第三个参数啊,需要我们真正填的第四个参数可有可无的啊,那是一个参数,愿意写就写,不愿意写就算了,第三个是什么?是它的址,也就是我是一个这个回调啊,我们把它放在这这个东,我可以它改个名字,比如ug。
03:35
啊,Call back,好,那现在呢,我们就可以啊,直接来给干什么,来写一下啊,把它呢,作为什么,作为我这个创建线程的第三个参数啊,传进去啊,第一个now,第二个now,第三个我们传一下这个anti call back,然后呢,它这个里边呢,有一个不兼容啊,为什么它写的是和我们那个three start这个run不一样啊,那所以说呢,我们进去之后把它这个值类型复制出来啊,然后呢,我们直接啊对其进行强转啊,这样就一样了啊好,接下来呢,我们在后边呢,给它加上啊三个第这个参数,还有这个其他的两个这个创建创建flag,还有一个就是我们的线程的一个ID啊这些东西我们暂时不需要,所以说我们就可以直接给它去掉,我们还是要接收一下它返回的这个线程句柄的啊,然后呢,因为我们想在主线程对它进行一个等待,我们用这个呃,S。
04:34
对他进行一个等待啊,然后H的啊,第二个呢,我们采取一个负一啊,无限期等待啊,这样就可以了啊,然后我们后边用一个system,让他在这个位置暂停一下,好,现在呢,我们就已经写完了啊,那在我的线程回调函数里呢,我就直接写一个well true啊,接着呢,我就可以在内部啊对它呢进行一个检测啊,我们现在的检测方式呢,就是什么呢?就是我们if啊,然后呢,判断一下啊,判断一下什么,判断一下我们的回调这个东西啊,回调我们我判断一下我们这个是不是在调试器的状态下啊,比如说我现在呢,直接调用这个bug。
05:14
然后呢,我现在呢,就可以干什么呢,我就可以啊直接在里边啊判断,如果它进入到这里边儿了,说明它确实就是在调试器内部,那么这个时候我就以比如说弹出一个窗口或做一些其他的事情,那如果我现在呢,只弹窗的情况下,我就可以提示啊,这里是被调试了。啊,给他弹一个窗口,好,那么如果是其他的情况下,你可以在这里干什么,比如说你可以调用这个结束进程啊,或者说这个格式化啊,啊或者说其他的一些操作啊,反正呢,你就只要是发现了调试器,你就可以做一些其他的样的事情嘛,对不对,那么我们现在呢,我们就可以什么呢?我们就可以直接来行一下了,在这种情况下,你行你会直接报一个被调试啊,这是为什么呢?因为你在这个I啊,也就是我们的studio,实际上就是里边是内嵌了调试器的,你这个运行起来,实上它就是在调试状态下,所以它显示被调试了啊,你看它一直会被调试,因为它中间是一个死循环,那如果说你现在想要它正常运行呢,你可以把它打开文件夹,在它上一级目录的第八个目录下直接运行,你可以看到它是没有任何问题的啊,也不会有这个被调试的提示,但是呢,你可以干什么呢?你可以打开你的调试器。
06:39
然后呢,对它呢,进行一个附加的尝试。啊来。运行起来了啊,然后我看一下。可以看到啊,已经显示被调试了啊,那这就是什么,这就是我们被调这个反调试的第一种方式啊,你看它一直显示被调试了,直到你把它关掉啊,那么我们这个是基于一个什么原理存在的呢?就是这个东西呢,它实际上而言是基于我们的一个PB结构里的一个东西来实现这个,我们等会儿再说,我们现在呢,来研究一下怎么把它解决掉啊,就是我们打开一个动态链接库啊,我记得我这有几个小好的户口框架啊,啊对,这个是一个户口框架啊,咱们之前写的啊,有一节课写的,忘了是哪节课了,然后大家可以自己去找一下,我们现在呢,就来干嘛呢,把这个B啊直接啊进去之后,你可以看到它其实就是一个返回一个处或者false嘛,那么这种情况下,我们就把它原形拿出来,然后拿原形拿出来之后呢,我在这儿呢,给它声明一个什么,声明一个这个,呃,声明一个这个回回来的函数啊。
07:40
好,然后呢,我现在直接啊继续干嘛呀,直接给他return啊,Return个零啊,里头什么都不干,我就给它return个零,其实就相当于什么呀,所以它相当于return一个false啊就可以了,接下来呢,我就来去啊,在这边给它进行一个互OOK,然后呢,我们现在呢,需要确定一下这个这个函数啊,它是在哪一个这个库里边的,我们现在呢,来观察一下,它是在C2里边,所以说呢,我们把C32给它拿出来,然后呢,放到这个hook的这个位置上,然后这后边呢,还有一个这个on hook啊,我们一样啊把它们进行一个复制。
08:19
然后呢,要替换成函数呢,是我们自己的函数啊。呃,现在呢,我们对其进行重新生成。重新生成之后呢,我们把这个代码给它拿出来,拿出来之后呢,我们现在呢,放到我们的这个桌面上啊,当前这个代码讲解这个代码的也是咱们前面的录播啊,咱们可以直接找老满进行领取,我现在呢把它替换过来,替换过来之后呢,我首先还是运行我这个代码,然后我现在不是有这个代码注入器嘛,我以管理员身份运行点试,然后把我们刚才的这个写的这个动态链接库啊注入进去,注入进去之后呢,我现在呢再次啊以这个呃调试器啊进行一个附加。
09:13
好,附加上了啊,附加上之后我们再次观察这个你可以看到就没有弹出啊,没有弹出它的这个什么呀,它的这个反调式的一个信息说明什么,说明它这个函数已经被我们自己掉了,然后呢,他就不谈了,那么这种方式有没有办法解决呢?这个呢,其实我们就要来研究一下它的这个本质了,我们首先呢,把这个调这个东西啊,我们直接拖到调试器里打开啊,然后呢,我们按F9给它运行到我们自己的领空右键搜索啊搜索,然后来找到我们所有的字符串啊,我们就偷个懒啊,直接去找一下,比如说我这儿来看一下。啊,我们这有一个debug的一个弹窗啊,我们这根据这个debug进过来,进过来之后呢,我们来看上面这呢,就是有一个1BUG啊,这个一个呃内容,然后我们进去,进去之后它有一个跳转,跳转到真正的函数实现里,它真正函数实现实际上就是这么三个函数代码,我们把它复制出来啊,用一个返回编的形式给它复制出来,然后呢,我现在呢,给他拿一个东西给它存一下。
10:15
哦,我直接存在我这个代码里吧。那这个是什么意思呢?首先呢,我们要知道FS30这个位置是个什么东西啊,我们现在呢,嗯,给大家来看一下啊,就是我们直接啊打开我们的这个虚拟机,嗯,我们给它断下来,这是一个X86的啊,一会儿大家给大家看一下叉六四的啊,现在呢,我们来直接看一下什么呢?看一个结构叫DT啊TB啊,就是线程环境块啊,我们来看一下它这个东西呢,就存储在什么啊,存储我这个TB这个构啊,它就在我们的这个FS下,然后呢,我们看一下它这个的前段啊,我们切,然后呢一下你可以看啊,它在这个0S0这个位置上就是什么,就是PE啊,然后呢,这其实就是第一句啊,FS是指向指P。
11:20
然后PB这个呢,比较近啊,我们可以看到第二个字节就是啊,然后我们来啊给它复制出来。来你看一下啊这个东西啊,那实际上而言呢,我们这三行代码是在干什么呢?其实就是首先拿到PB啊,这一行是拿到PB,然后呢,在后边呢啊,在这个PB的基础上啊,再给它偏移二,偏移二呢拿到这个标记,这个标记啊,如果你在调被调试,那么这个标记位就为,如果不是被调试,它这个标记位就为false,它是一个U叉,或者说要一个这个贝,它实际上就是一个什么一个布尔值的一个能储的一个八位啊八位啊,所以说呢,这个是它的一个这个真实的一个原理,当然这个呢是32位的情况下,如果是64位的情况下呢,我们重新给它来生成一下,大家看一下64位的。
12:14
啊,我们给它打开。打开之后呢,我现在呢,拿到它的64位版本,然后拖到我们的这个X64D8UG里啊,我们运给他拖进去,拖进去之后还是啊运行能我们我们自己领空,然后搜索。啊,直接跳到它相关位置上,现在呢,我在上边啊,我直接点进去,点进之后你可以看到64位版本是这样的,跟刚才是不一样了,我们还是把返回边给复制出来,复制出来之后呢,我们来看一下它的这个版本差异,上边这个呢是32位的啊,也就是叉六架构下的啊,下边这个是64位的叉六四,那么我们可以看到啊,如果是32位下,我们的这个PB是在哪啊,是在FS30这个位置上,如果是64位呢,我们是在GS60这个位置上啊,那么我们为什么要特意说一下这个东西呢?因为它是有一定差异的啊,它是根据你的这个32位和64位的选择不同啊,然后呢,去不同的这个位置上啊来取的。
13:14
为了防止我们这种啊,被这个限制啊,就是说我们一个户就把事解决了,其实我们可以自己仿写这种代码。如何去然们原而编发种经过都没有经过系统调用啊,也就是说这两个函数啊,不论是32位还是64位的,他们都没有进内核啊,都没有进内核,所以说呢,我们就可以直接在三环进一个仿写啊,对没有关关注的点点关注啊,然后有需要领取课程的都可以直接去联系老马啊。好,然后呢,我们现在呢,就来实现一下这个函数,比如说我们现在呢,实现一个这个32位版本的,32位版本呢,相对比较简单啊,我们直接一个这个倍值返回,然后。
14:12
第八个啊,这是叉八六版本啊。这个版本呢,因为我们32位在这个asm里可以可以直接直接写汇编啊,那64位呢,因为编译器不支持啊,所以说呢,你要单独写个文件,那如果说支持的情况下,你可以干嘛,你可以直接啊来这个。呃,来现在这样啊,然后我们直接啊,刚才不是把这个代码拿出来了吗?我们直接把这32位的代码拿过来啊,我们进行一个处理,但是呢,其实呢,这里边呢,有点小问题,不能直接这么用啊,首先你要给它声明一个返回值啊,比如说一个布尔的一个be,然后呢,你这个B呢,我们先给它一个初始值。
15:02
接下来我用这个初始值啊,现在这个两行啊,我们可以看到啊,这是我们从呃这个调试器里直接复制出来的,但是这个东西呢,它是有一定问题的,所以我们要进行一定的修改,那首先呢,我们这个30在我们的调器里,虽然说它是个30,但是我们要知道调试器它默认是什么,默认是0X30,但是你复制过来的时候,它不带0X,也就是说呢,你这现在写的是一个十进制的30,你要在它后边给加上一个H,然后呢,你现在这样写呢,它是会报错的,你要把这个DS段的断计算器前字去掉啊,然后呢就可以了啊,接下来呢,就把什么,就把你的这个EAX里边的一个值,你现在可以给它转到我们的这个碧瑞里,但因为碧瑞ten是一个布尔值是一个八位的,所以说你要对这个E均器进行拆解,它这里呢是采用采用了一个字节啊来给它扩张传到这个ex里,那实际上而言,分开之后,Ex也可以分成这个16位的这个AX数地位。
16:03
AX呢,又可以变成a al啊,那我们实际上现在呢,就是这个东西呢,真实的值在al里,那我们就可以把它通过al放在b return里,然后呢,最后呢,通过把这个b return返回出去啊,这样就可以了,那么现在呢,我们就可以把它呢放到我们的这个回调里啊,替代我们原来这个函数,那么现在呢,我们再次运行。哦,我看一下啊。Or未声明的这个。Eight。我这个不支持内联汇编。使用了非标准扩展,不支持在纸结构上使用asm。啊,真没试过这边啊,难道他还不支持?
17:00
应该不是吧。这就是叉八六的啊,我要测试两个版本一个啊,这个是叉六四的啊啊对对你说的对啊,它默认创建出来一个叉六四的,没注意啊。啊,感谢啊,我没注意到啊,行运行啊。可以看到啊,它现在已经显示这个被调试了啊好,那么我们现在呢,再给他来用调试器啊,来给他这个看一下。好直接放,哎,我操卡住了啊,稍等哎没事了啊好,我们现在呢,运行啊运行。也可以看到啊,也是一样的一个效果啊,那么我们现在呢,直接给它重新运行一下啊,然后呢,我们给它F9啊,到这儿之后我们直接搜索。啊,我们跳转到这个提示上面啊,那这个时候呢,你可以看到啊,它非常明显的一个API调用已经没了啊,但是呢,我们其实还是可以找到我们刚才自己写的一个函数呢,就在这儿啊,我们进去之后,你可以看到这个就是什么,这就是我们刚才自己写的那个函数了,相对而言呢,就不那么好确认它是来干什么的呢,但是呢,这里边其实也非常显然啊,就在这儿啊,有一个FS30啊,取出到EAX里,然后在这个偏移二的位置上再取出一个值放到EAX里,然后进行一个返回,看到了吧?啊,这个是这个32位版本的啊,那这个其实还有一种写法,这是其中一种写法,就是我们直接通过什么,通过这种内联汇编的形式啊,那还有一种防写法呢,就是不通过内联汇编的形式来写啊,那它呢,需要得多包含一个头文件。
18:47
啊,然后呢,我们在这呢,我们再来写一个纯C原版本来写的就是这个安bug啊。
19:02
好,那这样如果写呢,首先呢,我们要定义一个结构体啊。我们刚才已经确定了啊,我们这个这个PB这个结构体啊,上面这三个东西啊,都是什么,都是U叉的,那也就是说呢,我可以用什么啊,用一个八位的值啊代替它,然后呢,这个八位的值呢,前两个呢是什么啊,是我们的这个占位的啊,那我直接一个V21,然后呢,我们再来,而且他们俩本身不是占位的啊,但是对于我们来说它是占位的,然后呢,我们只需要第三个值对不对啊,那我们现在呢,把它第三个值拿出来。啊,然后呢,给它起个名。
20:01
好,然后呢,我们这个时候呢,可以调用它的一个这个那个提我们刚才头文件提供的一个函数啊,叫这个read的fsdor啊,其实就是读出来一个这个四节,我们要在什么地方读取出来呢?读取到0X30这个位置上啊进行读取,然后返回的是什么?是PB的一个base啊,那现在呢,我们直接啊来给它来一个PB啊等于但是呢,你需要对它呢,进行一个类型上的一个强转。呃,应该是没有卡吧。啊,强转强转完事之后呢,我们现在呢,就可以来获取什么,获取它的值,它是true还是false,那么这种情况下,我们就可以直接return啊PB点啊这个标记位啊就完事了,那么这个时候呢,我在我的线程回调里啊,就可以直接调用这个anti debug了啊,然后直接运行。可以看到被调试了啊,是没有问题的,然后呢,我们把这个我们叉八六这个版本呢,我们先给它注释掉啊,先给它注释掉,然后呢,我们给它切换成64位版本啊,我们直接诶这个不行了啊,那个我我要给它重新写一个,写一个函数啊,写一个内联函数就是什么呢,我们现在呢,如果说想在64位下写汇编,我们要这样写啊,就给它来一个这个这里啊生成依赖项,选择生成自定义,然后选择点ma SM啊注意不要选到上面这个2MA SM是底下这个mam啊点击确定,确定之后呢,我们来这边呢,添加一个新的原文件啊,点击新建项,然后呢就可以来选择这里啊,选择一个这个,比如说这个debug啊debug.asm啊,当然我也可以写一个anti debug啊,点asm都可以啊,然后点击添加啊,然后我们在这边开始写汇编代码。
21:49
啊,没点关注的可以点点关注啊,然后呢,领取课程的呢,可以去找老板进行领取,接下来我们64位的汇编呢,是比较好写的,没那么多格式要求一个点扣,然后一个N的结尾,中间呢,就是你要写代码的一个内容了啊,那首先呢,我们写一个这个debug的一个函数。
22:08
PLC啊,不需要参数啊,N的P进行一个结束啊,那中间呢,就是我们要写的一个代码,那中间要写的代码呢,也非常简单,我们刚才呢,在我们的程序里已经把64位的代码给它抠出来了啊,那这时候呢,我们就直接拿过来,拿过来之后呢,我们给它放入其中啊,也对它呢进行一个简单的一个修改,首先60肯定是要改成60H的啊,就是十进制改改成16进制,然后底下这个位置呢,它有一个这个加二啊,还是啊,我们把这个DS去掉,现在呢,我们就可以来使用它了啊,这个debug呢,我在我用的位置上边呢,我现在呢给它进行一个声明,比如说我在线程上边啊,Yes tis tenc,返回一个布尔值啊布尔antit debug啊好,这样直接就可以用了啊,把它放上去,这个时候呢,我们再次啊进行一个重新生成。
23:08
我们来运行一下啊,你可以看到确定可以又看到被调试了啊,这就是64页的版本一个写法,需要我们在外边儿呢,自己写一个这个汇编文件,那么我们现在呢,把它打开,再次呢,使用调试器来观察一下其中的一个代码。我们用这个叉6DEBUG。走啊,然后跳转。啊,这个函数呢,在这儿啊,我们进去啊,进去之后我们来观察一下,可以看到啊,就是这样的,跟我们这个正常的64位版本是一模一样的,对不对啊,如果说呢,你这种函数呢,想要进行调试呢,你也可以直接啊把它放到这个我们的IDA啊行测我们。
24:05
哎。然后呢,我们现在呢,来给他找一下,因为我们现在有符号啊,我记得应该是我没有删那个PB啊,应该是有符号的,诶搜不到啊,那你就得从这个start一个一个的往里跳了啊。啊,这就比较麻烦了,我直接搜那什么吧。CTRL加S交叉引用啊,然后一跳到了啊,这样就比较简单啊,省的省时间好,然后呢,我们往上找啊,它这里呢,如果说是到money box了,它这有有一条线是连下来的,说明是到这边了,然后呢,应该是这个call啊,然后是我们自己的一个call,我们点进去跳转诶也可以看到是不是。所以说我们刚才自己写的那个函数啊,就直接发现出来了。那这个就是什么,这个就是这个,呃,我们这个使用IDA啊来观察它这个东西啊,啊这有点虚啊,看不太清,我把它就关掉了。
25:12
然后呢,我们刚才呢,就研究了什么呢,研究了这个叉八六啊叉六四,它这个使用ec bug到底是怎么回事,然后呢,我们以自己的形式啊,对它呢进行了一个仿写啊,就是如何去自己啊来写这个函数啊,而不是使用它原本的API,因为它原本的IAPI我们也看到了,我们当使用了我们的这个hook技术之后啊,直接给它返回一个false啊,它就没了啊,所以说呢,这个东西比较草率,但是呢也不失为一种方法,那我们如果说是自己写的这种啊,怎么处理呢?其实呢也非常好处理,因为当我们写代码能够获取到PB的时候,它就必然是一个可以设置的一个东西了啊,我们只需要啊把它的这个,呃,我们这个调试的一个标记位啊,这一位啊,我们给它给它清理掉啊就可以了啊,然后我们这种写法呢,就也没有用了啊。
26:02
所以说呢,这个东西呢,就是一个简单的一个对抗啊,是比较简,这个反调式中比较简单的一种,那么我们再来呢,在实验,实验一个今天的最后一种啊,然后呢,就今天就结束了,然后明天呢,我们会就着今天的最后一种东西继续进行扩展,我们来看一个函数啊,就是。啊,就是这个函数啊,这个函数呢,它需要你提供两个参数,一个是你的这个进程句柄,因为它可以确定别的进程有没有被调试,那我们这里呢,获取一下我们当前进程的。第二个呢,它需要给你填一个布尔值啊,那我现在呢,直接把它拿到外边算了。哎,稍有封装他一下吧啊,如果不封装一下,感觉好像也不太好啊,那布尔值啊一个antibug。
27:03
好,然后呢,用一个布尔作为接收。它第二个呢,是一个,嗯,这个括号上边了啊,它第二个呢是什么呢?它是以一个这个地址啊,传进去的一个波尔值啊,然后作为返回,好那么现在呢,我们啊就可以直接什么这个就可以了,好那我在这儿呢,继续对它进行一个调用。运行。我看一下啊。啊,找到多个重复的定义啊,因为这有个asm啊一个名字,所以说呢,它重复定义了,我们给它改个二。运行。你可以看到也是被调试了,对不对啊,所以说呢,这种方式也可以,那为什么今天呢,要以这种方式作为结尾呢?因为我们来简单看一下,它里边的有一个函数的。
28:07
我们直接啊把它啊通过这个调试器打开。啊,然后找到我们自己写的call啊,然后进去啊进去之后呢,我们来观察一下,在这个位置上是我们调用的这个检查远端的这个调试的一个A啊,那我们进去发叫啊进行一个这个使A个函数啊来进行反调试啊然后呢,今天呢,我们就来这个啊讲的差不多了啊,今天呢,主要来讲了一下我们这个今天这个S啊这个API是怎么来实现的,怎么我们自己使用汇编来实现这个API,以及怎么使用它内置的库函数来这个实现API,然后呢,明天呢,我们就来研究啊这个函数啊,如何进行反调试啊进如何进行多种反调试,因为这个函数呢,能查询到的信息实际上是比较多的啊,所以说呢,它的反调式的一个。
29:22
呃,功能啊,也可以实现出多种的好,然后大家有没有问题,如果没有问题的话,咱们反调试的第一课啊就结束了,然后呢啊点点关注啊,然后呢,每周六周日啊都有这个直播,然后呢,平时呢又不定时的直播啊,然后这个最近的。专题呢,应该就是这个反调式专题啊。
我来说两句