调试器命令程序示例

以下部分介绍调试器命令程序。

使用 .foreach 令牌

以下示例使用 .foreach 令牌搜索 5a4d 的 WORD 值。 对于找到的每个 5a4d 值,调试器将显示 8 个 DWORD 值,从找到 5a4d DWORD 的地址开始。

0:000> .foreach (place { s-[1]w 77000000 L?4000000 5a4d }) { dc place L8 } 

以下示例使用 .foreach 令牌搜索 5a4d 的 WORD 值。 对于找到的每个 5a4d 值,调试器将显示 8 个 DWORD 值,在找到 5a4d DWORD 的地址之前从 4 个字节开始。

0:000> .foreach (place { s-[1]w 77000000 L?4000000 5a4d }) { dc place -0x4 L8 } 

以下示例显示相同的值。

0:000> .foreach (place { s-[1]w 77000000 L?4000000 5a4d }) { dc ( place -0x4 ) L8 } 

注意 如果要对命令的 OutCommands 部分中的变量名称进行操作,必须在变量名称后面添加一个空格。 例如,在之前的示例中,变量 位置 和减法运算符之间有一个空格。

-[1] 选项与 (搜索内存) 命令一起导致其输出仅包含找到的地址,而不包括在这些地址找到的值。

以下命令显示内存范围(从0x77000000到0x7F000000)的所有模块的详细模块信息。

0:000> .foreach (place { lm1m }) { .if ((${place} >= 0x77000000) & (${place} <= 0x7f000000)) { lmva place } } 

1m 选项与 lm (列出加载的模块) 命令会导致其输出仅包含模块的地址,而不是模块的完整说明。

前面的示例使用 ${ } (别名解释器) 令牌来确保替换别名,即使它们位于其他文本旁边。 如果命令不包含此标记,则 放置 旁边的左括号将阻止别名替换。 请注意, ${} 令牌适用于 .foreach 和 true 别名中使用的变量。

遍览进程列表

以下示例演练内核模式进程列表,并显示列表中每个条目的可执行名称。

此示例应存储为文本文件,并使用 $$>< (运行脚本文件) 命令执行。 此命令加载整个文件,将所有回车符替换为分号,并执行生成的块。 使用此命令,可以使用多行和缩进编写可读程序,而不必将整个程序挤压到单行上。

此示例演示了以下功能:

  • $t 0$t 1$t 2 伪寄存器用作此程序中的变量。 程序还使用名为 Procc$ImageName 的别名。

  • 此程序使用 MASM 表达式计算器。 但是, @@c++ ( ) 令牌出现一次。 此标记导致程序使用 C++ 表达式计算器分析括号中的表达式。 此用法使程序能够直接使用 C++ 结构令牌。

  • 标志与 r (Registers) 命令一起使用。 此标志将类型化值分配给伪寄存器 $t 2

$$  Get process list LIST_ENTRY in $t0.
r $t0 = nt!PsActiveProcessHead

$$  Iterate over all processes in list.
.for (r $t1 = poi(@$t0);
      (@$t1 != 0) & (@$t1 != @$t0);
      r $t1 = poi(@$t1))
{
    r? $t2 = #CONTAINING_RECORD(@$t1, nt!_EPROCESS, ActiveProcessLinks);
    as /x Procc @$t2

 $$  Get image name into $ImageName.
 as /ma $ImageName @@c++(&@$t2->ImageFileName[0])

 .block
    {
        .echo ${$ImageName} at ${Procc}
    }

    ad $ImageName
    ad Procc
}

浏览LDR_DATA_TABLE_ENTRY列表

以下示例演练用户模式LDR_DATA_TABLE_ENTRY列表,并显示每个列表条目的基址和完整路径。

与前面的示例一样,此程序应保存在 文件中,并使用 $$>< (运行脚本文件) 命令执行。

此示例演示了以下功能:

  • 此程序使用 MASM 表达式计算器。 但是,在两个位置显示 @@c++ ( ) 令牌。 此标记导致程序使用 C++ 表达式计算器分析括号中的表达式。 此用法使程序能够直接使用 C++ 结构令牌。

  • 标志与 r (Registers) 命令一起使用。 此标志将类型化值分配给伪寄存器 $t 0$t 1。 在循环的正文中, $t 1 的类型为 ntdll!_LDR_DATA_TABLE_ENTRY\*,因此程序可以进行直接成员引用。

  • 此程序使用用户命名别名 $Base$Mod 。 美元符号降低了以前在当前调试器会话中使用这些别名的可能性。 美元符号没有必要。 ${/v: } 令牌按字面解释别名,防止在运行脚本之前定义别名时替换别名。 还可以将此令牌与任何块一起使用,以防止在块之前使用别名定义。

  • .block 令牌用于添加额外的别名替换步骤。 加载整个脚本时发生一次别名替换,在输入每个块时发生一次别名替换。 如果没有 .block 标记及其大括号, .echo 命令不会接收上一行中分配的 $Mod$Base 别名的值。

$$ 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 /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}
}