1 00:00:00,000 --> 00:00:04,940 So this talk is called Power Pwning, Post Exploitation by Overpowering PowerShell. 2 00:00:05,720 --> 00:00:08,260 So my name is Joe Bilek. 3 00:00:08,320 --> 00:00:18,080 I'm a security engineer at a large software company or actually devices and services company in Redmond. 4 00:00:18,640 --> 00:00:22,780 You can find me on Twitter and I have a blog and GitHub. 5 00:00:25,300 --> 00:00:26,040 All right. 6 00:00:26,040 --> 00:00:29,980 So if any of you don't know anything about PowerShell, it's pretty awesome. 7 00:00:29,980 --> 00:00:30,580 It's awesome . 8 00:00:30,820 --> 00:00:36,380 PowerShell gives you full access to the Win32 API and all the .NET libraries. 9 00:00:37,000 --> 00:00:45,840 And PowerShell has a remoting functionality which allows you to connect to other systems and run scripts on them. 10 00:00:46,040 --> 00:00:52,160 And these scripts that you run over PowerShell remoting never touch disk on the remote system. 11 00:00:53,260 --> 00:01:05,900 And in addition to that, any scripts that you run are going to run inside either the PowerShell.exe process if you run it on the local computer or the WSM process if you run it on a remote computer. 12 00:01:06,260 --> 00:01:20,400 So this is pretty nice, too, because you don't have to execute any suspicious or unsigned processes which means that you can generally bypass antivirus and application whitelisting assuming that PowerShell is whitelisted. 13 00:01:23,760 --> 00:01:25,900 So that's all pretty great. 14 00:01:26,160 --> 00:01:31,320 But I have a lot of pen test tools that I use right now that are all written in unmanaged code. 15 00:01:31,760 --> 00:01:36,180 And I really don't feel like porting all of those tools over to PowerShell. 16 00:01:36,240 --> 00:01:42,100 But I really want to take advantage of PowerShell's forensic benefits because you can be really sneaky if you use PowerShell. 17 00:01:42,660 --> 00:01:58,920 So the solution that I came up with is to write a PowerShell script that will just reflectively load PEs, which is what an executable in an exe file or a DLL is, in the PowerShell process and execute them. 18 00:01:59,300 --> 00:02:01,200 So that's what I'm going to be talking about. 19 00:02:04,510 --> 00:02:11,510 So for those of you who don't know how loading PEs works, this is all documented on MSDN. 20 00:02:12,750 --> 00:02:21,130 I'm not going to go super in detail, but I want to give you an idea of kind of what Windows is doing when create process or load library is called. 21 00:02:21,950 --> 00:02:27,550 So what's going to happen is, first, memory is going to be allocated inside a process for the PE. 22 00:02:28,650 --> 00:02:31,370 Next, you're going to copy the PE into that memory. 23 00:02:32,190 --> 00:02:35,350 Then you're going to do what's called performing base relocations. 24 00:02:35,870 --> 00:02:41,270 So inside of a PE file, there's going to be a whole bunch of memory addresses that are hard coded. 25 00:02:41,690 --> 00:02:46,210 And the PE file and its header is going to supply a base address. 26 00:02:46,210 --> 00:02:50,210 So it's going to tell Windows, this is the memory address that I'd like to be loaded to. 27 00:02:50,510 --> 00:03:00,350 And if you don't load me to this memory address, then you need to go and change all of the hard coded memory addresses in the PE file so that they point to valid memory locations. 28 00:03:00,570 --> 00:03:02,330 So you're just patching up these addresses. 29 00:03:03,390 --> 00:03:09,350 Next, the PE file that you're loading is probably going to have dependencies on other DLLs, so you need to load those. 30 00:03:09,970 --> 00:03:14,590 And then the last thing that you're going to do is you're going to call the entry function. 31 00:03:14,890 --> 00:03:21,510 So for a DLL, this is the DLL main function, which just lets the DLL know that it's been loaded. 32 00:03:22,070 --> 00:03:34,330 And for an EXE, it's going to be a function which does some initialization and then eventually will call like int main in C++ or C or the equivalent in other programming languages. 33 00:03:37,520 --> 00:03:58,520 Oh, and I wanted to note that when I say that I'm going to be reflectively loading PE files, what I mean by this is that instead of relying on Windows load library or create process to go through and do those steps that I just listed, instead I'm going to do all of those steps in PowerShell. 34 00:03:59,200 --> 00:04:08,080 And what this will allow me to do is the Windows functions require that the PE file is stored on disk, so you can't call load library on something that's just in memory. 35 00:04:08,080 --> 00:04:09,680 It has to be a file on disk. 36 00:04:10,080 --> 00:04:15,360 But if I rewrite all those functions in PowerShell, then I don't have that same constraint. 37 00:04:15,580 --> 00:04:20,140 So I can just hard code the PE file in a byte array inside the script. 38 00:04:20,500 --> 00:04:21,540 So this is really nice. 39 00:04:21,540 --> 00:04:25,660 Now I don't need to write executables and DLLs to disk to execute them. 40 00:04:25,660 --> 00:04:30,620 They just need to be encoded in a PowerShell script, which can then be run on remote systems. 41 00:04:32,100 --> 00:04:32,660 Okay. 42 00:04:32,660 --> 00:04:52,620 So for DLLs in particular, loading a DLL reflectively is really not much different than loading a DLL using load library in the sense that when you call a DLL using load library, I mean, at the end of the day, a DLL is still being loaded inside of a process that already exists. 43 00:04:52,620 --> 00:04:58,240 So reflectively loading it, the only change here really is that I didn't call the load library function. 44 00:04:58,240 --> 00:04:59,100 I just wrote my own. 45 00:04:59,100 --> 00:05:00,820 But other than that, everything is the same. 46 00:05:00,820 --> 00:05:04,480 A DLL is still being loaded into a process which already exists. 47 00:05:05,100 --> 00:05:09,660 So the first thing you're going to want to do is you're going to want to call a DLL function that you exported. 48 00:05:10,440 --> 00:05:13,960 And this will contain your payload or the code you want to execute. 49 00:05:14,520 --> 00:05:29,380 One little caveat with doing this is that if your DLL or your EXE, for that matter, outputs data using standard out, so like if you use print F or C out and C++, PowerShell can't capture that output. 50 00:05:29,940 --> 00:05:38,180 And the significance of this is that if you run a script locally, it doesn't matter because the output is still just going to be sent through the console host window, so you're going to see the output. 51 00:05:38,840 --> 00:05:50,300 But if you're doing PowerShell remoting, the only way that you can see output is if PowerShell can capture that output and serialize it and send it back to the computer that you did the remoting call from. 52 00:05:50,800 --> 00:06:01,200 So if you want to be able to see the output, what you're going to need to do is make the DLL function that you're calling return you a character array or like a wide character array. 53 00:06:01,860 --> 00:06:09,000 And PowerShell can then take that pointer and marshal it into managed memory and then it can serialize that and send it back. 54 00:06:09,100 --> 00:06:11,300 So it's just a small change that you need to make. 55 00:06:11,300 --> 00:06:27,400 And on my blog, I actually have a post showing how it takes about maybe five minutes to change Mimikatz from outputting using standard out to outputting to a string that can be, you know, retrieved by PowerShell. 56 00:06:30,540 --> 00:06:42,240 So reflectively loading an EXE is a little more tricky because normally when an EXE is loaded, it's loaded using create process and a new process is created specifically for that EXE. 57 00:06:42,380 --> 00:06:49,900 But what we're doing is loading an EXE inside of a process that already exists with PowerShell.EXE running inside of it. 58 00:06:50,120 --> 00:07:03,680 So as an example, normally when an EXE exits, so the EXE that you reflectively loads ‑‑ that you reflectively loaded runs and quits, it's going to go and call exit process because it thinks everything is done. 59 00:07:03,980 --> 00:07:09,220 But the problem is that that's actually going to kill the PowerShell process because that's the process that's actually running. 60 00:07:09,600 --> 00:07:15,280 And that's not ideal because you probably wanted to see the output from the EXE that you just ran. 61 00:07:15,320 --> 00:07:17,140 And if PowerShell just dies, you can't. 62 00:07:17,580 --> 00:07:20,620 And so it turns out there's a pretty easy solution to this. 63 00:07:22,600 --> 00:07:26,980 Basically what I do is I call the EXE in its own thread. 64 00:07:27,040 --> 00:07:29,560 I call its entry function in a new thread. 65 00:07:29,720 --> 00:07:36,060 And then you overwrite the exit process function with shell code that just calls exit thread instead. 66 00:07:36,780 --> 00:07:42,740 And so the EXE will start up in its own thread and then when it's done running, it will just nicely kill the thread that you provided for it. 67 00:07:42,780 --> 00:07:44,280 So it's a really clean solution. 68 00:07:44,940 --> 00:07:49,840 And just for reference purposes, I put assembly in here, but we don't actually need to talk about this. 69 00:07:52,260 --> 00:07:52,780 Okay. 70 00:07:52,780 --> 00:07:59,100 So the other problem that I had reflectively loading an EXE is that you're going to want to pass it command line arguments. 71 00:07:59,660 --> 00:08:07,220 But the command line arguments that it's going to retrieve are the command line arguments that PowerShell started with because you're running inside the PowerShell process. 72 00:08:07,980 --> 00:08:22,220 And so it turns out that, well, the first thing I wanted to do was just go inside the process environment block, the PEB, which is where the command line arguments are stored, and just overwrite the string that stores the command line arguments. 73 00:08:22,220 --> 00:08:35,100 But that doesn't actually work because the way that EXE normally retrieves its command line arguments is it makes a function call to get command line or underscore, underscore get command line. 74 00:08:35,740 --> 00:08:38,960 And those are provided by two different DLLs. 75 00:08:39,200 --> 00:08:44,540 And when those DLLs are initially loaded, they actually cache a copy of the command line arguments. 76 00:08:44,640 --> 00:08:55,320 So if you just overwrite the PEB, you didn't overwrite the cached copy that's in the DLLs, and so you're still just going to end up getting the command line arguments that PowerShell was started with, which is no good. 77 00:08:56,140 --> 00:08:59,000 So I had to create solutions for both of these functions. 78 00:09:01,180 --> 00:09:03,740 So patching get command line is super easy. 79 00:09:04,280 --> 00:09:12,580 Get command line is a function that takes no arguments and it just returns you a string pointer to the command line arguments with no formatting or anything done. 80 00:09:12,680 --> 00:09:26,340 So all you need to do in PowerShell is just allocate a string on the heap and then overwrite the function call, get command line, with shell code that just returns the string, the address to the string that you allocated. 81 00:09:26,640 --> 00:09:29,000 So very, very simple, right? 82 00:09:30,340 --> 00:09:38,340 The next function, underscore, underscore get command line, is a little more complicated to overwrite. 83 00:09:38,460 --> 00:09:39,480 So I didn't want to do that. 84 00:09:39,480 --> 00:09:48,220 And the reason is it takes a number of parameters and this function actually goes and parses out your command line arguments into RdV and RdC. 85 00:09:48,320 --> 00:09:51,760 I didn't want to go through the trouble of parsing stuff if I didn't need to. 86 00:09:51,840 --> 00:10:00,460 But what I found is that the DLL that this function is exported from also exports two variables, A command line and W command line. 87 00:10:00,920 --> 00:10:03,300 And these variables are just string pointers. 88 00:10:03,480 --> 00:10:11,980 So if you go and overwrite these variables and then you call the get command line function, it will parse the variables that you just overwrote. 89 00:10:11,980 --> 00:10:19,620 So that's an even easier solution than the previous one because you just replace those string pointers with pointers to strings that you allocate. 90 00:10:23,640 --> 00:10:24,540 Okay. 91 00:10:25,160 --> 00:10:30,280 So what about remote reflective DLL injection? 92 00:10:30,380 --> 00:10:31,620 That's possible, too. 93 00:10:31,620 --> 00:10:41,780 It's a little more painful to do it in PowerShell compared to C or C++, which is what some of the current remote reflectors are written in. 94 00:10:42,140 --> 00:10:45,240 But it turns out it's still very possible to do. 95 00:10:45,440 --> 00:10:53,440 So the general process that I used for this is I allocate memory in both the remote process and the current process. 96 00:10:54,040 --> 00:10:59,620 And then I load the DLL into the current process, so into PowerShell.exe. 97 00:11:00,460 --> 00:11:05,240 And I'm pretty much just using PowerShell.exe to stage this DLL. 98 00:11:05,240 --> 00:11:09,080 So I want to get it ready before I write it into the remote process. 99 00:11:09,680 --> 00:11:18,020 And what that entails is I go and I look and see what DLLs does this DLL depend on, just like I did before, right? 100 00:11:18,180 --> 00:11:22,820 Except when I load those DLLs, I actually load them in the remote process. 101 00:11:23,020 --> 00:11:26,040 And I get the function addresses for everything in the remote process. 102 00:11:27,080 --> 00:11:39,340 And when you do your base relocations, similarly, you use the memory address for the memory you allocated in the remote process when you're doing your base relocation calculations. 103 00:11:40,600 --> 00:11:52,340 And once you've done all this, you end up with the DLL, which is currently in the PowerShell process, but all of the memory addresses have been patched as if it was being loaded in the remote process. 104 00:11:52,980 --> 00:12:00,920 So all you need to do is take those bytes and write them into the remote process, and the DLL is pretty much ready to go. 105 00:12:00,920 --> 00:12:03,960 You just give it its own thread, and it will start executing. 106 00:12:08,480 --> 00:12:21,180 So I just have, once again, more assembly here just for reference purposes for the shell code that you actually write into the remote process to call get ‑‑ or load library and get proc address. 107 00:12:31,950 --> 00:12:32,510 All right. 108 00:12:32,510 --> 00:12:32,990 Wait a second. 109 00:12:32,990 --> 00:12:34,650 How do we go back to that code? 110 00:12:34,650 --> 00:12:34,990 All right. 111 00:12:34,990 --> 00:12:35,530 Here we go. 112 00:12:35,530 --> 00:12:36,770 Everybody take notes. 113 00:12:37,770 --> 00:12:38,830 You got this? 114 00:12:46,700 --> 00:12:49,400 I know none of you understand why we're here. 115 00:12:52,460 --> 00:12:56,800 You know, it slays me how many new speakers there are at DEF CON this year. 116 00:12:56,800 --> 00:12:59,480 I'm beginning to think they're just saying that to fuck with us. 117 00:13:04,960 --> 00:13:06,020 Thank you, sir. 118 00:13:07,980 --> 00:13:11,120 And did we ‑‑ oh, we got one. 119 00:13:11,540 --> 00:13:11,780 Yes. 120 00:13:11,780 --> 00:13:12,440 What's your name? 121 00:13:12,440 --> 00:13:12,900 Rick. 122 00:13:12,940 --> 00:13:13,880 We got Rick. 123 00:13:13,880 --> 00:13:14,760 Rick, new attendee. 124 00:13:14,760 --> 00:13:16,420 He's representing all of you. 125 00:13:16,620 --> 00:13:17,240 All right. 126 00:13:17,300 --> 00:13:18,380 Cheers, gentlemen. 127 00:13:30,640 --> 00:13:31,920 We'll see you soon. 128 00:13:34,660 --> 00:13:36,020 Everybody got that code? 129 00:13:39,810 --> 00:13:42,230 Probably not the most interesting slide to leave up there. 130 00:13:43,370 --> 00:13:44,050 All right. 131 00:13:44,050 --> 00:13:46,070 So let's do some demos. 132 00:14:00,870 --> 00:14:01,470 Okay. 133 00:14:01,470 --> 00:14:03,410 So I have two VMs here. 134 00:14:04,030 --> 00:14:07,350 This one with the gray background is a domain controller and it's going to be targeted. 135 00:14:08,310 --> 00:14:09,910 And then I have this demo client. 136 00:14:11,330 --> 00:14:13,470 Let's see if I can resize these windows. 137 00:14:14,370 --> 00:14:15,470 There we go. 138 00:14:16,130 --> 00:14:19,310 There we go. 139 00:14:19,630 --> 00:14:20,530 All right. 140 00:14:21,550 --> 00:14:28,630 So just as an example of what you can do with this script, so I have this script here called invoke Mimikatz X64. 141 00:14:28,950 --> 00:14:37,570 All I did was just hard code the Mimikatz binary into the reflective PE injection script just so it's a little easier to use. 142 00:14:38,190 --> 00:14:42,310 So what I'm going to do here is you can supply computer name. 143 00:14:43,670 --> 00:14:47,310 And the computer name right now is DC1, which is my domain controller. 144 00:14:47,550 --> 00:14:59,330 Computer name is actually an array , though, so you can supply hundreds or thousands of computers to run this script against and PowerShell will do it all for you automatically, do the concurrency, blast the script off. 145 00:14:59,930 --> 00:15:01,510 So let's go ahead and run this. 146 00:15:01,790 --> 00:15:04,110 Hopefully it works because this is live. 147 00:15:07,030 --> 00:15:07,910 All right. 148 00:15:07,930 --> 00:15:09,750 And the output gets streamed back. 149 00:15:09,750 --> 00:15:11,990 So what just happened? 150 00:15:22,450 --> 00:15:23,170 All right. 151 00:15:23,170 --> 00:15:27,090 So it looks like the domain admin is running with the password 123. 152 00:15:27,130 --> 00:15:28,590 That's not very good. 153 00:15:29,730 --> 00:15:42,370 But anyway, so what this allows you to do is we just ran Mimikatz on a remote system without ever touching disk on that remote system, without ever triggering any application white listing products, without triggering antivirus. 154 00:15:42,970 --> 00:15:51,670 As far as the domain controller is concerned, all that happened was someone made a remote PowerShell connection to the server and ran a script that we now have no record of. 155 00:15:52,050 --> 00:15:53,610 That's pretty awesome, I think. 156 00:15:55,030 --> 00:16:01,070 So this next script, this is called invoke Ninja copy and this is online, too. 157 00:16:01,930 --> 00:16:07,170 So a little while ago I saw a blog post by someone who wrote a tool called SamX. 158 00:16:07,170 --> 00:16:24,270 Basically their observation was by default not even an administrator can make , can access certain files on a Windows system like the registry hive or the NTDS active directory database because LSAS has a lock on the file. 159 00:16:24,270 --> 00:16:27,890 So LSAS is the only process that is allowed to have the file open. 160 00:16:28,310 --> 00:16:45,850 However, if you know how to parse NTFS, an administrator can get a read handle to the C volume and then just read the NTFS structures on the C volume or D volume and scan to the exact position where the bytes that make up that file are located and read them off. 161 00:16:46,410 --> 00:16:48,050 So I thought that was pretty awesome. 162 00:16:48,950 --> 00:16:58,170 And I kind of wanted to write that in PowerShell but then I realized that someone had already written an open source NTFS parser in C++ and put it on CodePlex. 163 00:16:58,710 --> 00:17:05,210 So I figured maybe I should just use that and reflectively load it and save my time parsing NTFS on my own. 164 00:17:05,630 --> 00:17:13,310 And that's really the beauty of this script is that you can just take these tools that other people have written and then just turn them into PowerShell scripts that are super sneaky all of a sudden. 165 00:17:14,250 --> 00:17:16,270 And so that's what we're going to do here. 166 00:17:16,270 --> 00:17:19,070 So I'm actually, I'll go to the domain controller really quick. 167 00:17:19,810 --> 00:17:20,530 All right. 168 00:17:20,530 --> 00:17:21,290 Great background. 169 00:17:21,290 --> 00:17:22,570 We're on the domain controller. 170 00:17:22,750 --> 00:17:24,930 And here's the NTDS.dit file. 171 00:17:25,150 --> 00:17:26,830 And I'm going to try to open it. 172 00:17:28,430 --> 00:17:30,510 Yeah, let's try to open it with notepad. 173 00:17:30,850 --> 00:17:36,930 And we're going to get this error message saying the process cannot access the file because it is being used by another process. 174 00:17:37,050 --> 00:17:40,210 So even though I'm domain admin, I can't get this file. 175 00:17:40,230 --> 00:17:40,950 Bummer. 176 00:17:41,150 --> 00:17:42,410 I really wanted it. 177 00:17:44,370 --> 00:17:45,010 Okay. 178 00:17:45,010 --> 00:17:46,990 So let's go back to the demo client. 179 00:17:47,630 --> 00:17:53,930 And we're just going to go ahead and remote PowerShell into the domain controller. 180 00:17:54,270 --> 00:17:57,630 And I have here the file path, the NTDS file. 181 00:17:57,730 --> 00:18:00,710 And I'm going to copy it to the desktop of my attack box. 182 00:18:03,210 --> 00:18:06,130 And we'll go ahead and run this guy. 183 00:18:09,220 --> 00:18:17,240 You can see from the output here, it's just going to copy 5 megabytes at a time of this file directly to the desktop. 184 00:18:18,280 --> 00:18:24,760 And if I go ahead and open my desktop, we now have a copy of the NTDS Active Directory database file. 185 00:18:24,760 --> 00:18:29,760 And once again ‑‑ so 186 00:18:35,260 --> 00:18:37,940 once again, no suspicious processes were run. 187 00:18:37,940 --> 00:18:41,300 The only thing that ever touched disk ‑‑ well, nothing touched disk. 188 00:18:41,900 --> 00:18:43,640 This was all done in memory. 189 00:18:43,780 --> 00:18:46,200 And we streamed the file back over the wire. 190 00:18:46,440 --> 00:18:47,500 So that's pretty awesome. 191 00:18:47,500 --> 00:18:51,340 There's very little evidence left behind that an attacker ever did something here. 192 00:18:54,430 --> 00:19:05,150 And just for reference, it took me maybe a couple hours to turn the NTFS parser into a DLL that I could use with my script and, you know, good to go. 193 00:19:05,150 --> 00:19:13,030 So it's really, really fast to turn things into PowerShell ‑‑ turn arbitrary programs into PowerShell scripts that are really hard to catch. 194 00:19:18,420 --> 00:19:19,000 Okay. 195 00:19:19,000 --> 00:19:22,860 So the obligatory detection and prevention slide. 196 00:19:23,780 --> 00:19:29,220 So there's not a whole lot to say here. 197 00:19:29,560 --> 00:19:33,860 PowerShell remoting requires administrator access and it requires open ports. 198 00:19:34,040 --> 00:19:44,440 So if you don't want someone to do this, then don't let them have admin access on really sensitive servers of yours and don't let them have network connectivity to really sensitive servers of yours. 199 00:19:44,520 --> 00:19:52,800 Standard stuff like firewalls, limiting powerful accounts, right, like lock down your accounts, make sure that I can't just remote PowerShell into every server from any server. 200 00:19:53,200 --> 00:19:55,200 And it helps defend against this. 201 00:19:55,680 --> 00:19:59,500 Of course, nobody does that, but ‑‑ or maybe some people do that. 202 00:20:00,860 --> 00:20:11,220 PowerShell V3 has pipeline logging, which might help detect this, but it would be really easy for an attacker to turn it off or clear the logs if they wanted to. 203 00:20:12,360 --> 00:20:15,340 And there's also going to be a lot of noise if you actually use PowerShell. 204 00:20:16,380 --> 00:20:18,960 Constrained run spaces can help limit the power of PowerShell. 205 00:20:18,960 --> 00:20:25,680 So you can say even though someone's allowed to remote PowerShell into the server, there's only specific commands they're allowed to run. 206 00:20:25,800 --> 00:20:28,080 So I don't have full access to the Win32 API. 207 00:20:28,080 --> 00:20:31,540 I only have access to these 50 commandlets that are deemed to be safe. 208 00:20:32,780 --> 00:20:38,540 And another one is machine‑wide profile to log actions to a transcript. 209 00:20:38,920 --> 00:20:42,200 Once again, super easy for an attacker to turn this off. 210 00:20:42,200 --> 00:20:47,740 But if an attacker doesn't know that you're doing this, then you might be able to record what they're doing. 211 00:20:50,740 --> 00:20:53,420 So closing thoughts, this is not a vulnerability. 212 00:20:53,860 --> 00:20:55,460 All of this is by design. 213 00:20:55,760 --> 00:20:57,560 PowerShell is a very powerful language. 214 00:20:57,560 --> 00:20:59,880 It has full access to the Win32 API. 215 00:20:59,960 --> 00:21:02,220 It has full access to the .NET libraries. 216 00:21:02,360 --> 00:21:05,120 So yes, it is able to do this. 217 00:21:05,120 --> 00:21:10,460 And pretty much any programming language can do this if it has access to the Win32 API. 218 00:21:10,920 --> 00:21:17,800 And if a programming language has remoting functionality, then you can, you know, use the remoting functionality of other programming languages as well. 219 00:21:18,640 --> 00:21:21,320 I think PowerShell is a great way to manage Windows systems. 220 00:21:21,320 --> 00:21:36,080 So I hope this talk doesn't scare you away from PowerShell altogether, but I hope that it does encourage you to treat PowerShell sensitively and make sure that you configure it correctly, don't expose, you know, PowerShell to all the servers in your environment. 221 00:21:37,140 --> 00:21:42,440 And hopefully it also gives you awesome ideas on how to attack Windows networks, because that's kind of why I'm here. 222 00:21:43,700 --> 00:21:55,920 So I've got some references to helpful documentation, Microsoft documents, a lot about how PE loading and DLL loading works, links to some other reflective loaders, like Metasploit has a reflective loader, for example. 223 00:21:56,120 --> 00:21:59,040 That's how it does migration and whatnot. 224 00:21:59,200 --> 00:22:03,540 And then some really good PowerShell-related blogs, Exploit Monday especially is super good. 225 00:22:03,540 --> 00:22:08,140 Matt Graber has really great documentation on how to do awesome stuff in PowerShell. 226 00:22:09,720 --> 00:22:14,440 And just links, once again, to my blog and my Twitter and GitHub. 227 00:22:15,040 --> 00:22:16,600 And that's all I've got for you.