One man's paradise is another man's nightmare. Now that Microsoft has quitely added new functionality to NT, it's very easy to create password grabbing trojans!

The following information is from Jeremy Allison and is intended to show a valid use for this functionality in Windows NT 4.0. The code on this page is not a trojan mechanism. However, a trojan could be introduced easily using the registry entries dicussed on this page.

I am posting this to both the Samba list and the nt-security list as I believe this information will be of interest to both groups. This message is somewhat long and contains code fragments so my apologies if this is of no interest to you (just hit delete :-). Over several years helping to write Samba and dealing with UNIX and NT integration problems one of the most common requests I have seen is some way to get a UNIX box (maybe running Samba) to act as a NT domain controller, or for some way to unify the password databases between UNIX boxes and NT Domains. The first problem is not solveable due to the amount of Microsoft proprietary information they would have to reveal, and MS are not willing to make that available. The second problem however, is more tractable. It seems in NT4.x Microsoft have finally revealed enough information to make synchronisation between UNIX and NT password databases possible.

Sync'ing from a UNIX box to an NT box was always possible, as the API's to change an NT password have always been available in the old Lanman API set, the difficulty was sync'ing NT password changes to a UNIX box, as the password change API's always seemed to go into a 'black box'to which no external access was available. It had to be possible, however, as NT Domains are perfectly capable of synchronising with Netware LANs. As the password hash mechanisms in NT and Netware are different the Netware password update mechanism had to be able to get at the plaintext password at the update time, before it got hashed and placed in the NT SAM. This mechanism is now available to other libraries on NT 4.x.

On NT 4.x there is a Key

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa

and one of the defined name/value pairs is "Notification Packages" which is a Multi_string value which as shipped has a value of FPNWCLNT. This is obviously the name of a DLL (as I found it as FPNWCLNT.DLL in %SYSTEMROOT%\SYSTEM32) and logic would dictate that this was the place that the Netware password updates were done. The latest Microsoft SDK held the missing part of the puzzle, the neccessary API's that need to be in such a DLL in order for it to get password change notification. So here below, is a very simple DLL that will receive plaintext password change notifications from the NT LSA code. The sample code just logs all password change notifications to a file called C:\TEMP\PWDCHANGE.OUT, but it illustrates the technique. To test it, comple the C code and .DEF file into a DLL called pwdchange.dll, copy it to %SYSTEMROOT%\SYSTEM32 update the value
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\Notification Packages
to read

FPNWCLNT
PWDCHANGE

(NB. The newline between the two is *important*). and then reboot the machine. Tests show that all password changes are now funnelled through this DLL with the following information, Username, Plaintext password, RID (relative domain id). More interesting is that on creation of a new user this DLL is also called, this could be used to do centralized account management by creating a new UNIX user on the fly as a new NT user is created. Such a library would be installed on the Primary domain controller for an NT domain, and will then allow all users passwords to be propagated to non-NT systems as they are changed. The useful thing about this method is that it gets called on *all* forms of password update, from using CTRL-ALT-DEL to update your password, using USERMGR to change a password, or even by using the net user <username> <password> command from the command prompt.

My own uses for this will be to keep an smbpasswd file up to date for the use by Samba, but a proposed mechanism to keep a UNIX password database in synch would be as follows:

1). Keep the notify DLL simple, as it is called in the context of an NT security system - we don't want complexity here. Just write the change information down a named pipe from the DLL.

2). Create a service, that creates the read end of the above named pipe. This service is configured with the following information, held in the registry.

    a). The name of the UNIX machine and TCP port number of a process on it to communicate with.

    b). A 'secret' DES key (secret in quotes as anyone with Administrator access could read it) which is used to encrypt the change notifications going across the net. This service would just read password change notifications, encrypt the data and ship it to a UNIX machine where it could be processed. This service can get as complex as we like, with queueing, retry, handshaking etc.

3). Create a UNIX daemon, running as root, listening on the TCP port named above for password change data. This daemon also needs access to the 'secret' DES key to decrypt the data (probably in a root owned and read-only be root file).
This daemon could then be configured to keep whatever databases residing on the UNIX side in sync are required. Suggestions are the UNIX password database, the Samba database, a Kerberos password database, Oracle, Sybase.... be my guest :-).

If this above daemon is written so that new change notification modules can be plugged in to it (like the PAM spec as an example) it would be flexible enough for all the above. Of course this will make any securiy expert shudder, as compromising the DES key comprmises all new password changes, but that's the price we pay for simplicity (Bruce Schneier(sp?) would definately not approve :-). Anyway enough with the pontificating, here's the code :-). (Code was written with Microsoft Visual C++ 4.x, not tested on other compilers). As always, this code has no warranty, and using it may cause your system to self destruct in 5 seconds .. .etc, etc, etc.... (hope that's enough legal-ease to protect me :-)

Some comments by: Mark Joseph Edwards
Although some people think that this exploit only works on a PDC, this is NOT so. It works just fine on NT systems installed just as a server (non-domain controller), and it also works just fine on NT Workstation. This DOESN'T work on a Backup Domain Controller, but it DOES work on a Primary Domain Controller. Also, take note that NT 4.0 and Service Pack 2 (or greater) are required for this to work on any variety of NT installation. If you want more information on this hook, see Microsoft's Knowledge Base article # Q151082, located
here. You may also want to take note right here and now that the MSGINA.DLL, which is the default "Graphical Identification and Authorization" provider for the local console logon, could also be overwritten with a trojan .DLL. Once this happens, you're toast. Ouch!

Here's Jeremy's useful (non-trojan) code:

-----------------cut here-------pwdchange.c-----------------------------
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

struct UNI_STRING {
USHORT len;
USHORT maxlen;
WCHAR *buff;
};

static HANDLE fh;

BOOLEAN __stdcall InitializeChangeNotify ()
{
DWORD wrote;
fh = CreateFile("C:\\temp\\pwdchange.out",
GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE,
0,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH,
0);
WriteFile(fh, "InitializeChangeNotify started\n", 31, &wrote, 0);
return TRUE;
}

LONG __stdcall PasswordChangeNotify (
struct UNI_STRING *user,
ULONG rid,
struct UNI_STRING *passwd
)
{
DWORD wrote;
WCHAR wbuf[200];
char buf[512];
char buf1[200];
DWORD len;

memcpy(wbuf, user->buff, user->len);
len = user->len/sizeof(WCHAR);
wbuf[len] = 0;
wcstombs(buf1, wbuf, 199);
sprintf(buf, "User = %s : ", buf1);
WriteFile(fh, buf, strlen(buf), &wrote, 0);

memcpy(wbuf, passwd->buff, passwd->len);
len = passwd->len/sizeof(WCHAR);
wbuf[len] = 0;
wcstombs(buf1, wbuf, 199);
sprintf(buf, "Password = %s : ", buf1);
WriteFile(fh, buf, strlen(buf), &wrote, 0);

sprintf(buf, "RID = %x\n", rid);
WriteFile(fh, buf, strlen(buf), &wrote, 0);

return 0L;
}
-----------------------end of pwdchange.c------------------------------------

---------cut here-pwdchange.def----------------------------------------------
EXPORTS

InitializeChangeNotify=_InitializeChangeNotify@0
PasswordChangeNotify=_PasswordChangeNotify@12

--------------------end pwdchange.def-----------------------------------------

This page has been visited times since December 27 1996