Microsoft VC++, vsnprintf, and Pipes (IO) Bug

Microsoft VC++, vsnprintf, and Pipes (IO) Bug

本文关键字:IO Bug and VC++ vsnprintf Microsoft Pipes      更新时间:2023-10-16

>我正在使用DLL注入来启动文件管道的客户端,该文件管道与记录消息的服务器通信。问题是服务器只收到一个填充有问号 ('?'( 字符的缓冲区。

客户端/可注入 DLL:

#include <windows.h> 
#include <stdio.h> 
#include <tchar.h>
#include <strsafe.h>
#define BUFSIZE 1024*1024
HANDLE hPipe;
BOOL   fSuccess = FALSE;
DWORD  cbToWrite, cbWritten, dwMode;
const wchar_t* lpszPipename = TEXT("\\.\pipe\listen");
char write_buffer[BUFSIZE];
void init()
{
hPipe = CreateFile(
lpszPipename,   // pipe name 
GENERIC_READ |  // read and write access 
GENERIC_WRITE,
0,              // no sharing 
NULL,           // default security attributes
OPEN_EXISTING,  // opens existing pipe 
0,              // default attributes 
NULL);          // no template file 
// The pipe connected; change to message-read mode. 
dwMode = PIPE_READMODE_MESSAGE;
fSuccess = SetNamedPipeHandleState(
hPipe,    // pipe handle 
&dwMode,  // new pipe mode 
NULL,     // don't set maximum bytes 
NULL);    // don't set maximum time 
}
#pragma warning(disable:4996)
void report(const char* frmt, ...)
{
va_list args;
va_start(args, frmt);
vsnprintf(write_buffer, BUFSIZE, frmt, args);
va_end(args);
// Send a message to the pipe server. 
fSuccess = WriteFile(
hPipe,                  // pipe handle 
write_buffer,             // message 
strlen(write_buffer),              // message length 
&cbWritten,             // bytes written 
NULL);                  // not overlapped 
return;
}

服务器:

#include <windows.h> 
#include <stdio.h> 
#include <tchar.h>
#include <strsafe.h>
#define BUFSIZE 1024*1024
BOOL   fConnected = FALSE;
DWORD  dwThreadId = 0;
HANDLE hPipe = INVALID_HANDLE_VALUE, hThread = NULL;
const wchar_t* lpszPipename = TEXT("\\.\pipe\listen");
// The main loop creates an instance of the named pipe and 
// then waits for a client to connect to it. When the client 
// connects, a thread is created to handle communications 
// with that client, and this loop is free to wait for the
// next client connect request. It is an infinite loop.
for (;;)
{
_tprintf(TEXT("nPipe Server: Main thread awaiting client connection on %sn"), lpszPipename);
hPipe = CreateNamedPipe(
lpszPipename,             // pipe name 
PIPE_ACCESS_DUPLEX,       // read/write access 
PIPE_TYPE_MESSAGE |       // message type pipe 
PIPE_READMODE_MESSAGE |   // message-read mode 
PIPE_WAIT,                // blocking mode 
PIPE_UNLIMITED_INSTANCES, // max. instances  
BUFSIZE,                  // output buffer size 
BUFSIZE,                  // input buffer size 
0,                        // client time-out 
NULL);                    // default security attribute 
if (hPipe == INVALID_HANDLE_VALUE)
{
_tprintf(TEXT("CreateNamedPipe failed, GLE=%d.n"), GetLastError());
return -1;
}
// Wait for the client to connect; if it succeeds, 
// the function returns a nonzero value. If the function
// returns zero, GetLastError returns ERROR_PIPE_CONNECTED. 
fConnected = ConnectNamedPipe(hPipe, NULL) ?
TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
if (fConnected)
{
printf("Client connected, creating a processing thread.n");
// Create a thread for this client. 
hThread = CreateThread(
NULL,              // no security attribute 
0,                 // default stack size 
InstanceThread,    // thread proc
(LPVOID)hPipe,    // thread parameter 
0,                 // not suspended 
&dwThreadId);      // returns thread ID 
if (hThread == NULL)
{
_tprintf(TEXT("CreateThread failed, GLE=%d.n"), GetLastError());
return -1;
}
else CloseHandle(hThread);
}
else
// The client could not connect, so close the pipe. 
CloseHandle(hPipe);
}
DWORD WINAPI InstanceThread(LPVOID lpvParam)
// This routine is a thread processing function to read from and reply to a client
// via the open pipe connection passed from the main loop. Note this allows
// the main loop to continue executing, potentially creating more threads of
// of this procedure to run concurrently, depending on the number of incoming
// client connections.
{
HANDLE hHeap = GetProcessHeap();
TCHAR* pchRequest = (TCHAR*)HeapAlloc(hHeap, 0, BUFSIZE * sizeof(TCHAR));
TCHAR* pchReply = (TCHAR*)HeapAlloc(hHeap, 0, BUFSIZE * sizeof(TCHAR));
DWORD cbBytesRead = 0, cbReplyBytes = 0, cbWritten = 0;
BOOL fSuccess = FALSE;
HANDLE hPipe = NULL;
// Print verbose messages. In production code, this should be for debugging only.
printf("InstanceThread created, receiving and processing messages.n");
// The thread's parameter is a handle to a pipe object instance. 
hPipe = (HANDLE)lpvParam;
// Loop until done reading
while (1)
{
// Read client requests from the pipe. This simplistic code only allows messages
// up to BUFSIZE characters in length.
fSuccess = ReadFile(
hPipe,        // handle to pipe 
pchRequest,    // buffer to receive data 
BUFSIZE,    // size of buffer 
&cbBytesRead, // number of bytes read 
NULL);        // not overlapped I/O 
// Process the incoming message.
_tprintf(TEXT("Client Request String:"%s"n"), pchRequest);
}
FlushFileBuffers(hPipe);
DisconnectNamedPipe(hPipe);
CloseHandle(hPipe);
HeapFree(hHeap, 0, pchRequest);
HeapFree(hHeap, 0, pchReply);
printf("InstanceThread exitting.n");
return 1;
}

附言如果有一些可能使用带有可注入 DLL 的调试器,请告诉我!

这段代码有一些问题,我将在最后讨论。 首先,一些有效的代码。 请注意,我通过将其全部放入单个应用程序中(以便我可以轻松测试它(并摆脱线程来稍微简化了事情,但这些在您的问题的上下文中都不重要。

#define _CRT_SECURE_NO_WARNINGS
#include <windows.h> 
#include <stdio.h> 
#include <tchar.h>
#include <strsafe.h>
#define BUFSIZE     1024*1024
const TCHAR* lpszPipename = TEXT("\\.\pipe\listen");
char write_buffer [BUFSIZE];
HANDLE init()
{
HANDLE hPipe = CreateFile(
lpszPipename,   // pipe name 
GENERIC_READ |  // read and write access 
GENERIC_WRITE,
0,              // no sharing 
NULL,           // default security attributes
OPEN_EXISTING,  // opens existing pipe 
0,              // default attributes 
NULL);          // no template file 
if (hPipe == INVALID_HANDLE_VALUE)
{
printf ("CreateFile returned error %dn", GetLastError ());
return INVALID_HANDLE_VALUE;
}
// The pipe connected; change to message-read mode. 
DWORD dwMode = PIPE_READMODE_MESSAGE;
BOOL fSuccess = SetNamedPipeHandleState(
hPipe,    // pipe handle 
&dwMode,  // new pipe mode 
NULL,     // don't set maximum bytes 
NULL);    // don't set maximum time 
if (!fSuccess)
{
printf ("SetNamedPipeHandleState returned error %dn", GetLastError ());
CloseHandle (hPipe);
return INVALID_HANDLE_VALUE;
}
return hPipe;    
}
void report(HANDLE hPipe, const char *frmt, ...)
{
va_list args;
va_start(args, frmt);
_vsnprintf(write_buffer, BUFSIZE, frmt, args);
va_end(args);
// Send a message to the pipe server. 
DWORD cbWritten;
BOOL fSuccess = WriteFile(
hPipe,                              // pipe handle 
write_buffer,                       // message 
(DWORD) strlen (write_buffer) + 1,  // message length, including EOS
&cbWritten,                         // bytes written 
NULL);                              // not overlapped 
if (!fSuccess)
printf ("WriteFile returned error %dn", GetLastError ());
}
int _tmain (int argc, TCHAR **argv)
{
if (argc > 1 && _tcscmp (argv [1], __T ("send")) == 0)
{
// send
HANDLE hPipe = init ();
if (hPipe != INVALID_HANDLE_VALUE)
{
report (hPipe, "A message to you, Rudi");
CloseHandle (hPipe);
}
return 0;
}
// receive
for (;;)
{
_tprintf(TEXT("nPipe Server: Main thread awaiting client connection on %sn"), lpszPipename);
HANDLE hPipe = CreateNamedPipe(
lpszPipename,             // pipe name 
PIPE_ACCESS_DUPLEX,       // read/write access 
PIPE_TYPE_MESSAGE |       // message type pipe 
PIPE_READMODE_MESSAGE |   // message-read mode 
PIPE_WAIT,                // blocking mode 
PIPE_UNLIMITED_INSTANCES, // max. instances  
BUFSIZE,                  // output buffer size 
BUFSIZE,                  // input buffer size 
0,                        // client time-out 
NULL);                    // default security attribute 
if (hPipe == INVALID_HANDLE_VALUE)
{
printf ("CreateNamedPipe failed, GLE=%d.n", GetLastError());
return -1;
}
// Wait for the client to connect; if it succeeds, 
// the function returns a nonzero value. If the function
// returns zero, GetLastError returns ERROR_PIPE_CONNECTED. 
BOOL fConnected = ConnectNamedPipe(hPipe, NULL) ?
TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
if (!fConnected)
{
printf ("Error %d connecting named pipen", GetLastError());
return 255;
}
printf ("Client connectedn");
HANDLE hHeap = GetProcessHeap();
char* pchRequest = (char*) HeapAlloc(hHeap, 0, BUFSIZE);
// Loop until done reading
while (1)
{
// Read client requests from the pipe. This simplistic code only allows messages
// up to BUFSIZE characters in length.
DWORD cbBytesRead = 0;
BOOL fSuccess = ReadFile(
hPipe,        // handle to pipe 
pchRequest,    // buffer to receive data 
BUFSIZE,    // size of buffer 
&cbBytesRead, // number of bytes read 
NULL);        // not overlapped I/O 
if (!fSuccess)
break;
// Process the incoming message.
printf("Client Request String:"%s"n", pchRequest);
}
FlushFileBuffers(hPipe);
DisconnectNamedPipe(hPipe);
CloseHandle(hPipe);
HeapFree(hHeap, 0, pchRequest);
}
return 0;
}

要在"发送"模式下运行此操作,请在命令行上指定send。 否则,它将作为服务器运行。 我的服务器永远运行。 用 Ctrl+C 杀死它。

那么你的代码出了什么问题呢? 嗯,大多数情况下,它有点像ANSI和UNICODE字符串的混合体。 您需要对这种事情更加小心,并且还需要适当地计算缓冲区大小。 这一切都在上面的代码中得到了修复,这就是我发布它的原因。 此外,在良好的编程实践方面:

  • 您应该更彻底地检查错误。
  • 如前所述,服务器假设发送给它的字符串是 NUL 终止的,但客户端不会以这种方式发送它(所以我修复了客户端(。
  • 当发送方关闭其管道末端时,服务器需要中断其接收循环。
  • 在本地声明局部变量!(并在适当时将它们作为参数传递。 不要使用不必要的全局变量。
  • 使用 #define _CRT_SECURE_NO_WARNINGS 比显式禁用如果不这样做时收到的警告要好。

我的代码解决了所有这些问题。 呵呵。