调试器命令程序示例
以下部分介绍调试器命令程序。
使用 .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}
}