以下部分介绍调试器命令程序。
使用 .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] 选项以及 s (Search Memory) 命令会导致其输出仅包含查找的地址,而不是在这些地址中找到的值。
以下命令显示所有位于内存范围(从0x77000000到0x7F000000)的详细模块信息。
0:000> .foreach (place { lm1m }) { .if ((${place} >= 0x77000000) & (${place} <= 0x7f000000)) { lmva place } }
1m 选项以及 lm (List Loaded Modules) 命令会导致其输出仅包含模块的地址,而不是模块的完整说明。
前面的示例使用 ${ } (别名解释器) 令牌来确保别名被替换,即使别名位于其他文本旁边。 如果命令不包含此令牌,则位于 位置旁的左括号 阻止别名替换。 请注意,${} 令牌适用于 .foreach 和真实别名中使用的变量。
走进程列表
以下示例演练内核模式进程列表,并显示列表中每个条目的可执行文件名称。
此示例应存储为文本文件,并使用 $$><(运行脚本文件) 命令执行。 此命令加载整个文件,将所有回车符替换为分号,并执行生成的块。 使用此命令,可以使用多行和缩进编写可读程序,而不必将整个程序挤到一行上。
此示例演示了以下功能:
$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}
}