Advertisment

Inside Netstat

author-image
PCQ Bureau
New Update

The Internet has become a part of our daily lives and so have threats of hacking and intrusion. This threat has of late reached

newer heights wherein hackers have designed two-part Trojans: one part is made to install on the victim’s machine, which gets activated whenever the victim’s system starts up. The other part of the Trojan is used to remotely connect to the victim’s system, and get anything done. This is made possible by the first Trojan installed on the user’s system, that opens a socket port without the user’s knowledge and enables the hacker to connect to this socket port to do the damage.

Advertisment

Thus, it is all the more important to know what socket ports are open on your system, and are connected to which remote systems. To accomplish this, you don’t have to do anything special: Win NT-based systems have a utility called NetStat that does this. In fact, under Win XP, the utility uses some of the OSs undocumented APIs to even name the process that has created the socket port.

Instead of dissecting Netstatit and explaining how it works, we have designed a utility similar to NetStat. Hence, this article will introduce you to a little known set of APIs of the Windows OSs, called the IP Helper APIs, using which we shall design our utility, gNetStat. Consequently, you shall also see how NetStat does, what it does and probably, for the first time ever, see the undocumented APIs used by Win XP’s NetStat, to name the process that

creats.

IP Helper



IP HELPER APIs are present in Win 98 and later OS architectures, in Win NT 4.0 SP4 and later versions. Essentially, these APIs provide a means to manipulate the IP configuration and to retrieve information about the same. For example, if you wish to programmatically allocate/de-allocate IP addresses to/from an adapter, IP HELPER APIs are the way to go. Two API functions that form part of this API set help enumerate details for all socket connections active on a given system; one each for the TCP and UDP connections. Prototyped in IPHLPAPI.H with their import library being IPHLPAPI.LIB, the two APIs are called GetTcpTable and GetUdpTable, and are prototyped as shown below:

Advertisment

DWORD GetTcpTable(



PMIB_TCPTABLE pTcpTable, // buffer for the connection table


PDWORD pdwSize, // size of the buffer


BOOL bOrder // sort the table?


);


DWORD GetUdpTable(


PMIB_UDPTABLE pUdpTable, // buffer for the listener table 


PDWORD pdwSize, // size of buffer


BOOL bOrder // sort the table?


);







The former API is used for retrieving TCP table details, while the latter is used for retrieving UDP table details. In both the APIs, the first parameter is the pointer to the buffer that will contain the respective table details if the API call succeeds. The second parameter is the pointer to the variable that contains the size of the table buffer being sent to the API call. If the buffer is not big enough to hold the entire TCP/UDP table information, then the API call returns

ERROR_INSUFFICIENT_BUFFER, and the required buffer size is returned to the variable pointed to. Finally, the third parameter is a Boolean value, which when set to TRUE, returns the table in the sorted manner. If the API calls are successful, they return

NO_ERROR.

Important here is the type of table buffer being pointed to by the first parameter. For the GetTcpTable call, the table type is defined by the MIB_TCPTABLE structure, which is declared as shown below:

Advertisment

typedef struct _MIB_TCPTABLE {



DWORD dwNumEntries; // number of entries in the table 


MIB_TCPROW table; // array of TCP connections 


} MIB_TCPTABLE, *PMIB_TCPTABLE;

Likewise, for GetUdpTable, the table type is defined by the MIB_UDPTABLE structure, which has a similar declaration, as shown below:

typedef struct _MIB_UDPTABLE {



DWORD dwNumEntries; // number of entries in the table


MIB_UDPROW table; // table of MIB_UDPROW structs


} MIB_UDPTABLE, *PMIB_UDPTABLE;

Advertisment

Though the two structures may seem identical, they are not. Hence, the stress on similar in the lines above. While the first parameter, dwNumEnteries, means the same thing in both the calls, (namely the number of rows present in the table after the call has returned), it is the second parameter, which is an array of the rows returned and ranging from 0 to dwNumEntries-1, that makes the difference. In the MIB_TCPTABLE, the second parameter is an array of the MIB_TCPROW structure that is declared as shown below:

typedef struct _MIB_TCPROW {



DWORD dwState; // state of the connection


DWORD dwLocalAddr; // address on local computer


DWORD dwLocalPort; // port number on local computer


DWORD dwRemoteAddr; // address on remote computer


DWORD dwRemotePort; // port number on remote computer


} MIB_TCPROW, *PMIB_TCPROW;




Since TCP is a connection-oriented protocol (refer to Comer or Stevens for details), the first parameter defines the state of the connection in question. The second and the fourth parameters contain the IP address of the local system and the remote system respectively, if a connection is established between them. If no connection is established, dwRemoteAddr will have a value of zero. If dwLocalAddr is zero, then that means that connection can be accepted on any of the associated local IP addresses. The dwLocalPort and dwRemotePort variables contain the port number of the local and remote systems, between which a connection is established. If dwRemoteAddr is zero, then dwRemotePort is also zero.

Advertisment

On the other hand, second parameter in the MIB_UDPTABLE structure is of the type of MIB_UDPROW structure that has the following declaration:

typedef struct _MIB_UDPROW {



DWORD dwLocalAddr; // IP address on local computer


DWORD dwLocalPort; // port number on local computer


} MIB_UDPROW, *PMIB_UDPROW;

Since UDP is connectionless in nature, there is no concept of connection state, and hence, the remote system. Thus, this structure has only two parameters: dwLocalAddr and dwLocalPort, which contain the IP address and port of the active UDP socket.

Advertisment

Now that we have all the basics clear regarding IP HELPER APIs, especially the ones which are going to be used to design gNetStat, let’s move onto designing

gNetStat.

Dissecting gNetStat



Before we understand how gNetStat (similarly NetStat also) works, let’s understand the goals of

gNetStart:

  • It will be console based like NetStat.
  • It can display the TCP and UDP tables, individually or together, depending upon the command line arguments passed to the program.
    • To view only TCP tables, the command line parameter is /tcp
    • To view only UDP tables, the command line parameter is /udp
    • To view both TCP and UDP tables together, the command line parameter is /all
  • Command-line parameters are case-insensitive.
Advertisment

Now that our design goals are clear, let’s have a look at the source of gNetStat, and see how it implements our goals and NetStat like functionality (see source code on CD for this).

Let’s begin by tracing functionality from the main function. Firstly, it is to be verified if any command line parameters are given. If not, the user is then displayed the usage of the application and the program exits. 

Next, the working variables are then declared, followed by a check of the command line parameter passed to the application. The variable Action has its value set. We use the stricmp function to compare the command line parameter passed by the user with the valid values, since one of the goals of the application is to be case-insensitive regarding command line parameters passed. If an invalid parameter is passed, the user is displayed appropriate message, and the program exits.

Next, the application checks if it has been asked to retrieve the TCP table. If so, we proceed to the allocate memory for the TCP table buffer, the pointer to which is stored in the variable ptblTCP. If memory allocation is unsuccessful, error message is shown to the user and the application exits.

However, if memory allocation is successful, we proceed to store the size of the table buffer being pointed to in the sizeTCP variable. This done, we call the GetTcpTable API. If the API returns with ERROR_INSUFFICIENT_BUFFER, we try to allocate as much memory as is required by the API call, by using the value returned by the call in the sizeTCP variable, a pointer which was passed to the API call. We free the currently allocated memory and attempt re-allocation. Note that numbers of TCP table buffers required are calculated as shown below:

sizeTCP/sizeof(MIB_TCPTABLE)

If this memory allocation also fails, the application displays the error message and exits. If, however, the allocation is successful, GetTcpTable is called again. This loop continues until GetTcpTable returns NO_ERROR, or any error other than

ERROR_INSUFFICIENT_BUFFER.

Once the GetTcpTable call is successful and the table is returned, we proceed to display the table details. The dwState variable can have various values, which are parsed by the GetState function, and an appropriate message string is returned for display. Likewise, the IP addresses, which the table returns as a DWORD, are converted to the dotted form by the GetIPAddress function, which returns the dotted IP address as a string to the caller. Finally, the remote IP address and remote port are displayed only if the remote IP address is non-zero. The variable iCount is used as an index to iterate through the array of MIB_TCPROW structures. Lastly, the allocated memory is freed. Likewise, the application proceeds to display the UDP table if it has been asked to do so. The logic to display the UDP table is the same as that of displaying the TCP table. The only difference is that the UDP table display shows only the local IP address and port.

The Time-wait-state is a consequence of a disrupted SMTP connection with my system’s SMTP server, while the Established Connections are a result of the connection establishment with the

webserver.

Documenting the undocumented



Earlier, we had mentioned that the NetStat utility available under Win XP not only shows the respective protocol tables, but also goes out to associate a running process with each open socket. This is made possible by the use of two undocumented IP HELPER APIs which are not prototyped, nor declared any place, and don’t even have their import libraries, (at least until the November 2001 version of the platform SDK. However, they are present, and here are the two API calls:

AllocateAndGetUdpExTableFromStack(PMIB_UDPTABLE_EX*,BOOL,HANDLE,DWORD,DWORD);



AllocateAndGetTcpExTableFromStack(PMIB_TCPTABLE_EX*,BOOL,HANDLE,DWORD,DWORD);

These undocumented APIs are present in IPHLPAPI.DLL, a core IP HELPER DLL. To use them, you will have to dynamically load them at runtime. The last two parameters have the values of 0 and 2 respectively, while the HANDLE parameter is the return value of the GetProcessHeap API call. The BOOL parameter is used for sorting, and is set to TRUE if the results are expected to be sorted.

Finally, we come to the main parameter, which is the pointer to the respective protocol’s table, the first parameter. They are declared as shown below:

typedef struct _MIB_TCPTABLE_EX



{


DWORD dwNumEntries;


MIB_TCPROW_EX table;


} MIB_TCPTABLE_EX, *PMIB_TCPTABLE_EX;


typedef struct _MIB_UDPTABLE_EX


{


DWORD dwNumEntries;


MIB_UDPROW_EX table;


} MIB_UDPTABLE_EX, *PMIB_UDPTABLE_EX;







If you notice, apart from the structure having an _EX suffixed to its name, the second member of these structures have also changed. These _EX row structures contain the process ID, and are declared as shown below:

typedef struct _MIB_TCPROW_EX



{


DWORD dwState; // MIB_TCP_STATE_*


DWORD dwLocalAddr;


DWORD dwLocalPort;


DWORD dwRemoteAddr;


DWORD dwRemotePort;


DWORD dwProcessId;


} MIB_TCPROW_EX, *PMIB_TCPROW_EX;






typedef struct _MIB_UDPROW_EX



{


DWORD dwLocalAddr;


DWORD dwLocalPort;


DWORD dwProcessId;


} MIB_UDPROW_EX, *PMIB_UDPROW_EX;



A new member has been added to the original structures, and contains the process ID. This process ID can then be mapped to its appropriate process executable using the ToolHelp API.

Not only have we seen how a utility like NetStat works, but also designed one. We also took a step into the undocumented and saw how, under Win XP, process IDs can be associated with open socket ports. 

Kumar Gaurav Khanna runs www.wintoolzone.com

Advertisment