2

I need to check certain behavior in process creation events in windows, i need to implement a rule that check the startupinfo structure passed to createprocess api call and extracting the std input/std output handles values for the created process. Then i have to check if this handle is belong to tcp socket or not. Is there any api function that might help me to get any info about the handle number i have (whether it's file handle or socket handle)?

3 Answers3

4

Use GetNamedPipeInfo(s, NULL, NULL, NULL, NULL) to differentiate pipe from socket.

bool is_socket(LPVOID s)
{
  if (GetFileType(s) != FILE_TYPE_PIPE) return false;
  return !GetNamedPipeInfo(s, NULL, NULL, NULL, NULL);
}
POCEH
  • 51
  • 3
1

Building on @RemyLebeau's answer, I thought I would see if I could find a reliable way to distinguish a socket from a pipe (which GetFileType() cannot do), and I came up with the following, which seems to work and has no obvious downside.

The gist of it is that getsockopt() will return WSAENOTSOCK (= 10038) if passed anything that is not a SOCKET. Therefore, this test is sufficient in and of itself, just so long as any handle you pass to it is either a SOCKET or a file or pipe HANDLE. Don't just pass it any old HANDLE (there are all sorts of these), or it might get confused as per @HansPassant's first comment below.

Sample code:

#include <winsock2.h>                   // * before* windows.h (!)
#include <windows.h>
#include <assert.h>
#include <iostream>

int main ()
{
    WSADATA wsa_data;
    int err = WSAStartup (2, &wsa_data);
    assert (err == 0);

    // Pipe

    HANDLE hReadPipe, hWritePipe;
    BOOL ok = CreatePipe (&hReadPipe, &hWritePipe, NULL, 2048);
    assert (ok);

    int opt;
    int optlen = sizeof (opt);
    err = getsockopt ((SOCKET) hReadPipe, IPPROTO_TCP, TCP_NODELAY, (char *) &opt, &optlen);

    if (err)
    {
        DWORD dwErr = GetLastError ();
        std::cout << "Pipe: " << dwErr << std::endl;
    }
    else
        std::cout << "Pipe: OK" << std::endl;

    CloseHandle (hReadPipe);
    CloseHandle (hWritePipe);

    // Socket

    SOCKET skt = WSASocket (AF_INET, SOCK_STREAM, 0, NULL, 0, 0);
    assert (skt != INVALID_SOCKET);

    optlen = sizeof (opt);
    err = getsockopt (skt, IPPROTO_TCP, TCP_NODELAY, (char *) &opt, &optlen);

    if (err)
    {
        DWORD dwErr = GetLastError ();
        std::cout << "Socket: " << dwErr << std::endl;
    }
    else
        std::cout << "Socket: OK" << std::endl;

    closesocket (skt);
    return 0;
}

Output:

Pipe: 10038
Socket: OK

Edit: If you read the comments below you will see there has been some discussion about whether this code can be led to believe that a file or pipe HANDLE is actually a SOCKET. Well, it can't. We can know this because functions like ReadFile() and WriteFile() work equally well on both file / pipe HANDLE's and SOCKET's, and if there were any chance of mistaking one for the other then that would not work.

So, this code is (a) safe, (b) simple, and (c) effective in all situations (including redirected output, which Remy's code will think is a socket). I therefore recommend it. Just make sure you call WSAStartup() before you do anything else.

Thank you to @HansPassant and @eryksun for making significant contributions to this post.

Paul Sanders
  • 24,133
  • 4
  • 26
  • 48
  • This needs a guarantee that a SOCKET value is always distinct from a kernel HANDLE. Pretty hard to come by, good luck :) – Hans Passant Jun 22 '18 at 10:18
  • @HansPassant It does? Could you elaborate? Isn't this, in effect, a requirement for WinSock to work properly anyway, given that all it knows about a socket is the SOCKET value passed in to any particular call, whatever it happens to represent under the hood. – Paul Sanders Jun 22 '18 at 10:34
  • Winsock won't have any trouble recognizing a SOCKET since it allocated it. The failure mode is passing it a HANDLE, allocated by kernel32 instead of winsock, whose value happens to coincide with a SOCKET. That is not going to happen very often, but you need a *never* guarantee. Come to think of it, you might get somewhere by reasoning how Read/WriteFile() could work for a socket. Which is oddball usage, but legal. – Hans Passant Jun 22 '18 at 10:44
  • @HansPassant OK, makes sense, I have updated my post. – Paul Sanders Jun 22 '18 at 11:13
  • 1
    There is no case where a `SOCKET` value conflicts with a File `HANDLE`. They're the same. Even if a socket provider doesn't directly use a kernel File object, it has Winsock create a File object proxy. Otherwise there would be no way you could use a non-IFS `SOCKET` with `ReadFile`, `WriteFile`, `GetFileType`. Winsock and the provider also keep a lot of state in user mode -- more or less depending on the provider, and the `SOCKET` is the index into those per-process data structures. It wouldn't work to inherit or use `DuplicateHandle` with a non-IFS socket; you need `WSADuplicateSocket`. – Eryk Sun Jun 22 '18 at 11:35
  • @eryksun Ah yes! Otherwise, how would `ReadFile()` and `WriteFile()` on a SOCKET work? Likewise, a SOCKET cannot clash with a pipe HANDLE for the same reason. I like it, I will edit my post again once Hans has had a chance to comment. I will ping him. Seems we're back in Kansas again :) – Paul Sanders Jun 22 '18 at 11:38
  • @HansPassant Hans, could you comment on eryksun's comment above please? If I read this right, my method is safe after all. – Paul Sanders Jun 22 '18 at 11:40
  • In theory they could have used tagged handles to route sockets passed to File functions to specialized Winsock versions -- like what they used to do for console handles prior to Windows 8. But there are only 3 bits available for tagging (0b01, 0b10, 0b11). I know 0b11 was for console handles, but I don't know about the other two. Anyway, they didn't do it this way. You won't find special dispatching code for tagged socket handles. – Eryk Sun Jun 22 '18 at 12:02
  • @eryksun Sure, but then the handles would be different and so the problem that Hans describes would never arise in the first place. – Paul Sanders Jun 22 '18 at 12:10
  • That assumes socket functions would bother to look at the tag bits. In this scenario maybe only file functions would check the tag, since they would be tasked to work with both files and sockets. – Eryk Sun Jun 22 '18 at 13:10
  • @eryksun I think we're safe enough, see https://devblogs.microsoft.com/oldnewthing/20080829-00/?p=21033 – Paul Sanders Jun 22 '18 at 13:14
0

Use the GetFileType() function

Retrieves the file type of the specified file.

Syntax

DWORD WINAPI GetFileType( _In_ HANDLE hFile ); 

Parameters

hFile [in]

A handle to the file.

Return value

The function returns one of the following values.

FILE_TYPE_CHAR

The specified file is a character file, typically an LPT device or a console.

FILE_TYPE_DISK

The specified file is a disk file.

FILE_TYPE_PIPE

The specified file is a socket, a named pipe, or an anonymous pipe.

FILE_TYPE_REMOTE

Unused.

FILE_TYPE_UNKNOWN

Either the type of the specified file is unknown, or the function failed

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • 1
    Does `STARTUPINFO::hStdOutput` etc. even accept socket handles? – Jonathan Potter Jun 22 '18 at 06:06
  • @JonathanPotter I would think so, although I've never actually tested it. You can certainly call things like `WriteFile()` on a socket. – Paul Sanders Jun 22 '18 at 06:11
  • Pretty questionable, that socket isn't going to be shutdown cleanly. Note that GetFileType() doesn't help either, it *normally* is a pipe when I/O is redirected. Can't see the difference. – Hans Passant Jun 22 '18 at 08:09
  • @PaulSanders, it's possible that a socket provider doesn't use kernel File objects (i.e. "\Device\Afd") directly, in which case File access (e.g. `WriteFile`) uses a helper File object from [`WPUCreateSocketHandle`](https://msdn.microsoft.com/en-us/library/ms741476). This incurs a performance penalty, so first ensure the socket's protocol info includes `XP1_IFS_HANDLES`. Also, the normal way to share a socket in Windows is via `WSADuplicateSocket`, which duplicates the handle and provides the required `WSAPROTOCOL_INFO` struct. This gets sent to the target process to be passed to `WSASocket`. – Eryk Sun Jun 22 '18 at 09:31
  • @HansPassant OK, so the OP can do it my way then. – Paul Sanders Jun 22 '18 at 10:07
  • @eryksun On behalf of the OP, I'd just like to say ["Toto, I've a feeling we're not in Kansas anymore."](https://en.wiktionary.org/wiki/not_in_Kansas_anymore). – Paul Sanders Jun 22 '18 at 10:09
  • yes, this is possible use socket handle in place hStdXXX in `STARTUPINFO` for new create process. not sure, are this will be full functional (because in user mode saved some additional context with socket), but in general, connect, send data - will be work in child process with this socket – RbMm Jun 22 '18 at 10:13
  • @sherif This has evolved into a long and complex discussion (which I personally have quite enjoyed) but I believe my answer gives you the solution you seek in half a dozen lines of code. Remy's answer will be fooled if you redirect stdin or stdout. But why, pray, do you need to know this? – Paul Sanders Jun 22 '18 at 13:17
  • @PaulSanders i 'm implementing some heuristic rules for anti-malware engine that monitor process behaviors, and thanks for your helpful answers. – Sherif Magdy Jun 22 '18 at 14:14