网络安全编程:编写密码显示程序

安全
本文使用调试API针对CrackMe来编写一个显示密码的程序。

 [[399113]]

本文使用调试API针对CrackMe来编写一个显示密码的程序。

在编写关于CrackMe的密码显示程序以前需要准备两项工作,第一项工作是知道要在什么地方合理地下断点,第二项工作是从哪里能读取到密码。带着这两个问题重新来思考一下。在这里的程序中,要对两个字符串进行比较,而比较的函数是strcmp(),该函数有两个参数,分别是输入的密码和真正的密码。也就是说,在调用strcmp()函数的位置下断点,通过查看它的参数是可以获取到正确的密码的。在调用strcmp()函数的位置设置INT3断点,也就是将0xCC机器码写入这个地址。用OD看一下调用strcmp()函数的地址,如图1所示。

图1  调用strcmp()函数的地址

从图1中可以看出,调用strcmp()函数的地址为00401E9E。有了这个地址,只要找到该函数的两个参数,就可以找到输入的错误的密码及正确的密码。从图1中可以看出,正确的密码的起始地址保存在EDX中,错误的密码的起始地址保存在ECX中。只要在00401E9E地址处下断点,并通过线程环境读取EDX和ECX寄存器值就可以得到两个密码的起始地址。

进行准备的工作已经做好了,下面来写一个控制台的程序。先定义两个常量,一个是用来设置断点的地址,另一个是INT3指令的机器码。定义如下: 

  1. // 需要设置 INT3 断点的位置  
  2. #define BP_VA 0x00401E9E  
  3. // INT3 的机器码  
  4. const BYTE bInt3 = '\xCC'

把CrackMe的文件路径及文件名当参数传递给显示密码的程序。显示的程序首先要以调试的方式创建CrackMe,代码如下: 

  1. // 启动信息  
  2. STARTUPINFO si = { 0 };  
  3. si.cb = sizeof(STARTUPINFO);  
  4. GetStartupInfo(&si);  
  5. // 进程信息  
  6. PROCESS_INFORMATION pi = { 0 };  
  7. // 创建被调试进程  
  8. BOOL bRet = CreateProcess(pszFileName,  
  9.   NULL,NULL,NULL,FALSE,  
  10.   DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS,  
  11.   NULL,NULL,&si,&pi);  
  12. if ( bRet == FALSE )  
  13.  
  14.   printf("CreateProcess Error \r\n");  
  15.   return -1;  
  16. }  

然后进入调试循环,要处理两个调试事件,一个是CREATE_PROCESS_DEBUG_EVENT,另一个是EXCEPTION_DEBUG_EVENT下的EXCEPTION_BREAKPOINT。处理CREATE_PROCESS_DEBUG_EVENT的代码如下: 

  1. // 创建进程时的调试事件  
  2. case CREATE_PROCESS_DEBUG_EVENT:  
  3.  
  4.   // 读取欲设置 INT3 断点处的机器码  
  5.   // 方便后面恢复  
  6.   ReadProcessMemory(pi.hProcess,(LPVOID)BP_VA,  
  7.     (LPVOID)&bOldByte,sizeof(BYTE),&dwReadWriteNum); 
  8.   // 将 INT3 的机器码 0xCC 写入断点处  
  9.   WriteProcessMemory(pi.hProcess,(LPVOID)BP_VA,  
  10.     (LPVOID)&bInt3,sizeof(BYTE),&dwReadWriteNum);  
  11.   break;  

在CREATE_PROCESS_DEBUG_EVENT中对调用strcmp()函数的地址处设置INT3断点,再将0xCC写入这里时要把原来的机器码读取出来。读取原机器码使用ReadProcess Memory(),写入INT3的机器码使用WriteProcessMemory()。读取原机器码的作用是当写入的0xCC产生中断以后,需要将原机器码写回,以便程序可以正确继续运行。

再来看一下EXCEPTION_DEBUG_EVENT下的EXCEPTION_BREAKPOINT是如何进行处理的,代码如下: 

  1. // 产生异常时的调试事件  
  2. case EXCEPTION_DEBUG_EVENT:  
  3.  
  4.   // 判断异常类型  
  5.   switch ( de.u.Exception.ExceptionRecord.ExceptionCode )  
  6.   {  
  7.     // INT3 类型的异常  
  8.     case EXCEPTION_BREAKPOINT:  
  9.     {  
  10.       // 获取线程环境  
  11.       context.ContextFlags = CONTEXT_FULL 
  12.       GetThreadContext(pi.hThread, &context);  
  13.       // 判断是否断在设置的断点位置处  
  14.       if ( (BP_VA + 1) == context.Eip )  
  15.       {  
  16.         // 读取正确的密码  
  17.         ReadProcessMemory(pi.hProcess,(LPVOID)context.Edx,  
  18.           (LPVOID)pszPassword,MAXBYTE,&dwReadWriteNum);  
  19.         // 读取错误密码  
  20.         ReadProcessMemory(pi.hProcess,(LPVOID)context.Ecx,  
  21.           (LPVOID)pszErrorPass,MAXBYTE,&dwReadWriteNum);  
  22.         printf("你输入的密码是: %s \r\n", pszErrorPass);  
  23.         printf("正确的密码是: %s \r\n", pszPassword);  
  24.         //指令执行了 INT3 而被中断  
  25.         // INT3 的机器指令长度为 1 字节  
  26.         // 因此需要将 EIP 减一来修正 EIP  
  27.         // EIP 是指令指针寄存器  
  28.         // 其中保存着下条要执行指令的地址  
  29.         context.Eip --;  
  30.         // 修正原来该地址的机器码  
  31.         WriteProcessMemory(pi.hProcess,(LPVOID)BP_VA,  
  32.           (LPVOID)&bOldByte,sizeof(BYTE),&dwReadWriteNum); 
  33.         // 设置当前的线程环境  
  34.         SetThreadContext(pi.hThread, &context);  
  35.       }  
  36.       break;  
  37.     }  
  38.   }  

对于调试事件的处理,应该放到调试循环中。上面的代码给出的是对调试事件的处理,再来看一下调试循环的大体代码: 

  1. while ( TRUE )  
  2.  
  3.   // 获取调试事件  
  4.   WaitForDebugEvent(&de, INFINITE);  
  5.   // 判断事件类型  
  6.   switch ( de.dwDebugEventCode )  
  7.   {  
  8.     // 创建进程时的调试事件  
  9.     case CREATE_PROCESS_DEBUG_EVENT:  
  10.     {  
  11.       break;  
  12.     }  
  13.     // 产生异常时的调试事件  
  14.     case EXCEPTION_DEBUG_EVENT:  
  15.     { 
  16.       // 判断异常类型  
  17.       switch ( de.u.Exception.ExceptionRecord.ExceptionCode )  
  18.       {  
  19.         // INT3 类型的异常  
  20.         case EXCEPTION_BREAKPOINT:  
  21.         {  
  22.         }  
  23.         break;  
  24.       }  
  25.     }  
  26.   }  
  27.   ContinueDebugEvent(de.dwProcessId,de.dwThreadId,DBG_CONTINUE);  

只要把调试事件的处理方法放入调试循环中,程序就完整了。接下来编译连接一下,然后把CrackMe直接拖放到这个密码显示程序上。程序会启动CrackMe进程,并等待用户的输入。输入账号及密码后,单击“确定”按钮,程序会显示出正确的密码和用户输入的密码,如图2所示。

图2  显示正确密码

根据图2显示的结果进行验证,可见获取的密码是正确的。程序到此结束,大家可以把该程序改成通过附加调试进程来显示密码,以巩固所学的知识。 

 

责任编辑:庞桂玉 来源: 计算机与网络安全
相关推荐

2021-03-03 12:20:42

网络安全DLL编程

2021-02-23 10:20:07

网络安全进程代码

2021-03-05 13:46:56

网络安全远程线程

2021-01-26 13:45:03

网络安全Winsock编程

2021-02-02 14:02:48

网络安全对称密码密钥

2016-10-10 00:18:27

2021-02-21 18:19:43

网络安全网络安全编程创建进程

2021-12-12 22:01:59

网络安全密码技术

2021-06-18 09:55:09

网络安全目录监控

2021-06-15 11:16:24

网络安全U盘软件

2021-04-19 10:26:41

网络安全PE文件

2021-02-04 10:50:11

网络安全非阻塞模Winsock编程

2021-06-24 08:37:34

网络安全内核代码

2021-05-24 11:55:55

网络安全Windows钩子函数

2009-07-03 06:57:32

2021-02-04 10:10:48

网络安全公钥密码密码技术

2021-04-26 10:32:38

网络安全PE编程工具

2021-03-01 11:20:13

网络安全多线程代码

2021-02-07 10:55:01

网络安全文件API

2021-01-18 10:35:18

网络安全Windows代码
点赞
收藏

51CTO技术栈公众号