[00:00.000 --> 00:04.940] So this talk is called Power Pwning, Post Exploitation by Overpowering PowerShell. [00:05.720 --> 00:14.680] So my name is Joe Bilek. I'm a security engineer at a large software company or actually devices [00:14.680 --> 00:22.780] and services company in Redmond. You can find me on Twitter and I have a blog and GitHub. [00:25.300 --> 00:29.980] All right. So if any of you don't know anything about PowerShell, it's pretty awesome. It's [00:29.980 --> 00:36.380] awesome. PowerShell gives you full access to the Win32 API and all the .NET libraries. [00:37.000 --> 00:42.660] And PowerShell has a remoting functionality which allows you to connect to other systems [00:43.440 --> 00:49.760] and run scripts on them. And these scripts that you run over PowerShell remoting never [00:49.760 --> 00:56.540] touch disk on the remote system. And in addition to that, any scripts that you run are going [00:56.540 --> 01:01.700] to run inside either the PowerShell.exe process if you run it on the local computer or the [01:01.700 --> 01:09.440] WSM process if you run it on a remote computer. So this is pretty nice, too, because you don't [01:09.440 --> 01:14.560] have to execute any suspicious or unsigned processes which means that you can generally [01:14.560 --> 01:20.400] bypass antivirus and application whitelisting assuming that PowerShell is whitelisted. [01:23.760 --> 01:29.500] So that's all pretty great. But I have a lot of pen test tools that I use right now that are all [01:29.500 --> 01:35.220] written in unmanaged code. And I really don't feel like porting all of those tools over to [01:35.220 --> 01:40.080] PowerShell. But I really want to take advantage of PowerShell's forensic benefits because you [01:40.080 --> 01:46.500] can be really sneaky if you use PowerShell. So the solution that I came up with is to write a [01:46.500 --> 01:53.040] PowerShell script that will just reflectively load PEs, which is what an executable in an [01:53.040 --> 01:59.960] exe file or a DLL is, in the PowerShell process and execute them. So that's what I'm going to [01:59.960 --> 02:09.470] be talking about. So for those of you who don't know how loading PEs works, this is all [02:09.470 --> 02:17.210] documented on MSDN. I'm not going to go super in detail, but I want to give you an idea of [02:17.210 --> 02:22.970] kind of what Windows is doing when create process or load library is called. So what's [02:22.970 --> 02:29.330] going to happen is, first, memory is going to be allocated inside a process for the PE. Next, [02:29.330 --> 02:33.410] you're going to copy the PE into that memory. Then you're going to do what's called [02:33.410 --> 02:39.150] performing base relocations. So inside of a PE file, there's going to be a whole bunch of [02:39.150 --> 02:45.110] memory addresses that are hard coded. And the PE file and its header is going to supply a [02:45.110 --> 02:49.310] base address. So it's going to tell Windows, this is the memory address that I'd like to be [02:49.310 --> 02:54.350] loaded to. And if you don't load me to this memory address, then you need to go and change [02:54.350 --> 02:59.510] all of the hard coded memory addresses in the PE file so that they point to valid memory [02:59.510 --> 03:05.390] locations. So you're just patching up these addresses. Next, the PE file that you're loading [03:05.390 --> 03:10.750] is probably going to have dependencies on other DLLs, so you need to load those. And then the [03:10.750 --> 03:16.570] last thing that you're going to do is you're going to call the entry function. So for a DLL, [03:16.570 --> 03:22.690] this is the DLL main function, which just lets the DLL know that it's been loaded. And for an [03:22.690 --> 03:28.910] EXE, it's going to be a function which does some initialization and then eventually will [03:28.910 --> 03:38.700] call like int main in C++ or C or the equivalent in other programming languages. Oh, and I [03:38.700 --> 03:45.380] wanted to note that when I say that I'm going to be reflectively loading PE files, what I [03:45.380 --> 03:52.040] mean by this is that instead of relying on Windows load library or create process to go [03:52.040 --> 03:57.480] through and do those steps that I just listed, instead I'm going to do all of those steps in [03:57.480 --> 04:03.740] PowerShell. And what this will allow me to do is the Windows functions require that the PE [04:03.740 --> 04:08.180] file is stored on disk, so you can't call load library on something that's just in memory. It [04:08.180 --> 04:13.960] has to be a file on disk. But if I rewrite all those functions in PowerShell, then I don't [04:13.960 --> 04:19.480] have that same constraint. So I can just hard code the PE file in a byte array inside the [04:19.480 --> 04:24.880] script. So this is really nice. Now I don't need to write executables and DLLs to disk to [04:24.880 --> 04:29.440] execute them. They just need to be encoded in a PowerShell script, which can then be run on [04:29.440 --> 04:39.700] remote systems. Okay. So for DLLs in particular, loading a DLL reflectively is really not much [04:39.700 --> 04:46.360] different than loading a DLL using load library in the sense that when you call a DLL using [04:46.360 --> 04:51.200] load library, I mean, at the end of the day, a DLL is still being loaded inside of a process [04:51.200 --> 04:56.620] that already exists. So reflectively loading it, the only change here really is that I didn't [04:56.620 --> 05:00.540] call the load library function. I just wrote my own. But other than that, everything is the [05:00.540 --> 05:05.880] same. A DLL is still being loaded into a process which already exists. So the first thing [05:05.880 --> 05:08.860] you're going to want to do is you're going to want to call a DLL function that you [05:08.860 --> 05:15.620] exported. And this will contain your payload or the code you want to execute. One little [05:15.620 --> 05:22.600] caveat with doing this is that if your DLL or your EXE, for that matter, outputs data using [05:22.600 --> 05:28.640] standard out, so like if you use print F or C out and C++, PowerShell can't capture that [05:28.640 --> 05:33.420] output. And the significance of this is that if you run a script locally, it doesn't matter [05:33.420 --> 05:36.920] because the output is still just going to be sent through the console host window, so [05:36.920 --> 05:42.380] you're going to see the output. But if you're doing PowerShell remoting, the only way that [05:42.380 --> 05:47.800] you can see output is if PowerShell can capture that output and serialize it and send it back [05:47.800 --> 05:52.860] to the computer that you did the remoting call from. So if you want to be able to see the [05:52.860 --> 05:57.160] output, what you're going to need to do is make the DLL function that you're calling [05:57.740 --> 06:03.720] return you a character array or like a wide character array. And PowerShell can then take [06:03.720 --> 06:08.360] that pointer and marshal it into managed memory and then it can serialize that and send it [06:08.360 --> 06:12.840] back. So it's just a small change that you need to make. And on my blog, I actually have a [06:12.840 --> 06:18.800] post showing how it takes about maybe five minutes to change Mimikatz from outputting [06:20.120 --> 06:27.400] using standard out to outputting to a string that can be, you know, retrieved by PowerShell. [06:30.540 --> 06:36.800] So reflectively loading an EXE is a little more tricky because normally when an EXE is [06:36.800 --> 06:41.120] loaded, it's loaded using create process and a new process is created specifically for [06:41.120 --> 06:47.400] that EXE. But what we're doing is loading an EXE inside of a process that already exists [06:47.400 --> 06:55.040] with PowerShell.EXE running inside of it. So as an example, normally when an EXE exits, [06:55.040 --> 07:00.120] so the EXE that you reflectively loads ‑‑ that you reflectively loaded runs and quits, [07:00.120 --> 07:04.540] it's going to go and call exit process because it thinks everything is done. But the problem [07:04.540 --> 07:07.880] is that that's actually going to kill the PowerShell process because that's the process [07:07.880 --> 07:13.160] that's actually running. And that's not ideal because you probably wanted to see the output [07:13.160 --> 07:18.700] from the EXE that you just ran. And if PowerShell just dies, you can't. And so it turns out [07:18.700 --> 07:26.980] there's a pretty easy solution to this. Basically what I do is I call the EXE in its own thread. [07:27.040 --> 07:32.440] I call its entry function in a new thread. And then you overwrite the exit process function [07:32.940 --> 07:38.580] with shell code that just calls exit thread instead. And so the EXE will start up in its [07:38.580 --> 07:41.620] own thread and then when it's done running, it will just nicely kill the thread that you [07:41.620 --> 07:47.620] provided for it. So it's a really clean solution. And just for reference purposes, I put assembly [07:47.620 --> 07:49.840] in here, but we don't actually need to talk about this. [07:52.260 --> 07:57.440] Okay. So the other problem that I had reflectively loading an EXE is that you're going to want [07:57.440 --> 08:01.780] to pass it command line arguments. But the command line arguments that it's going to [08:01.780 --> 08:05.480] retrieve are the command line arguments that PowerShell started with because you're running [08:05.480 --> 08:12.240] inside the PowerShell process. And so it turns out that, well, the first thing I wanted to [08:12.240 --> 08:17.740] do was just go inside the process environment block, the PEB, which is where the command [08:17.740 --> 08:22.220] line arguments are stored, and just overwrite the string that stores the command line arguments. [08:22.220 --> 08:28.380] But that doesn't actually work because the way that EXE normally retrieves its command [08:28.380 --> 08:33.620] line arguments is it makes a function call to get command line or underscore, underscore [08:33.620 --> 08:40.820] get command line. And those are provided by two different DLLs. And when those DLLs are [08:40.820 --> 08:45.260] initially loaded, they actually cache a copy of the command line arguments. So if you just [08:45.260 --> 08:50.860] overwrite the PEB, you didn't overwrite the cached copy that's in the DLLs, and so you're [08:50.860 --> 08:53.820] still just going to end up getting the command line arguments that PowerShell was started [08:53.820 --> 08:59.000] with, which is no good. So I had to create solutions for both of these functions. [09:01.180 --> 09:06.540] So patching get command line is super easy. Get command line is a function that takes [09:06.540 --> 09:10.920] no arguments and it just returns you a string pointer to the command line arguments with [09:10.920 --> 09:15.180] no formatting or anything done. So all you need to do in PowerShell is just allocate [09:15.300 --> 09:22.440] a string on the heap and then overwrite the function call, get command line, with shell [09:22.440 --> 09:27.740] code that just returns the string, the address to the string that you allocated. So very, [09:27.740 --> 09:33.120] very simple, right? The next function, underscore, underscore [09:33.120 --> 09:39.480] get command line, is a little more complicated to overwrite. So I didn't want to do that. [09:39.480 --> 09:44.860] And the reason is it takes a number of parameters and this function actually goes and parses [09:44.860 --> 09:49.360] out your command line arguments into RdV and RdC. I didn't want to go through the trouble [09:49.360 --> 09:53.760] of parsing stuff if I didn't need to. But what I found is that the DLL that this function [09:54.430 --> 10:01.340] is exported from also exports two variables, A command line and W command line. And these [10:01.340 --> 10:06.800] variables are just string pointers. So if you go and overwrite these variables and then [10:06.800 --> 10:11.980] you call the get command line function, it will parse the variables that you just overwrote. [10:11.980 --> 10:15.920] So that's an even easier solution than the previous one because you just replace those [10:16.480 --> 10:19.620] string pointers with pointers to strings that you allocate. [10:23.640 --> 10:32.140] Okay. So what about remote reflective DLL injection? That's possible, too. It's a little [10:32.140 --> 10:38.320] more painful to do it in PowerShell compared to C or C++, which is what some of the current [10:38.320 --> 10:45.240] remote reflectors are written in. But it turns out it's still very possible to do. [10:45.440 --> 10:51.440] So the general process that I used for this is I allocate memory in both the remote process [10:51.440 --> 10:59.620] and the current process. And then I load the DLL into the current process, so into PowerShell.exe. [11:00.460 --> 11:06.480] And I'm pretty much just using PowerShell.exe to stage this DLL. So I want to get it ready [11:06.480 --> 11:13.080] before I write it into the remote process. And what that entails is I go and I look and [11:13.080 --> 11:19.520] see what DLLs does this DLL depend on, just like I did before, right? Except when I load [11:19.520 --> 11:24.220] those DLLs, I actually load them in the remote process. And I get the function addresses [11:24.220 --> 11:30.920] for everything in the remote process. And when you do your base relocations, similarly, [11:30.920 --> 11:36.920] you use the memory address for the memory you allocated in the remote process when you're [11:36.920 --> 11:43.780] doing your base relocation calculations. And once you've done all this, you end up with [11:43.780 --> 11:49.080] the DLL, which is currently in the PowerShell process, but all of the memory addresses have [11:49.080 --> 11:54.340] been patched as if it was being loaded in the remote process. So all you need to do [11:54.820 --> 12:00.060] is take those bytes and write them into the remote process, and the DLL is pretty much [12:00.060 --> 12:10.100] ready to go. You just give it its own thread, and it will start executing. So I just have, [12:10.100 --> 12:15.180] once again, more assembly here just for reference purposes for the shell code that you actually [12:15.180 --> 12:21.180] write into the remote process to call get ‑‑ or load library and get proc address. [12:31.950 --> 12:35.770] All right. Wait a second. How do we go back to that code? All right. Here we go. Everybody [12:35.770 --> 12:53.680] take notes. You got this? I know none of you understand why we're here. You know, it [12:53.680 --> 12:57.460] slays me how many new speakers there are at DEF CON this year. I'm beginning to think [12:57.460 --> 13:09.140] they're just saying that to fuck with us. Thank you, sir. And did we ‑‑ oh, we got [13:10.400 --> 13:15.760] one. Yes. What's your name? Rick. We got Rick. Rick, new attendee. He's representing all [13:15.760 --> 13:36.020] of you. All right. Cheers, gentlemen. We'll see you soon. Everybody got that code? [13:39.810 --> 13:45.470] Probably not the most interesting slide to leave up there. All right. So let's do some [13:45.470 --> 14:05.490] demos. Okay. So I have two VMs here. This one with the gray background is a domain [14:05.490 --> 14:12.030] controller and it's going to be targeted. And then I have this demo client. Let's see if I [14:12.030 --> 14:23.910] can resize these windows. There we go. There we go. All right. So just as an example of what [14:23.910 --> 14:29.450] you can do with this script, so I have this script here called invoke Mimikatz X64. All I did [14:29.450 --> 14:36.410] was just hard code the Mimikatz binary into the reflective PE injection script just so it's a [14:36.410 --> 14:44.650] little easier to use. So what I'm going to do here is you can supply computer name. And the [14:44.650 --> 14:49.110] computer name right now is DC1, which is my domain controller. Computer name is actually an [14:49.110 --> 14:53.250] array, though, so you can supply hundreds or thousands of computers to run this script [14:53.250 --> 14:58.370] against and PowerShell will do it all for you automatically, do the concurrency, blast the [14:58.370 --> 15:07.290] script off. So let's go ahead and run this. Hopefully it works because this is live. All [15:07.290 --> 15:23.830] right. And the output gets streamed back. So what just happened? All right. So it looks like [15:23.830 --> 15:30.930] the domain admin is running with the password 123. That's not very good. But anyway, so what [15:30.930 --> 15:36.110] this allows you to do is we just ran Mimikatz on a remote system without ever touching disk on [15:36.110 --> 15:40.790] that remote system, without ever triggering any application white listing products, without [15:40.790 --> 15:46.070] triggering antivirus. As far as the domain controller is concerned, all that happened was [15:46.070 --> 15:50.750] someone made a remote PowerShell connection to the server and ran a script that we now have no [15:50.750 --> 15:59.030] record of. That's pretty awesome, I think. So this next script, this is called invoke Ninja [15:59.030 --> 16:05.990] copy and this is online, too. So a little while ago I saw a blog post by someone who wrote a [16:05.990 --> 16:12.430] tool called SamX. Basically their observation was by default not even an administrator can [16:12.430 --> 16:19.490] make, can access certain files on a Windows system like the registry hive or the NTDS active [16:19.490 --> 16:26.470] directory database because LSAS has a lock on the file. So LSAS is the only process that is [16:26.470 --> 16:32.890] allowed to have the file open. However, if you know how to parse NTFS, an administrator can get [16:33.010 --> 16:39.090] a read handle to the C volume and then just read the NTFS structures on the C volume or D [16:39.090 --> 16:44.870] volume and scan to the exact position where the bytes that make up that file are located and [16:44.870 --> 16:50.090] read them off. So I thought that was pretty awesome. And I kind of wanted to write that in [16:50.090 --> 16:55.930] PowerShell but then I realized that someone had already written an open source NTFS parser in [16:55.930 --> 17:02.410] C++ and put it on CodePlex. So I figured maybe I should just use that and reflectively load it [17:02.410 --> 17:07.270] and save my time parsing NTFS on my own. And that's really the beauty of this script is that [17:07.270 --> 17:10.710] you can just take these tools that other people have written and then just turn them into [17:10.710 --> 17:15.730] PowerShell scripts that are super sneaky all of a sudden. And so that's what we're going to do [17:15.730 --> 17:20.790] here. So I'm actually, I'll go to the domain controller really quick. All right. Great [17:20.790 --> 17:25.830] background. We're on the domain controller. And here's the NTDS.dit file. And I'm going to try [17:25.830 --> 17:31.650] to open it. Yeah, let's try to open it with notepad. And we're going to get this error [17:31.650 --> 17:36.130] message saying the process cannot access the file because it is being used by another [17:36.130 --> 17:42.410] process. So even though I'm domain admin, I can't get this file. Bummer. I really wanted it. [17:44.370 --> 17:50.490] Okay. So let's go back to the demo client. And we're just going to go ahead and remote [17:50.490 --> 17:58.030] PowerShell into the domain controller. And I have here the file path, the NTDS file. And I'm [17:58.030 --> 18:09.700] going to copy it to the desktop of my attack box. And we'll go ahead and run this guy. You [18:09.700 --> 18:15.220] can see from the output here, it's just going to copy 5 megabytes at a time of this file [18:15.760 --> 18:22.540] directly to the desktop. And if I go ahead and open my desktop, we now have a copy of the [18:22.540 --> 18:36.660] NTDS Active Directory database file. And once again ‑‑ so once again, no suspicious [18:36.660 --> 18:41.300] processes were run. The only thing that ever touched disk ‑‑ well, nothing touched disk. [18:41.900 --> 18:46.900] This was all done in memory. And we streamed the file back over the wire. So that's pretty [18:46.900 --> 18:51.340] awesome. There's very little evidence left behind that an attacker ever did something here. [18:54.430 --> 19:00.870] And just for reference, it took me maybe a couple hours to turn the NTFS parser into a DLL that I [19:00.870 --> 19:07.990] could use with my script and, you know, good to go. So it's really, really fast to turn things [19:07.990 --> 19:12.030] into PowerShell ‑‑ turn arbitrary programs into PowerShell scripts that are really hard [19:12.030 --> 19:27.940] to catch. Okay. So the obligatory detection and prevention slide. So there's not a whole [19:27.940 --> 19:32.920] lot to say here. PowerShell remoting requires administrator access and it requires open [19:32.920 --> 19:38.920] ports. So if you don't want someone to do this, then don't let them have admin access on [19:38.920 --> 19:42.860] really sensitive servers of yours and don't let them have network connectivity to really [19:42.860 --> 19:47.440] sensitive servers of yours. Standard stuff like firewalls, limiting powerful accounts, [19:47.440 --> 19:50.700] right, like lock down your accounts, make sure that I can't just remote PowerShell into [19:50.700 --> 19:56.740] every server from any server. And it helps defend against this. Of course, nobody does [19:56.740 --> 20:04.400] that, but ‑‑ or maybe some people do that. PowerShell V3 has pipeline logging, which [20:04.980 --> 20:09.780] might help detect this, but it would be really easy for an attacker to turn it off or clear [20:09.780 --> 20:14.380] the logs if they wanted to. And there's also going to be a lot of noise if you actually [20:14.380 --> 20:19.100] use PowerShell. Constrained run spaces can help limit the power of PowerShell. So you [20:19.100 --> 20:23.540] can say even though someone's allowed to remote PowerShell into the server, there's only [20:23.540 --> 20:28.080] specific commands they're allowed to run. So I don't have full access to the Win32 API. [20:28.080 --> 20:34.080] I only have access to these 50 commandlets that are deemed to be safe. And another one [20:34.080 --> 20:40.520] is machine‑wide profile to log actions to a transcript. Once again, super easy for [20:40.520 --> 20:44.740] an attacker to turn this off. But if an attacker doesn't know that you're doing this, then [20:44.740 --> 20:53.420] you might be able to record what they're doing. So closing thoughts, this is not a vulnerability. [20:53.860 --> 20:58.260] All of this is by design. PowerShell is a very powerful language. It has full access [20:58.260 --> 21:05.120] to the Win32 API. It has full access to the .NET libraries. So yes, it is able to do this. [21:05.120 --> 21:10.460] And pretty much any programming language can do this if it has access to the Win32 API. [21:10.920 --> 21:15.000] And if a programming language has remoting functionality, then you can, you know, use [21:15.000 --> 21:17.800] the remoting functionality of other programming languages as well. [21:18.640 --> 21:22.180] I think PowerShell is a great way to manage Windows systems. So I hope this talk doesn't [21:22.180 --> 21:26.760] scare you away from PowerShell altogether, but I hope that it does encourage you to treat [21:26.760 --> 21:32.760] PowerShell sensitively and make sure that you configure it correctly, don't expose, [21:32.760 --> 21:38.400] you know, PowerShell to all the servers in your environment. And hopefully it also gives [21:38.400 --> 21:42.440] you awesome ideas on how to attack Windows networks, because that's kind of why I'm here. [21:43.700 --> 21:49.220] So I've got some references to helpful documentation, Microsoft documents, a lot about how PE loading [21:49.220 --> 21:54.660] and DLL loading works, links to some other reflective loaders, like Metasploit has a [21:54.660 --> 21:59.880] reflective loader, for example. That's how it does migration and whatnot. And then some [21:59.880 --> 22:03.940] really good PowerShell-related blogs, Exploit Monday especially is super good. Matt Graber [22:03.940 --> 22:11.220] has really great documentation on how to do awesome stuff in PowerShell. And just links, [22:11.220 --> 22:16.600] once again, to my blog and my Twitter and GitHub. And that's all I've got for you.