[[399113]]

本文使用调试API针对CrackMe来编写一个显示密码的程序。
在编写关于CrackMe的密码显示程序以前需要准备两项工作,第一项工作是知道要在什么地方合理地下断点,第二项工作是从哪里能读取到密码。带着这两个问题重新来思考一下。在这里的程序中,要对两个字符串进行比较,而比较的函数是strcmp(),该函数有两个参数,分别是输入的密码和真正的密码。也就是说,在调用strcmp()函数的位置下断点,通过查看它的参数是可以获取到正确的密码的。在调用strcmp()函数的位置设置INT3断点,也就是将0xCC机器码写入这个地址。用OD看一下调用strcmp()函数的地址,如图1所示。
图1 调用strcmp()函数的地址
从图1中可以看出,调用strcmp()函数的地址为00401E9E。有了这个地址,只要找到该函数的两个参数,就可以找到输入的错误的密码及正确的密码。从图1中可以看出,正确的密码的起始地址保存在EDX中,错误的密码的起始地址保存在ECX中。只要在00401E9E地址处下断点,并通过线程环境读取EDX和ECX寄存器值就可以得到两个密码的起始地址。
进行准备的工作已经做好了,下面来写一个控制台的程序。先定义两个常量,一个是用来设置断点的地址,另一个是INT3指令的机器码。定义如下:
- // 需要设置 INT3 断点的位置
 - #define BP_VA 0x00401E9E
 - // INT3 的机器码
 - const BYTE bInt3 = '\xCC';
 
把CrackMe的文件路径及文件名当参数传递给显示密码的程序。显示的程序首先要以调试的方式创建CrackMe,代码如下:
- // 启动信息
 - STARTUPINFO si = { 0 };
 - si.cb = sizeof(STARTUPINFO);
 - GetStartupInfo(&si);
 - // 进程信息
 - PROCESS_INFORMATION pi = { 0 };
 - // 创建被调试进程
 - BOOL bRet = CreateProcess(pszFileName,
 - NULL,NULL,NULL,FALSE,
 - DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS,
 - NULL,NULL,&si,&pi);
 - if ( bRet == FALSE )
 - {
 - printf("CreateProcess Error \r\n");
 - return -1;
 - }
 
然后进入调试循环,要处理两个调试事件,一个是CREATE_PROCESS_DEBUG_EVENT,另一个是EXCEPTION_DEBUG_EVENT下的EXCEPTION_BREAKPOINT。处理CREATE_PROCESS_DEBUG_EVENT的代码如下:
- // 创建进程时的调试事件
 - case CREATE_PROCESS_DEBUG_EVENT:
 - {
 - // 读取欲设置 INT3 断点处的机器码
 - // 方便后面恢复
 - ReadProcessMemory(pi.hProcess,(LPVOID)BP_VA,
 - (LPVOID)&bOldByte,sizeof(BYTE),&dwReadWriteNum);
 - // 将 INT3 的机器码 0xCC 写入断点处
 - WriteProcessMemory(pi.hProcess,(LPVOID)BP_VA,
 - (LPVOID)&bInt3,sizeof(BYTE),&dwReadWriteNum);
 - break;
 - }
 
在CREATE_PROCESS_DEBUG_EVENT中对调用strcmp()函数的地址处设置INT3断点,再将0xCC写入这里时要把原来的机器码读取出来。读取原机器码使用ReadProcess Memory(),写入INT3的机器码使用WriteProcessMemory()。读取原机器码的作用是当写入的0xCC产生中断以后,需要将原机器码写回,以便程序可以正确继续运行。
再来看一下EXCEPTION_DEBUG_EVENT下的EXCEPTION_BREAKPOINT是如何进行处理的,代码如下:
- // 产生异常时的调试事件
 - case EXCEPTION_DEBUG_EVENT:
 - {
 - // 判断异常类型
 - switch ( de.u.Exception.ExceptionRecord.ExceptionCode )
 - {
 - // INT3 类型的异常
 - case EXCEPTION_BREAKPOINT:
 - {
 - // 获取线程环境
 - context.ContextFlags = CONTEXT_FULL;
 - GetThreadContext(pi.hThread, &context);
 - // 判断是否断在设置的断点位置处
 - if ( (BP_VA + 1) == context.Eip )
 - {
 - // 读取正确的密码
 - ReadProcessMemory(pi.hProcess,(LPVOID)context.Edx,
 - (LPVOID)pszPassword,MAXBYTE,&dwReadWriteNum);
 - // 读取错误密码
 - ReadProcessMemory(pi.hProcess,(LPVOID)context.Ecx,
 - (LPVOID)pszErrorPass,MAXBYTE,&dwReadWriteNum);
 - printf("你输入的密码是: %s \r\n", pszErrorPass);
 - printf("正确的密码是: %s \r\n", pszPassword);
 - //指令执行了 INT3 而被中断
 - // INT3 的机器指令长度为 1 字节
 - // 因此需要将 EIP 减一来修正 EIP
 - // EIP 是指令指针寄存器
 - // 其中保存着下条要执行指令的地址
 - context.Eip --;
 - // 修正原来该地址的机器码
 - WriteProcessMemory(pi.hProcess,(LPVOID)BP_VA,
 - (LPVOID)&bOldByte,sizeof(BYTE),&dwReadWriteNum);
 - // 设置当前的线程环境
 - SetThreadContext(pi.hThread, &context);
 - }
 - break;
 - }
 - }
 - }
 
对于调试事件的处理,应该放到调试循环中。上面的代码给出的是对调试事件的处理,再来看一下调试循环的大体代码:
- while ( TRUE )
 - {
 - // 获取调试事件
 - WaitForDebugEvent(&de, INFINITE);
 - // 判断事件类型
 - switch ( de.dwDebugEventCode )
 - {
 - // 创建进程时的调试事件
 - case CREATE_PROCESS_DEBUG_EVENT:
 - {
 - break;
 - }
 - // 产生异常时的调试事件
 - case EXCEPTION_DEBUG_EVENT:
 - {
 - // 判断异常类型
 - switch ( de.u.Exception.ExceptionRecord.ExceptionCode )
 - {
 - // INT3 类型的异常
 - case EXCEPTION_BREAKPOINT:
 - {
 - }
 - break;
 - }
 - }
 - }
 - ContinueDebugEvent(de.dwProcessId,de.dwThreadId,DBG_CONTINUE);
 - }
 
只要把调试事件的处理方法放入调试循环中,程序就完整了。接下来编译连接一下,然后把CrackMe直接拖放到这个密码显示程序上。程序会启动CrackMe进程,并等待用户的输入。输入账号及密码后,单击“确定”按钮,程序会显示出正确的密码和用户输入的密码,如图2所示。
图2 显示正确密码
根据图2显示的结果进行验证,可见获取的密码是正确的。程序到此结束,大家可以把该程序改成通过附加调试进程来显示密码,以巩固所学的知识。
                网站名称:网络安全编程:编写密码显示程序
                
                文章链接:http://www.csdahua.cn/qtweb/news26/414126.html
            
网站建设、网络推广公司-快上网,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 快上网