Full transcript
0:00What if every time you changed your
0:01password, your new password was
0:04instantly stolen and sent to a hacker?
0:07Turns out on your Windows computer,
0:09that's pretty easy to do because
0:11password filters are a way for
0:14organizations to enforce stricter
0:16password requirements on Windows
0:18accounts than those that are available
0:20by default in the Active Directory Group
0:22Policy. And it's pretty well documented.
0:25This is a write-up and blog post from
0:28Mebix or Rob Fuller titled stealing
0:31passwords every time they change. Now,
0:33granted, this is a very old blog post
0:37and very old write-up and very old
0:39technique, but it still works on modern
0:42Windows. Turns out this password filters
0:45functionality basically lets you run any
0:48code you want whenever any user changes
0:51their password. So, what's to stop you
0:53from listening in in the middle and
0:55capturing that new password? That's
0:57exactly what he did for the National
1:00Collegiate Cyber Defense Competition way
1:02back in 2013.
1:04He created an evil password filter that
1:07basically installed itself and anytime a
1:09password changed, it would store the
1:11password change to a log file in
1:13cleartext as well as issue a little HTTP
1:16post authentication to a server they
1:19own. So, they could exfiltrate the
1:21password and see what those new
1:22sensitive credentials are. But, let's go
1:24take a look at what these password
1:26filters really do based off the
1:28documentation. Now, it's pretty simple.
1:30It provides a way for you to implement a
1:32password policy and change notification.
1:35Whenever a password change request is
1:37made, the Local Security Authority or
1:39LSA calls the password filters
1:42registered on the system. Each password
1:43filter is called twice, interesting.
1:46First to validate the new password and
1:48then after all filters have validated to
1:50notify the filters that a change has
1:51been made. So, basically, we could
1:53register a new notification. If we
1:56listen in on some of the password filter
1:59functions that are defined here in the
2:00security management functions, the
2:03password filter function section, are
2:05the ones that we're most interested in.
2:06The following password filter functions
2:08are implemented by custom password
2:10filter DLLs or dynamic link libraries to
2:14provide password filtering and the
2:16change notification. So, these are the
2:18three functions that we'll need to sort
2:19of wrap around or recreate, right? Add
2:22our own functionality whenever a
2:24password has been changed. And when we
2:27want to put this to use, we go ahead and
2:29install it on the endpoint. Here's the
2:31thing though, this is basically a
2:33backdoor. This is a sort of persistence
2:36mechanism. So, that means you will need
2:39to already be an administrator or have
2:41compromised the machine to the point
2:42where you could set this up. We'll need
2:44to put a DLL or dynamic link library
2:47into C Windows System32, like all the
2:49native and natural Windows binaries are
2:51installed to. And then, we simply update
2:54the registry. But, this is the
2:56HKEY_LOCAL_MACHINE hive, which again
2:58requires administrator privileges. And
3:00then, once you've got this all set up,
3:02you will need to reboot the machine. So,
3:04keep in mind, this is a
3:05post-exploitation technique. Little bit
3:08of persistence, bit of a backdoor, this
3:09is something after you've already gained
3:12admin capability on that host and now
3:14maybe you want to listen and lurk and
3:16capture and collect other credentials
3:18that might be used elsewhere across the
3:19network. And I'm here on the MITRE
3:21ATT&CK framework and you can see some
3:23other hackers or threat actors have done
3:25this in the wild. So, for this video,
3:28let's go ahead and do it. Let's try and
3:30create our own rogue malicious password
3:32filter that could steal passwords every
3:35time they are changed. Now, because we
3:38know we'll need to write a DLL or
3:40dynamic link library, I'm going to open
3:42up Visual Studio so that I could create
3:44a new project and let's look for how we
3:47could make a DLL or dynamic link library
3:49in Windows. I'll click next here and we
3:52could call this like test pass or
3:54whatever, something for us to build out
3:56with the scratchpad. Now, Visual Studio
3:59gives us the boilerplate sort of syntax
4:01and setup for a DLL, but truth be told,
4:04we really don't need that. We're
4:05actually going to sort of get in the
4:07middle of those other Win32 API
4:10functions. We're implementing them as
4:12exports so that our password filter and
4:15now notification provider could respond
4:18to those. Since we're going to be
4:19working with the Windows API, we should
4:21include windows.h,
4:24get the header file, and I think for our
4:26example, we can just write to a log. So,
4:29let's get standard input and output for
4:31a header file as well. Now, let's begin
4:33adding the functions that we need, but
4:35before we do, while we're chatting about
4:37passwords and Active Directory and how
4:39this could very well be used in a domain
4:41controller environment, let me take a
4:43quick second to tell you about the
4:44sponsor of today's video. Let's be
4:46honest, passwords are still a mess.
4:49Forgotten logins, weak credential reuse,
4:52reset tickets clogging up the help desk,
4:54it's a nightmare and attackers know it.
4:57That's why I'm a fan of SpecOps
4:59Software. They take password security
5:02from reactive to proactive. With their
5:04breached password protection, you're
5:06blocking over 4 billion compromised
5:09passwords updated daily from real-world
5:11leaks, [music] live attacks, and even
5:13malware botnets. And it's not just
5:15password resets. SpecOps actively scans
5:18your Active Directory environment and
5:20flags compromised passwords the same day
5:23they show up in the wild. You can even
5:25build custom blocklists so users can't
5:28set passwords with your company name,
5:30project code names, or that one sports
5:32team that everyone's obsessed with in
5:34the office. And for the end users, they
5:36get dynamic feedback that helps them
5:39choose strong passwords that meet policy
5:42without the guessing game. Fewer bad
5:44passwords, fewer help desk tickets.
5:46Throw in self-service password reset and
5:49built-in multi-factor authentication,
5:51and suddenly passwords aren't your
5:53weakest link anymore. They're just one
5:55more thing you've locked down smart.
5:58Check out SpecOps Software and get
6:00started with my link below in the video
6:02description, jh.live/specops.
6:05Huge thanks to SpecOps for sponsoring
6:07this video.
6:09All right, I did forget my using
6:12namespace standard and I think that
6:13might be helpful here, so let's make
6:15sure that's included. And then, let's go
6:17review these password filter functions
6:19that we need to implement in our custom
6:21DLL. First one should be
6:23InitializeChangeNotify
6:25and it looks like that's coming from the
6:26ntsecapi.h
6:29header file, so that might still be
6:30worth including for us because some of
6:32the other maybe parameters or arguments
6:34that will go to other functions would be
6:36worth having. Um, this looks different
6:39in that example than what I was
6:41expecting here, but I don't believe that
6:43takes any arguments. So, let's try to
6:45run with that. And to reference this as
6:47some sort of export, I think we should
6:50be using the extern C declspec thing so
6:54that knows this is an export and we'll
6:58make that a boolean as that's what that
7:00is. But, we do want our
7:02InitializeChangeNotify
7:03function here that takes no arguments.
7:06Void is fine inside the parentheses and
7:09since this is the function that is
7:11called when, okay, it's initialized,
7:13it's not really going to be capturing or
7:15being in the middle of a password. So,
7:18we just need to have this functionality
7:20implemented, like define this function,
7:22but it doesn't need to do anything. So,
7:24we could just like return true even
7:26though it is a boolean, right? That
7:29should make sure it's going to return
7:31some true value. It succeeded, it
7:33worked. Okay. Now, PasswordChangeNotify
7:35is the real thing that we want to get in
7:37the middle of, but before we do, let's
7:39go take a quick look at PasswordFilter
7:41since that validates a new password
7:43based on password policy. Let's go ahead
7:45and look at this. Oh, we should still
7:48import that ntsecapi.h
7:50header. Let me get that included. Now,
7:52this is the password policy portion,
7:55right? This is the functionality in this
7:56callback or this function where you
7:58could test against the password or the
8:01value that's provided to see does it
8:02have however many certain special
8:04characters or whatever. But, again, we
8:07really don't need to get in the middle
8:09of this. We can do that for the latter
8:11function. This being a boolean, we could
8:14just recreate this uh and return true
8:16after the fact. But, we should use a
8:19puny code string as the type in all of
8:21this. Now that this has parameters, it's
8:23worth knowing what these are. So, let me
8:25paste this into our code and let's use
8:27the same sort of setup to declare this
8:30function as we did with the previous.
8:33Uh, we can remove the extra boolean
8:35there and that should just be called
8:37PasswordFilter,
8:38right? And we don't need these in setup.
8:41That's an artifact from copying it from
8:43the documentation, but that's a good way
8:44to know what arguments we would need.
8:47But, remember, this doesn't need to do
8:48anything. We can return true on this one
8:51and then the next function is where the
8:52real magic will happen. So, let's look
8:54at this last one here,
8:55PasswordChangeNotify,
8:57and I think we could, again, just use
9:00this setup and structure for the
9:03function declaration. This is the
9:04notification section and we can see in
9:07the parameters, again, more information
9:09about what username has been set with a
9:11new password. So, let's paste those in
9:14and change these up. This should be a
9:16PasswordChangeNotify
9:18function and that should return an
9:20NTSTATUS
9:22and let's use the same setup here, make
9:26this an export, and I do still want to
9:29use standard call in between that. Okay,
9:31now we need to actually define the
9:33function itself. And if we are just
9:35wanting to write to a file, to log this
9:39for our playground understanding, of
9:41course, you could make this forwarded
9:43out over HTTP or exfiltrate or send the
9:45password and credentials however you
9:47wanted. But, let's just simply start
9:49with a file. Let's try to open that file
9:53and that takes a file stream as like uh
9:56pointer with the ampersand here, so you
9:59get the raw value. We'll need a location
10:01as to where we want to put this on the
10:02file system. We can just call this log
10:04file.text, and let's write it in append
10:07mode. So, if it could not open that
10:11file, if F is equal to null, then we'll
10:14just kind of bail out, return true.
10:18But otherwise, we should write to this
10:20file. So, we could F print F to put data
10:23there using our file handler, and then
10:26what we want to add in. We know we're
10:28going to be working with strings here,
10:30but they are wide strings because it's
10:32Windows world, and we could go ahead and
10:34add in the wide string and another wide
10:38string that follows. And if we're in the
10:39Windows world, I guess backslash R for a
10:42character turn is also worthwhile. And
10:43then, we could just get the username,
10:47since that's included as an argument,
10:48and it is a pointer, P unicode string.
10:51So, let's use the kind of arrow syntax,
10:54rather than using the actual dot syntax
10:57to get a property out of it. Let's use
10:59the buffer value, so we get the real
11:02constant thing there alongside of the
11:05new password. There we go. With that, we
11:08have written, or at least logged and
11:10stolen, whatever password was set. And
11:13let's close our file. Then at the end of
11:15this function, let's be sure to like
11:17return zero. We're good. We're done.
11:19Now, I don't see any errors, and this is
11:22about maybe 37, or like less than 40
11:25lines of C code that was fine. Just
11:28wrapping around the functions that are
11:29necessary to be implemented and exported
11:32for this DLL notification provider with
11:35LSA. So, let's try to compile this. I'm
11:38going to set this in release mode.
11:4064-bit is fine for the computer that I'm
11:42on. So, let me click build solution, and
11:44fingers crossed, we get it to work.
11:46Okay, it's built with testpass.dll.
11:50Uh actually, I did miss something super
11:52important. Uh this string here, where
11:55we're using an absolute file path, using
11:57the backslashes for like a Windows file
12:00path, uh we will need to escape our
12:03backslash, because that's normally
12:04considered an escape character on its
12:06own. To have a literal backslash, we do
12:08need to have two of these here, and
12:10let's make sure to compile and use that
12:13one. So, I'm going to open up a terminal
12:16now, and I will run this as
12:17administrator, right? Let me hit control
12:19shift enter, so I have a UAC prompt, and
12:21because I am backdooring, or applying
12:24this little trick to the machine I'm
12:26using, let me move into the directory
12:28that we had that built in. There it is.
12:31And we know we need to put this into
12:33system32, but we know we're also going
12:35to be making changes to the registry,
12:37and that probably already has some data
12:40there for the regular, normal
12:43notification providers that LSA uses.
12:46So, let's get to HKEY_LOCAL_MACHINE.
12:49Looking back at the documentation to
12:51install and register that, it does need
12:53to be in system, current control set,
12:55control, LSA. System, current control
12:59set, control,
13:02LSA. Okay.
13:04So, take a look. We have a notification
13:07packages key here, or a value set with
13:10the type REG_MULTI_SZ,
13:12with the default value of SCECLI.
13:15So, I could double-click and modify
13:17this, but if that's already a DLL hiding
13:21in C:\Windows\System32,
13:24could we maybe tamper, or get a little
13:27bit clever as to how we hide our new
13:29rogue DLL? Like we know right now, ours
13:32is called testpass.dll,
13:34and if it were like evil password proxy
13:37filter, that would probably, you know,
13:39just be sticking out like a sore thumb.
13:41So, what if we tried to hide this in a
13:42clever way, where we were to copy this
13:46test DLL file into C:\Windows\System32,
13:51but I actually want to call it something
13:52a little bit neat, SCECII.dll,
13:59and you might be able to see it there,
14:01right? Not a lowercase L, but a capital
14:05I, so that in a serif font, excuse me, a
14:09sans font, as you can see our registry
14:11explorer working here, this won't look
14:14all that shady once we've made the
14:16changes here to add in SCEC
14:20capital II.
14:23Looks almost the same as the other one,
14:25but now that is our rogue and malicious
14:28little trick here. This will whine about
14:30some of the empty strings that it found
14:31if you're making this change manually,
14:33but if you double-click, you will still
14:34see this value provided here. So, with
14:37that done, and with that DLL that we've
14:40just created now put into
14:42C:\Windows\System32 in that directory,
14:45let's look for SCECLI.
14:47Take a look.
14:48>> [laughter]
14:49>> Can you tell the difference? Kind of
14:50hard to see. Obviously, the date
14:52modified gives it away, but now our code
14:55should go ahead and log new passwords to
14:57the C drive logfile.text
15:01file that we specified. It doesn't exist
15:03right now, because we haven't rebooted
15:05yet, and the changes haven't been made,
15:07and we haven't tried to change a
15:09password, but let's reboot, and let's
15:11see this in action. Let me go ahead and
15:14restart. Restart anyway. All right, now
15:16let me log in as a separate user. Let's
15:19just log in as John with the password
15:21that he has set for the moment. And now
15:23that I am logged in, we could go explore
15:26in the root of our file system here, the
15:29C drive. Remember our logfile.text that
15:32we're looking for is only told to be
15:34created and written to when we have a
15:37new password being changed. So, let's
15:40try to change our password. While you
15:43could do this with the net user command,
15:45you could do this in some other way.
15:47Obviously, any of the methods that are
15:49really still going to be hooked into, we
15:50can change our password by the GUI, by
15:54whatever method, and let's use a fun one
15:57for our new password. Password hint can
15:59be LOL. Let's click next and finish. And
16:03now, when I go ahead and take a look at
16:05our C drive, we have our log file.
16:08Logfile.text has been created, and you
16:11can see the password that I set. And we
16:13just stole that clear text, plain text
16:16password as it was being changed. Bit of
16:19an odd unicode or some strange character
16:22at the end here, we could see this is
16:23pretty well clear the plain text
16:25password, though. That could have been
16:27anything. And if we look back at the
16:29blog post from Mebix, or Rob Fuller,
16:31again, that was written way back in
16:332013. It says this works from Windows
16:352000, XP, all the way up to Windows 8
16:38and 2012. Granted, I'm testing this on
16:40Windows 11 now in 2025, uh probably
16:43still kicking around. While it's an old
16:45technique, it does work. And let me say,
16:48uh I don't know if I actually drew
16:49attention to this, but like Windows
16:51Defender virus and threat detection is
16:53still on. Uh I could go ahead and go
16:55take a look at all this. Real-time
16:57protection's on, cloud-delivered
16:58protection's on. We can turn on
17:00automatic sample submission if you all
17:01want. Granted, we didn't do anything
17:03malicious. We wrote to a text file,
17:06again using the native and real
17:07functionality of a password filter, but
17:10the registry tweak, all the things that
17:12we did, and that living in system32,
17:15totally A-okay. To your point, though,
17:16you may very well be asking, "Okay,
17:18well, how often are local passwords
17:20changed?" Maybe not often, maybe often
17:23enough, maybe still something that could
17:25glean and glisten some new information
17:27if you're doing some long-term, I don't
17:29know, threat emulation. And when this is
17:31put on a domain controller inside of an
17:33active directory environment, every time
17:36a password is changed by that domain
17:38controller, it's going to be verified by
17:40the password filters. So, if this is
17:42sitting there, listening in, set up as
17:44some post-exploitation, set up as a
17:45little bit of a persistence, or another
17:47trap, you could still be capturing those
17:50credentials. So, there's another trick
17:52for your toolkit, another technique,
17:54maybe old but gold, right? Way back from
17:562013, but still some other trickery you
17:59could do with passwords on Windows.
18:02Stealing a password every time it
18:04changes. And all it takes is just one
18:07file on the file system, and one change
18:09to the registry, and you can get a
18:10little bit sneaky there. So, hey, thanks
18:12so much for watching, everyone. I hope
18:13you enjoyed this video. If you did,
18:15please do all those YouTube algorithm
18:16things, like, comment, subscribe, and
18:18please do give some love to our
18:19sponsors, link below in the video
18:21description. Thanks again. I'll see you
18:23in the next video.