【问题标题】:C++ and C# Communication using Named Pipe使用命名管道的 C++ 和 C# 通信
【发布时间】:2015-07-15 22:34:32
【问题描述】:

我正在尝试对注入到进程中的 dll 进行逆向工程,它确实挂钩 winsock send() 并通过 PipeStream 发送数据。

这是读取管道流的 C# 代码:

    [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
    public struct PipeHeader
    {
        [MarshalAs(UnmanagedType.I1)]
        public byte command;
        [MarshalAs(UnmanagedType.I4)]
        public int sockid;
        public int datasize;
    }

    public static object RawDeserializeEx(byte[] rawdatas, Type anytype)
    {
        int rawsize = Marshal.SizeOf(anytype);
        if (rawsize > rawdatas.Length)
            return null;
        GCHandle handle = GCHandle.Alloc(rawdatas, GCHandleType.Pinned);
        IntPtr buffer = handle.AddrOfPinnedObject();
        object retobj = Marshal.PtrToStructure(buffer, anytype);
        handle.Free();
        return retobj;
    }
    private void PipeRead()
    {
        byte[] dbPipeMsgIn = new byte[9];
        byte[] zero = new byte[] { 0 };

        byte[] dbPipeMsgInData;
    PipeLoop:
        while (pipeIn.Read(dbPipeMsgIn, 0, 9) != 0)
        {
            strPipeMsgIn = (PipeHeader)RawDeserializeEx(dbPipeMsgIn, typeof(PipeHeader));
            if (strPipeMsgIn.datasize != 0)
            {
                dbPipeMsgInData = new byte[strPipeMsgIn.datasize];
                pipeIn.Read(dbPipeMsgInData, 0, dbPipeMsgInData.Length);
                //do something with dbPipeMsgInData
            }
        }
        if (pipeIn.IsConnected) goto PipeLoop;
    }

到目前为止,我已经连接了send() 函数,通过管道连接并发送消息。 问题是收到的数据不是我期望的数据,所以我可能以错误的方式发送。我需要帮助,因为我几乎没有 C++ 知识。

C++ 代码:

#pragma pack(1)
typedef struct
{
    byte command;
    int sockid;
    int datasize;
} PipeHeader;
#pragma pack(0)

int WINAPI MySend(SOCKET s, const char* buf, int len, int flags)
{
    PipeHeader ph;
    string p(buf);
    if(p.find("<TalkMsg") == 0){
        byte cmd = 0;
        ph.command = cmd;
        ph.sockid = s;
        ph.datasize = len;
        char buffer[sizeof(ph)];
        memcpy(buffer, &ph, sizeof(ph)); 
        if(SendPipeMessage(buffer, sizeof(buffer))){
            if(SendPipeMessage(buf, sizeof(buf))){
                MessageBox(NULL,"Message Sent", NULL, NULL);
            }
        }
        fopen_s(&pSendLogFile, "C:\\SendLog.txt", "a+");
        fprintf(pSendLogFile, "%s\n", buf);
        fclose(pSendLogFile);
    }
    return pSend(s, buf, len, flags);
}

BOOL SendPipeMessage(LPCVOID lpvMessage, DWORD ctToWrite){
    // Send a message to the pipe server. 
   cbToWrite = sizeof(lpvMessage);

   fSuccess = WriteFile( 
      hPipe,                  // pipe handle 
      lpvMessage,             // message 
      cbToWrite,              // message length 
      &cbWritten,             // bytes written 
      NULL);                  // not overlapped 

   if ( ! fSuccess) 
   {
      return false;
   }else return true;
}

编辑,更多信息:

目标是通过管道发送一条包含 9 字节长的 PipeHeader 结构的消息,然后发送另一条包含 winsock send() 数据的消息(buf 变量),在 C# 应用程序上读取管道并解析第一条消息以获取下一条传入消息的DataSize(即PipeHeaderdatasize var),然后使用datasize 再次读取管道以获取send() 缓冲区。 我认为这是以这种方式工作的。我不太了解 Pipes 的工作原理。

无论如何,主要目标是将send() 缓冲区从 C++ Dll 发送到 C# 应用程序。

更新: 似乎我必须首先以某种方式序列化 PipeHeader 结构,以便我可以在 C# 代码中使用 RawDeserializeEx() 反序列化它。 我尝试这样做:

char buffer[sizeof(ph)];
memcpy(buffer, &ph, sizeof(ph)); 

问题在于,在 C++ 中 sizeof(ph)sizeof(buffer) 返回 12 个字节。 而在 C# 中,相同 Struct 的非托管大小 (Marshal.SizeOf()) 返回 9 个字节。

更新 2: 通过更改结构包装解决了大小差异。 但我仍然没有在 C# 中得到正确的值。 代码已更新。

【问题讨论】:

  • 请发布整个 C++ 代码。有许多变量未在您的代码中声明,例如 hPipecbWrittenpSend 是什么?

标签: c# c++ named-pipes


【解决方案1】:

我的第一篇文章,所以请放轻松:-)

BOOL SendPipeMessage(LPCVOID lpvMessage){
    // Send a message to the pipe server. 
   cbToWrite = sizeof(lpvMessage);

sizeof 使用不正确。它将返回size of typeLPCVOID,而不是您计划的缓冲区大小。当您想要发送 9 个字节时,您正在发送 4 个字节(在 x86 上)。

在我看来,您需要为您的 SendPipeMessage() 提供新参数

BOOL SendPipeMessage(LPCVOID lpvMessage, DWORD cbToWrite)

您可以在第一次调用时使用 sizeof。在第二次调用时,您可以使用 len 作为参数。

if(SendPipeMessage((LPCVOID)&ph, sizeof(ph))){
    if(SendPipeMessage(buf, len)){

【讨论】:

  • sizeof(buf) - 将返回类型指针的大小(通常为 4)。 buf 长度在 len 中提供。这就是为什么你应该尝试SendPipeMessage(buf, len) 而不是SendPipeMessage(buf, sizeof(buf))
【解决方案2】:

没有检查整个代码,因为我现在没时间了。但我注意到的是:

  • 我很确定你不能指望int 在 C++ 中总是 4 个字节。如果大小很重要,int 不是最佳选择。
  • 您在 C# 代码中忘记了成员 datasize[MarshalAs(UnmanagedType.I4)]。属性仅适用于下一项,而不适用于所有后续项。
  • SendPipeMessage(buffer, sizeof(buffer)) 替换为SendPipeMessage((char*)&amp;ph, sizeof(ph)) 以获得所需的结果。不需要memcpy 到那里的 char 数组和 M.L.提到sizeof(buffer)不是你想要的。
  • sizeof(buf) 替换为len。您已经有了长度,为什么还要使用 sizeof? sizeof(buf) 又不是你想要的。
  • 您可以将最后的返回代码简化为return fSuccess;。那里不需要if-else。或者只是return WriteFile(...);
  • 为什么使用cbToWrite = sizeof(lpvMessage);?这又不是你想要的,你已经传递了ctToWrite,你应该在WriteFile的调用中使用它。

【讨论】:

  • 在大多数 32 位和 64 位平台上,尤其是在我所知道的所有 x64 平台上,一个 int 是 4 个字节。
  • 可能是真的,但在大小很重要的地方使用int 并不是一个好的选择。我想这不会是一个问题,但我想指出它。
  • 你说得对,int 不是这里的最佳选择,但声明“尤其是 64 位它不会是真的”。是错误的 - 特别是如果您考虑到 c# 通常用于 windows 系统,整数在 32 位和 64 位模式下的 x86、x64 和 Itanium 上具有 4 个字节。
【解决方案3】:

你的问题对我来说不是特别清楚。我不确定问题是处理管道还是序列化。我在下面提供了完整的 C++ 管道服务器代码和 C# 管道客户端代码。我试图让事情变得简单,以对演示对话有用。我使用了您的结构和反序列化代码并添加了序列化方法。在这两个代码示例中,都有一个指向 MS doc 的链接,如果需要,它应该可以很容易地反转客户端服务器关系。服务器和客户端代码是独立的,设计用于在不同的进程(两个控制台应用程序)中运行。没有线程或服务器不会处理多个连接。

简单(反/)序列化而言,我建议您使用结构内的 BitConverter 类和方法来手动与字节数组进行转换。这为您提供了更精细的控制,并且更易于调试。如果您有复杂的结构来表示,我建议您查看 Google 的“protobuf”。当然是 YMMV。

C++ 管道服务器代码 (Windows)

#include <stdio.h>
#include <stdint.h>
#include <winsock.h>
#include <string>

// see https://msdn.microsoft.com/en-us/library/bb546085(v=vs.110).aspx?cs-save-lang=1&cs-lang=csharp#code-snippet-1
// for information on creating pipe clients and servers in c++

using namespace std;

#pragma pack(1)
typedef struct {
    int8_t command;
    int32_t sockid;
    int32_t datasize;
    } PipeHeader;
#pragma pack()

int wmain( int argc, wchar_t* argv[] ) {
    auto pipename = "\\\\.\\pipe\\pipey"; // can be any name, but must start '\\.\pipe\'
    printf("Create pipe '%s'\r\n", pipename);
    auto pipeHandle = CreateNamedPipe(pipename, 
                                      PIPE_ACCESS_DUPLEX, 
                                      PIPE_TYPE_MESSAGE | PIPE_WAIT,
                                      1,  // # instances
                                      64, // out buff
                                      64, // in buff
                                      0,  // timeout, 0 = default of 50ms
                                      NULL); // security attrs
    printf("Waiting for pipe connect\r\n");
    if (ConnectNamedPipe(pipeHandle, NULL)) {
        printf("Pipe connected\r\n");
        PipeHeader hdr;
        DWORD bytesRead;
        int8_t cmd = -1;
        while (cmd != 0) {
            if (ReadFile(pipeHandle, &hdr, sizeof(PipeHeader), &bytesRead, NULL)) {
                // you can check or assert here that bytesRead == sizeof(PipeHeader)
                printf("Read %d bytes from pipe, {command: %d, sockid: %d, datasize: %d}\r\n", 
                       bytesRead, hdr.command, hdr.sockid, hdr.datasize);
                if (hdr.command == 0) { // assume 0 is the exit cmd
                    break;
                    }
                else if (hdr.command == 1) {
                    // do whatever cmd 1 is ...
                    }
                hdr.command = 4;
                hdr.sockid = 101;
                hdr.datasize *= 2;
                if (WriteFile(pipeHandle, &hdr, sizeof(PipeHeader), &bytesRead, NULL)) {
                    printf("Data written to pipe\r\n"); 
                    }
                else {
                    printf("Pipe write failed\r\n");
                    break;
                    }
                }
            else {
                printf("Read error: %d\r\n", GetLastError());
                break; // exit
                }
            }
        if (DisconnectNamedPipe(pipeHandle) == FALSE) {
            printf("Disconnect error: %d\r\n", GetLastError());
            }
        }
    else {
        printf("Pipe connect failed\r\n");
        }
    CloseHandle(pipeHandle);
    printf("Exiting program\r\n");
    return 0;
    }

C# 客户端管道代码 (Windows)

using System;
using System.Runtime.InteropServices;
using System.IO.Pipes;

namespace Pipes {
    // see https://msdn.microsoft.com/en-us/library/bb546085(v=vs.110).aspx?cs-save-lang=1&cs-lang=csharp#code-snippet-1
    // for information on creating pipe clients and servers in c#
    class Program {
        [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
        public struct PipeHeader {
            [MarshalAs(UnmanagedType.I1)]
            public byte command;
            [MarshalAs(UnmanagedType.I4)]
            public Int32 sockid;
            public Int32 datasize;
        }

        static void Main(string[] args) {
            var pipename = "pipey";
            var pipeClient = new NamedPipeClientStream(pipename);
            Console.WriteLine("Connecting to server pipe '{0}'", pipename);
            pipeClient.Connect();
            var hdr = new PipeHeader();
            var hdrSize = Marshal.SizeOf(hdr);
            hdr.command = 1;
            hdr.sockid = 1912;
            hdr.datasize = 32;
            var buf = Serialize(hdr);
            Console.WriteLine("Writing to server pipe");
            pipeClient.Write(buf, 0, hdrSize);
            pipeClient.Read(buf, 0, hdrSize);
            hdr = (PipeHeader) Deserialize(buf, hdr.GetType());
            Console.WriteLine("Pipe read {{ command: {0}, sockid: {1}, datasize: {2} }}",
                               hdr.command, hdr.sockid, hdr.datasize);
            hdr.command = 0; // tell server to disconnect
            buf = Serialize(hdr);
            Console.WriteLine("Sending disconnect");
            pipeClient.Write(buf, 0, hdrSize);
            pipeClient.Close();
            Console.WriteLine("Pipe closed");
            }

        public static object Deserialize(byte[] rawdatas, Type anytype) {
            int rawsize = Marshal.SizeOf(anytype);
            if (rawsize > rawdatas.Length)
                return null;
            GCHandle handle = GCHandle.Alloc(rawdatas, GCHandleType.Pinned);
            IntPtr buffer = handle.AddrOfPinnedObject();
            object retobj = Marshal.PtrToStructure(buffer, anytype);
            handle.Free();
            return retobj;
            }

        public static byte[] Serialize(object obj) {
            int rawsize = Marshal.SizeOf(obj);
            var rv = new byte[rawsize];
            IntPtr ptr = Marshal.AllocHGlobal(rawsize);
            Marshal.StructureToPtr(obj, ptr, true);
            Marshal.Copy(ptr, rv, 0, rawsize);
            Marshal.FreeHGlobal(ptr);
            return rv;
            }
        }
    }

编译两组代码。启动服务器代码,然后启动客户端。输出有点少,但显示了数据交换,并且服务器在将数据发送回之前修改了数据(显示了双向数据交换)。除了作为一个例子之外没有其他真正的目的。

希望这个示例能提供您解决问题所需的基本信息。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-24
    • 2018-12-15
    相关资源
    最近更新 更多