能够伪造Windows控制台API吗?

11

我用C#编写了一个SSH服务器,并想把PowerShell作为shell连接到它上面。我试过两种方法,但都不完美。下面是我尝试过的:

  1. 启动powershell.exe并重定向其标准输入/输出。这种方法效果不好,因为powershell.exe检测到被重定向后会改变其行为。更重要的是,它期望在stdid上接收输入数据,而不是命令。因此,它使用控制台API来读取命令。
  2. 在“包装器”应用程序中托管powershell。这样做的好处是可以通过PSHostRawUserInterface为powershell提供“控制台”实现。这个方法效果更好,但你仍然可以调用像“...| more”这样期望使用控制台API并尝试从包装进程的控制台读取的命令(大多数是真正的控制台应用程序)。

所以我想做的是编写一组函数,替换掉控制台应用程序使用的常规控制台输入/输出函数,以便我可以处理它们。但这似乎太过激烈,甚至是一个糟糕的设计想法(在我看来)。

目前我的想法是通过发送相关键值对控制台进行操纵,使用本地/Pinvoke函数(如WriteConsoleInput)。我了解到可能可以通过这种方式虚拟控制台。但问题在于我无法“读取”控制台上发生的事情。

另外,请记住,这是一个服务,所以最好不要生成一个实际的控制台窗口,尽管在Windows会话0中可能不会显示出来,也不会影响到操作。

2个回答

3
您可以使用PSSession和Enter-PSSession CmdLet进行此操作。那么,使用PowerShell的SSH与PSSession相比有什么不同呢?
但是,如果您想要做到这一点,这里有一个解决方案,而不需要编写任何内容:通过SSH使用PowerShell

编辑于02/11/2011

PowerShell inside提供了另一种方法,无需编写任何内容(个人使用免费)。

Host03示例可能提供了基本代码来实现您想要做的事情。


首先,我想从我的安卓移动设备上访问我的SSH。我还没有看到真正的PowerShell客户端。这个项目肯定有一个“只是好玩”的部分。至于Cygwin……我对它有一些(也许是不理性的)问题。 - Captain Coder
你尝试过这个 PowerShellInside 吗?它有一个免费的连接版本。 - JPBlanc
嗯,一个有趣的发现,我会去看看的。不过让我想知道他们是如何解决这个问题的。 - Captain Coder
我曾认为他们只是嵌入了PowerShell Runspace和Pipeline,但是我没有问自己太多问题。 - JPBlanc

1

我按照JPBlanc的建议安装了PowerShellInside,但并没有使用它很长时间。一次只能连接一个东西太限制了,而且我不喜欢被限制(尤其是如果那种限制是以盈利为基础的,但这是另一个我不该涉足的讨论)。虽然它解决了最初的问题,但感觉仍然不尽人意,因为它没有解决我遇到的编程问题。

然而,我最终还是设法解决了这个问题,确实是通过在包装进程中使用Windows API调用。由于有很多陷阱,我决定回答自己的问题,并给其他看到同样问题的人一些指南。基本结构如下:

  • 使用重定向的stdin/-out(如果需要,还可以重定向stderr)启动包装器进程。 (在我的情况下,stdin和out将是xterm控制序列和数据流,因为这是ssh的方式)
  • 使用GetStdHandle()检索重定向的输入和输出句柄。接下来,将SetStdHandle()设置为“CONIN $”和“CONOUT $”的CreateFile(),以便子进程继承控制台并且不具有包装器进程的重定向。(请注意,需要允许继承的安全描述符用于createfile)
  • 设置控制台模式、大小、标题、Ctrl-C处理程序等。注意:如果要支持Unicode,请务必设置字体,我使用了Lucida Console(.FontFamily = 54,.FaceName =“Lucida Console”)。如果没有这个,从控制台输出读取字符将返回代码页版本,在托管代码中处理这些版本非常困难。
  • 可以使用SetWinEventHook()读取输出,确保使用上下文外通知,因为我相当确定让您的托管应用程序突然在另一个进程上下文/地址空间中运行是一个坏主意™(我很确定我甚至都没有尝试)。该事件将为每个控制台窗口触发,而不仅仅是您自己的窗口。因此,通过窗口句柄过滤回调的所有调用。使用GetConsoleWindow()检索当前控制台应用程序的窗口句柄。还要在应用程序完成时取消挂钩回调。
  • 请注意,到此为止,请务必不要使用(或执行任何导致加载)System.Console类的内容,否则很可能会出现问题。在此之后的使用将表现为子进程已写入输出。
  • 生成所需的子进程(请注意,您必须使用.UseShellExecute = false,否则它将不会继承控制台)
  • 可以使用WriteConsoleInput()开始向控制台提供输入
  • 此时(或在单独的线程上),您必须运行Windows消息循环,否则您将无法接收控制台事件通知回调。您可以简单地使用无参数的Application.Run()来执行此操作。要中断消息循环,您必须在某个时候向消息循环发布退出消息。我在子进程的.Exited事件中使用了Application.Exit()来完成此操作。(请注意,使用.EnableRaisingEvents使其工作)
  • 当控制台发生更改时,现在将调用您的win事件回调。请注意滚动事件,这可能会产生一些意外的效果。还要注意同步传递方面的假设。如果子进程写入3行,则在您处理第一个事件时,剩余的3行可能已经被写入。公平地说,Windows做得很好,可以组合事件,使您不会被单个字符更改淹没,并且可以跟上更改。
  • 请务必使用CharSet = CharSet.Unicode标记所有PInvoke定义,如果它们在输入或输出中的任何位置包含字符。
所有这些的最终结果是:一个用于Windows控制台API的包装应用程序。该包装器可以读取/写入重定向的标准输入和标准输出以与世界通信。当然,如果您想变得花哨一些,您可以在此处使用任何流(命名管道、TCP/IP等)。我实现了一些xterm控制序列,并成功地获得了一个完全工作的终端包装器,应该能够包装任何Windows控制台进程,将xterm输入转换为目标应用程序的控制台输入,并将应用程序的输出处理为xterm控制序列。我甚至让鼠标工作了。现在启动powershell.exe作为子进程解决了在ssh会话中运行powershell的原始问题。Cmd.exe也可以工作。如果有人感兴趣,我会考虑在某个地方发布完整的代码。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接