RichardSharpe.com

Generously hosted by FreeBSD Systems
Ethereal Stuff
Here you can find some info about extending Ethereal.


 
Writing a Dissector
OK, so you want to write a dissector? A good place to start is to read doc/README.developer in the ethereal source directory.

While there are a number of include files you must add and some other infrastructure you need, you can get most of the information from an existing dissector, or from README.developer mentioned above.

Here, I will illustrate the code you must include by looking at the dissector for the POP protocol.

All dissectors go in a file called packet-<protocol-name>.c in the main ethereal source directory. You will find the dissector for the POP protocol in packet-pop.c.

Before getting into the code proper, the first thing that you should realize is that Ethereal is going to call your dissector under a number of circumstances, and for different reasons.

To understand some of these, look at the following screen shot of Ethereal:

Ethereal puts up three panes:

  1. The summary pane. This is the top pane and shows summary information about all the packets that fit into the view. It contains one column for each piece of information that you have selected to display.
  2. The packet detail pane. This is the middle pane and shows each layer that Ethereal understands with a break-out symbol next to it. You can explore each layer by clicking on the symbol on the left hand side of each layer.
  3. The byte view. This is the bottom pane, and it displays the raw bytes in each packet, synchronized with your selection actions.

Now, in some sense, all of the information displayed in the three panes is handled by a dissector of some sort. Ethereal reads through the frames in your capture and hands each of them to the Frame dissector which can be found in packet-frame.c. This dissector examines the frame, performs some basic functions, and calls a dissector for the contents of the frame. In the example shown above, that is the Etherenet-II dissector, which can be found in packet-ethertype.c.

Generally you will have a single protocol dissector to a file, however, you can have more than one. Each dissector will have an entry point of the following form (taken from packet-pop.c):

static void
dissect_pop(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
    

That is, each dissector should be called dissect_<proto>, and it will be passed three parameters:

  1. A tvbuf_t *tvb. This is a TVB (testy virtual buffer). This contains the portion of the packet that the dissector is responsible for dissecting.
  2. A packet_info *pinfo. This contains a bunch of information that has been accumulated during the dissection of the packet. It contains things like the source and destination IP address, and so on.
  3. A proto_tree *tree. This contains the protocol tree that your code will add information to if your dissector was called to add info to the protocol tree.

Your dissector will be called in two different ways:

  1. When Ethereal is adding packets to the summary pane. Here, your dissector will be called with the tree parameter set to NULL, as it is not building a protocol tree at that time and only needs your dissector to add summary information.
  2. When Ethereal is fully dissecting a packet and building a protocol tree. This happens for a number of reasons, one of which is when you click on a packet in the summary pane. Here, tree will be non-NULL, and you need to add fields and sub-trees to it.

So, here is an example of how these are handled in the POP dissector:

if (check_col(pinfo->cinfo, COL_PROTOCOL))
       col_set_str(pinfo->cinfo, COL_PROTOCOL, "POP");
    

This adds the protocol name POP to the protocol column in the summary pane.

The next few things that the POP dissector does are:

        linelen = tvb_find_line_end(tvb, offset, -1, &next_offset, FALSE);
        line = tvb_get_ptr(tvb, offset, linelen);
                                                                                
        if (pinfo->match_port == pinfo->destport) {
                is_request = TRUE;
                is_continuation = FALSE;
        } else {
                is_request = FALSE;
                is_continuation = response_is_continuation(line);
        }
    

The above determines whether or not the packet we have been called to dissect is a request or response, and if it is not a request, whether or not it is a continuation.

Some of this is a bit clunky now that Ethereal knows how to desegment at the TCP layer, however, since POP uses CRLF to delimit messages, desegmentation is not so useful here perhaps.

Now we get to some of the meat of the dissection. Here is the next part of packet-pop.c:
        if (tree) {
                ti = proto_tree_add_item(tree, proto_pop, tvb, offset, -1,
                    FALSE);
                pop_tree = proto_item_add_subtree(ti, ett_pop);
    

Here, we check if we have been asked to build the protocol tree shown in the middle pane, and if so, we start building it. First, we add POP as an item and then add a sub-tree for the POP decode items.

Next, we check if we are dealing with a continuation:
                if (is_continuation) {
                        /*
                         * Put the whole packet into the tree as data.
                         */
                        call_dissector(data_handle,tvb, pinfo, pop_tree);
                        return;
                }
    

If we are dealing with a continuation, we simply insert the packet data as data into the tree and return. We do that by directly calling the dissector referred to by data_handle. We will look at how this dissector is discovered later.

Complete this ...

Time Sequence Graphs
Ethereal can produce time sequence graphs for TCP connections. Simply a packet from the TCP connection you are interested in in the summary window and then select Tools->TCP Stream Analysis and then select one of the graphs you are interested in. I find that the tcptrace style graph produces the most useful information.

Here is an example from a trace I was looking at recently:

This graph shows that the TCP connection stalls regularly. Everyone of those horizontal lines is where sequence numbers did not advance for a period of time.

This is an indication that perhaps the window size is not large enough, and the window is filling up causing the sender to have to stop and wait before sending any more.

However, as it turns out, the problem was two-fold:

  1. This was a trace of NFS traffic over TCP from FreeBSD to a Linux client. Linux was holding onto the ack for the last segment of the 32-kB block FreeBSD had sent. It was doing so incase there was traffic in the other direction, or another segment arrived and it could ack two segments with one ack.
  2. The FreeBSD NFS code used routines that would not put data into the socket buffer until there was enough available to put all the data in in one go, and SO_SNDBUF was clamped at 32kB plus a bit (NFS_MAXSIZE + 4 or something).

    Since there was one outstanding segment, there was not enough space available, and the server had to wait for the delayed ACK to arrive and clear out that last segment before it could put in the next 32kB (plus a bit) of the response.

The following is possibly related to the above.

It suggests that if you want to keep your actual bandwidth up near the available bandwidth, you need to ensure that the window size is correct and that the sender's buffer space is adequate.

This diagram shows what happens when delayed-ack is in use.

The diagram above adds sequence numbers to each segment. Of course, the sequence numbers should be in multiples of 1460 (or 1448 etc), but for simplicity, I have used 1, 2, 3, etc.

Some Fancy Features
If you select Tools-->Statistics on a recent version of Ethereal, as shown below, you can gain access to some interesting statistics.

Now, if you have a capture of SMB traffic, you can sellect SMB from the Service Response Time menu, and you will get a display like that shown below, which was produced from a trace of NetBench traffic.

Do any of the commercial packet capture tools do this?

You can also get similar information from tethereal. The following command:

    tethereal -r some-file.cap -R "not frame" -z smb,rtt
    
produces the following output (Note, the -R "not frame" prevents tethereal from displaying each frame):

   ===================================================================
   SMB RTT Statistics:
   Filter:
   Commands                   Calls   Min RTT   Max RTT   Avg RTT
   Close                        290   0.00002   0.00647   0.00016
   Flush                         52   0.01910   0.28100   0.06226
   Delete                        39   0.00034   0.00189   0.00065
   Rename                        14   0.00045   0.01539   0.00168
   Locking AndX                 147   0.00002   0.00365   0.00009
   Echo                           1   0.00017   0.00017   0.00017
   Read AndX                   2490   0.00001   0.10880   0.00027
   Write AndX                   772   0.00001   0.04795   0.00022
   NT Create AndX               351   0.00009   0.10797   0.00065
 
   Transaction2 Commands      Calls   Min RTT   Max RTT   Avg RTT
   FIND_FIRST2                  138   0.00019   0.01057   0.00048
   QUERY_FS_INFO                 54   0.00004   0.00057   0.00011
   QUERY_PATH_INFO              201   0.00010   0.00573   0.00026
   QUERY_FILE_INFO              427   0.00003   0.00790   0.00016
   SET_FILE_INFO                 42   0.00018   0.02355   0.00088
 
   NT Transaction Commands    Calls   Min RTT   Max RTT   Avg RTT
   ===================================================================
    

Dissecting MS RPC (NDR)
While there are plenty of examples to help people write dissectors for a number of protocols, and there is example code for dissectecting MS RPCs (eg, packet-dcerpc-svrscv.c), the code is difficult to understand without a lot of other knowledge.

Here I will try to give you an idea of how to create a dissector for an RPC (and not just an MS RPC, but any DCE RPC). Of course, if you have the IDL for the RPC, you will be a long way ahead, but with MS RPC, you usually don't have the IDL.

NOTE. It is not my goal in this little tutorial to teach you how to write an Ethereal dissector. You should check the Ethereal source and the documentation that comes with it for info on that. Also, this page might be incomplete, and you will not be able to just throw together the code fragments here to get a working dissector. However, if you understand much of what I am saying here, you will know how to create the rest of what is needed.

To really understand this material, you should firstly review the Network Data Representation (NDR) spec: Network Data Representation.

Here we will look at how to create a dissector once you have a capture of the request and reply generated by the RPC. However, you will also need additional information, like the function prototype to get some idea of the actual parameters. Here, MSDN can give you a great deal of help. It can also help you to create a small program in Visual C++ that will generate traffic to test your dissector.

We will look at two RPCs: NetWkstaGetInfo and NetWkstaUserEnum, because they illustrate the case where relatively simple parameters are passed (at least, no arrays of variant structures), but also the case where arrays of variant structures are passed, which Microsoft seems to love.

The first problem you will face is in generating some RPC requests and replies you can use to figure out what the RPC arguments look like and so you can test that your dissector works. Here, MSDN and Visual C++ are very useful. Using the information in MSDN you can write a simple Visual C++ program that will generate RPC requests and replies.

The following is such a program that generates a few RPCs:

// NetWKSTA.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include 
#include 
#include 
#include 

int main(int argc, char* argv[])
{
   LPWKSTA_USER_INFO_0 pBuf = NULL;
   LPWKSTA_USER_INFO_0 pTmpBuf;
   LPWKSTA_INFO_102 pInfoBuf100 = NULL;
   LPWKSTA_INFO_502 pInfoBuf502 = NULL;
   WKSTA_INFO_502 wi;
   DWORD dwLevel = 0;
   DWORD dwMaxLen = -1;
   DWORD dwEntriesRead = 0;
   DWORD dwTotalEntries = 0;
   DWORD dwResumeHandle = 0;
   DWORD i;
   DWORD dwTotalCount = 0;
   NET_API_STATUS nStatus;
   LPTSTR pszServerName = NULL;

   if (argc > 2)
   {
      fprintf(stderr, "Usage: %s [\\\\ServerName]\n", argv[0]);
      return 1;
   }
   // The server is not the default local computer.
   //
   if (argc == 2)
      MultiByteToWideChar(CP_ACP, 0, argv[1], strlen(argv[1]) + 1,
			  wszServerName, sizeof(wszServerName)/sizeof(WCHAR));
   fprintf(stdout, "\nSending RPCs to %s:\n", argv[1]);
   //
   // Call the NetWkstaGetInfo function, specifying level 100.
   //
   dwLevel = 100;
   nStatus = NetWkstaGetInfo(pszServerName,
                             dwLevel,
                             (LPBYTE *)&pInfoBuf100);
   // Next, call it at level 101
   dwLevel = 101;
   nStatus = NetWkstaGetInfo(pszServerName,
                             dwLevel,
                             (LPBYTE *)&pInfoBuf100);
   // Next, call it at level 102
   dwLevel = 102;
   nStatus = NetWkstaGetInfo(pszServerName,
                             dwLevel,
                             (LPBYTE *)&pInfoBuf100);

   dwLevel = 0;
   nStatus = NetWkstaUserEnum(pszServerName,
                              dwLevel,
                              (LPBYTE*)&pBuf,
                              dwMaxLen,
                              &dwEntriesRead,
                              &dwTotalEntries,
                              &dwResumeHandle);	
   if ((nStatus != NERR_Success) && (nStatus != ERROR_MORE_DATA))
      fprintf(stderr, "A system error has occurred: %d\n", nStatus);

   return 0;
}
     

Simply build the project under Visual C++ and run it from a CMD prompt, and have Ethereal running and capturing packets at the same time.

Once you have a capture of the traffic, we can go to work.

First, you need the basic infrastructure for your dissector.

Apart from the normal stuff you need for a dissector, you need a table of OpNums, names and dissection routines for the RPC calls in the interface you are interested in. Such a table would be declared something like:
static dcerpc_sub_dissector dcerpc_wkssvc_dissectors[] = {
        { WKS_NetWkstaGetInfo, "NetWkstaGetInfo",
          wkssvc_dissect_netwkstagetinfo_rqst,
          wkssvc_dissect_netwkstagetinfo_reply},
        { WKS_NetWkstaSetInfo, "NetWkstaSetInfo",
          wkssvc_dissect_netwkstasetinfo_rqst,
          wkssvc_dissect_netwkstasetinfo_reply},
        { WKS_NetWkstaEnumUsers, "NetWkstaUserEnum",
          wkssvc_dissect_netwkstaenumusers_rqst,
          wkssvc_dissect_netwkstaenumusers_reply},
        { WKS_NetWkstaUnkn_003, "NetWkstaUnknown_003",
          NULL,
          NULL,},
        { WKS_NetWkstaUnkn_004, "NetWkstaUnknown_004",
          NULL,
          NULL},
        { WKS_NetWkstaTransportEnum, "NetWkstaTransportEnum",
          wkssvc_dissect_netwkstatransportenum_rqst,
          wkssvc_dissect_netwkstatransportenum_reply},
        {0, NULL, NULL,  NULL }
};
     

As you can see, this table specifies the OpNum (eg, WKS_NetWkstaGetInfo, which will probably be declared in an include file), the name of the routine (eg NetWkstaGetInfo), and a dissection routine for each of the request and reply. If either is missing, the body of that request or reply will not be dissected.

You also need to define the UUID for the interface and register a UUID-based handoff table using the sub-dissector table above.

Here is how you specify the UUID (but you need to know the UUID):

static e_uuid_t uuid_dcerpc_wkssvc = {
        0x6bffd098, 0xa112, 0x3610,
        { 0x98, 0x33, 0x46, 0xc3, 0xf8, 0x7e, 0x34, 0x5a }
};
     

The following small segment of the middle pane of an Ethereal display of RPC packets shows what the UUID looks like.

The UUID for the interface will show up in a similar way even if Ethereal knows nothing about the RPC interface you want to code dissectors for.

Then you must create a handoff routine like that below.

void
proto_reg_handoff_dcerpc_wkssvc(void)
{
        /* Register protocol as dcerpc */
  
        dcerpc_init_uuid(proto_dcerpc_wkssvc, ett_dcerpc_wkssvc,
                         &uuid_dcerpc_wkssvc, ver_dcerpc_wkssvc,
                         dcerpc_wkssvc_dissectors, -1);
}

     

Then you can start writing the dissectors.

Let's first look at NetWkstaGetInfo. If you look at a trace generated from the above program, you will see that the opnum associated with the first DCERPC request is 0. This allows you to fill in the table above with:
        { 0, "NetWkstaGetInfo",
          wkssvc_dissect_netwkstagetinfo_rqst,
          wkssvc_dissect_netwkstagetinfo_reply},
    

Although you might find it better to
#define WKS_NetWkstaGetInfo 0
    

And use the symbol.

The next step is to look at the stub data that Ethereal shows and use that, as well as information from MSDN about the prototype for NetWkstaGetInfo to dissect the requests and replies.

Here is what the stub data for a NetWkstaGetInfo request looks like:
00a0                    5c 50  42 00 09 00 00 00 00 00   ......\P B.......
00b0  00 00 09 00 00 00 5c 00  5c 00 4e 00 54 00 34 00   ......\. \.N.T.4.
00c0  50 00 44 00 43 00 00 00  c9 11 64 00 00 00         P.D.C... É.d...  
    

If you read the NDR document referred to above, you will note that arguments are marshalled in units of 32 bits. However, what are we to make of the stub data above. Well, firstly, we need to know what the function prototype looks like, and then guess, by looking at the stub data and other dissectors what we have.

The function prototype for NetWkstaGetInfo is roughly:
    NET_API_STATUS NetWkstaGetInfo(
         LPWSTR Server,
         DWORD  Level, 
         LPBYTE *bufptr
    );
    

The MSDN entry goes on to explain that Server and Level are [in] parameters, while bufptr is an [out] parameter. Thus, we would expect to only see Server and Level in the request, and whatever bufptr points to in the reply.

Now, Microsoft's stub compiler seems to regard an LPWSTR as a pointer to a varying string, which explains this much of the stub data:
00a0                    5c 50  42 00 09 00 00 00 00 00   ......\P B.......
00b0  00 00 09 00 00 00 5c 00  5c 00 4e 00 54 00 34 00   ......\. \.N.T.4.
00c0  50 00 44 00 43 00 00 00                            P.D.C...   
    

Those first four bytes (5c504200) are simply a REF (incase there are several pointers to the same data), the next twelve bytes are the header associated with a varying length string (Max Len, offset, actual length).

You will notice that the length of the string is 9 WCHARs (or UNICODE characters), and consists of \\NT4PDC. The last pair of 00 bytes is actually the null terminator.

This leaves the last six bytes of the stub data:
00c0                           c9 11 64 00 00 00                  É.d...  
    

The first two bytes are simply alignment bytes (where the alignment is 32-bit alignment and is to the beginning of the stub data). The remaining four bytes are the Level parameter.

MSDN suggests that the level parameter can take the values 100, 101, or 102. Each of these results in more data being returned from the call.

The value contained in the last four bytes is 0x64, or 100. Thus it seems pretty clear that we have a DWORD parameter directly encoded in the stub data stream.

Armed with this info, the dissector for requests should look like this:
static int
wkssvc_dissect_netwkstagetinfo_rqst(tvbuff_t *tvb, int offset,
                                    packet_info *pinfo, proto_tree *tree,
                                    char *drep)
{
        offset = dissect_ndr_str_pointer_item(tvb, offset, pinfo, tree, drep,
                                              NDR_POINTER_UNIQUE, "Server",
                                              hf_wkssvc_server, 0);

        offset = dissect_ndr_uint32(tvb, offset, pinfo, tree, drep,
                                    hf_wkssvc_info_level, 0);

        return offset;

}

    

Of course, you will have to create entries for those hf_wkssvc_* items above, but looking at other Ethereal dissectors will give you an idea of what is needed.

So how about the reply? Well, here is the stub data:
0080                                 64 00 00 00 00 fe   ..\..... ..d....þ
0090  17 00 f4 01 00 00 2e fe  17 00 1a fe 17 00 04 00   ..ô....þ ...þ....
00a0  00 00 00 00 00 00 07 00  00 00 00 00 00 00 07 00   ........ ........
00b0  00 00 4e 00 54 00 34 00  50 00 44 00 43 00 00 00   ..N.T.4. P.D.C...
00c0  64 00 0a 00 00 00 00 00  00 00 0a 00 00 00 54 00   d....... ......T.
00d0  49 00 47 00 45 00 52 00  54 00 45 00 41 00 4d 00   I.G.E.R. T.E.A.M.
00e0  00 00 00 00 00 00                                  ...... 
    

The first field appears to be the Level parameter again, followed by a bunch of stuff and then some interesting looking strings: NT4PDC and TIGERTEAM, which happen to be the NetBIOS name of the server the request was sent to and the domain that it lives in.

However, to understand this stub data, you have to realize that bufptr referred to in the function prototype above is actually a pointer to a UNION type, with one structure for each different Level parameter that can be used. It will be defined something like the following:
typedef union {
  struct WKSTA_INFO_100 info100;
  struct WKSTA_INFO_101 info101;
  struct WKSTA_INFO_102 info102;
} wksta_info_union;
    

In order for the receiver to be able to distinguish what structure was returned without retaining state in the stub, this info is returned in stub data. A look at the NDR document mentioned above will also tell us that a union is marshalled as the discriminator followed by the data for the actual structure that it contains.

So, we need to write a dissector for the wksta_info_union before we can write a dissector for the reply. However, that will simply consist of dissecting the discriminator, which is the level byte, and then using that in a switch statement to dissect the actual info returned. Here is an example:
static int
wkssvc_dissect_WKS_GETINFO_UNION(tvbuff_t *tvb, int offset,
                                 packet_info *pinfo, proto_tree *tree,
                                 char *drep)
{
        guint32 level;

        ALIGN_TO_4_BYTES;

        offset = dissect_ndr_uint32(tvb, offset, pinfo, tree, drep, 
                                    hf_wkssvc_info_level, &level);

        switch(level){
        case 100:
                offset = dissect_ndr_pointer(tvb, offset, pinfo, tree, drep,
                        wkssvc_dissect_WKS_INFO_100,
                        NDR_POINTER_UNIQUE, "WKS_INFO_100:", -1);
                break;

        case 101:
                offset = dissect_ndr_pointer(tvb, offset, pinfo, tree, drep,
                        wkssvc_dissect_WKS_INFO_101,
                        NDR_POINTER_UNIQUE, "WKS_INFO_101:", -1);
                break;

        case 102:
                offset = dissect_ndr_pointer(tvb, offset, pinfo, tree, drep,
                        wkssvc_dissect_WKS_INFO_102,
                        NDR_POINTER_UNIQUE, "WKS_INFO_102:", -1);
                break;

        }

        return offset;

}
    

Now, each of the routines to dissect the WKS_INFO_100, WKS_INFO_101, or WKS_INFO_102 structures are going to be similar. Here is the definition of wkssvc_dissect_WKS_INFO_100:
static int
wkssvc_dissect_WKS_INFO_100(tvbuff_t *tvb, int offset,
                            packet_info *pinfo, proto_tree *tree,
                            char *drep)
{
        offset = dissect_ndr_uint32(tvb, offset, pinfo, tree, drep,
                        hf_wkssvc_platform_id, NULL);

        offset = dissect_ndr_str_pointer_item(tvb, offset, pinfo, tree, drep,
                        NDR_POINTER_UNIQUE, "Server", hf_wkssvc_server, 0);

        offset = dissect_ndr_str_pointer_item(tvb, offset, pinfo, tree, drep,
                        NDR_POINTER_UNIQUE, "Net Group", hf_wkssvc_net_group, 0);

        offset = dissect_ndr_uint32(tvb, offset, pinfo, tree, drep,
                        hf_wkssvc_ver_major, NULL);

        offset = dissect_ndr_uint32(tvb, offset, pinfo, tree, drep,
                        hf_wkssvc_ver_minor, NULL);

        return offset;
}
    

The routines for the other structures will be similar. Refer to the structure defininitions to see how they should be written.

Now we can dissect the reply. Another thing to realize is that in addition to the info structure, the return code from NetWkstaGetInfo will be encoded as the last four-byte quantity in the stub data stream.

Here is what the dissector for the reply look like:
static int wkssvc_dissect_netwkstagetinfo_reply(tvbuff_t *tvb, int offset,
                                                packet_info *pinfo,
                                                proto_tree *tree,
                                                char *drep)
{
        offset = dissect_ndr_pointer(tvb, offset, pinfo, tree, drep,
                        wkssvc_dissect_WKS_GETINFO_UNION,
                        NDR_POINTER_REF, "Server Info", -1);

        offset = dissect_doserror(tvb, offset, pinfo, tree, drep,
                        hf_wkssvc_rc, NULL);

        return offset;
}
    

As you can see, it dissects the WKS_GETINFO_UNION first, by calling dissect_ndr_pointer, and then dissects the error code that was returned.

OK, now we can look at a more complex function like NetWkstaUserEnum, where an array of structures is returned, and someone in MS seems to have created slightly incorrect IDL, in that an [out] parameter is sent over in the request.

If we look at the function prototype for NetWkstaUserEnum, we see it is something like:
    NET_API_STATUS NetWkstaUserEnum(
      LPWSTR servername,
      DWORD level,
      LPBYTE *bufptr,
      DWORD prefmaxlen,
      LPDWORD entriesread,
      LPDWORD totalentries,
      LPDWORD resumehandle
    );
    

Now, MSDN says that bufptr is a pointer to an array of USER_INFO structures returned by the call. Since level can take one of several values, bufptr will point to an array of union types. MSDN also says that servername, level, and prefmaxlen are all [in] parameters, while bufptr, entriesread, totalentries are [out] parameters, and resumehandle is an [in, out] parameter.

Based on that information, let's look at the stub parameters and see if we can recognize any of them:
00a0                    5c 50  42 00 09 00 00 00 00 00   ......\P B.......
00b0  00 00 09 00 00 00 5c 00  5c 00 4e 00 54 00 34 00   ......\. \.N.T.4.
00c0  50 00 44 00 43 00 00 00  c9 11 01 00 00 00 01 00   P.D.C... É.......
00d0  00 00 30 fe 12 00 00 00  00 00 00 00 00 00 ff ff   ..0þ.... ......ÿÿ
00e0  ff ff d0 fe 12 00 00 00  00 00                     ÿÿÐþ.... ..
    

It is pretty clear that the first four bytes (5c 50 42 00) are a ref parameter and they refer to the varying length string that follows, which has the UNICODE value \\NT4PDC. That consumes the following bytes:
00a0                    5c 50  42 00 09 00 00 00 00 00   ......\P B.......
00b0  00 00 09 00 00 00 5c 00  5c 00 4e 00 54 00 34 00   ......\. \.N.T.4.
00c0  50 00 44 00 43 00 00 00                            P.D.C... 
    

However, the next bit looks troublesome. There are two bytes of alignment again (c9 11) but then there are far too many fields for level, prefmaxlen and resumehandle.

The field following the filler is clearly the level parameter, and then there are some funny fields, but towards the end we have:
00d0                                             ff ff                  ÿÿ
00e0  ff ff d0 fe 12 00 00 00  00 00                     ÿÿÐþ.... ..
    

This looks like prefmaxlen, which had the value -1, followed by resumehandle, which is a pointer to a DWORD. The question is, what are the bytes before these last two fields?

What they are will become obvious later, so let's proceed to dealing with the NetWkstaUserEnum reply.

Here is an example of what the stub data looks like:
0080                                 00 00 00 00 00 00   ..T..... ........
0090  00 00 88 29 18 00 01 00  00 00 88 05 17 00 01 00   ...).... ........
00a0  00 00 8c 05 17 00 0e 00  00 00 00 00 00 00 0e 00   ........ ........
00b0  00 00 41 00 64 00 6d 00  69 00 6e 00 69 00 73 00   ..A.d.m. i.n.i.s.
00c0  74 00 72 00 61 00 74 00  6f 00 72 00 00 00 01 00   t.r.a.t. o.r.....
00d0  00 00 98 29 18 00 00 00  00 00 00 00 00 00         ...).... ...... 
    

While these bytes look a bit difficult to understand, the first parameter is the bufptr, which is a union type consisting of arrays of USER_INFO_0 or USER_INFO_1 structures. As specified by the NDR spec, a non-encapsulated union will contain the discriminant marshalled twice, once as the field and once as the first part of the union representation. Following that looks like a referent (88 05 17 00), and then the count of elements in the array (one in this case), and then another referent (8c 05 17 00) to the string that follows (this is actually the USER_INFO_0 structure).

After the USER_INFO_0 structure, we find the totalentries field (01 00 00 00), the resume handle (98 29 18 00 00 00 00 00) and the return code of all zeros.

In order to dissect this, we have to know what a USER_INFO_0 and USER_INFO_1 looks like. However, MSDN helps here, and we arrive at the following code:
tatic int
wkssvc_dissect_USER_INFO_0(tvbuff_t *tvb, int offset,
                                     packet_info *pinfo, proto_tree *tree,
                                     char *drep)
{
        offset = dissect_ndr_str_pointer_item(tvb, offset, pinfo, tree, drep,
                        NDR_POINTER_UNIQUE, "User Name",
                        hf_wkssvc_user_name, 0);

        return offset;
}

static int
wkssvc_dissect_USER_INFO_1(tvbuff_t *tvb, int offset,
                                     packet_info *pinfo, proto_tree *tree,
                                     char *drep)
{
        offset = dissect_ndr_str_pointer_item(tvb, offset, pinfo, tree, drep,
                                              NDR_POINTER_UNIQUE, "User Name",
                                              hf_wkssvc_user_name, 0);

        offset = dissect_ndr_str_pointer_item(tvb, offset, pinfo, tree, drep,
                        NDR_POINTER_UNIQUE, "Logon Domain",
                                              hf_wkssvc_logon_domain, 0);

        offset = dissect_ndr_str_pointer_item(tvb, offset, pinfo, tree, drep,
                        NDR_POINTER_UNIQUE, "Other Domains",
                                              hf_wkssvc_other_domains, 0);

        offset = dissect_ndr_str_pointer_item(tvb, offset, pinfo, tree, drep,
                        NDR_POINTER_UNIQUE, "Logon Server",
                                              hf_wkssvc_logon_server, 0);


        return offset;

}
    

It turns out that this array of USER_INFO_0 or USER_INFO_1 structures will be marshalled in a container that provides the number of entries returned (entriesread) followed by the actual array. Here is the code to dissect them:
static int
wkssvc_dissect_USER_INFO_0_array(tvbuff_t *tvb, int offset,
                                     packet_info *pinfo, proto_tree *tree,
                                     char *drep)
{
        offset = dissect_ndr_ucarray(tvb, offset, pinfo, tree, drep,
                        wkssvc_dissect_USER_INFO_0);

        return offset;
}

static int
wkssvc_dissect_USER_INFO_0_CONTAINER(tvbuff_t *tvb, int offset,
                                     packet_info *pinfo, proto_tree *tree,
                                     char *drep)
{
        offset = dissect_ndr_uint32(tvb, offset, pinfo, tree, drep,
                hf_wkssvc_num_entries, NULL);

        offset = dissect_ndr_pointer(tvb, offset, pinfo, tree, drep,
                wkssvc_dissect_USER_INFO_0_array, NDR_POINTER_UNIQUE,
                "USER_INFO_0 array:", -1);

        return offset;
}

static int
wkssvc_dissect_USER_INFO_1_array(tvbuff_t *tvb, int offset,
                                     packet_info *pinfo, proto_tree *tree,
                                     char *drep)
{
        offset = dissect_ndr_ucarray(tvb, offset, pinfo, tree, drep,
                        wkssvc_dissect_USER_INFO_1);

        return offset;
}

static int
wkssvc_dissect_USER_INFO_1_CONTAINER(tvbuff_t *tvb, int offset,
                                     packet_info *pinfo, proto_tree *tree,
                                     char *drep)
{
        offset = dissect_ndr_uint32(tvb, offset, pinfo, tree, drep,
                hf_wkssvc_num_entries, NULL);

        offset = dissect_ndr_pointer(tvb, offset, pinfo, tree, drep,
                wkssvc_dissect_USER_INFO_1_array, NDR_POINTER_UNIQUE,
                "USER_INFO_1 array:", -1);

        return offset;
}
    

Finally, the piece of glue needed to tie all this together is:
static int
wkssvc_dissect_USER_ENUM_UNION(tvbuff_t *tvb, int offset,
                                     packet_info *pinfo, proto_tree *tree,
                                     char *drep)
{
        guint32 level;

        ALIGN_TO_4_BYTES;

        offset = dissect_ndr_uint32(tvb, offset, pinfo, tree, drep, 
			            hf_wkssvc_info_level, &level);

        switch(level){
        case 0:
                offset = dissect_ndr_pointer(tvb, offset, pinfo, tree, drep,
                        wkssvc_dissect_USER_INFO_0_CONTAINER,
                        NDR_POINTER_UNIQUE, "USER_INFO_0_CONTAINER:", -1);
                break;
        case 1:
                offset = dissect_ndr_pointer(tvb, offset, pinfo, tree, drep,
                        wkssvc_dissect_USER_INFO_1_CONTAINER,
                        NDR_POINTER_UNIQUE, "USER_INFO_1_CONTAINER:", -1);
                break;
        }

        return offset;
}
    

OK, so now we can see what those funny bytes were in the request. Here they are again:
00c0                           c9 11 01 00 00 00 01 00            É.......
00d0  00 00 30 fe 12 00 00 00  00 00 00 00 00 00         ..0þ.... ......  
    

After the filler (c9 11) we have a uint32 value of 1, followed by another uint32 value of 1, then what looks like a referent (30 fe 12 00) and then two uint values of 0. That is just what you would get if bufptr, which points to a union type and is an array of structures, was defined in the IDL as an [in, out] parameter, and a null value was passed in the call.

So, now we know how to dissect the request:
static int
wkssvc_dissect_netwkstaenumusers_rqst(tvbuff_t *tvb, int offset,
                                      packet_info *pinfo, proto_tree *tree,
                                      char *drep)
{
        offset = dissect_ndr_str_pointer_item(tvb, offset, pinfo, tree, drep,
                                              NDR_POINTER_UNIQUE, "Server",
                                              hf_wkssvc_server, 0);
  
        offset = dissect_ndr_uint32(tvb, offset, pinfo, tree, drep,
                                    hf_wkssvc_info_level, 0);
 
        offset = dissect_ndr_pointer(tvb, offset, pinfo, tree, drep,
                        wkssvc_dissect_USER_ENUM_UNION,
                        NDR_POINTER_REF, "User Info", -1);

        offset = dissect_ndr_uint32(tvb, offset, pinfo, tree, drep,
                                    hf_wkssvc_pref_max, 0);

        offset = dissect_ndr_pointer(tvb, offset, pinfo, tree, drep,
                                     wkssvc_dissect_ENUM_HANDLE,
                                     NDR_POINTER_UNIQUE, "Enum Handle", -1);

        return offset;

}
    

Acknowledgements
I would like to thank Todd Sabin for writing a great set of dissection routines for DCERPC, Tim Potter for making a start on the MSRPC dissection routines, and especially Ronnie Sahlberg for figuring out many of the details of MSRPC calls and IDL that were of interest to us.

For corrections/additions/suggestions for this page, please send email to: rsharpe[AT]richardsharpe[dot]com
Last modified: Sat, October 11 2003.