如何获取Windows 系统的内核变量
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何获取Windows 系统的内核变量相关的知识,希望对你有一定的参考价值。
PsLoadedModuleList等重要内核变量并未被ntoskrnl.exe导出,也没有公开的函数可以获取。而这些内核变量对于Rootkit、Anti-Rootkit 以及内核溢出的利用等都
是至关重要的。
下面我们以PsLoadedModuleList、PsActiveProcessHead 等为例,介绍得到这些
变量的方法。
对于Windows NT 4.0和Windows 2000,尚没有“温柔”的办法可以获取这些变量,
比较理想的办法也就是特征代码搜索,这种方法虽然暴力,但通常都很有效,一般也
不会出问题;对于Windows XP和Windows 2003,我们找到了一些更加优雅的选择。
下面首先介绍特征代码搜索的方法。
[DWORD KernelBase]
要进行特征代码搜索,首先要定位ntoskrnl.exe在内核的加载地址KernelBase。
这个地址可以通过ZwQuerySystemInformation Class 10的SystemModuleInformation
来得到。参考资源[1]中给出了相关代码。事实上,KernelBase 这个值对同一操作系
统来说非常固定,可以作为常量来用:
Windows NT: 0x80100000
Windows 2000:0x80400000
Windows XP: 0x804d1000
Windows 2003: 0x804e0000
Windows NT 4.0 ntoskrnl.exe 的OptionalHeader->ImageBase = 0x80100000,
ntldr 也会按照这个值来加载内核,但是从Windows 2000开始就不是这样了。可能基
于这个历史原因,各系统的*(DWORD *)PsNtosImageBase始终初始化为0x80100000。
另外,内核变量PsNtosImageBase、KdpNtosImageBase等也指向KernelBase:
KernelBase = *(DWORD *)PsNtosImageBase
KernelBase = *(DWORD *)KdpNtosImageBase
[LIST_ENTRY PsLoadedModuleList]
PsLoadedModuleList这个全局变量指向一个保存着所加载驱动信息的双向链表。
通过它可以枚举系统中所有的驱动模块。
虽然很多内核函数都用到了PsLoadedModuleList,但是大部分并没有被导出,而
从基址开始搜会很花时间。对于Windows 2000来说,从下面这个地方入手是个好主意:
nt!MmGetSystemRoutineAddress+0x66:
804f0ed0 8b35f0e84680 mov esi,[nt!PsLoadedModuleList (8046e8f0)]
804f0ed6 81fef0e84680 cmp esi,0x8046e8f0
流程如下:
1、ImageBase = LoadLibraryA("ntoskrnl.exe")
2、GetProcAddress(ImageBase,"MmGetSystemRoutineAddress")
3、搜索特征代码:
*(WORD *)(MmGetSystemRoutineAddress + i) = 0x358b && \\
*(WORD *)(MmGetSystemRoutineAddress + i + 6) = 0xfe81
&&
*(DWORD *)(MmGetSystemRoutineAddress + i + 2) == \\
*(DWORD *)(MmGetSystemRoutineAddress + i + 8)
4、定位内核中的地址:
PsLoadedModuleList = \\
*(DWORD *)(MmGetSystemRoutineAddress + i + 2) + (KernelBase - ImageBase)
从SP0到SP4,i值并不相同,但是肯定不大于0x100。
对Windows NT来说,就没这么好运气了,没有理想的可用于定位的API,只能从头
开始搜索。下面这段代码至少对SP1~SP6a的来说都具有很好的稳定性和唯一性:
801CEB1C: 8B 4D 08 mov ecx,dword ptr [ebp+8]
801CEB1F: 89 01 mov dword ptr [ecx],eax
801CEB21: 8B 45 0C mov eax,dword ptr [ebp+0Ch]
801CEB24: 89 10 mov dword ptr [eax],edx
801CEB26: 8B 36 mov esi,dword ptr [esi]
801CEB28: 81 FE 70 0B 15 80 cmp esi,80150B70h //PsLoadedModuleList
如果是用驱动做这件事情,就不必暴力搜索了,fuzen_op(fuzen_op@yahoo.com)
在FU_Rootkit2.0(参考资源[2])中使用了一段比较巧妙的代码:
DWORD FindPsLoadedModuleList (IN PDRIVER_OBJECT DriverObject)
PMODULE_ENTRY pm_current;
if (DriverObject == NULL)
return 0;
pm_current = *((PMODULE_ENTRY*)((DWORD)DriverObject + 0x14));
if (pm_current == NULL)
return 0;
gul_PsLoadedModuleList = pm_current;
while ((PMODULE_ENTRY)pm_current->le_mod.Flink != gul_PsLoadedModuleList)
if ((pm_current->unk1 == 0x00000000) && \\
(pm_current->driver_Path.Length == 0))
return (DWORD) pm_current;
pm_current = (MODULE_ENTRY*)pm_current->le_mod.Flink;
return 0;
[LIST_ENTRY PsActiveProcessHead]
理论上PsActiveProcessHead 也可以用搜索代码的方法来取,但是还有更简单的
方法。
ntoskrnl.exe导出的PsInitialSystemProcess 是一个PEPROCESS,指向system进
程的EPROCESS。这个EPROCESS的结构成员EPROCESS.ActiveProcessLinks.Blink 就是
PsActiveProcessHead:
kd> dt _EPROCESS ActiveProcessLinks.Blink poi(PsInitialSystemProcess)
+0x0a0 ActiveProcessLinks : [ 0x81356900 - 0x8046e728 ]
+0x004 Blink : 0x8046e728 [ 0x81a2fb00 - 0xff5a4ce0 ]
kd> ? PsActiveProcessHead
Evaluate expression: -2142836952 = 8046e728
EPROCESS这个结构在不同的操作系统上各不相同,需要分别对待。
[struct _KDDEBUGGER_DATA64 KdDebuggerDataBlock]
Windows 2000 开始,系统引入了变量KdDebuggerDataBlock。其中包含了大量的
内核变量。如果能够获取到的话,可以解决许多问题。遗憾的是,Windows NT上没有
这个变量。WinDBG SDK的wdbgexts.h中包含了它的结构:
typedef struct _KDDEBUGGER_DATA64
因为比较长,这里就不引用了。
从对5.0.2195.6902版本ntoskrnl.exe 的逆向工程结果来看,只有两个函数使用
了该变量,并且,两个函数都未导出,且代码前后没有明显特征,无法靠直接搜索代
码来获取。
但是,我们发现,ntoskrnl.exe导出了KdEnableDebugger,KdEnableDebugger会
调用KdInitSystem,而KdInitSystem 中引用了KdDebuggerDataBlock:
n < 100
Windows 2000:
KdEnableDebugger + n:
6A 00 push 0
6A 00 push 0
C6 05 28 41 48 00 01 mov _PoHiberInProgress, 1
E8 1C DC 10 00 call _KdInitSystem@8 ; KdInitSystem(x,x)
KdInitSystem + n:
68 70 02 00 00 push 270h // sizeof(KdDebuggerDataBlock)
B9 50 D1 54 00 mov ecx, offset _KdpDebuggerDataListHead
68 D8 FA 46 00 push offset KdDebuggerDataBlock
8B 40 18 mov eax, [eax+18h]
68 4B 44 42 47 push 4742444Bh // "KDBG",可以用作搜索的定位标志
A3 3C D1 54 00 mov ds:_KdpNtosImageBase, eax
89 0D 54 D1 54 00 mov ds:dword_54D154, ecx
89 0D 50 D1 54 00 mov ds:_KdpDebuggerDataListHead, ecx
Windows XP
KdEnableDebugger + n:
6A 00 push 0
6A 00 push 0
C6 05 8C 98 47 00 01 mov _PoHiberInProgress, 1
E8 2B 17 13 00 call _KdInitSystem@8 ; KdInitSystem(x,x)
KdInitSystem + n:
68 90 02 00 00 push 290h
68 E0 9D 46 00 push offset KdDebuggerDataBlock
BE 74 96 59 00 mov esi, offset _KdpDebuggerDataListHead
68 4B 44 42 47 push 4742444Bh
89 35 78 96 59 00 mov ds:dword_599678, esi
89 35 74 96 59 00 mov ds:_KdpDebuggerDataListHead, esi
Windows 2003
KdEnableDebugger + n:
56 push esi
56 push esi
C6 05 0C 08 49 00 01 mov PoHiberInProgres, 1
E8 CB AD 15 00 call _KdInitSystem@8 ; KdInitSystem(x,x)
KdInitSystem + n:
68 18 03 00 00 push 318h
68 D0 A3 47 00 push offset KdDebuggerDataBlock
BE 18 10 5D 00 mov esi, offset _KdpDebuggerDataListHead
68 4B 44 42 47 push 4742444Bh
89 35 1C 10 5D 00 mov ds:dword_5D101C, esi
89 35 18 10 5D 00 mov ds:_KdpDebuggerDataListHead, esi
可以看出,上面代码特征的唯一性很好。用于搜索是没有问题的。我在上面同时
列出了三个系统的代码,仅仅只是为了比较,事实上,对Windows XP和Windows 2003
是完全没有必要采取如此暴力手段的。 参考技术A 这个得抓包,得检测
如何在 windows 的变量中获取命令的结果?
【中文标题】如何在 windows 的变量中获取命令的结果?【英文标题】:How do I get the result of a command in a variable in windows? 【发布时间】:2010-09-11 15:04:07 【问题描述】:我希望将命令的结果作为 Windows 批处理脚本中的变量获取(请参阅 how to get the result of a command in bash 以获取等效的 bash 脚本)。首选可以在 .bat 文件中运行的解决方案,但也欢迎使用其他常见的 Windows 脚本解决方案。
【问题讨论】:
约翰,很难找到这个非常有用的问题。您能否考虑添加替代措辞,例如 How to capture aa program 的 output 到 Windows 批处理文件中的变量中? Google 显示此页面排名第三。 Windows batch assign output of a program to a variable的可能重复 @MichaelFreidgeim 这个问题实际上是在那个重复之前 2 年被问到的 - 但是这个问题有多个重复。请参阅***.com/questions/6359820/…上的 cmets @icc97,“可能重复”是一种清理方法 - 关闭类似问题并保留最佳答案。日期不是必需的。见meta.stackexchange.com/questions/147643/…如果你同意它需要澄清,请投票meta.stackexchange.com/questions/281980/…如果你看到多个重复,你应该选择一个作为规范并投票关闭其他。 【参考方案1】:不起眼的 for 命令多年来积累了一些有趣的功能:
D:\> FOR /F "delims=" %i IN ('date /t') DO set today=%i
D:\> echo %today%
Sat 20/09/2008
请注意,"delims="
会覆盖默认的空格和制表符分隔符,因此 date 命令的输出会被一次性全部吞噬。
要捕获多行输出,它本质上仍然可以是单行(使用变量 lf 作为结果变量中的分隔符):
REM NB:in a batch file, need to use %%i not %i
setlocal EnableDelayedExpansion
SET lf=-
FOR /F "delims=" %%i IN ('dir \ /b') DO if ("!out!"=="") (set out=%%i) else (set out=!out!%lf%%%i)
ECHO %out%
要捕获管道表达式,请使用^|
:
FOR /F "delims=" %%i IN ('svn info . ^| findstr "Root:"') DO set "URL=%%i"
【讨论】:
与@PabloG 的回答一样,这仅适用于获取命令的最后一行输出,在这种情况下为“date /t”。 我发现你的答案只有在我使用双百分号时才有效,即FOR /F "delims=" %%i IN ('date /t') DO set today=%%i
@Rabarberski:如果您直接在命令行上键入for
命令,则使用单个%
。如果在批处理文件中使用它,则使用%%
。
@tardate:如果命令需要使用%
传递参数,请告诉脚本的外观,例如:for /f "delims=" %%i in ('date +%F_%H-%M-%S') do set now=%%i
@degenerate 如果使用的date
命令来自Cygwin,则该命令有效。我同意我应该提到这一点。【参考方案2】:
如果您必须捕获所有命令输出,您可以使用这样的批处理:
@ECHO OFF
IF NOT "%1"=="" GOTO ADDV
SET VAR=
FOR /F %%I IN ('DIR *.TXT /B /O:D') DO CALL %0 %%I
SET VAR
GOTO END
:ADDV
SET VAR=%VAR%!%1
:END
所有输出行都存储在用“!”分隔的 VAR 中。
但如果只需要单行控制台输出,请尝试:
@ECHO off
@SET MY_VAR=
FOR /F %%I IN ('npm prefix') DO @SET "MY_VAR=%%I"
@REM Do something with MY_VAR variable...
@John:这有什么实际用途吗?我认为您应该观看 PowerShell 或任何其他能够轻松执行脚本任务的编程语言(Python、Perl、PHP、Ruby)
【讨论】:
单行答案将解决我的具体情况。我只是想有一个多行选项(或更简单的单行选项)。我猜 bat 文件的功能并没有那么好。需要它是用于包装 Java 应用程序的 bat 脚本。主要是构建类路径。 对此要非常小心。 GWT 还尝试使用批处理文件来启动具有特定类路径的 Java,一旦您进入具有非 ASCII 字符的目录(在这种情况下,它就是我的家 :)),它就失败了。他们后来用 VBS 重写了它。 这有什么实际用途吗? 你在开玩笑吗?这一直在其他 shell 中使用(我猜都是 GNU/Linux 的),而且它非常很有用。 @PiotrDobrogost 我认为他想说的是 bash 脚本是过去的可怕遗迹(您需要使用 for 循环 来捕获一个程序?真的吗?),如果你发现自己需要使用变量,你应该使用更现代的东西,比如 PowerShell。 有一个实际用途,其实...有点。我想使用 PowerShell 脚本来查找特定的 Visual Studio 路径,但我需要从该路径中取出一个批处理文件,它设置了一堆环境变量,因此我可以调用 editbin ...所以我需要找到路径并将其返回到cmd
的调用实例,以便我可以在那里运行它。 ...是的,这和声音一样可怕。 Visual Studio 有时让我想哭。 @Ajedi32 我认为 you 的意思是说 batch 脚本是过去的可怕遗物。 ;)【参考方案3】:
要获取当前目录,可以这样:
CD > tmpFile
SET /p myvar= < tmpFile
DEL tmpFile
echo test: %myvar%
虽然它使用的是临时文件,所以它不是最漂亮的,但它确实有效! 'CD' 将当前目录放在 'tmpFile' 中,'SET' 加载 tmpFile 的内容。
这里是多行的“数组”的解决方案:
@echo off
rem ---------
rem Obtain line numbers from the file
rem ---------
rem This is the file that is being read: You can replace this with %1 for dynamic behaviour or replace it with some command like the first example i gave with the 'CD' command.
set _readfile=test.txt
for /f "usebackq tokens=2 delims=:" %%a in (`find /c /v "" %_readfile%`) do set _max=%%a
set /a _max+=1
set _i=0
set _filename=temp.dat
rem ---------
rem Make the list
rem ---------
:makeList
find /n /v "" %_readfile% >%_filename%
rem ---------
rem Read the list
rem ---------
:readList
if %_i%==%_max% goto printList
rem ---------
rem Read the lines into the array
rem ---------
for /f "usebackq delims=] tokens=2" %%a in (`findstr /r "\[%_i%]" %_filename%`) do set _data%_i%=%%a
set /a _i+=1
goto readList
:printList
del %_filename%
set _i=1
:printMore
if %_i%==%_max% goto finished
set _data%_i%
set /a _i+=1
goto printMore
:finished
但是您可能想要考虑迁移到另一个更强大的 shell 或为这些东西创建一个应用程序。这大大扩展了批处理文件的可能性。
【讨论】:
是否有可以从命令中捕获多行的变体? 获取当前目录可以使用 echo %CD%【参考方案4】:您需要使用带有参数/P
的SET
命令并将输出定向到它。
例如参见http://www.ss64.com/nt/set.html。适用于 CMD,不确定 .BAT 文件
来自对该帖子的评论:
该链接的命令“
Set /P _MyVar=<MyFilename.txt
”表示它将设置_MyVar
到第一行 来自MyFilename.txt
。这可能是 用作“myCmd > tmp.txt
”和“set /P myVar=<tmp.txt
”。但它只会 获取输出的第一行,而不是 所有输出
【讨论】:
该链接具有命令“Set /P _MyVar=在“V”环境变量中设置最新文件的示例
FOR /F %I IN ('DIR *.* /O:D /B') DO SET V=%I
在批处理文件中,您必须在循环变量中使用双前缀:
FOR /F %%I IN ('DIR *.* /O:D /B') DO SET V=%%I
【讨论】:
我以前见过这种方法,但它看起来真的很hacky。它还有一个问题是它只会捕获变量中命令的最后一行输出。【参考方案6】:我想对上述解决方案补充一点:
如果在路径中找到您的命令,或者如果命令是没有空格或特殊字符的 cmdpath,则所有这些语法都可以很好地工作。
但是,如果您尝试使用位于路径包含特殊字符的文件夹中的可执行命令,则需要将命令路径用双引号 (") 括起来,然后 FOR /F 语法不起作用。
例子:
$ for /f "tokens=* USEBACKQ" %f in (
`""F:\GLW7\Distrib\System\Shells and scripting\f2ko.de\folderbrowse.exe"" Hello '"F:\GLW7\Distrib\System\Shells and scripting"'`
) do echo %f
The filename, directory name, or volume label syntax is incorrect.
或
$ for /f "tokens=* USEBACKQ" %f in (
`"F:\GLW7\Distrib\System\Shells and scripting\f2ko.de\folderbrowse.exe" "Hello World" "F:\GLW7\Distrib\System\Shells and scripting"`
) do echo %f
'F:\GLW7\Distrib\System\Shells' is not recognized as an internal or external command, operable program or batch file.
或
`$ for /f "tokens=* USEBACKQ" %f in (
`""F:\GLW7\Distrib\System\Shells and scripting\f2ko.de\folderbrowse.exe"" "Hello World" "F:\GLW7\Distrib\System\Shells and scripting"`
) do echo %f
'"F:\GLW7\Distrib\System\Shells and scripting\f2ko.de\folderbrowse.exe"" "Hello' is not recognized as an internal or external command, operable program or batch file.
在这种情况下,我发现使用命令并将其结果存储在变量中的唯一解决方案是将(临时)默认目录设置为命令本身:
pushd "%~d0%~p0"
FOR /F "tokens=* USEBACKQ" %%F IN (
`FOLDERBROWSE "Hello world!" "F:\GLW7\Distrib\System\Layouts (print,display...)"`
) DO (SET MyFolder=%%F)
popd
echo My selected folder: %MyFolder%
那么结果就是正确的:
My selected folder: F:\GLW7\Distrib\System\OS install, recovery, VM\
Press any key to continue . . .
当然,在上面的示例中,我假设我的批处理脚本与我的可执行命令之一位于同一文件夹中,以便我可以使用“%~d0%~p0”语法。如果这不是您的情况,那么您必须找到一种方法来定位您的命令路径并将默认目录更改为它的路径。
注意:对于那些想知道的人,这里使用的示例命令(选择文件夹)是 FOLDERBROWSE.EXE。我在网站 f2ko.de (http://f2ko.de/en/cmd.php) 上找到了它。
如果有人对通过复杂路径访问的那种命令有更好的解决方案,我会很高兴听到它。
吉尔
【讨论】:
您可以在命令前加上CALL
。 FOR /F doesn't work if path has spaces
@jeb:非常感谢!现在我的代码 CALL "%SOURCEDIR%\FOLDERBROWSE" ... "quoted parameters"> 完美运行。【参考方案7】:
只需使用FOR
命令的结果即可。例如(在批处理文件中):
for /F "delims=" %%I in ('dir /b /a-d /od FILESA*') do (echo %%I)
您可以使用%%I
作为您想要的值。就像这样:%%I
。
并且预先%%I
没有任何空格或CR字符,可以用来比较!!
【讨论】:
【参考方案8】:如果您正在寻找Using the result of a command as an argument in bash?中提供的解决方案
下面是代码:
@echo off
if not "%1"=="" goto get_basename_pwd
for /f "delims=X" %%i in ('cd') do call %0 %%i
for /f "delims=X" %%i in ('dir /o:d /b') do echo %%i>>%filename%.txt
goto end
:get_basename_pwd
set filename=%~n1
:end
这将使用 CD 命令的结果调用自身,与 pwd 相同。
对参数的字符串提取将返回文件名/文件夹。
获取此文件夹的内容并附加到文件名.txt
[Credits]:感谢所有其他答案以及Windows XP commands 页面上的一些挖掘。
【讨论】:
【参考方案9】:@echo off
ver | find "6.1." > nul
if %ERRORLEVEL% == 0 (
echo Win7
for /f "delims=" %%a in ('DIR "C:\Program Files\Microsoft Office\*Outlook.EXE" /B /P /S') do call set findoutlook=%%a
%findoutlook%
)
ver | find "5.1." > nul
if %ERRORLEVEL% == 0 (
echo WinXP
for /f "delims=" %%a in ('DIR "C:\Program Files\Microsoft Office\*Outlook.EXE" /B /P /S') do call set findoutlook=%%a
%findoutlook%
)
echo Outlook dir: %findoutlook%
"%findoutlook%"
【讨论】:
感谢您发布答案!虽然代码 sn-p 可以回答这个问题,但添加一些附加信息仍然很棒,比如解释等 ..【参考方案10】:您可以在一个变量中捕获所有输出,但行将由您选择的字符(下例中的#)分隔,而不是实际的 CR-LF。
@echo off
setlocal EnableDelayedExpansion
for /f "delims=" %%i in ('dir /b') do (
if "!DIR!"=="" (set DIR=%%i) else (set DIR=!DIR!#%%i)
)
echo directory contains:
echo %DIR%
第二版,如果需要逐行打印出来。这得益于“dir /b”不会有重复的输出行,因此它在一般情况下可能不起作用。
@echo off
setlocal EnableDelayedExpansion
set count=0
for /f "delims=" %%i in ('dir /b') do (
if "!DIR!"=="" (set DIR=%%i) else (set DIR=!DIR!#%%i)
set /a count = !count! + 1
)
echo directory contains:
echo %DIR%
for /l %%c in (1,1,%count%) do (
for /f "delims=#" %%i in ("!DIR!") do (
echo %%i
set DIR=!DIR:%%i=!
)
)
【讨论】:
【参考方案11】:@echo off
setlocal EnableDelayedExpansion
FOR /F "tokens=1 delims= " %%i IN ('echo hola') DO (
set TXT=%%i
)
echo 'TXT: %TXT%'
结果是'TXT: hola'
【讨论】:
【参考方案12】:你应该使用for
命令,这里是一个例子:
@echo off
rem Commands go here
exit /b
:output
for /f "tokens=* useback" %%a in (`%~1`) do set "output=%%a"
您可以使用call :output "Command goes here"
,然后输出将在%output%
变量中。
注意:如果您有一个多行命令输出,此工具会将set
输出到您的多行命令的最后一行。
【讨论】:
【参考方案13】:请参阅此http://technet.microsoft.com/en-us/library/bb490982.aspx,它解释了您可以使用命令输出做什么。
【讨论】:
那篇文章只涉及重定向和相关主题。它没有回答如何获取一个命令的输出并将其放入变量的问题。 使用他的答案我想出了这个:git pull>0 set /P RESULTVAR=<0
编辑:我想,我不知道如何在这里使用换行符......每个命令都在它自己的行上。跨度>
以上是关于如何获取Windows 系统的内核变量的主要内容,如果未能解决你的问题,请参考以下文章