首页
社区
课程
招聘
[原创]reversing.kr第10题Twist1
发表于: 2019-1-16 21:05 4175

[原创]reversing.kr第10题Twist1

2019-1-16 21:05
4175

该函数的唯一一个参数为异常处理函数指针。当程序发生异常是,且程序不处于调试模式(在VS或者其他调试器里运行)则首先调用该异常处理函数。因此,程序可以主动抛出一个异常来判断当前程序是否正在被调试,这是该程序安装的其中一个异常处理函数,当有异常发生并且当前程序没有被调试的情况下,该异常处理函数将得以执行。

我们回到OD中,看到SetUnhandledExceptionFilter的调用处。

进程处于调试状态时,系统会为它分配一个调试端口,第2个参数传7时,NtQueryInformationProcess()就能获取调试端口的值,调试状态该值为0xFFFFFFFF,正常运行时该值为0。同样,有一个API也可以检测调试端口的值:CheckRemoteDebuggerPresent()。CTRL+G定位到该函数实现部分,发现该函数其实就是内部调用了一下第2个参数为7的NtQueryInformationProcess:(实际调用的Zw版的函数,功能相同)

       进程被调试时会生成调试对象,当函数的第2个参数值为0x1E(ProcessDebugObjectHandle)时就能获取调试对象句柄,正常运行时该值为0,调试运行时该值为非0值。

       破解方法:如果被调试程序只是调用几次API,而不是在多个地方反复调用,我们可以在调用的时候手动修改特定参数下的调用结果:修改返回值或函数参数指向的内存。像上面的例子一样,我们可以在该函数调用结束的后手动修改参数指向的内存,修改调用结果。当函数调用传参为7/0x1E的时候,修改其第3个参数获取的值: 如果该函数在多个地方被反复调用,那么我们可以采取API HOOK的技术,修改函数内部执行代码的流程。

      这里选择将两个反调试全部改成nop(发觉还是不行,后面还有调试)所以这里选择使用od自带的反调试插件。这里还是不行,直接将409150处改成retn


14.还是手动绕过吧

将0x409150函数改成retn]

位置0x4072a3在进入

位置:0x4072c9处nop

位置:0x407437处nop

位置:0x407463处nop


15.进入之后0x40b970处为输入的数据。取出第一个数据放入cl,取出第7个数据放入dl,然后将第一个数据和第七个数据放入0x40b990处

 16.0x407488处又取出第七个数据,将数据和0x36异或之后放入到0x40c450处 


GetThreadContext这个API。GetThreadContext的原型如下: 

BOOL WINAPI GetThreadContext(
  __in     HANDLE hThread,
  __inout  LPCONTEXT lpContext
)

typedef struct DECLSPEC_ALIGN(16) _CONTEXT {
...
// Debug registers
//
DWORD64 Dr0;
DWORD64 Dr1;
DWORD64 Dr2;
DWORD64 Dr3;
DWORD64 Dr6;
DWORD64 Dr7 
...

} CONTEXT;  

1.peid查壳,没有找到相关信息,od载入出现熟悉的加壳程序提示框。先运行程序,然后搜索字符串。发现关键字符串,但是程序一运行就终止了,下面开始单步分析程序,因为我暂时也没啥办法。
找到关键call
单步到步过这个call的时候程序跑飞,进入这个call
2.这里会对代码自身进行解密操作:地址为从0x407077~0x40715f,将对应的代码进行异或0x34操作
3.下面继续运行,遇到一堆的call我也不知道是干什么的,只知道遇到call就f8步过,遇到向上的跳转就在跳转下面f4,一直单步调试,很顺利的来到了,0x4071bd处,发现这里有个大跳转,可以猜测为跳转到oep.使用ollydump脱壳后发现程序能够正常运行。
peid查壳显示为:

4.od载入程序,搜索字符串,在关键call处下断点
5.od直接运行程序,发现程序直接退出了,可以猜测可能有反调试函数,那就单步运行函数看看反调试函数在哪个位置。在0x40162b处程序跑飞
6.跟进去发现了一个反调试函数,SetUnhandleExceptionFilter函数。

该函数的唯一一个参数为异常处理函数指针。当程序发生异常是,且程序不处于调试模式(在VS或者其他调试器里运行)则首先调用该异常处理函数。因此,程序可以主动抛出一个异常来判断当前程序是否正在被调试,这是该程序安装的其中一个异常处理函数,当有异常发生并且当前程序没有被调试的情况下,该异常处理函数将得以执行。

我们回到OD中,看到SetUnhandledExceptionFilter的调用处。

通过调试可以发现0x401140处为异常处理函数,0x401297处的call eax,eax里面保存的是SetUnhandledExceptionFilter函数的地址。现在我们在0x401297处下断点。
调试状态异常处理的过程:
       程序有异常发生,并且当前程序正在被调试,所以,并不会首先调用程序之前设置的入口为0x401140的异常处理函数,而异常转交给调试器处理了,而调试器也无法处理该异常,所以最终调用系统默认的异常处理函数UnhandledExceptionFilter来处理。 所以我们在系统默认的UnhandledExceptionFilter函数处下断点。

7.发现程序没有按照预定的进行运行,发现函数0x401160全为nop

8.执行到 0x40129b处出现错误,由于这里edx的值为全0,所以出现错误,这里将他nop掉就可以,这里参考了别人的writeup.

9.运行到0x4012b4处程序就正常运行起来了。运行到0x4012c6处的时候程序又直接跑飞了。下面对这个函数进行分析。

10跟踪到这程序又崩溃了,这里不能nop掉,因为nop掉之后后面全部都是00了,这里不知道怎么做,再次参考别人的writeup,才知道,原来0x40720D处本来是有一段代码的,只不过每次OlleyDump的时候都会清零,所以只能每次都手动脱壳一次,由于程序会对自身代码在运行的时候进行解密操作,所以就不能用软件断点,这个时候就要考虑到硬件断点的作用啦。

11.在动态调试的时候就可以对程序进行正常的分析了0x40b970存放的是输入的key,这里将输入的key保存到ebp-0xc+eax处,最多为0xa个

12下面继续运行发现一个反调试函数ZwQueryInformationProcess()当然了这里只是保存字符串
运行完call函数之后,eax保存的就是函数地址.
发现运行到这会进入一个死循环,这是别人说是对系统的检查,需要在32位系统上运行。

13这里开始继续分析,在virtualbox里面安装了win32位,程序能够正常运行。发现在32位系统中运行的时候程序之前的那些需要在0x40129b处进行nop操作的地方也没有执行了。在32位系统中运行的时候就能直接跳过第12步的那个死循环。

14.下面会调用 ZwQueryInformationProcess() 反调试函数。调用了两次
该反调试函数简单的介绍:
NTSTATUS WINAPI ZwQueryInformationProcess(
  _In_      HANDLE           ProcessHandle,
  _In_      PROCESSINFOCLASS ProcessInformationClass,
  _Out_     PVOID            ProcessInformation,
  _In_      ULONG            ProcessInformationLength,
  _Out_opt_ PULONG           ReturnLength
);
其中第1个参数是要查询的进程的句柄值,第2个参数是要查询进程的哪些信息,该参数是一个枚举类型的值,可以查询的信息如下:
        第2个参数不同的值对应于第3个参数指向不同类型的结构体,比如第2个参数填0(ProcessBasicInformation),就表示你想获取PEB的信息,然后就可以判断BeingDebugged,NtGlobalFlag的值了,这里我们主要使用该函数查询2个值:ProcessDebugPort、ProcessDebugObjectHandle, ProcessDebugFlags对应的枚举值为7和0x1E,0x1F。

进程处于调试状态时,系统会为它分配一个调试端口,第2个参数传7时,NtQueryInformationProcess()就能获取调试端口的值,调试状态该值为0xFFFFFFFF,正常运行时该值为0。同样,有一个API也可以检测调试端口的值:CheckRemoteDebuggerPresent()。CTRL+G定位到该函数实现部分,发现该函数其实就是内部调用了一下第2个参数为7的NtQueryInformationProcess:(实际调用的Zw版的函数,功能相同)

       进程被调试时会生成调试对象,当函数的第2个参数值为0x1E(ProcessDebugObjectHandle)时就能获取调试对象句柄,正常运行时该值为0,调试运行时该值为非0值。

       破解方法:如果被调试程序只是调用几次API,而不是在多个地方反复调用,我们可以在调用的时候手动修改特定参数下的调用结果:修改返回值或函数参数指向的内存。像上面的例子一样,我们可以在该函数调用结束的后手动修改参数指向的内存,修改调用结果。当函数调用传参为7/0x1E的时候,修改其第3个参数获取的值: 如果该函数在多个地方被反复调用,那么我们可以采取API HOOK的技术,修改函数内部执行代码的流程。

      这里选择将两个反调试全部改成nop(发觉还是不行,后面还有调试)所以这里选择使用od自带的反调试插件。这里还是不行,直接将409150处改成retn


14.还是手动绕过吧

将0x409150函数改成retn]

位置0x4072a3在进入

位置:0x4072c9处nop

位置:0x407437处nop

位置:0x407463处nop


15.进入之后0x40b970处为输入的数据。取出第一个数据放入cl,取出第7个数据放入dl,然后将第一个数据和第七个数据放入0x40b990处

 16.0x407488处又取出第七个数据,将数据和0x36异或之后放入到0x40c450处 


17.ox40757a处通过GetThreadContext进行反调试

GetThreadContext这个API。GetThreadContext的原型如下: 

BOOL WINAPI GetThreadContext(
  __in     HANDLE hThread,
  __inout  LPCONTEXT lpContext
)

传入目标线程的句柄,就能得到上下文。
 简单版本

typedef struct DECLSPEC_ALIGN(16) _CONTEXT {
...
// Debug registers
//
DWORD64 Dr0;
DWORD64 Dr1;
DWORD64 Dr2;
DWORD64 Dr3;
DWORD64 Dr6;
DWORD64 Dr7 
...

} CONTEXT;  

Drx ,就是硬件调试寄存器,其中Dr0-3,就是对应OD里看到的四个硬件断
点,而 Dr7 则是状态控制寄存器。简单来说,如果用 OD 下了硬件断点,那么这五个寄存器
的值就至少有两个不为0。
这里应该将0x407587处的给nop掉,不然程序又会崩溃(看来得好好研究一下反调试技术)

18.这里遇到向上的跳转就需要nop掉,不然程序有会崩溃

19.这里用0x40c450处的值和0x36进行比较,应该相等,0x40c450=input[6]^0x36;说明输入的字符的第七位应该为0。key长度为6位

20.下面取出输入的第一个字符:0x40b990处存放的是输入的第一个字符
0x407614处的call要跟进去,下面遇到的call都最好跟进去(这里都是短跳转)

1)ror al,0x6,然后将al放入[0x40b000]处
然后rol al,0x4,将al 放入[0x40b001]处
xor al,0x34,将al放入[0x40b002]处



[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2019-1-17 19:52 被wwzzww编辑 ,原因:
上传的附件:
收藏
免费 5
支持
分享
最新回复 (4)
雪    币: 13650
活跃值: (19)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
韩国的reversing.kr所有题都能做出来。
美国的CTF有些题做得出来,有些题做不出来。
2019-1-17 09:55
1
雪    币: 26205
活跃值: (63302)
能力值: (RANK:135 )
在线值:
发帖
回帖
粉丝
3
感谢分享!
2019-1-17 15:59
0
雪    币: 13650
活跃值: (19)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4

19.这里用0x40c450处的值和0x36进行比较,应该相等,0x40c450=input[6]^0x36;说明输入的字符的第七位应该为0。key长度为6位

应该是“key的长度”吧。
“key长度”是密码学专用术语,值为2的正整数次方。
最后于 2019-1-18 08:19 被Mr Julius编辑 ,原因:
2019-1-18 00:08
0
雪    币: 299
活跃值: (199)
能力值: ( LV4,RANK:45 )
在线值:
发帖
回帖
粉丝
5
感谢分享
2019-10-30 22:49
0
游客
登录 | 注册 方可回帖
返回
//