windbg 脚本程序编写[1]

由于 softice 的逝去 , 现在学习 windbg 调试器的人越来越多了。我很高兴看到这样的情景, 因为我是从 windbg 调试器学习起,那还是一年半以前的事了。那时网上有关 windbg 的资料比较少,只能看 windbg 自带的帮助文档,学起来很费劲。 softice  宣布停止开发后这一年多时间里,由于使用windbg 的人数增多,很多相关的技术文档也随之出现,这些技术文档多数是作者从实践中得来得。我也很高兴劲自己得一点努力给后来者提供更多可以参考得资料。

debugger 脚本程序实例

下面小节描述了debugger 脚本程序的例子,我们就从学习例子代码出发 !

使用 .foreach 符号

.foreach ( place { s-[1]w 77000000 L4000000 5a4d } ) { dc  ${place} L8 }

这条简单的语句功能是:  从线性地址 0x77000000 处查起,查找长度是 0x4000000,  凡是值为 0x5a4d 的地址,就用 dc 命令打印出该地址的内容。

 place 是自己定义的别名符号,代表符合查找条件的每个内存地址。

s-[l]w:参看 windbg 帮助文档,可知 -[l] 说明只显示找到的内存地址而不显示其内容,w 指定了查找目标的类型, 即 5a4d 是 WORD 类型的。

当执行了 place { s-[1]w 77000000 L4000000 5a4d } 后,可以认为 place 中有很多查找到的地址,  .foreach 语句作用于其中的每一项内容(找到的地址),  然后对该地址执行 dc  dump 内存的命令。

 我们看第 2 个例子:

.foreach (place { lm 1m }) { .if ((${place} >= 0x77000000) & (${place} <= 0x 7f 000000)) { lmva ${place} } }

上面的命令显示处于0x0x77000000 0x 7F 000000范围内的详细的模块信息。

 

lm 命令: 列出已加载的模块, 命令的输出信息包括模块的状态和路径. 其中 1m 选项是使列出的模块信息只包含模块名。

注意: 用该lm 1m 命令显示的模块信息不包含模块的后缀名。

place 用户自定义的别名, 代表每个模块名字。假设我们用 lm 1m 命令得到了已加载的模块信息。.foreach 语句解析每个输出的模块信息,

然后把这些信息作为后面命令的输入。 .foreach 后面的命令中, 凡是出现我们自定义的变量名 place 时,就用真正值替换它们。

我们经常用 ${place} 来引用 place 代表的变量。

 

lmva ${place} 命令显示详细的指定模块信息.

lm 1m 命令显示信息:

 

 

从图中能够看出: 当前的 lm 1m 列出了

 

    lm 1m 模块信息         ${place} 被替换         替换后的命令

       virtual_fun       ------------------------->  lmva virtual_fun                               

       MSVCR80D      ------------------------->  lmva MSVCR80D                          

       msvcrt               ------------------------->  lmva msvcrt                                          

       kernel32           ------------------------->  lmva kernel32                                         

       ntdll                  ------------------------->  lmva ntdll                                                                  

 

这时我们就容易理解了, 脚本命令就相当于是在每个模块上调用 lmva 显示它的基本信息.

 

做为对上面脚本命令的修改,我们可以这样:

.foreach (place { lm 1m }) { .if ((${place} >= 0x77000000) & (${place} <= 0x 7f 000000)) { .echo "find a module: ${place} , Memory Dump: " ; db ${place} } }

这次脚本修改后的功能是, 列出所有位于线程地址 0x77000000 0x 7f 000000 之间模块内存的 dump 信息。内存 dump 是从每个模块加载地址开始的, 所有我们可以看到熟悉的 DOS 头标志 'M''Z'

注意: 这些命令要用一行来完成。

我们也可以把上面命令保存到 DumpModule.txt 文本文件中, 文件内容是

.foreach (place { lm 1m })

{

       .if ((${place} >= 0x77000000) & (${place} <= 0x 7f 000000))

       {

              .echo "find a module: ${place} , Memory Dump: " ;

              db ${place}

       }

}

这样可读性会增强, 同时更像一个脚本程序了。

如果保存在文件中, 我们需要把 DumpModule.txt 文件放在 windbg 的安装目录, 然后输入下面的命令来执行: $$>< DumpModule.txt

执行结果如下图:

         

 

 

 

在源帮助文件中有这样一段话指出了 ${} 符号的作用

The ${ }  (Alias Interpreter) token is used here to make sure aliases are replaced even if they are adjacent to other text. If this were not included, the opening parentheses adjacent to place would prevent alias replacement.

 

我们就用上面的脚本代码来说明 ${} 符号的作用.

(1) ${ aliase} 明确的指出了, 大括号 {} 内的变量名是可以被替换的,即使 aliase 和其它文本相连也要做替换.

 

例如: 下面的脚本命令中, lmva ${place} 紧相连在一起,解析程序能否把 place 解析出呢? 可以的,因为我们用了 ${} 来明确告诉解析程序要解析这个符号,所以命令能够成功执行。

.foreach (place { lm 1m }) { .if ((${place} >= 0x77000000) & (${place} <= 0x 7f 000000)) { lmva${place} } }

 

但是如果去掉 ${} 符号, 即是下面的命令   

.foreach (place { lm 1m }) { .if ((${place} >= 0x77000000) & (${place} <= 0x 7f 000000)) { lmvaplace } }

 

哈哈, 这次解析程序解析出错了, 因为不知道 lmvaplace 是做什么用的.

 

我们再次更改一下命令脚本, 看看解析程序有什么反应:

.foreach (place { lm 1m }) { .if ((${place} >= 0x77000000) & (${place} <= 0x 7f 000000)) { lmva ${Change_place} } }

因为前面没有定义过 Change_place, 所以解析程序不能解析 Change_place 而出错。

 

让我们来看另外的一个例子:

遍历用户模式的 LDR_DATA_TABLE_ENTRY 链表, 显示了每个链表的基地址和完整路径。

首先我们看一下脚本命令:

$$ Get module list LIST_ENTRY in $t0.

r? $t0 = &@$peb->Ldr->InLoadOrderModuleList

 

$$ Iterate over all modules in list.

.for (r? $t1 = *(ntdll!_LDR_DATA_TABLE_ENTRY**)@$t0;

      (@$t1 != 0) & (@$t1 != @$t0);

      r? $t1 = (ntdll!_LDR_DATA_TABLE_ENTRY*)@$t1->InLoadOrderLinks.Flink)

{

    $$ Get base address in $Base.

    as /msu /x ${/v:$Base} @@c++(@$t1->DllBase)

   

    $$ Get full name into $Mod.

    as /msu ${/v:$Mod} @@c++(&@$t1->FullDllName)

 

    .block

    {

        .echo ${$Mod} at ${$Base}

    }

    ad ${/v:$Base}

    ad ${/v:$Mod}

}

把上面的脚本命令保存为文件 "ModuleList.txt", 放在 windbg 的安装目录, 然后执行 $$><ModuleList.txt

执行结果如下图:

 

 

这次我们遇到了很多陌生点,别着急,一个一个来解决:

 

(1) 首先是 $$ 符号, 你一定猜到了: 这是个注释符号?!

    是的,就像 C++ 中的 //,  '$$' 注释一行或者是在本行中遇到 ';' 即结束作用。

(2) r? 是做什么用的,先不管他,继续向下分析.

(3) 一共有 20 个自定义伪寄存器,$t0,$t1.$t2......$t19. 调试器可以对这些寄存器进行读写等操作,在脚本命令中较常用.

(4) 下面就是 &@$peb->Ldr->InLoadOrderModuleList 了。开始分析这条语句时你可能会很奇怪:

    peb 是怎么来的? 它前面的 $, @, & 是什么意思?

    我首先会纳闷 $peb 是怎么来的, peb 前面的 $ 使它(peb)看起来好像伪寄存器, 带着猜测我们查看帮助文件。哈哈!

    Pseudo-Register Syntax 一节可以查到: $peb 是自动伪寄存器,大概意思是说 $peb 可以直接引用并且是调试器自动赋值的, 难怪我们可     以不经过定义而直接用呢.

    $peb 前面的 at 符号作用是明确告诉调试器 @ 后面内容是寄存器或伪寄存器而不是符号。对于使用寄存器的方法, 我们最好在寄存器和      伪寄存器之前都加上 @ 符号,这是个好习惯.

   

    回答(2) 的问题: r? 给伪寄存器 $t0 赋值. 现在$t0值是 InLoadOrderModuleList 的地址了.

    我们验证一下:     

    1. 输入命令: r? $t0 = &@$peb->Ldr->InLoadOrderModuleList ; db $t0  我们可以看到                 $t0 的值是 0x00251eac.

    2. 输入命令: dt _PEB @$peb Ldr , 得到 _PEB 成员 Ldr 的地址是 0x00251ea0,

      输入命令: dt  _PEB_LDR_DATA  0x00251ea0 可以看到从地址0x00251ea0 处偏         移量为 0x 0c 的位置就是 InLoadOrderModuleList

 

    经过验证正确无误.

(3) 好了, 如果你弄懂了上面几点 .for 语句中的条件语句就自己能够看懂了.

    .for 语句中是遍历链表的过程, 语法上也就没有什么可说的了。

(4) 下面遇到的问题是:

    as /msu /x ${/v:$Base} @@c++(@$t1->DllBase) 

  

    as 命令的作用是 定义一个新的变量名. /msu 选项指出了 $Base 数值为 @$t1->DllBase UNICODE_STRING 结构体地址.

   

    ${/v: aliase} : 评估各种和 自定义的 aliase 相关的值。/v 选项指出了阻止对该别名的评估(替换), 紧保留它原来的字面值。

  ${} 的使用方法中有这样一种应用情况: 要求 ${} 这个别名不被评估, 即不被解析程序替换成其他值, 只保留它当前的字面值.  

                 

下面说点相关的:

为了更好地理解上面的命令,我们写几条语句测试一下:

>as Msg hello world 回车

>al

  Alias            Value 

 -------          -------

 Msg              hello world

>ad Msg

=========================================================

>as ${Msg}  hello world 回车

>al

  Alias            Value 

 -------          -------

 ${Msg}           hello world

=========================================================

>as ${/v:Msg} hello world 回车

>al

 Alias            Value 

 -------          -------

 Msg              hello world

 

从上面的例子可以看到 as 把第一个参数做为自定义的 Aliase, 不管他是否含有 ${} 符号. 但例外情况是 S{/v:aliase}

在这种情况下解析程序会定义  aliase, 而不是 S{/v:aliase}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Blue_Dream_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值