Shark恒 发表于 2015-1-20 17:28

使用OllyDbg从零开始Cracking 第四十七章-Patrick的CrackMe-Part2

                  第四十七章-Patrick的CrackMe-Part2我们接着上一章的继续讲,上一章的结尾处我们是通过如下方式来附加新创建的进程的,首先将NTDLL.DLL中调用其他模块的入口点那一条指令设置为一个死循环,然后将OD设置为即时调试器(JIT),接着打开任务管理器,选中新创建的进程单击鼠标右键选择调试,这样OD就附加了新创建的进程。接着在死循环的指令处设置一个断点,然后将Patch过的字节码恢复为原始字节,然后直接按F9键运行两次后,模块列表窗口中就会出现AntiDebugDll.dll了,接着我们给AntiDebugDll.dll的代码段设置内存访问断点,然后删除掉之前设置的INT 3断点,运行起来,这样就可以断在AntiDebugDll.dll的入口点处了。现在我们打开了两个OD,其中一个被调试的为父进程,另一个被调试的为子进程。现在父进程处于CreateProcessA调用语句的返回地址处。子进程处于AntiDebugDll.dll的入口点处。这里我将调试子进程的OD换了一种配色方案,这样可以防止大家在阅读的时候将两个OD搞混淆了。理论上来说,现在我们需要同时模拟执行这两个进程,但实际上我们无法做到同时调试。我们只能够分别协同调试两个进程。我们知道父进程中调用CreateMutexA这个函数,这里我们给子进程的中CreateMutexA也设置一个断点。我们运行起来,断在了这里,这里由于父进程已经创建了MYFIRSTINSTANCE这个互斥体,所以我们如果执行了该函数,GetLastError的错误码将返回0xB7,即ERROR_ALREADY_EXISTS。这里我们执行到返回验证一下。这里我们可以看到LastErr为ERROR_ALREADY_EXISTS,即0xB7。表示互斥体已经存在。接着这里调用RtlGetLastWin32Error获取错误码,这里错误码我们已经知道了是0xB7了,即ERROR_ALREADY_EXISTS。这里将错误码保存到10010ECC指向的内存单元中,我们给该内存单元设置一个内存访问断点,运行起来。断在了这里,这里是读取该错误码进行比较。这里将RtlGetLastWin32Error返回的错误码与0xB7做比较。接着如果相等的话,通过SETE指令将EAX置1,这里明显错误码等于0xB7,所以EAX被置1,如果是父进程的话,EAX会被置0。接着我们返回到了这里,子进程中1000262C这处条件跳转将成立,而父进程此处跳转将不会成立。所以之后父进程与子进程将执行不同的分支流程,有待我们进一步分析。这里有没有觉得这样一点点单步跟踪有点麻烦?其实有网上很多好用的API监视工具,比如说KAM,APISPY,这些工具可以记录程序中执行了哪些API函数,不需要我们一步步的跟踪,可以节省很多时间。这里其实我们可以利用内存访问断点间接的完成API函数的监视工作,不需要这样一步步单步跟踪。具体操作如下:首先我们需要定位子进程中AntiDebugDll.dll这个模块的IAT,这里我们将反汇编窗口往上拉,随便找一个API函数调用处。我们可以看到100024B5处调用了WideCharToMultiByte这个API函数,该API函数的指针位于1000C034地址处,它是AntiDebugDll.dll的IAT中的一项。我们在数据窗口中定位到该IAT项。这里就是IAT了,我们定位到该IAT的起始位置和结束位置,然后选中整个IAT,单击鼠标右键选择Breakpoint-Memory,on access,给整个IAT设置内存访问断点。接下来我们同样给父进程的AntiDebugDll.dll的整个IAT表设置内存访问断点。这里我们就给父子进程的AntiDebugDll.dll的IAT都设置了内存访问断点,也就说当程序中调用AntiDebugDll.dll模块IAT中的API函数时就会断下来,这里还需要注意一点,有可能该Dll会通过调用GetProcAddress来获取其他的API函数指针,所以这里我们给GetProcAddress也设置一个断点,以防万一。好了,现在我们继续调试子进程,我们按F9键运行起来,看看调用哪些API函数。断在了这里,这里要创建MYMAININSTANCE这个互斥体,这个互斥体之前未被创建过。大家应该还记得之前父进程创建的那个互斥体吧,叫做MYFIRSTINSTANCE,顾名思义:”第一个实例”。这里要创建的互斥体叫做MYMAININSTANCE。顾名思义:”主体实例”。我们按F8键执行该API函数。这里我们可以看到错误码为ERROR_SUCCESS,说明互斥体创建成功。好,我们按F9键运行起来,看看下面会调用哪个API函数。这里又是调用RtlGetLastWin32Error获取上次调用CreateMutexA这个API函数的错误码,我们继续按F9键运行。这里是获取进程句柄,我们按F8键执行调用API函数,会返回(-1)FFFFFFFF。代表当前进程。该句柄不在句柄表中,不是真正的句柄,我们叫它伪句柄。继续按F9键运行。这里调用的是GetModuleHandleA,这样的跟踪对于我们练习哪些不熟悉的API函数是一种极佳的锻炼。我们按F8键执行。这里返回的是NTDLL.DLL这个模块的句柄。我猜测该程序会用该句柄作为GetProcessAddress的参数来获取NTDLL.DLL中包含的API函数指针。我希望我猜测是正确的,我们按F9键运行起来。嘿嘿,看来我们的猜测是正确的。我们来看看其参数。这里可以看到其要获取DbgUiRemoteBreakin这个API函数的指针。我们继续F9运行。这里我们可以看出一点该程序的意图了,它想通过Patch DbgUiRemoteBreakin这个函数的实现代码来达到反调试的目的。这里是修改DbgUiRemoteBreanin这个API函数首字节的访问属性,将访问属性修改为可读可写可执行。这里要调用WriteProcessMemory开始修改DbgUiRemoteBreakin的首字节了,我们来看看参数。这里我们可以看到进程句柄为FFFFFFFF,也就是当前进程,通过之前那个GetCurrentProcess获取到的。这里我们来看看DbgUiRemoteBreakin的实现代码。这里我们可以看到DbgUiRemoteBreakin中会调用DbgBreakPoint,我们执行该API函数看看其将DbgUiRemoteBreakin的首字节修改为了什么。这里我们可以看到DbgUiRemoteBreakin的首字节被修改为了RET指令。我们继续运行。这里又是获取NTDLL.DLL的模块句柄,接着调用GetProcAddress获取DbgBreakPoint的函数指针,接着Patch之。修改DbgBreakPoint首字节的访问权限。这里要Patch DbgBreakPoint首字节了,我们来看看参数。这里我们来看看DbgBreakPoint的实现代码。我们按F8键执行,看看DbgBreakPoint的首字节被修改为了什么。这里我们可以看到DbgBreakPoint的首字节被修改为了RET。这样也可以达到反调试的目的。我们继续F9运行。这里调用OpenMutexA这个API函数,打开名为WAIT的互斥体,大家应该还记得父进程已经创建了一个名为WAIT的互斥体吧,我们打开父进程所在OD的句柄列表窗口看看。我们可以看到父进程中的确存在一个WAIT的互斥体,这里子进程中并没有调用CreateMutexA,而是调用OpenMutexA来打开这个互斥体,这里可以顺利获取到之前父进程中创建的WAIT互斥体的句柄。我们按F8键执行该函数,可以看到成功获取到了WAIT互斥体的句柄,为0x54。现在我们来看看子进程所在OD的句柄列表窗口。恩,成功打开了WAIT这个互斥体。我们继续F9键运行。这里我们可以看到调用WaitForSingleObject这个函数进行父进程与子进程的同步处理。我们来看看其参数。这里第一个参数为WAIT这个互斥体的句柄,第二个参数为5000ms,即超时时间为5秒钟。也就是说子进程会等待父进程释放WAIT互斥体的信号量,如果超过5秒父进程还没有释放该函数就直接返回。下面一条语句将WaitForSingleObject的返回值保存到变量中。这里我们可以看到将WaitForSingleObject的返回值与零进行比较,如果等于零(即WAIT_OBJECT_0:成功等到信号量释放)就可以绕过下面的ExitProcess的调用处。这里为了让子进程不因为WaitForSingleObject等待超时而退出,我们将WaitForSingleObject的超时时间修改为INFINITE(无穷大)。这里我们将WaitForSingleObject的第二个参数由0x1388修改为了FFFFFFFF(-1),即INFINITE。这里为了让父进程释放WAIT互斥体信号量的时候我们能够子进程能够顺利断下,我们给下一条指令处设置一个断点。按F9键运行起来。现在我们可以看到子进程已经运行起来了。也就是说子进程正在等待父进程释放WAIT这个互斥体的信号量。由于子进程所在OD的右下方现在显示的是Running状态,所以现在我们不能调试子进程了。转而我们现在来调试父进程。大家应该还记得子进程中的WaitForSingleObject的超时时间为5秒吧?如果是5秒的话,那么子进程过了5秒,父进程还没有释放WAIT信号量,子进程就会调用ExitProcess退出了。所以我们将超时时间修改为了FFFFFFFF(即INFINITE),那么子进程只能乖乖的一直等待,直到父进程释放WAIT信号量为止,嘿嘿。现在我们来调试父进程,我们已经对父进程中AntiDebugDll.dll模块的整个IAT设置内存访问断点了,所以我们直接运行起来,看看会调用哪些API函数。我们可以看到这里调用Sleep休眠片刻,我们继续F9运行起来,看看还会调用其他的什么重要的API函数。如果断在不是很重要的API函数处的话,我们继续运行。我们可以看到这里调用CreateFileA打开文件,有点可疑。我们来看看其参数。这里调用CreateFileA要打开AntiDebugDll.dll,很可能要检测AntiDebugDll.dll中的代码是否被修改,防止被下INT 3断点。所以我们这里我们将之前设置的INT 3都删除掉。这里我们将断点列表窗口中的断点都删除掉。下面我们就不设置INT 3断点了,如果实在需要设置断点,我们就用内存断点替代。我们来看CreateFileA的参数情况:这里明显这个程序想通过CreateFileA打开AntiDebugDll.dll,然后通过ReadFile读取相应字节码进行比较,看看是否被修改。这里我们并没有修改Dll文件中的内容,我们修改的都是内存中的内容。继续看其他参数。这里大部分参数我们都不是很关系,我们只需要注意一下dwShareMode这个参数,共享模式为FILE_SHARE_READ,我们来看一下MSDN中的解释。如果是读取操作的话,会返回成功。我们按F8键执行,可以看到返回的句柄值。这里打开的AntiDebugDll.dll的文件句柄为0x58。我们继续执行,看看还会调用什么API函数。这里调用的是CreateFileMappingA这个API函数,创建一个文件映射。接着这里调用MapViewOfFile将文件的内容映射到内存中,我们按F8键执行该函数,看看被映射的地址是多少。这里AntiDebugDll.dll就被映射到了内存中,起始地址为9F0000,接着它将会怎么做呢?将所有的字节都逐一比较吗?不,它没有,它仅仅只比较了起始的几个字节。我们在数据窗口中定位到9F0000地址处。而我们当前正在运行的AntiDebugDll.dll的基地址为10000000,我们再到数据窗口中定位到该地址,我们可以看到两者的内容是一致的。我们继续F9键运行。这里是读取DLL文件的大小然后校验,下面接着判断文件扩展名是否为,DLL,dll,EXE,exe。继续:删除9F0000地址处的内存映射,说明不再需要这些字节了。关闭掉文件句柄。这里又调用了CreateFileA,参数如下:dwDesiredAccess的值为GENERIC_READ,但是dwShareMode的值被设置为了0。MSDN中的解释是如果dwShareMode被设置为零,该句柄不共享,再关闭之前不能被再次打开。这里如果我们按F8键执行的话,将打开文件失败。EAX返回-1(即FFFFFFFF)。这里我们可以看到返回的句柄为FFFFFFFF,即打开文件失败,LastErr为ERROR_SHARING_VIOLATION。继续。我们可以看到下面这部分代码,如果刚刚读取文件打开文件成功的话,那么接着将读取文件成功。接着这里会对读取到的文件内容进行处理,有可能是解密代码哟!下面10003901地址处调用的10005010是解密的核心部分。这里刚刚由于打开文件失败,所以并不会执行刚刚解密操作,这里我们需要重来一遍前面的步骤,回到刚刚CreateFileA处。我们将dwShareMode修改为FILE_SHARE_READ,即1,让打开文件成功。接下来会读取文件,然后对读取到的文件内容进行相应的处理,我们来看看对文件内容处理以后后续流程会有什么影响。按F8键执行。这里获取到了句柄,我们继续。又是ReadFile,按F8键。这里EAX返回1,接下来会对读取到的文件内容进行相应的处理,很可能是解密处理。我们继续按F9键运行。关闭句柄。这里又到了CreateFileA处。我们会发现该程序多次调用CreateFileA,但是要注意了,这里要打开的Patrick.exe这个文件。这里由于是第一次打开,所以成功获取到了文件句柄。现在又要对Patrick.exe创建文件映射了,跟之前AntiDebugDll.dll的类似。创建内存映射,映射到了AF0000这个地址处。这里我们在数据窗口中定位AF0000地址处,可以看到Patrick.exe的PE头部情况。我们再在数据窗口中定位当前程序的基地址400000。我们可以看到内容是一致的。我们按F9键继续运行。判断文件扩展名。删除内存映射,貌似没有做文件内容的检测呀。这里经过了几个不重要的API函数以后到了这里,这里要修改起始地址401000内存单元的访问权限。这里我们可以看到是要修改子进程的401000地址处的访问权限。这里调用ReadProcessMemory读取进程内容。这里我们可以看到是读取起始地址为401000,长度为16个字节的内容。起始地址为401000的这个区段是主程序的代码段。我们先在来看看401000地址处的代码。明显经过加密处理了。这里在循环调用WriteProcessMemory,写入主程序的整个代码段,我们得一直按F9键运行,直到循环结束为止。写入整个代码段,貌似像是在解密区段的样子。继续。这里马上循环写入快结束了。这里是释放WAIT互斥体信号量,也就是子进程调用WaitForSingleObject正在等待的信号量。现在我们按F8键,子进程就会运行起来。我们直接按F9键运行。这里调用的是WaitForInputIdle这个函数,我们来看看MSDN中的说明。我们可以看到该函数可以使一个线程挂起,直到规定线程初始化完成为止。对于父子进程之间的同步极为有用。我们直接对下一条指令处设置一个断点,然后运行起来。我们可以看到父进程现在已经处于运行状态了,我们现在接着来看子进程。现在子进程调用ReleaseMutex释放WAIT互斥体的信号量,但是父进程并没有调用WaitForSingleObject进行WAIT信号量的等待,所以我们继续跟踪子进程,继续按F9键运行。这里又是跟父进程一样检查AntiDebugDll.dll文件。不再赘述。现在到了关键部位了。跟之前一样我们将dwShareMode修改为1。成功返回句柄。期间又调用了几个不重要的API,然后就到了ReadFile这里,读取起始的几个字节判断有没有被修改。接着这里关闭文件句柄。这里再次调用CreateFileA打开Patrick.exe。成功返回句柄。这部分与父进程是一样的,我们不再赘述。这里我们依然将dwShareMode修改为1。我们继续按F9键运行,经过几个API函数以后会再次调用ReadFile,读取并检测起始的几个字节内容。这里这些步骤都是相似的,我们只要遇到CreateFileA,将dwShareMode修改为1即可。又是调用ReadFile进行检查。继续往下跟,这里调用GetCurrentProcess,获取当前进程的句柄,貌似要进行一些新的处理了,我们一起来看看。这里又是跟父进程一样调用VirtualProtectEx修改内存访问权限,为下一步调用ReadProcessMemory,WriteProcesMemory读写内存做铺垫。接着又是WriteProcessMemory。这里又是写入起始地址为401000,长度为16的内容。我们定位到401000看看。这里又是循环写入,我们一直按F9键,直到整个主程序代码段都被写完毕为止。好,现在已经循环写入完毕了,通过PEEditor可以得知主程序的OEP为1D55,我们现在来看看401D55处的内容是什么。可能看到的确像是入口点的样子,说明之前循环写入的确是在解密主程序代码段。现在区段已经解密完毕了,接着这里要调用CreateThread创建线程,我们将CreationFlags修改为4,即挂起状态。我们定位该线程的起始地址看看,这是是创建的第一个线程,我们继续往下看,看看会不会再创建其他进程。这里是创建第二个线程,我们依然将dwCreationFlags修改为0x4。好了,现在区段已经解密完毕了,我们接下来需要顺利断在OEP处,我们直接删除之前设置的内存访问断点,然后对主程序代码段设置内存访问断点。接着运行起来。现在我们将成功断在了OEP处,下面我们来进行dump。现在我们再打开一个OD,加载Patrick.exe,断在了系统断点处。现在我们将子进程代码段的所有字节都拷贝到这个新开的OD中。接着将修改保存到文件,我们来看下IAT,IAT项都是正常的。现在我们就不需要执行AntiDebugDll.dll这个反调试模块中的内容了,所以我们可以直接将AntiDebugDll.dll的入口点处的指令修改为RET。我们再次用OD加载刚刚保存的文件,断在了系统断点处,这里我们定位到AntiDebugDll.dll的入口地址:100064D4,直接将第一条指令修改为RET,让其返回。接着保存修改到文件。接着我们打开PEEditor程序的所有区段访问权限都修改为E0000020(可读可写可执行)。这里我们还可以看到一个主程序代码段中还有一处CALL是指向的AntiDebugDll.dll的,我们直接把它NOP掉。我们保存修改到文件,直接运行修改好的CrackMe。我们现在运行起来了,我们可以看到程序正常运行。下面我们需要进一步干掉这个AntiDebugDll.dll这个模块,让主程序无需加载这个DLL。为了DLL不加载,我们应该进行如下处理:我们定位到导入表(IT)的起始地址为406F3C。大家应该可以记得吧,每个DLL项占5个DWORD,第4个DWORD为DLL名称字符串指针。这里第一个DLL名称字符串指针为40712E,我们一起来看一看。是WINMM.DLL,第二个DLL为AntiDebugDll.dll,这里我们为了剔除掉AntiDebugDll.dll这个模块,我们可以将AntiDebugDll.dll对应的DLL项修改得与WINMM.DLL这一个DLL项一致。保存修改到文件。我们重启OD,再来看看导入表。如下:现在我们会发现并没有加载AntiDebugDll.dll了。好了,程序完美运行。嘿嘿,这个CrackMe我们就搞定了。
本系列文章汉化版转载看雪论坛
感谢原作者:RicardoNarvaja(西班牙人) 原作者个人主页:http://www.ricardonarvaja.info/
感谢热心翻译的朋友:1~3章译者:BGCoder4~58章译者:安于此生
全集配套程序下载地址:
链接: http://pan.baidu.com/s/1eQzTWfo 密码: vytv



hackysh 发表于 2022-2-8 17:53


[快捷回复]-感谢楼主热心分享!

别管我了行 发表于 2022-3-3 04:26

曾经沧海 发表于 2022-10-31 20:06

比较实用,谢谢分享!

一生逍遥 发表于 2022-12-5 08:03

支持楼主   来学习学习下!

曾经沧海 发表于 2023-4-5 23:27

就算荧光棒变成拐杖,你依旧是我信仰

一生逍遥 发表于 2023-4-22 17:22

谢谢你好东西 支持你!
页: [1]
查看完整版本: 第四十七章-Patrick的CrackMe-Part2