从头开始编写驱动程序加载器 [DLoad]
加载设备驱动程序的工具



引言
嗯,我不能说这是一篇常规文章。它更像是一个小手册和我的工具:“驱动加载器”,“DLoad
”的演示。有人可能会说——网上有很多设备驱动加载器,但是当我在谷歌搜索引擎中输入:“驱动加载器”时——我在第一页只找到了OSR组提供的一个。有人可能会说,深入搜索会发现很多这样的控制台工具,我会回答:我很久以前就毕业了,现在我是一个老家伙,每天没有10小时的空闲时间浪费在搜索这样一个简单的工具上——我需要它立刻,马上;我也不喜欢摆弄控制台工具,我们现在是什么时代了?我想要一个漂亮的图形用户界面,直观明了的用户界面(将来我可能会让我的驱动加载器支持语音控制)。那么我为什么要编写这个呢?因为OSRLoader出于某些原因不符合我的需求?因为我根本不想遍历整个谷歌只为了找到一个?无论如何,这个版本是DLoad
的第三版,包中还会找到第二版,第二版是用C++ Gtk+编写的,而第三版是用C# .NET编写的。那么,让我们看看这里有什么。在写这篇介绍时,让我提两件事
[1]. 代码的组织方式使得您可以轻松地从中获取一些部分并应用到自己的应用程序中,将其拆开、混合然后重新组合。
[2]. 第三版实际能做什么
- 使用
ZwSetSystemInformation
加载驱动 - 使用
NtLoadDriver
加载驱动 - 使用服务控制管理器加载驱动
- 卸载驱动
- 删除驱动文件
- 删除驱动注册表项
- 使用线程注入技术
- 使用
RtlCreateUserThread
注入 - 使用
CreateRemoteThread
注入 - 使用
NtCreateThreadEx
注入 - 加载模式
- 卸载模式
- 重启系统
- 关闭系统
- 以上任何功能的组合
好的,我们开始。我不打算解释DLoad
v.2的代码,因为它提供了:“就这样”、“作为替代”、“也许你喜欢Gtk?”、“随便……”。而且它已经过时了。我们继续。
进入代码
我将按照它们出现的顺序解释。再看看GUI。我认为没有必要解释“选择驱动”按钮……好的。
ZwSetSystemInformation
NtLoadDriver
- 服务控制管理器
通过这些方法,您可以加载驱动。ZwSetSystemInformation
几乎没有文档记录,但它已经使用了很长时间。实际上,它不是最好的方法——它是最糟糕的,但它仍然存在。最糟糕的是因为您的驱动程序进入了分页池,您将无法卸载它,也无法在系统重启之前删除驱动文件。好的,我们如何从C#代码中调用它。第一件事是声明所有原生元素——为此创建了“Native”类,它看起来像这样
public class Native {
.......
public const int NtCurrentProcess = -1;
public const int NtCurrentThread = -2;
public const long NT_SUCCESS = 0x00000000L;
public const int STATUS_SUCCESS = 0;
........
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct CLIENT_ID
{
int UniqueProcess;
int UniqueThread;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct UNICODE_STRING
{
public ushort Length;
public ushort MaximumLength;
public string Buffer;
}
...............
[DllImport("ntdll.dll", CharSet = CharSet.Unicode, SetLastError = true)]
unsafe public static extern int ZwSetSystemInformation(
int Value1,
IntPtr Value2,
int Value3
);
我已将DLoad
所需的元素包含在其中,您可以非常简单地根据自己的需求扩展该类,并创建类似网上著名的“ntdll.h”的东西。接下来,我们有我们的“DLoad
”命名空间和“DriverLoader
”类,其中包含我们的方法。与之前的类一样,您可以在项目中轻松使用它。让我们看看这个函数。
namespace DLoad {
public class DriverLoader {
public static long ZwStyleLoader(
String DriverPath
)
{ // Load driver with 'ZwSetSystemInformation'
bool en;
int Status; // status
String FullDriverPath = "\\??\\" + DriverPath + '\0'; // driver path
Native.SYSTEM_LOAD_AND_CALL_IMAGE img; // structure initialization
Status = Native.RtlInitUnicodeString( // initialization of unicode string
out (img.ModuleName), // pointer to UNICODE_STRING
FullDriverPath // PCWSTR
);
if (Status != Native.STATUS_SUCCESS)
return Native.STATUS_INITUNISTRING_FAILURE; // unicode string
// is not initialized
Status = Native.RtlAdjustPrivilege( // setting privileges
10, // int
true, // BOOL
Native.ADJUST_PRIVILEGE_TYPE.AdjustCurrentProcess, // BOOL
out en //BOOL *
);
if (Status != Native.STATUS_SUCCESS)
return Native.STATUS_PRIVILEGES_NOT_SET; // privileges not sat
IntPtr buffer =
Marshal.AllocCoTaskMem(Marshal.SizeOf(img)); // this will point to
// our structure like 'PVOID'
Marshal.StructureToPtr(img, buffer, false); // structure to pointer
// ^ that is the working example how PVOID stuff is converted to C# style
// we are passing a pointer to structure to IntPtr which is actually a handle
// previously allocating some memory, then we passing 'buffer' as IN parameter
Status = Native.ZwSetSystemInformation(
Native.SystemLoadAndCallImage, // dword
buffer, // pvoid
Marshal.SizeOf(img) // ulong
);
return Status; // just get status code to know what to do next
}
/***************************************************************************************/
这段代码最酷的地方在于,伙计,它是 C#!对我来说,C# 比解释性语言(比如 Perl)更高级,它几乎已经像真正的自然语言了。只需比较一下
static inline char * x_strcpy(char *dest, char *src)
{
int d1, d2, d3;
__asm__ __volatile__ (
"1:\tlodsb\n\t"
"stosb\n\t"
"testb %%al, %%al\n\t"
"jne 1b\n\t"
: "=&a" (d1), "=&S" (d2), "=&D" (d3)
: "1" ((ulong) src), "2" ((ulong) dest));
return dest;
}
和
String.Copy
而且,我们仍然可以访问原生Windows API!:) 嗯,否则我永远不会用C#编写任何东西 ;) 回到主题。就是这样做的。对于NtLoadDriver
和服务控制管理器也是如此,这3个方法都在一个类中。现在我们调用它
Status = DriverLoader.ZwStyleLoader(FileNamePath);
// or...
Status = DriverLoader.NtStyleLoader
(FileNamePath, UnloadDriver, DeleteDriver, DeleteRegEntry, UnloadMode);
// or ..
DriverLoader.ScmStyleLoader
(FileNamePath, UnloadDriver, DeleteDriver, DeleteRegEntry, UnloadMode);
接下来是:退出操作。
- 卸载驱动
- 删除驱动文件
- 删除驱动注册表项
所以,这是一个在加载驱动程序例程之后执行的标准操作。当DriverEntry
最终被调用时,我们该怎么办?例如,如果您的驱动程序只是打印“Hello World from driver!”并返回,您只是希望它在执行后立即卸载。在这种情况下,您勾选“卸载驱动程序”和“删除驱动程序注册表项”。如果您希望删除驱动程序文件,则勾选“删除驱动程序文件”。依此类推。但是,如果您正在测试一个更高级的驱动程序,例如服务器或使用IOCTL的驱动程序,您不希望它在执行后立即卸载,在这种情况下,您需要取消勾选所有退出操作选项。
接下来是“注入”选项——如果勾选,DLoad
将展开并显示一组新选项
- 使用
RtlCreateUserThread
注入 - 使用
CreateRemoteThread
注入 - 使用
NtCreateThreadEx
注入
首先,注入功能是在一个单独的DLL中实现的,该DLL内置在DLoad
本身中。如果勾选“使用注入”,DLoad
会将此DLL解压到windows/文件夹中并从中导入函数。这是原型
DWORD LoadDriverWithInjection(
int ProcID,
char *DriverPath,
int LoadMode,
BOOL UnloadDriverMode,
BOOL DeleteDriverMode,
BOOL DelDrvRegMode,
int Mode,
char *name,
bool un_load
);
所以如果你以后需要这样的功能——你已经有了我的DLL。:)
你可能会问,为了什么?我的意思是注入?嗯,其实我自己也不确定:P 它就在这里。无论如何,给我看另一个使用注入方法加载驱动程序的驱动加载器?哈!没有这样的!我的就因此而独特。好的,关于函数。
[1]. RtlCreateUserThread
- 应该适用于任何 Windows 系统上的任何类型的进程(记事本、svchost 等)
[2]. CreateRemoteThread
- 关于这个,除了已经说过的话,我不知道还能说什么
[3]. NtCreateThreadEx
- 这是最新的 Windows 特有函数(Vista、Server 2008 等)
主应用程序中的用法
[DllImport("DLoadDLL.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int LoadDriverWithInjection(
uint TargProc,
byte[] DriverPath,
int LoadMode,
Boolean UnloadDriverMode,
Boolean DeleteDriverMode,
Boolean DelDrvRegMode,
int Mode,
byte[] DriverName,
Boolean UN_LOAD
);
然后,在我们的主类中,我们调用它
String TargetProcess = targ_proc_ent.Text;
uint ProcID = Native.GetPidByName(TargetProcess);
String DriverFile = Path.GetFileName(FileNamePath);
System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
byte[] Test1 = encoding.GetBytes(DriverFile);
byte[] Test2 = encoding.GetBytes(FileNamePath);
StaTus = Native.LoadDriverWithInjection(
ProcID,
Test2,
Native.NTLOADMODE,
UnloadDriver,
DeleteDriver,
DeleteRegEntry,
InjectionModeEx,
Test1,
UnloadMode
);
下一个选项,如果你勾选了“使用注入”,就是选择目标进程,默认是记事本。
然后我们有 2 个单选按钮
- 加载模式
- 卸载模式
所以,默认模式是加载模式,DLoad v.2 [Gtk+]只有这种模式。C#版本也有卸载模式。卸载模式负责……卸载驱动!:) 简单。例如,如果你加载了一个驱动,没有快速卸载(我上面某个地方提到过),现在想卸载它,只需再次选择你的驱动(选择驱动文件),勾选“卸载”并点击“执行”按钮。驱动就会被卸载。这里有一点,在选择卸载方法时,你不能选择“通过 ZwSetSystemInformation
”,而且方法应该与你加载驱动时相同,所以如果你的驱动是使用 NtLoadDriver
方法加载的,它也应该使用 NtLoadDriver
方法卸载(我指的是方法,而不是函数 xD)。希望这很清楚。
最后我们这里有这些“小部件”:最底部的按钮。从左到右
执行,信息,退出,重启,关机。
重启和关机按钮负责重启或关闭机器。它们使用原生函数
void button_reboot_Click(object sender, EventArgs e)
{
bool en;
int Status;
Status = Native.RtlAdjustPrivilege(
19,
true,
Native.ADJUST_PRIVILEGE_TYPE.AdjustCurrentProcess,
out en
);
Status = Native.NtShutdownSystem(
Native.SHUTDOWN_ACTION.ShutdownReboot
);
if (Status != Native.STATUS_SUCCESS)
{
String Debug = String.Format("Failure, Status: {}", Status);
status_output.Text = Debug;
}
}
是什么让它们更有效:没有等待时间,没有注销窗口。它们之所以在这里,是因为我需要它们在这里。:) 我个人在vmware上测试驱动程序,有时会出现需要重启机器的情况。出了问题但你无法卸载驱动程序,因为它卡在某个地方,或者可能发生了任何其他糟糕的事情,或者只是需要快速重启(我讨厌等待所有注销的业务完成)。离题了,这里有一个有趣的事情:在编写Linux内核模块时,有一个函数可以在不到一秒的时间内关闭计算机,无需任何注销!现在不记得它的名字了……KernelShutdown 也许?;) 嗯,没什么好解释的了。阅读代码——它是最好的自解释的东西。感谢您的关注,干杯。

历史
- 2009年11月1日:初始版本