分类 调试 下的文章

Shim是微软系统中一个小型函数库,用于透明地拦截API调用,修改传递的参数、自身处理操作、或把操作重定向到其他地方。Shim主要用于解决遗留应用程序在新版Windows系统上的兼容性问题,但Shim也可用于其他方面。例如上周微软紧急推出针对微软Office Powerpoint 0day漏洞(CVE-2014-6352)”的Fix It,其中就采用了Shim技术,用于修复存在安全缺陷的函数。

一、什么是Shim

Shim是微软极少使用的四字母单词之一,也不是某种形式的缩写。它是英语单词Shim的引申含义。Shim是一个工程术语,描述为了让两个物体更好地组装在一起而插入的一块木头或金属。在计算机编程中,shim是一个小型的函数库,用于透明地拦截API调用,修改传递的参数、自身处理操作、或把操作重定向到其他地方。Shim也可以用来在不同的软件平台上运行程序。

二、shim如何工作

 

Shim架构实现了一种API钩子,而Windows API是通过一组DLL来实现的。Windows系统上的每个应用程序导入这些DLL,并在内存中维护一个存储调用函数地址的表(导入表)。由于Windows函数的地址位于一个表中,Shim直接把导入表中的地址替换为shim DLL中的地址。通常,应用程序没有意识到请求被重定向到一个Shim DLL而不是Windows系统,而Windows系统也没意识到请求并非来自应用程序(因为Shim DLL刚好也位于应用程序的进程中)。

在这个例子中,两个主体分别是应用程序和Windows系统,而shim是能够两者更好协作的附加代码,如下所示

shim代码被注入,并能够修改发向Widows系统的请求、从Windows系统返回的响应或全部,

尤其是,shim利用链接的特性将API调用重定向至替换的代码-Shim。通过导入表(IAT)实现调用外部的二进制文件。因此,调用Windows的函数类似于:

 

 

 你可以修改IAT表中已解析的Windows函数地址,然后替换为指向shim中替代函数的指针,如图4所示。

 

 

静态链接的DLL重定向发生在应用程序启动的时候。你也可以通过拦截GetProcAddressAPI调用来重定向动态链接的DLL文件。

三、为什么使用Shim

 

你无需访问源代码就可以修复应用程序,或甚至不需要修改应用程序。你只需承担极少的管理开销(针对Shim数据库),然而你通过这种方式可以修复数量相当可观的应用程序。缺点是支撑不足,因为大部分供应商不支持经Shim修复的应用程序。你不能够应用Shim来修复所有的应用程序。在软件供应商已经倒闭关门、软件已被淘汰而不予支持、或只购买一段时间的授权的情况下,人们可能考虑对应用程序进行Shim修复。

例如,最经常使用的Shim是version-lie(版本欺骗)Shim。为了实现这个Shim,我们拦截应用程序用于判断Windows版本的几个API。正常情况下,请求能够直接发送到Windows系统,并能够给予真实的回复。使用了Shim之后,向应用程序回复一个伪造的Windows版本(例如,Windows XP 而不是 Windows 7)。如果应用程序只能运行在Windows XP上,通过这种方式就能够让应用程序误以为自己运行在正确的操作系统上。(通常用来解决兼容性问题)。

你可以利用shim玩很多花样,例如:

1.ForceAdminAccess shim试图欺骗应用程序相信当前用户是本地管理员用户组,即使实际情况并不是如此。(如果你不是一个本地管理员用户,尽管你可能使用了其他的技巧来解决这个问题,如UAC文件和注册表虚拟化,许多应用程序还是启动失败)。Shim如何实现版本检查是相当简单明了。例如shim拦截shell32.dll中IsUserAnAdminAPI调用。shimmed修复后的函数(相对于实际的API,修复后的函数具有极佳的性能)只是简单返回True。

2.WrpMitigation shim欺骗应用程序的安装程序相信可以写入被WRP保护的文件。如果你试图一个被WRP保护的文件,shim首先创建一个新的临时文件,标记为文件句柄关闭后立即删除,然后返回临时文件的句柄冒充为实际被保护的文件。

3.CorrectFilePaths shim可以把文件从一个位置重定向到另一个位置。因此,你如果有一个程序试图写入c:\myprogramdir(不能利用UAC文件和注册表虚拟化来自动解决),你可以把运行时修改的文件重定向到每个用户的位置。这样就允许你作为标准用户运行,同时又不放松ACL。

Freebuf科普

WRP(Windows资源保护)对系统稳定性有重要影响的文件进行隐秘拷贝,但是存储的位置变成了%Windir%\WinSxS\Backup,依靠Access Control List(访问控制列表,ACL)为系统提供实时保护,在
WRP的管理下允许对被保护的资源进行写入只授权给了TrustedInstaller,即使是系统管理员也没有权限。

注意:因为Shim代码运行在用户模式的程序进程中,所以你不能使用shim来修复内核模式的代码。例如,你不能利用shim来解决设备驱动或其他内核模式代码的兼容性问题。(例如,一些反病毒软件、防火墙以及反间谍软件代码运行内核模式下)

四、何时使用Shim

1.从已破产的供应商获得的应用程序:既然供应商已经倒闭,技术支持自然无从说起。然而,因为源码不能获得,shim是解决兼容性问题的唯一选择。

2.内部开发的应用程序:虽然大部分用户更倾向于让自己开发的应用程序本身可以解决兼容性问题,但有些场景中时间上并不允许这么做。团队也许不能在新版Windows部署计划之前解决所有的兼容性问题,因此他们可能选择利用shim修复应用程序,同时shim不能修复的部分就需要修改应用程序的源代码。

3.供应商将要发布一个兼容性的版本,但当前的技术支持不够:当现有的应用程序既不是关键业务也不是很重要,一些用户使用Shim作为临时解决方案。理论上讲,用户可以等到兼容性的版本发布,但会阻碍整个部署计划。在兼容版本可用之前,先为用户提供一个Shim修复过的且能正常运转的应用程序不失为两全之计。

五、创建一个应用程序兼容性的Shim

如果你试图在Windows 7运行专为2000或XP创建的应用程序,并出现了问题,你可能总是需要在你的机器上开启兼容模式。然而,如果创建了Shim,你也可在其他机器上运行这个程序,而不需要每次手动开启兼容模式。Shim是体积小且只需运行一次,常常和机器上的特定应用程序联系在一起。

ACT是应用程序兼容性工具包(Application Compatibility Toolkit),可以从这里下载:

一旦我们从“开始”菜单->”微软应用程序兼容性工具包”->”兼容性管理工具”( Compatibility AdministratorTool)启动。

 

在“New Database”点击右键:

 

 选择“Application Fix”,然后从下面的对话框中选择需要修复的应用程序:

①输入需要修复的程序
②输入供应商名称
③浏览可执行程序的位置

这些属性页位于 "项目 > 属性 > " "配置属性 > " "调试" 下。 在下拉控件中选择 "调试器类型"。

 

"本地 Windows 调试器" 属性页

命令

要执行的调试命令。

命令参数

要传递给应用程序的命令行参数。

工作目录

应用程序的工作目录。 默认情况下,包含项目文件的目录。

Attach

指定调试器是否应尝试在启动调试时附加到现有进程。

调试器类型

指定要使用的调试器类型。 如果设置为 "自动",则将根据 exe 文件的内容选择调试器类型。

方案

  • 仅限本机-仅限本机
  • 仅限托管
  • 混合混合
  • 自动自动
  • 脚本-脚本
  • 仅限 GPUC++ (amp) -仅限C++ gpu (amp)

环境

指定要调试的程序的环境,或要与现有环境合并的变量。

调试加速器类型

用于调试 GPU 代码的调试加速器类型。 (当 GPU 调试器处于活动状态时可用。)

GPU 默认断点行为

设置 GPU 调试器中断的频率。

方案

  • 每个弯曲一次中断一次
  • 为每个线程中断(例如 cpu 行为) -每个线程中断(如 cpu 行为)

合并环境

将指定的环境变量与现有环境合并。

SQL 调试

附加 SQL 调试器。

Amp 默认快捷键

覆盖C++ AMP 的默认快捷键选择。 调试托管代码时,属性不适用。

"远程 Windows 调试器" 属性页

远程命令

要执行的调试命令。

远程命令参数

要传递给应用程序的命令行参数。

工作目录

应用程序的工作目录。 默认情况下,包含项目文件的目录。

远程服务器名称

指定远程服务器名称。

连接

指定连接类型。

方案

  • 带 windows 身份验证的远程-远程和windows 身份验证。
  • 不带身份验证的远程远程身份验证(无身份验证)。

调试器类型

指定要使用的调试器类型。 如果设置为 "自动",则将根据 exe 文件的内容选择调试器类型。

方案

  • 仅限本机-仅限本机
  • 仅限托管
  • 混合混合
  • 自动自动
  • 脚本-脚本
  • 仅限 GPUC++ (amp) -仅限C++ gpu (amp)

环境

指定要调试的程序的环境,或要与现有环境合并的变量。

调试加速器类型

用于调试 GPU 代码的调试加速器类型。 (当 GPU 调试器处于活动状态时可用。)

GPU 默认断点行为

设置 GPU 调试器中断的频率。

方案

  • 每个弯曲一次中断一次
  • 为每个线程中断(例如 cpu 行为) -每个线程中断(如 cpu 行为)

Attach

指定调试器是否应尝试在启动调试时附加到现有进程。

SQL 调试

附加 SQL 调试器。

部署目录

当在远程计算机上进行调试时,如果希望将项目输出的内容(PDB 文件除外)复制到远程计算机,请在此指定路径。

其他要部署的文件

在远程计算机上进行调试时,此处指定的文件和目录(项目输出除外)将被复制到部署目录(如果已指定)。

部署 Visual C++ 调试运行时库

指定是否为活动平台(Win32、x64 或 ARM)部署调试运行库。

Amp 默认快捷键

覆盖C++ AMP 的默认快捷键选择。 调试托管代码时,属性不适用。

Web 浏览器调试器属性页

HTTP URL

指定项目的 URL。

调试器类型

指定要使用的调试器类型。 如果设置为 "自动",则将根据 exe 文件的内容选择调试器类型。

方案

  • 仅限本机-仅限本机
  • 仅限托管
  • 混合混合
  • 自动自动
  • 脚本-脚本

"Web 服务调试器" 属性页

HTTP URL

指定项目的 URL。

调试器类型

指定要使用的调试器类型。 如果设置为 "自动",则将根据 exe 文件的内容选择调试器类型。

方案

  • 仅限本机-仅限本机
  • 仅限托管
  • 混合混合
  • 自动自动
  • 脚本-脚本

SQL 调试

附加 SQL 调试器。

什么是内存腐败

当堆内存位置的内容由于编程行为而被修改,超出了原始程序构造的意图时,计算机程序就会发生内存腐败,也可以叫内存破坏;这被称为违反内存安全。内存腐败的最可能原因是编程错误。当腐败的内存内容稍后在该程序中使用时,它要么导致程序崩溃,要么导致奇怪的程序行为。Windows系统上近10%的应用程序崩溃是由于堆腐败造成的。像C和C++这样的现代编程语言具有显式内存管理和指针运算的强大功能。这些特性是为开发高效的应用程序和系统软件而设计的。但是,错误地使用这些功能可能会导致内存腐败错误。

在win32里,当程序损坏分配程序对堆的视图时,就会发生堆损坏。结果可能是相对良性的,并导致内存泄漏(有些内存没有返回到堆中,之后程序无法访问),或者可能是致命的,并导致内存错误,通常是在分配器内部。内存错误通常发生在分配程序中,当堆损坏后分配程序处理一个或多个空闲列表时。

内存腐败是最难处理的编程错误之一,原因有二:

  • 内存腐败的根源和表现形式可能相距甚远,很难将因果联系起来。
  • 症状出现在不寻常的情况下,使得很难持续地再现错误。

产生的原因

内存腐败错误大致可分为四类:

  • 使用未初始化内存:未初始化内存的内容被视为垃圾值。使用这些值可能会导致不可预测的程序行为。未初始化状态是一个常见的编程错误,可能会导致数小时的调试。基本上,未初始化状态是指已成功分配但尚未初始化为可供使用的状态的内存块。内存块可以从简单的本机数据类型(如整数)到复杂的数据块。
  • 使用非自有内存:通常使用指针访问和修改内存。如果此类指针是空指针、悬挂指针(指向已释放的内存)或指向当前堆栈或堆边界之外的内存位置,则它指的是程序当时未拥有的内存。使用这样的指针是一个严重的编程缺陷。访问这样的内存通常会导致操作系统异常,这些异常通常会导致程序崩溃(除非正在使用合适的内存保护软件)。
  • 使用已分配内存以外的内存(缓冲区溢出):如果在循环中使用数组,且终止条件不正确,则可能意外操作数组边界以外的内存。缓冲区溢出是计算机病毒利用的最常见的编程缺陷之一,在广泛使用的程序中会导致严重的计算机安全问题(如返回libc攻击、堆栈崩溃保护)。在某些情况下,程序也可能在缓冲区启动之前错误地访问内存。
  • 堆内存管理错误:内存泄漏和释放非堆或未分配的内存是堆内存管理错误导致的最常见错误。

在win32里,下面情况可能发生:

  • 程序试图释放内存
  • 程序在被释放后试图分配内存
  • 堆早在释放内存块之前就已损坏
  • 故障发生在随后的内存块上
  • 使用连续内存块
    当使用连续块时,写在边界之外的程序可能会损坏分配器关于它正在使用的内存块的信息,以及分配器对堆的视图。视图可以包括在使用块之前或之后的内存块,并且可以或不可以对其进行分配。在这种情况下,在不相关的分配或释放内存的尝试过程中,分配器可能会发生故障。
  • 你的程序是多线程的
    多线程执行可能导致错误发生在与实际损坏堆的线程不同的线程中,因为线程交错请求分配或释放内存。
  • 内存分配策略改变
    在特定内存分配策略中工作的程序,当分配策略以次要方式改变时,可能会中止。

在c++语言编程里,具体有如下原因:

  • 缓冲区溢出(上溢或下溢),这是最常见的原因;
  • 强制转换到一个错误的类型
  • 未初始化的指针
  • 错误使用. 和 ->
  • 错误使用& 和 .
  • delete & new 和 delete[] new[] 不配套
  • 缺少或者不完整的拷贝构造
  • 指向已回收内存的指针
  • 重复delete同一块内存
  • 多重基类,但是没有虚析构函数

表现形式

根据被破会的内存位置,程序会有不同的表现形式,常见就是程序崩溃掉。如果被破坏的是是堆/堆段/堆块等管理边界结构,那么在win32里,一般会触STATUS_HEAP_CORRUPTION

异常,此时的异常结构EXCEPTION_RECORD成员值一般如下:

EXCEPTION_RECORD:  
ExceptionAddress:异常地址
ExceptionCode: c0000374
ExceptionFlags: 00000001
NumberParameters: 1
Parameter[0]: 导致此异常的最终函数

0xC0000374就是堆内存腐败的异常代码,他定义如下

file:..../winnt.h#define STATUS_HEAP_CORRUPTION           ((DWORD   )0xC0000374L)   

“选项”对话框 ->“调试”->“实时”

若要访问“实时”页,请转到“工具”菜单,然后单击“选项”。 在“选项”对话框中,展开“调试”节点并选择“实时”。 该页允许为托管代码、本机代码和脚本启用实时调试。

可以为以下程序类型启用实时调试:

  • Managed

  • 本机

  • 字符集

实时调试是调试在 Visual Studio 之外启动的程序的一种方法。 可以在 Visual Studio 环境之外运行在 Visual Studio 中创建的程序。 如果已启用实时调试,则发生崩溃时会显示一个对话框询问是否进行调试。

“选项”对话框 ->“调试”->“输出”窗口

可以指定在“输出”窗口中显示哪些类型的调试信息。 若要显示这些选项,请打开“工具”菜单,单击“选项”,展开“调试”节点,再单击“输出窗口”。

  • 常规输出设置
    可以使用此类别包含的控件,确定是否在“输出”窗口中显示常规调试消息。 您可以指定是否显示每种类型的消息。

  • WPF 跟踪设置
    可以使用此类别包含的控件,确定在“输出”窗口中显示的 WPF 跟踪消息的级别。 您可以指定是否显示每种类型的消息,并且可以指定“关键”到“全部”范围内的某个级别。

比如,常规设置里所有的都设置位ON

将所有的设置为OFF时

“选项”对话框 ->“调试”->“符号”

若要访问“符号”页,请选择“工具”菜单上的“选项”。 在“选项”对话框中,展开“调试”节点并选择“符号”。 在此页中可以设置以下常规调试选项。

  • 符号文件(.pdb)位置

    调试器将在其中查找符号文件的位置的列表。 位置可以包括目录、URL、共享和符号服务器位置。本列表只包含已知的和受信任的位置。 如前面的“安全说明”所述,从不可信位置加载符号可能产生有害作用。

  • Microsoft 符号服务器

    选择此选项可以从 Microsoft 公共符号服务器下载符号。 必须连接到 Internet 才能访问 Microsoft 符号服务器。

    使用源服务器时必须考虑以下潜在的安全威胁:

    任何命令都可以嵌入到应用程序的 .pdb 文件中。 因此,请确保只将要执行的命令放入 srcsrv.ini 文件。 任何尝试执行不在 srcsvr.ini 文件中的命令都将导致出现一个确认对话框。

    未对命令参数执行任何验证,因此请慎用受信任的命令。 例如,如果您信任 cmd.exe,恶意用户则可能会指定使该命令变得危险的参数。

  • 文件夹图标按钮
    单击此按钮在列表中创建新位置。

  • “X”图标按钮
    在列表中选择一个位置,然后单击此按钮从列表中移除该目录。

  • 上箭头图标按钮
    在列表中选择了一个目录时,单击此按钮在列表中向上移动。

  • 下箭头图标按钮
    在列表中选择了一个目录时,单击此按钮在列表中向下移动。

  • 在此目录下缓存符号


    如果使用服务器中的符号,则可以使用本地目录提高调试器的性能。 此目录会保存从符号服务器获取的系统软件的符号。 缓存符号会提高磁盘使用率。 在通过
    Internet 使用符号服务器时,缓存尤其有用。 注意,如果调试的是远程计算机上的程序,则缓存目录指的是远程计算机上的目录。


  • 单击此按钮将显示一个对话框,您可以在该对话框中浏览并选择要用于前一项的目录。

  • 加载所有符号
    立即从所有指定的位置加载符号。

  • 清空符号缓存
    删除在“在此目录下缓存符号”中指定的位置处的所有符号文件。 也将删除目录本身。

  • 除排除模块之外的所有模块
    使用此设置可以加载符号文件位置中所有模块的符号,但排除列表中指定的模块除外。

  • 指定排除的模块
    单击此链接可以创建或修改符号文件排除列表。

  • 仅指定的模块
    使用此设置可以只加载包括列表中指定的模块的符号。

  • 指定模块
    单击此链接可以创建或修改符号文件包括列表。

将来,任何开发人员都将需要调试应用程序,并且将无法访问Visual Studio,在某些情况下甚至无法访问源代码。例如,在生产web或应用服务器上调试问题时,我真的不想安装Visual Studio并跨所有源代码进行复制;这是不实际的,有时甚至是不允许的。正是在这种时候,我们需要另一个工具,一个调试windows应用程序的工具,而微软正好提供了一系列这样的调试器,非常适合这种情况。在本文中,我将解释哪些调试器可用,以及在Visual Studio不实用或不可用的情况下,如何使用它们来简化调试.NET应用程序的过程。在本文中,我将解释哪些调试器可用,以及如何调试一个简单的、相当常见的示例。我希望这将展示如何以简单直接的方式调试代码。