《C++ 黑客编程揭秘与防范(第2版)》—第6章6.7节打造一个密码显示器

6.7 打造一个密码显示器
C++ 黑客编程揭秘与防范(第2版)
关于系统提供的调试API函数已经学习了不少,而且基本上常用到的函数都已学过。下面用调试API编写一个能够显示密码的程序。读者别以为这里写的程序什么密码都能显示,这是不可能的。下面针对前面的CrackMe来编写一个显示密码的程序。

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

从图6-75中可以看出,调用strcmp()函数的地址为00401E9E。有了这个地址,只要找到该函数的两个参数,就可以找到输入的错误的密码及正确的密码。从图6-75中可以看出,正确的密码的起始地址保存在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;
  }

然后进入调试循环,要处理两个调试事件,一个是CREATEPROCESS_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进程,并等待用户的输入。输入账号及密码后,单击“确定”按钮,程序会显示出正确的密码和用户输入的密码,如图6-76所示。

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

本文仅用于学习和交流目的,不代表异步社区观点。非商业转载请注明作译者、出处,并保留本文的原始链接。

时间: 2024-10-04 02:29:06

《C++ 黑客编程揭秘与防范(第2版)》—第6章6.7节打造一个密码显示器的相关文章

《C++ 黑客编程揭秘与防范(第2版)》—第6章6.4节PE相关编程实例

6.4 PE相关编程实例 C++ 黑客编程揭秘与防范(第2版) 前面讲的都是概念性的知识,本节主要编写一些关于PE文件结构的程序代码,以帮助读者加强对PE结构的了解. 6.4.1 PE查看器 写PE查看器并不是件复杂的事情,只要按照PE结构一步一步地解析就可以了.下面简单地解析其中几个字段内容,显示一下节表的信息,其余的内容只要稍作修改即可.PE查看器的界面如图6-26所示. PE查看器的界面按照图6-26所示的设置,不过这个可以按照个人的偏好进行布局设置.编写该PE查看器的步骤为打开文件并创建

《C++ 黑客编程揭秘与防范》—第1章1.1节编程语言和开发环境的选择

第1章 黑客编程入门 C++ 黑客编程揭秘与防范 你是否曾经在用别人开发的工具尝试"入侵",你是否希望开发出自己的黑器--相信很多人有着这种近似相同的经历.本章将简单介绍黑客编程及工具开发.如果你是初学编程,如果你从来没有接触过黑客软件的开发,如果你急于想了解黑客编程方面的知识--那么就请继续往下阅读. 1.1 编程语言和开发环境的选择 C++ 黑客编程揭秘与防范 初学者刚开始学习编程语言最头疼的问题就是如何选择编程语言及合适的开发环境,下面就来具体介绍一下. 有人认为学编程就是学编程

《C++ 黑客编程揭秘与防范》——1.1 编程语言和开发环境的选择

1.1 编程语言和开发环境的选择 C++ 黑客编程揭秘与防范 初学者刚开始学习编程语言最头疼的问题就是如何选择编程语言及合适的开发环境,下面就来具体介绍一下. 有人认为学编程就是学编程语言,而VC.VB这样的开发环境只是工具,不需要学.这个想法是错误的,因为开发环境提供了很多开发工具,如VC这个集成开发环境就提供了与之对应的PSDK.MFC等.除了语言以外,要开发特定的软件是需要开发包和开发工具支持的.况且,编程语言也是一种工具,用于和计算机进行交流的工具.所以我们既要学习编程语言,也要学习开发

《C++ 黑客编程揭秘与防范》——1.3 简单API的介绍

1.3 简单API的介绍 C++ 黑客编程揭秘与防范 下面介绍一些在黑客编程中会用到的API函数,尽量排一点简单易用的函数,用简单的几行代码来完成一定的功能,希望大家能在这里体会到编程乐趣,不至于被大段的代码影响了自己前进的心情. 1.3.1 复制自身程序到Windows目录和系统目录下 一般的病毒木马都有这种类似的功能,完成这个功能其实并不复杂,我们来拆解思考一下实现这段代码的步骤. 复制是一个拷贝的过程.既然是拷贝,就要知道拷贝的原位置和目的位置.也就是整个过程其实分3步,首先要得到自身程序

《C++ 黑客编程揭秘与防范》——1.2 应用程序的调试

1.2 应用程序的调试 C++ 黑客编程揭秘与防范 在开发程序的过程中,除了编码以外还需要对程序进行调试,当编写的程序出现问题后,就要对程序进行调试.调试不是仅使用一个printf()或MessageBox()进行简单的输出来观察某个函数的返回值(虽然在调试的时候的确是对返回值观察较多),也不是对某个变量.某一时间的具体值的输出.调试是有专业的调试分析工具的,VC6不但提供代码编辑.代码编译.编译连接等功能,还提供了一个非常好用的调试工具.在编写完代码后,如果程序输出的结果是未知的,或者是没有预

《C++ 黑客编程揭秘与防范(第2版)》——6.1 PE文件结构

6.1 PE文件结构 C++ 黑客编程揭秘与防范(第2版) PE(Portable Executable),即可移植的执行体.在Windows平台(包括Win 9x.Win NT.Win CE--)下,所有的可执行文件(包括EXE文件.DLL文件.SYS文件.OCX文件.COM文件--)均使用PE文件结构.这些使用PE文件结构的可执行文件也称为PE文件. 普通的程序员也许没有必要掌握PE文件结构,因为其大多是开发服务性.决策性.辅助性的软件,比如MIS.HIS.CRM等软件.但是对于学习黑客编程

《C++ 黑客编程揭秘与防范(第2版)》—第6章6.3节PE结构的3种地址

6.3 PE结构的3种地址 C++ 黑客编程揭秘与防范(第2版) 在上一章中用OD调试器调试程序时看到的地址与本章使用C32Asm以十六进制形式查看程序时的地址形式有所差异.程序在内存中与在文件中有着不同的地址形式,而且PE相关的地址不只有这两种形式.与PE结构相关的地址形式有3种,且这3种地址形式可以进行转换. 6.3.1 与PE结构相关的3种地址 与PE结构相关的3种地址是VA(虚拟地址).RVA(相对虚拟地址)和FileOffset(文件偏移地址). VA(虚拟地址):PE文件映射到内存后

《C++ 黑客编程揭秘与防范(第2版)》——6.3 PE结构的3种地址

6.3 PE结构的3种地址 C++ 黑客编程揭秘与防范(第2版) 在上一章中用OD调试器调试程序时看到的地址与本章使用C32Asm以十六进制形式查看程序时的地址形式有所差异.程序在内存中与在文件中有着不同的地址形式,而且PE相关的地址不只有这两种形式.与PE结构相关的地址形式有3种,且这3种地址形式可以进行转换. 6.3.1 与PE结构相关的3种地址 与PE结构相关的3种地址是VA(虚拟地址).RVA(相对虚拟地址)和FileOffset(文件偏移地址). VA(虚拟地址):PE文件映射到内存后

《C++ 黑客编程揭秘与防范(第2版)》——第6章 加密与解密

第6章 加密与解密 C++ 黑客编程揭秘与防范(第2版) 本章介绍的是关于加密与解密的知识,但是从整篇的内容上来看很难找到具体的加密与解密的知识.本章主要介绍PE结构.调试API函数等相关的内容.加密与解密,简单来说,主要就是逆向与调试.这些知识在前面的章节已经介绍过了,而掌握本章的知识以后会提高逆向与调试的能力. PE结构是Windows下可执行文件的标准结构,可执行文件的装载.内存分布.执行等都依赖于PE结构,而在逆向分析软件时,为了有目的.更高效地了解程序,必须掌握PE结构.要掌握反病毒.