[00:00.000 --> 00:05.660] All right, we're back live. Thank you, Liron, for your support and supporting DEF CON and the [00:05.660 --> 00:10.860] Red Team Village. It's a pleasure to have you here. So without further ado, take it away. [00:11.440 --> 00:18.920] All right, thank you. So thank you all for coming to this talk. It's my first time presenting at [00:18.920 --> 00:23.900] DEF CON. I think last year I got caught up in the DEF CON Blue Team Village trying to win that CTF [00:23.900 --> 00:29.660] from OpenSOC. It was pretty dope. But I think this time today, especially, we're going to talk about [00:30.600 --> 00:33.980] something more Red Team-ish. Why? Because I'm on the Red Team, and it just seems [00:34.360 --> 00:39.760] appropriate. So this is the talk, y'all trying to bypass Python 3.8 autohooks or nah? [00:43.270 --> 00:49.090] So who am I? My name is Liron Gray. I go by DaddyCocoman in most areas. You can check out [00:49.090 --> 00:54.910] my website, DaddyCocoman.dev. I haven't updated it in a while, but there's some cool stuff there, [00:54.910 --> 00:59.610] I think. You can also check out my GitHub. I have a couple of projects I've been working on, [00:59.610 --> 01:05.110] and hopefully I might have some more interesting projects up there very shortly. I was in the Navy [01:05.110 --> 01:11.050] for about ten years. I used to do electronics. I used to work on radar and communication systems, [01:11.050 --> 01:16.090] and I moved over into the cyber field. And I'm on the Microsoft Azure Red Team now. [01:16.130 --> 01:21.310] So you may hear some Azure-related things if you follow me on Twitter or wherever you may [01:21.310 --> 01:25.390] follow me. Hopefully it's only on Twitter and not anywhere else like real life. [01:25.390 --> 01:31.070] I also love Python and Pythonic things. So we'll talk about some other Pythonic [01:31.070 --> 01:37.070] things later in this talk that I really got into at a certain point in time. [01:38.190 --> 01:43.990] Because it was... it's pretty cool. I like the way Python is written as a language, [01:43.990 --> 01:49.450] more so than statically compiled languages. And I'm also a Nerdcore rapper. I go by OhMy. [01:49.450 --> 01:56.990] It's an I, not a one. You can find my music at MCOhMy.com. I'm actually on the DEF CON 28 tape. [01:56.990 --> 02:02.390] I got that hot track. I got that heat. Xs and Ram. If you want to listen to that, [02:03.230 --> 02:10.370] please do. And hopefully you enjoy all the music. So let's talk about the agenda. So first let's [02:10.370 --> 02:15.350] talk about why we're using Python 3. Then we'll talk about the two peps that have influenced [02:15.350 --> 02:21.290] audit hooks for Python 3.8. We'll talk about how to implement them, how to bypass them for Windows, [02:21.290 --> 02:28.710] how to bypass them for Linux, and how to bypass Python. And bypass Python is very... it's in [02:28.710 --> 02:33.450] quotes for a reason. But we'll talk about some interesting concepts that came up over the last [02:33.450 --> 02:42.090] year. So quick question. Why are you still using Python 2? I want you to look every single one of [02:42.090 --> 02:47.710] these people in the face and convince them that they have the time to listen to you explain why [02:47.710 --> 03:01.940] you're still using Python 2. I'll wait. All right. So you might have a legitimate reason, right? [03:01.940 --> 03:07.520] Sometimes there are tools out there that haven't been updated in the last 10 years or whatever. [03:07.600 --> 03:13.320] And if you are using something that is written in Python 2, I just suggest making a pull request, [03:13.320 --> 03:19.760] helping them move up to Python 3, and try not to break that backward compatibility if you really [03:19.760 --> 03:26.780] need it. So here's some Python 3 changes that have happened in the last couple of versions. [03:26.780 --> 03:33.620] Python 3.5, we had the async, await keywords that came out. Python 3.6, we had format strings. [03:33.620 --> 03:38.600] So you could say things like, hey, my name is Daddy Coco Man. And then you could print my name [03:38.600 --> 03:45.080] is in Curly Brace's name, and you would get my name is Daddy Coco Man. We also had ordered [03:45.080 --> 03:51.820] dictionaries. So now Python recognizes the insertion order for dictionaries, and you can [03:51.820 --> 03:56.540] always keep track of it. Previously, dictionaries would just be a random result every time you would [03:56.540 --> 04:03.040] try to print them or call them. But now they're ordered, which is great. In Python 3.7, async and [04:03.040 --> 04:07.600] await actually became reserved keywords. We had the breakpoint functions, so we could enter [04:07.600 --> 04:12.000] debuggers in the middle of our scripts. And we had the data class decorators, so we could make [04:12.000 --> 04:18.500] these struct-like classes. So you can say, here are these fields that a class is supposed to have, [04:18.500 --> 04:23.820] and here are some functions that it may want to implement, just like classes, but a little bit [04:23.820 --> 04:29.280] better, in my opinion. They serve different purposes. So for Python 3.8, we have this [04:29.280 --> 04:34.380] controversial thing called the walrus operator, where you can assign values while evaluating an [04:34.380 --> 04:41.840] expression. So on something like this, we can say, it reads, if the length of a is greater than 10, [04:41.840 --> 04:49.860] then we can assign it to n, and then be moving to the loop logic for print, list is too long, [04:49.860 --> 04:55.520] blah, blah, blah. But if length of a was not equal to 10, then we wouldn't assign it to n, [04:55.520 --> 05:00.480] and we would just move on. So it's nice, it saves you a line of code. A lot of Python purists [05:00.480 --> 05:05.520] really don't like it, and that's okay. People generally don't like changes to languages that [05:05.520 --> 05:11.980] they've been using for so long, but oh, well. So next, we have the async IO enabled REPL, [05:11.980 --> 05:18.520] so you can do Python dash M, async IO, and you can start using those await and async keywords [05:18.520 --> 05:24.700] inside of your REPL. And what we're here to talk about primarily are these audit hooks. [05:24.700 --> 05:30.280] So audit hooks are runtime events, are hooking those runtime events to perform some action, [05:30.280 --> 05:35.980] some arbitrary action, whatever you want to do. So the first pep we'll talk about explains how [05:37.360 --> 05:41.720] the audit hooks are supposed to be implemented, or how they're supposed to work, why you should [05:41.720 --> 05:48.260] have them. So the proposal was that sysadmins could use audit hooks in order to add some feature [05:48.260 --> 05:56.320] to their Python executable. So some suggestions that came in were integrating AMSI, script block [05:56.920 --> 06:05.120] for Windows, and then for Linux, you could send events to syslog or audit D. And [06:06.840 --> 06:11.340] if you... I meant to link it in here. I guess I didn't. I must have removed it by accident. [06:11.340 --> 06:18.840] There is a GitHub, GitHub.com slash ZOOBA, Z-O-O-B-A, from Steve Dower, who has a bunch of [06:19.960 --> 06:24.860] examples of what these audit hooks can look like in different implementations. So I would [06:24.860 --> 06:30.040] definitely check that out. Another thing you can do is restrict the importable modules. So you can [06:30.040 --> 06:36.380] say, well, maybe I need my people who are using Python in my environment to use the request [06:36.380 --> 06:43.300] module, but maybe for whatever reason, they don't need to use the mmap module. So you can say, [06:43.300 --> 06:48.700] well, I want them to use specific modules that are only for my environment, and I don't want [06:48.700 --> 06:53.620] them to use anything else. You can also do things like remove command line arguments. So before I [06:53.620 --> 07:00.460] mentioned with async IO or just a lot of other command line Python modules like HTTP server, [07:00.460 --> 07:07.020] you can actually remove that dash M or the dash C to prevent people from using those switches. [07:08.420 --> 07:13.240] But one thing to keep in mind is that the PEP actually reads that you should [07:13.820 --> 07:18.220] detect the code over preventing the code. And the reason for that is because [07:21.080 --> 07:27.880] sometimes you may use the Python dependency tree graph is out of control sometimes, right? You may [07:27.880 --> 07:34.380] try to use the NumPy module, and underneath it, it uses C types. So if you try to block C types [07:34.380 --> 07:38.920] completely because you didn't want someone importing it, you may actually mess up using NumPy. [07:38.920 --> 07:44.600] So it's important to just detect what's occurring rather than prevent what's occurring because you [07:44.600 --> 07:51.180] might mess up something that you had no idea was going to go wrong. Another thing that the PEP [07:51.180 --> 07:55.800] recommends you do is implement OS security level features to protect your modified Python. [07:55.800 --> 08:01.640] So that means device guard for Windows, maybe do some exe signing, use catalog files, that kind of [08:01.640 --> 08:07.720] stuff, and extended attributes for Unix. So maybe you want to add file attributes that have the [08:07.720 --> 08:16.600] hashes of the permitted Python files that are allowed to be used. PEP 578 says here's how we're [08:16.600 --> 08:22.320] going to implement or how you can implement these audit hooks. And they can be implemented inside [08:22.320 --> 08:30.080] the executable or inside of the script. So if you want to do it in C, you can actually recompile a [08:30.080 --> 08:37.060] new executable, a new Python.exe, and all the hooks that you apply to that code will be run by this [08:37.060 --> 08:45.780] new exe. Or if you want to just do it in Python inside of your script, you can, without compiling [08:45.780 --> 08:51.760] it, you can use the sys module, so import sys, and then sys.addAuditHook, and then the audit hook [08:51.760 --> 08:58.400] will be a function where you can handle all of your audit hook logic the same way that you can do it [08:58.400 --> 09:06.060] in C, except it will only apply to the script where it's implemented. So here's two examples. [09:06.060 --> 09:12.460] On the left side, we have the Python implementation of network prompt hook, right? We have, this is [09:12.460 --> 09:22.120] some bad Python, so ignore this, but we have an event that gets passed into the function, [09:22.120 --> 09:26.340] and then we're looking at this event and we're saying, does it equal socket.getAddressInfo? [09:26.340 --> 09:30.340] If so, I want to take some action. I want to warn the user that we're attempting to resolve [09:30.340 --> 09:36.480] some URL. If it's socket.connect, we're saying we're attempting to connect to this URL. Do you [09:36.480 --> 09:41.940] want to continue? On the right side, we have the C version, which you can use to compile [09:42.560 --> 09:48.060] a new Python executable. Notice it's considerably longer, and personally, I never really learned C [09:48.060 --> 09:55.200] and I don't really plan on writing C any time soon, so good luck to you if that's your thing. [09:55.900 --> 10:01.020] So here's a list of audit events that are currently implemented in Python 3.8. You can [10:01.020 --> 10:08.260] actually pull this from the documentation itself, but what I have highlighted are some of the [10:08.260 --> 10:13.840] things that you may really be interested in if you see these events occurring. Now, keep in mind [10:13.840 --> 10:22.200] that you have to modify this for your particular environment, but some things that you may find [10:22.200 --> 10:29.420] interesting are when if a script uses the compile function or the exec function or maybe some of [10:29.420 --> 10:33.780] these winreg functions on the right, things that you may not normally see in your environment, [10:33.780 --> 10:40.800] you have to be able to adjust your audit hooks appropriately. Pty.spawn, I don't know a single [10:40.800 --> 10:47.400] case of anyone using pty.spawn outside of trying to get a shell after they've already compromised [10:47.400 --> 10:53.560] your system. So things like that may be of interest to you, and you may want to capture those events [10:54.280 --> 11:01.080] as they occur. But again, the focus is on detection and not prevention. So we wanted to [11:01.080 --> 11:04.780] make sure that we are aware of these things that are happening, but we don't want to actually [11:04.780 --> 11:11.560] prevent them from happening unless you have a really good use case or use scenario to not [11:12.120 --> 11:18.000] let a particular event happen. I mean, you can totally do. You can block all events if you want [11:18.000 --> 11:23.820] to. It's not like a fake Python just sitting there. I don't know, honeypot Python or whatever. [11:24.620 --> 11:28.920] So bypassing audit hooks for Windows. So first we'll talk about how auditing works. [11:29.180 --> 11:36.620] So when an action is or when an audit hook is signaled, Python determines whether the action [11:36.620 --> 11:42.880] matches an audit rule. And that's kind of maybe not the best explanation. What happens is it will [11:42.880 --> 11:49.300] run through every single audit event or audit hook that you have, but you have to write your audit [11:49.300 --> 11:54.800] hooks to only care about the events that you want. So for example, in this network prompt hook, we [11:54.800 --> 11:59.580] only care about socket events. If it doesn't equal socket, then we're going to return because we don't [11:59.580 --> 12:07.900] want the rest of the hook to go through all that logic. So when you write your audit hooks, [12:07.900 --> 12:12.480] you have to make sure that you only process the events that you care about. Also, you probably [12:12.480 --> 12:19.320] shouldn't do anything like a while loop, some unbreakable condition, which is not a good idea. [12:19.820 --> 12:25.000] So the other thing to know is that all audit hooks are processed for all events. So even if I open a [12:25.000 --> 12:30.660] file, which is an audit hook event open, the open function, it's still going to get passed to this [12:30.660 --> 12:34.500] network prompt hook, but it won't do anything because I set a condition to only care about [12:34.500 --> 12:42.960] socket. So what's in the source code to figure out how these things work? So on the left side, [12:42.960 --> 12:48.940] we have the PySys add audit hook function. And what it does when it adds an audit hook, [12:48.940 --> 12:56.020] it literally just adds the hook to the end of a linked list. When the hook is called in the middle [12:56.020 --> 13:03.420] or the upper right, it will actually just go through each hook that has been defined and [13:03.940 --> 13:08.040] just goes through all those functions and determines whether or not it needs to do anything. [13:08.820 --> 13:14.900] Or based on... it will... you decide whether or not it needs to do anything, but it will just run all [13:14.900 --> 13:22.020] those functions at once. In order, I should say. On the right side, we also see that [13:22.020 --> 13:27.900] tracing is disallowed. Now, what I originally thought this meant was that I could not see into [13:27.900 --> 13:34.160] what an audit hook looks like. But it turns out that that tracing set to false is only for the [13:34.160 --> 13:40.120] Python debugger. So if you're using the Python debugger, for whatever reason, maybe in your IDE [13:40.120 --> 13:47.080] or maybe on the command line, you actually can't trace into the audit hooks. But if you're using [13:47.080 --> 13:52.460] an external debugger, so let's say maybe you grabbed a copy of a modified Python off of a [13:52.460 --> 13:57.520] system and you took it back to your station and you opened up a debugger, you can actually see [13:57.520 --> 14:02.420] what's going on. So here's an example, right? The same example where we had the socket.getaddress [14:02.420 --> 14:07.680] info. I can actually look into this audit hook to determine what it's doing. I'm not the greatest at [14:07.680 --> 14:12.880] assembly, and so I know what this says simply because I know what the code says. But you can [14:12.880 --> 14:18.100] still get a general idea. You can see the comparison strings, the compare, spython.string [14:18.100 --> 14:23.160] compare, and you can see that it's looking for certain audit hooks to occur before moving into [14:23.160 --> 14:30.100] some other logic. And you can also see the strings there attempt to resolve the URL that it [14:30.100 --> 14:38.900] tried to resolve. So here are some ideas for bypassing. By implementation, audit hooks can't [14:38.900 --> 14:44.180] be cleared once they're set. So there is a function, I think, to clear audit hooks, I think, [14:44.180 --> 14:48.740] but it doesn't actually work. You also can't overwrite. You can't, like, reload the sys module [14:48.740 --> 14:55.840] in order to clear all the audit hooks either. So one thing we could do is maybe overwrite the head [14:55.840 --> 15:03.020] of the linked list to point to null, but it might be possible. And attempting to locate the start of [15:03.120 --> 15:08.180] a linked list in memory might be a little bit difficult. But I'm actually really new to exploit [15:08.180 --> 15:12.180] development, so it might be possible. So if someone knows something I don't, please tell me, [15:12.180 --> 15:16.840] and maybe we'll figure that out. But in the meantime, maybe we can just overwrite the bytes [15:16.840 --> 15:25.140] in memory that calls the hook functions. And that smells like PowerShell, right? Everybody [15:25.140 --> 15:31.000] knows PowerShell and AMSI bypassing, and we can actually apply the same concept here. [15:31.040 --> 15:37.040] So there's plenty of resources to draw inspiration from. Adam Chester over at MDSec has plenty of [15:37.040 --> 15:43.760] articles about bypassing AMSI, so does RastaMouse, and there's a lot of inspiration just to draw [15:43.760 --> 15:49.920] just by reading their blogs. And it's also significantly easier to determine the address [15:49.920 --> 15:54.940] that you need to overwrite with these new bytes that you want to overwrite with. [15:55.400 --> 16:04.280] So let's go that way. So if I take the Python 3.8 DLL, and I open it up, or if I run Python, [16:04.280 --> 16:10.160] rather, this modified Python, and I go check out the Python 3.8 DLL, I can go find the address [16:10.160 --> 16:16.860] for PySysAudit, and we can see this is the 32-bit version. We can see the address and set [16:16.960 --> 16:24.100] a breakpoint on it. If I scroll further down, I'll get to a point where a function is called, [16:24.100 --> 16:30.860] and under call EAX, it has the name of the hook that is being called, right? So if I were to step [16:30.860 --> 16:39.240] into that call, I would actually see the logic that I showed you earlier. So the offending, [16:39.240 --> 16:47.260] I say offending, but the interesting call that we want to overwrite is where EIP is currently [16:47.260 --> 16:53.480] pointing, and that is call EAX for 32-bit, and call keyword pointer for 64-bit. [16:57.370 --> 17:02.370] So, again, it's very similar to PowerShell. We need to load the Python 3.8 DLL, we need to find [17:02.370 --> 17:07.550] the git address for PySysAudit, we need to change the memory permissions, and we need to just [17:07.550 --> 17:17.330] overwrite those bytes. And I learned this week, a couple of days ago, that I really need to go [17:17.330 --> 17:22.890] through all these versions of Python and grab their offsets. So I went through the 32 and 64-bit [17:22.890 --> 17:31.810] versions of Python, from 3.8 to 3.85, and even just for poops and laughter, I went to 3.9.0 [17:32.570 --> 17:38.090] Beta 5, just to see if it still worked, and it does. So we just have to set these offsets, [17:38.090 --> 17:44.170] and we can change the memory permissions and overwrite those offsets. [17:45.210 --> 17:53.030] Let's talk about Linux real quick. So I have never done any sort of, like, major [17:54.570 --> 17:58.990] Linux exploitation other than, like, a buffer overflow, right, that you find in, like, CTFs. [17:58.990 --> 18:05.090] I've never done any patching, so I didn't fully understand at the time how the Python [18:07.290 --> 18:14.410] packages worked on Linux. So there are no DLLs, right, because it's not Windows, [18:14.410 --> 18:21.410] and there was no sysmodule.so library that I thought was going to be there, and it just wasn't. [18:21.410 --> 18:27.330] So it's like the same, though, right? You can find the PySysAudit address by searching the Python [18:27.330 --> 18:32.150] executable itself. So I guess it's statically compiled into the binary, and you can just pull [18:32.150 --> 18:41.710] it out. So if you use the readelf, we can use readelf to figure out what the address of PySysAudit [18:41.710 --> 18:49.630] is, and we're going to cheat a bit, because I actually don't know how to parse ELF files. [18:49.630 --> 18:57.850] So in my code, I just subprocess readelf and grab the output, because I actually have no [18:57.850 --> 19:03.090] idea how to do it otherwise. And then we have to locate this address in memory. [19:03.650 --> 19:09.890] So once we grab that address here, it's 53007 foxtrot 0. We need to figure out where it's [19:09.890 --> 19:15.710] located and what region of memory that it's mapped to. And in this picture, we can see that it's [19:15.710 --> 19:25.530] under... it falls in the range of F230 and 6AE. So we need to modify those permissions. [19:26.070 --> 19:32.390] And we can do that, of course, with libc and mprotect. So we need the write execute permissions [19:32.390 --> 19:38.870] to change and to read write execute permissions. And sometimes, yes, sometimes it just be like [19:38.870 --> 19:44.930] that. I am watching the Discord, by the way. So what this looks like is we load [19:44.930 --> 19:53.210] the DLL for libc. Or we load libc, rather, I should say. And then we find the readelf. We find the [19:53.210 --> 20:00.930] address of pysis audit. We find the location of the memory mapped files. And then we determine [20:00.930 --> 20:08.050] in what region it falls under. And then we change the permissions. And then we just overwrite the [20:09.810 --> 20:18.670] the bytes in memory with these nops. So one thing I didn't go too far in depth in Linux [20:18.670 --> 20:25.950] is because every build for Python in Linux is different. So it's... I don't have the time [20:26.450 --> 20:31.250] of the day. Like, I'm stuck at home. I only go out to the supermarket if I need to. [20:31.250 --> 20:36.250] But I still don't have the time in the day to go through all these Linux versions and try to [20:36.250 --> 20:44.190] find these offsets. So later today I'll probably put this code up on GitHub. And if you have a [20:44.190 --> 20:53.090] version of Linux where you want to bypass audit hooks for 3.8, then you can send a pull request [20:53.090 --> 21:00.040] and I will happily accept once it's been tested. Let's talk about detection. [21:01.120 --> 21:08.300] While everything that I've mentioned works, everything up until how... up until you patch [21:08.300 --> 21:15.040] the binaries... the bytes, rather, can be detected. So importing C types can be detected, [21:15.040 --> 21:20.680] importing platform, importing sys. Every single function call that you make can be detected [21:21.960 --> 21:29.920] until you hit that men move and you overwrite the bytes. The way to defeat this [21:30.040 --> 21:35.600] is ironically better audit hook logging. Because if you can detect that these things are occurring [21:35.600 --> 21:40.340] and you send them off and you ship them to your seam or however you... your Windows event logs [21:40.340 --> 21:44.860] or however you collect your logs and you see that these are occurring, well, then you have a better [21:44.860 --> 21:50.540] chance of understanding what's going on with your Python executable on a random given station. [21:51.100 --> 21:59.960] So, yes, it's sort of like... it's like running AMSI bypass in PowerShell where you might get [21:59.960 --> 22:07.320] caught. It's the same thing. So that is something you should probably implement better audit hooks. [22:07.400 --> 22:11.840] And it might be harder for someone who doesn't program in C to implement, right? I don't think [22:11.840 --> 22:17.600] most sys admins have a lot of C knowledge. I could be wrong. I could be naive. I've never [22:17.600 --> 22:23.240] been a sys admin. I have no idea. But it's probably not something that you come across [22:23.720 --> 22:30.140] in the general resume where you need 10 years of Adobe XD that came out last year. [22:30.660 --> 22:37.820] So it would be great if somebody came up with a template of audit options and an action to [22:37.820 --> 22:43.920] help sys admins compile those custom Python executables. But as I mentioned, I don't have [22:43.920 --> 22:50.600] the time. So it's not me. So I want to give a demo real quick. [22:57.770 --> 23:04.790] Hopefully we can all still see the screen that I have up. I think it should be working. [23:06.210 --> 23:09.790] A Discord confirmation would be great if you can see this terminal. [23:11.590 --> 23:13.210] We can see it. [23:15.760 --> 23:17.700] Or a confirmation from Omar. [23:18.320 --> 23:20.300] Yeah, we can see it. [23:25.180 --> 23:36.080] Thank you. Great. So I have this executable here. This sPython that's been compiled. [23:36.680 --> 23:43.360] And I'm just going to enter it directly. And I'm going to try to [23:45.120 --> 23:51.020] run this code. And all I want to do is get the status code [23:53.680 --> 23:57.260] of the Red Team Village.io. I just want to make sure it's up. Because Red Team Village [23:57.260 --> 24:00.800] has been doing a great job. And I want to make sure that their website is up. [24:00.880 --> 24:06.580] But if I do that, I get this warning saying you're attempting to resolve Red Team Village.io. [24:06.580 --> 24:09.980] Do you want to continue? Yes, I do want to continue. Yes, I do want to handle the socket [24:09.980 --> 24:15.380] event. Yes, I do want to connect to this. And finally, I can get the HTTP status code. [24:15.580 --> 24:21.740] That's great. But I don't want to have to go through all that again. So maybe I'll just do [24:21.740 --> 24:27.380] this audit bypass. And we'll see. We're getting the handle. I'm printing out all this arbitrary [24:27.380 --> 24:32.080] stuff that doesn't really need to be printed out. We get the audit address. We find the [24:32.080 --> 24:36.660] protections. Using the offsets, we find the region of memory that we need to overwrite. [24:36.660 --> 24:41.180] And we successfully change the memory. And if you ever played NBA Jam, like the video game [24:41.180 --> 24:46.280] of your life, this is the point in time where I say, boom, shakalaka! And we bypass the audit [24:46.280 --> 24:56.080] hooks. So now when I run the same code, again, we just get status code 200. And yay! Right? [24:56.080 --> 25:01.020] We win. We don't have to worry about the audit hooks and going through all that stuff again. [25:01.020 --> 25:16.500] So that's great. Let's get back to these slides. Whoops. I lost my cursor. There we go. [25:18.920 --> 25:25.300] So let's talk about bypassing Python. Because as I mentioned, everything up until that point [25:25.300 --> 25:33.280] where you overwrite those bytes can be detected in memory. Or detected by an audit hook. And [25:33.280 --> 25:39.240] that could be bad. Right? So let's take this a step further. So since the bypass is easy to detect, [25:39.240 --> 25:46.880] are there any other methods that we can use to overwrite or to bypass Python in a sense? Right? [25:46.880 --> 25:51.440] So there are other ways to load and patch DLLs. I've had conversations with smart people [25:51.440 --> 25:59.060] about Python internals. And they've suggested other ways to do it. So, you know, just loading [26:01.400 --> 26:05.280] DLLs to kernel 32 and virtual protect and all that stuff is one way to do it. But there are [26:05.280 --> 26:12.300] other ways to do it. But everything that you do that way has to be run in the context of the [26:12.300 --> 26:16.920] Python interpreter. So no matter what I do, or no matter what you do, rather, [26:18.300 --> 26:24.480] you can be detected. No matter how you do it. So the question is, can we run Python [26:24.980 --> 26:28.840] without running the Python executable? And obviously, the answer is yes. Because I [26:28.840 --> 26:32.580] wouldn't be asking you this question and wasting your time with that bit of the slide. [26:32.600 --> 26:38.060] So there's a package called Python net, which is a legitimate package. It's great. [26:38.060 --> 26:46.020] And it uses it allows you to access the .NET CLR from normal Python. It's not Iron Python, [26:46.020 --> 26:52.980] right? It's not Iron Python at all. Iron Python is an implementation of Python that is, [26:52.980 --> 27:00.700] as far as I'm aware, not even MSIL compliant. But the syntax is still similar to Iron Python. [27:01.760 --> 27:07.760] And as of May 2020, it supports Python 3.8. That's great. [27:08.500 --> 27:12.460] And my camera just went out. So I'm going to turn my camera off, if that's okay. [27:14.900 --> 27:22.980] So we have the here's some syntax, right? We can import CLR from collections that we import [27:22.980 --> 27:29.060] counter. And then we can access the CLR the same way that we do it in sort of Iron Python-ish ways, [27:29.060 --> 27:33.800] right? Where from system.diagnostics, we can import process, and we can actually list all [27:33.800 --> 27:41.980] the processes in a CLR manner. CLR-like syntax. Or .NET-like syntax, I should say. [27:43.200 --> 27:49.840] You can actually also embed Python in your .NET executable. So Python.NET comes with a .NET [27:49.840 --> 27:55.600] assembly called Python.runtime.dll. And that can be used with .NET languages to run Python. [27:56.160 --> 28:02.480] The Python types for the basic primitive types are actually written in C sharp and compiled as [28:02.480 --> 28:08.140] part of this assembly. The only requirement is that your path variable must include a directory [28:08.140 --> 28:12.440] with the Python runtime installed. So in this case, we're looking for Python 3.8.dll. [28:13.700 --> 28:17.660] So here's an example of what it looks like in C sharp. We have this main function, [28:17.660 --> 28:24.120] and then we are using the Python gill, and we can import modules that Python knows about, [28:24.120 --> 28:29.160] such as numpy. And when we run this function, we get this output to the right, [28:29.600 --> 28:34.940] and it's pretty cool that we can actually run Python inside of .NET. [28:36.980 --> 28:42.220] And we can use C sharp to write this, but I like boolang. The reason I like boolang is because [28:42.220 --> 28:47.340] of Bytebleeder and the Silent Trinity project, that C2 framework that he has. It's pretty dope. [28:47.340 --> 28:54.520] I've contributed to it in the past, and I really like boolang because it's very Pythonic. If you [28:54.520 --> 28:58.540] look at the code that's on the screen right now, it almost seems like it's Python, but it's not. [28:58.540 --> 29:04.900] It's boolang. Also, support Bytebleeder in his open source sponsorship program that he has. It's [29:05.860 --> 29:11.740] definitely worth investing in people who have helped you in your corporate endeavors for [29:11.740 --> 29:18.240] pentesting and retting. So what we need to do, as I mentioned before, is point the path variable [29:18.240 --> 29:25.080] to a Python installation folder, which gives me an idea, right? Because the path variable [29:25.700 --> 29:32.480] can be set to any folder on the disk or on a network share. So you can already see where I'm [29:32.480 --> 29:39.320] going with this, hopefully. Hmm. I tried to find a black one, but there are no black emojis. [29:39.840 --> 29:46.480] So we can do Python across a network share by simply by pointing the Python path to it. So in [29:46.480 --> 29:53.220] this case, I had a share called winwork slash Python 3.7. And this is 3.7 because at the time [29:53.220 --> 29:58.060] I was testing it with 3.7 before 3.8 came out. It still works with 3.8, so ignore that. [29:58.600 --> 30:02.040] And that's what it looks like, right? So the path variable can be set to [30:02.040 --> 30:08.620] the server slash share, just as long as that share points to a Python 3x installation. [30:09.820 --> 30:13.660] And this is what it looks like. So you can see all this SMB traffic, [30:13.660 --> 30:20.260] and you can clearly see that I'm loading Python 3.7 across a share. [30:20.260 --> 30:26.120] Now, this isn't like an abuse of anything, right? You can actually install Python when you install [30:26.120 --> 30:30.520] Python normally on a network share and have everybody access it. It's slower because you [30:30.520 --> 30:36.480] have to load those resources remotely, but you can actually do that. But if we can use SMB, [30:36.480 --> 30:44.660] that probably means we can also use WebDAV. And sure enough, we can actually load Python [30:44.660 --> 30:56.840] across WebDAV by setting the server and the port. So whack whack server at port, [30:56.840 --> 31:05.960] and SSL is optional. There's a registry setting in Windows that determines how you can authenticate [31:05.960 --> 31:13.960] with WebDAV, and you may have to set that in some environments. I was having issues with [31:13.960 --> 31:21.040] this earlier today, or earlier, I guess it was today, or last night, whatever. And something [31:21.040 --> 31:25.720] broke, and I couldn't figure out what it was. So I can't show you what it looks like going across [31:26.180 --> 31:31.600] a WebDAV server. But it works. Here are the packets. It worked at some point in time. [31:31.600 --> 31:38.300] I just need to figure out what happened. So we can also host our own repo to remotely [31:38.300 --> 31:42.160] load packages, right? We don't need to pip install anything on the target box, [31:42.160 --> 31:47.680] assuming that we need extra modules that aren't already there on the Python 3.8 installation. [31:47.820 --> 31:53.180] Empire did this by implementing a custom import hook that allows you to modify the logic and how [31:53.180 --> 31:57.100] modules are loaded. So it allows you to load things remotely, and if you want more information [31:57.100 --> 32:01.960] on that, you can check out Chris Ross's blog on in-memory Python imports, or you can check out [32:01.960 --> 32:10.700] the Sulink's GitHub repo for remote importer, and you can check that out. It's really interesting [32:10.700 --> 32:15.100] stuff. There's also another module out there called HTTP importer, or something like that, [32:15.100 --> 32:21.760] that allows you to import modules over HTTP directly. And of course, Empire was written [32:22.640 --> 32:27.520] a long time ago. I know a new company is picking up Empire, I think it was BC Security, and I think [32:27.520 --> 32:31.360] they recently released a tool called Starkiller that seems really interesting. You should probably [32:31.360 --> 32:38.360] go check that out. If you want to load a package remotely, you need to just have it in the standard [32:38.360 --> 32:44.080] format. So here's the request module, and I want to just import this. This is what it needs to look [32:44.080 --> 32:48.480] like. It needs to be the zip file. Inside it, you need to have the name of the package, and then [32:48.480 --> 32:52.660] inside a folder with the name of the package, and inside of that, you need to have all the files [32:52.660 --> 33:10.120] that you need for the package. Let's do a demo of that real quick. All right. So in this folder, [33:10.120 --> 33:17.100] I have some code called scarysnake.boo. Everybody who writes C Sharp tools likes [33:17.100 --> 33:22.060] to call their tools Sharp something, but no, this is Boo. It's about ghosts, so we're doing [33:22.060 --> 33:30.420] scarysnake. And so we can actually compile this. I wrote a little thing using the compiler that [33:30.420 --> 33:39.240] comes with Boo, and we can just compile it real quick. We'll also use ILRepack to package the [33:39.680 --> 33:46.620] the additional assemblies that we need. That includes the Boo.lang assembly and some other [33:46.620 --> 33:52.660] additional things. Most importantly, the Python runtime.dll that you see here. That's the most [33:52.660 --> 34:00.660] important one, right? And now we have this tool called scarysnake, and it tells me that the Python [34:00.660 --> 34:07.160] path must be set. All right? So I'm going to set this Python path [34:14.620 --> 34:18.640] by just passing it as an argument. Sorry, I had to go find it. [34:20.320 --> 34:24.620] And then we can do something like maybe enter interactive mode. [34:25.960 --> 34:29.500] And this is the output that we get. It's a little, well, it's basically, I tried to [34:29.500 --> 34:33.740] imitate the same output that Python gives you when you start the interpreter. [34:34.220 --> 34:38.940] It shows us where the path is. And again, if you were doing this remotely, it would be over [34:39.720 --> 34:48.420] WebDAV. I think I had set up in the past, I had set up a server in Azure, and the load time was [34:48.420 --> 34:54.480] about 30 seconds just to run Python. But if it takes that much time to further escalate or do [34:54.480 --> 34:58.340] whatever you need to do with the Python and not get caught, I think it's worth the investment. [34:58.340 --> 35:08.700] So we can do things like, here we go again. And we're going to print out the status code, [35:08.700 --> 35:18.120] of course, from red team village.io. And without changing the normal Python executable, [35:18.120 --> 35:23.860] we basically brought our own interpreter to the system, and we were able to run Python, [35:23.860 --> 35:28.440] using the Python that's on the system, be able to run Python without worrying about all the hooks. [35:28.440 --> 35:35.140] Once again, boom, shakalaka, I'm on fire. I'm not literally on fire. [35:36.480 --> 35:41.820] Don't call the fire department. So some of the other options that are here [35:41.820 --> 35:48.220] include the local file. So you can run a local file. You can also run a remote file. I forgot [35:48.220 --> 35:52.100] to actually test this this morning, so I don't know if it works. So I'm not going to actually [35:52.100 --> 35:57.900] do it. Because that would be embarrassing. But it has worked in the past of trying to [35:57.900 --> 36:06.060] run a remote file. So you basically download the Python file, right, from GitHub or wherever [36:06.060 --> 36:13.420] you have it hosted, and you just sort of run it in memory. So let's try something real quick, [36:13.420 --> 36:26.330] right? I forget which system I'm on sometimes. There we go. Windows terminal. Split pane. [36:26.330 --> 36:34.330] Great. So I'm going to try doing this, right? I want to import the request module. [36:35.110 --> 36:40.410] But I don't have the request module. Just to show you that [36:41.950 --> 36:50.390] it works, that I'm actually using the actual Python that's hosted on the system, [36:50.390 --> 36:57.130] I'm going to Python-npip. Which, by the way, you should always do Python-npip because if you have [36:57.130 --> 37:01.310] several different Python versions, you don't want to confuse yourself. So you should always [37:01.310 --> 37:06.790] specify the version of Python that you're using when you run things like pip. It's just good [37:06.790 --> 37:17.740] practice. And we're going to just pip install request. And if I've installed request, and now [37:17.740 --> 37:25.880] I can import it with no problem. So, again, you can definitely use the Python libraries [37:25.880 --> 37:32.140] that are on the system to do it. Or you can host your own remotely and sort of just pull from a [37:32.140 --> 37:36.460] remote repo without actually touching anything on the disk. It's really up to you and what's [37:36.460 --> 37:43.980] in the best case scenario for your immediate situation. But I think it's great that we can [37:43.980 --> 37:48.960] actually just do this in the first place. So, so far, just to reiterate, we've talked about [37:49.600 --> 37:57.640] how to bypass autohooks for Windows, how to bypass autohooks for Linux, and how to not run [37:57.640 --> 38:04.700] Python on a system without running the Python executable whatsoever. For detections, it's a [38:04.700 --> 38:10.200] little bit difficult because everything is in .NET. So you have the same problem of .NET detections [38:10.200 --> 38:16.800] that you would find in any other .NET binary. If you're collecting ECW logs somehow and you [38:16.800 --> 38:21.680] have the space for that, wow, that's impressive. But if you have that space, you'll actually see [38:21.680 --> 38:25.060] that the Python runtime is loaded. So if you're in an environment where you don't normally see [38:25.060 --> 38:29.780] that, that could be something you detect. In this case, because I wrote it in Boo, [38:30.260 --> 38:38.360] it actually also detects the Boo.lang binary. But you could write this in C Sharp if you really [38:38.360 --> 38:43.080] wanted to. If you wanted to be a conformist and write all your tools in C Sharp, you could do [38:43.080 --> 38:52.700] that. And obviously, you wouldn't have the Boo.lang. So in conclusion, Python autohooks are [38:52.700 --> 38:59.640] great when they're implemented correctly. You should protect your custom Python executables [38:59.640 --> 39:07.320] with some OS-level protections. You need to do something, right? The same way you do any [39:07.320 --> 39:12.880] protection of any other binaries on your device. You need to make sure that your Python executables [39:12.880 --> 39:16.700] are protected. Because if I can pull your executable off of the system and you have [39:16.700 --> 39:25.440] autohooks enabled, I can debug it and get a pretty good idea of what autohooks you've [39:25.440 --> 39:31.720] implemented just by looking at the code or looking at the assembly. And of course, that ties in with [39:31.720 --> 39:37.000] don't rely solely on Python to protect your Python. So even if you had all these autohooks, [39:37.000 --> 39:42.800] I can pull your executable off and do some other nonsense with it and figure out how to bypass [39:44.280 --> 39:52.840] your autohooks. So thanks to everyone who has had some influence on this talk. Steve Dower, [39:52.840 --> 39:57.260] who works at Microsoft, he's a Python core dev, and he's actually the one who's done most of the [39:57.260 --> 40:04.480] implementation work for autohooks. Bytebleeder, who got me into Boo.lang and embedded scripting, [40:04.480 --> 40:11.600] Chris Ross over at SpecterOps, worked on Empire, and Adam Chester.