WEBVTT

00:00.000 --> 00:02.100
All right, we're back live.

00:02.100 --> 00:06.640
Thank you, Liron, for your support and supporting DEF CON and the Red Team Village.

00:06.980 --> 00:08.620
It's a pleasure to have you here.

00:08.620 --> 00:10.860
So without further ado, take it away.

00:11.440 --> 00:12.980
All right, thank you.

00:13.220 --> 00:15.960
So thank you all for coming to this talk.

00:16.960 --> 00:19.840
It's my first time presenting at DEF CON.

00:20.000 --> 00:24.860
I think last year I got caught up in the DEF CON Blue Team Village trying to win that CTF from OpenSOC.

00:24.860 --> 00:25.940
It was pretty dope.

00:25.940 --> 00:32.180
But I think this time today, especially, we're going to talk about something more Red Team-ish.

00:32.180 --> 00:32.480
Why?

00:32.480 --> 00:35.080
Because I'm on the Red Team, and it just seems appropriate.

00:35.360 --> 00:39.760
So this is the talk, y'all trying to bypass Python 3.8 autohooks or nah?

00:43.270 --> 00:44.110
So who am I?

00:44.110 --> 00:45.290
My name is Liron Gray.

00:45.470 --> 00:48.270
I go by DaddyCocoman in most areas.

00:48.470 --> 00:51.650
You can check out my website, DaddyCocoman.dev.

00:51.970 --> 00:55.710
I haven't updated it in a while, but there's some cool stuff there, I think.

00:56.070 --> 00:57.470
You can also check out my GitHub.

00:57.470 --> 01:04.190
I have a couple of projects I've been working on, and hopefully I might have some more interesting projects up there very shortly.

01:04.470 --> 01:06.990
I was in the Navy for about ten years.

01:07.210 --> 01:08.870
I used to do electronics.

01:08.870 --> 01:13.010
I used to work on radar and communication systems, and I moved over into the cyber field.

01:13.630 --> 01:16.090
And I'm on the Microsoft Azure Red Team now.

01:16.130 --> 01:22.330
So you may hear some Azure-related things if you follow me on Twitter or wherever you may follow me.

01:22.330 --> 01:25.390
Hopefully it's only on Twitter and not anywhere else like real life.

01:25.390 --> 01:29.390
I also love Python and Pythonic things.

01:29.510 --> 01:37.070
So we'll talk about some other Pythonic things later in this talk that I really got into at a certain point in time.

01:38.190 --> 01:41.010
Because it was... it's pretty cool.

01:41.010 --> 01:46.530
I like the way Python is written as a language, more so than statically compiled languages.

01:46.630 --> 01:48.330
And I'm also a Nerdcore rapper.

01:48.330 --> 01:49.450
I go by OhMy.

01:49.450 --> 01:51.850
It's an I, not a one.

01:52.230 --> 01:54.810
You can find my music at MCOhMy.com.

01:54.810 --> 01:56.990
I'm actually on the DEF CON 28 tape.

01:56.990 --> 01:58.390
I got that hot track.

01:58.390 --> 01:59.470
I got that heat.

01:59.510 --> 02:00.790
Xs and Ram.

02:00.930 --> 02:04.370
If you want to listen to that, please do.

02:04.370 --> 02:07.370
And hopefully you enjoy all the music.

02:08.490 --> 02:09.850
So let's talk about the agenda.

02:09.850 --> 02:12.290
So first let's talk about why we're using Python 3.

02:12.290 --> 02:17.830
Then we'll talk about the two peps that have influenced audit hooks for Python 3.8.

02:17.990 --> 02:25.790
We'll talk about how to implement them, how to bypass them for Windows, how to bypass them for Linux, and how to bypass Python.

02:25.990 --> 02:30.050
And bypass Python is very... it's in quotes for a reason.

02:30.050 --> 02:34.190
But we'll talk about some interesting concepts that came up over the last year.

02:34.550 --> 02:36.170
So quick question.

02:37.410 --> 02:39.970
Why are you still using Python 2?

02:40.130 --> 02:49.270
I want you to look every single one of these people in the face and convince them that they have the time to listen to you explain why you're still using Python 2.

02:49.270 --> 02:50.130
I'll wait.

02:57.740 --> 02:58.420
All right.

02:58.420 --> 03:01.940
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:20.580
And if you are using something that is written in Python 2, I just suggest making a pull request, helping them move up to Python 3, and try not to break that backward compatibility if you really need it.

03:21.860 --> 03:26.780
So here's some Python 3 changes that have happened in the last couple of versions.

03:26.780 --> 03:31.480
Python 3.5, we had the async, await keywords that came out.

03:31.480 --> 03:33.620
Python 3.6, we had format strings.

03:33.620 --> 03:36.880
So you could say things like, hey, my name is Daddy Coco Man.

03:36.880 --> 03:43.920
And then you could print my name is in Curly Brace's name, and you would get my name is Daddy Coco Man.

03:44.320 --> 03:45.940
We also had ordered dictionaries.

03:45.940 --> 03:52.860
So now Python recognizes the insertion order for dictionaries, and you can always keep track of it.

03:52.860 --> 03:59.020
Previously, dictionaries would just be a random result every time you would try to print them or call them.

03:59.020 --> 04:00.700
But now they're ordered, which is great.

04:00.920 --> 04:05.060
In Python 3.7, async and await actually became reserved keywords.

04:05.580 --> 04:09.560
We had the breakpoint functions, so we could enter debuggers in the middle of our scripts.

04:09.680 --> 04:14.380
And we had the data class decorators, so we could make these struct-like classes.

04:14.380 --> 04:25.000
So you can say, here are these fields that a class is supposed to have, and here are some functions that it may want to implement, just like classes, but a little bit better, in my opinion.

04:25.540 --> 04:26.980
They serve different purposes.

04:27.360 --> 04:35.240
So for Python 3.8, we have this controversial thing called the walrus operator, where you can assign values while evaluating an expression.

04:35.420 --> 04:50.600
So on something like this, we can say, it reads, if the length of a is greater than 10, then we can assign it to n, and then be moving to the loop logic for print, list is too long, blah, blah, blah.

04:51.060 --> 04:56.980
But if length of a was not equal to 10, then we wouldn't assign it to n, and we would just move on.

04:56.980 --> 04:58.980
So it's nice, it saves you a line of code.

04:58.980 --> 05:02.560
A lot of Python purists really don't like it, and that's okay.

05:03.040 --> 05:08.560
People generally don't like changes to languages that they've been using for so long, but oh, well.

05:09.040 --> 05:20.800
So next, we have the async IO enabled REPL, so you can do Python dash M, async IO, and you can start using those await and async keywords inside of your REPL.

05:20.820 --> 05:24.700
And what we're here to talk about primarily are these audit hooks.

05:24.700 --> 05:32.380
So audit hooks are runtime events, are hooking those runtime events to perform some action, some arbitrary action, whatever you want to do.

05:33.160 --> 05:42.580
So the first pep we'll talk about explains how the audit hooks are supposed to be implemented, or how they're supposed to work, why you should have them.

05:42.740 --> 05:51.640
So the proposal was that sysadmins could use audit hooks in order to add some feature to their Python executable.

05:51.740 --> 06:04.480
So some suggestions that came in were integrating AMSI, script block for Windows, and then for Linux, you could send events to syslog or audit D.

06:04.880 --> 06:09.140
And if you... I meant to link it in here.

06:09.140 --> 06:09.860
I guess I didn't.

06:09.860 --> 06:11.340
I must have removed it by accident.

06:11.340 --> 06:24.600
There is a GitHub, GitHub.com slash ZOOBA, Z-O-O-B-A, from Steve Dower, who has a bunch of examples of what these audit hooks can look like in different implementations.

06:24.600 --> 06:26.160
So I would definitely check that out.

06:26.880 --> 06:29.700
Another thing you can do is restrict the importable modules.

06:29.700 --> 06:41.660
So you can say, well, maybe I need my people who are using Python in my environment to use the request module, but maybe for whatever reason, they don't need to use the mmap module.

06:41.740 --> 06:50.120
So you can say, well, I want them to use specific modules that are only for my environment, and I don't want them to use anything else.

06:50.240 --> 06:53.020
You can also do things like remove command line arguments.

06:53.020 --> 07:07.020
So before I mentioned with async IO or just a lot of other command line Python modules like HTTP server, you can actually remove that dash M or the dash C to prevent people from using those switches.

07:08.420 --> 07:16.340
But one thing to keep in mind is that the PEP actually reads that you should detect the code over preventing the code.

07:16.340 --> 07:27.400
And the reason for that is because sometimes you may use the Python dependency tree graph is out of control sometimes, right?

07:27.400 --> 07:33.000
You may try to use the NumPy module, and underneath it, it uses C types.

07:33.000 --> 07:38.920
So if you try to block C types completely because you didn't want someone importing it, you may actually mess up using NumPy.

07:38.920 --> 07:48.240
So it's important to just detect what's occurring rather than prevent what's occurring because you might mess up something that you had no idea was going to go wrong.

07:49.560 --> 07:55.800
Another thing that the PEP recommends you do is implement OS security level features to protect your modified Python.

07:55.800 --> 08:04.480
So that means device guard for Windows, maybe do some exe signing, use catalog files, that kind of stuff, and extended attributes for Unix.

08:04.480 --> 08:12.880
So maybe you want to add file attributes that have the hashes of the permitted Python files that are allowed to be used.

08:14.060 --> 08:20.780
PEP 578 says here's how we're going to implement or how you can implement these audit hooks.

08:20.780 --> 08:25.300
And they can be implemented inside the executable or inside of the script.

08:25.900 --> 08:37.980
So if you want to do it in C, you can actually recompile a new executable, a new Python.exe, and all the hooks that you apply to that code will be run by this new exe.

08:40.040 --> 08:59.460
Or if you want to just do it in Python inside of your script, you can, without compiling it, you can use the sys module, so import sys, and then sys.addAuditHook, and then the audit hook will be a function where you can handle all of your audit hook logic the same way that you can do it in C,

08:59.460 --> 09:03.440
except it will only apply to the script where it's implemented.

09:04.560 --> 09:06.060
So here's two examples.

09:06.060 --> 09:10.700
On the left side, we have the Python implementation of network prompt hook, right?

09:10.700 --> 09:26.340
We have, this is some bad Python, so ignore this, but we have an event that gets passed into the function, and then we're looking at this event and we're saying, does it equal socket.getAddressInfo?

09:26.340 --> 09:28.060
If so, I want to take some action.

09:28.060 --> 09:31.720
I want to warn the user that we're attempting to resolve some URL.

09:31.780 --> 09:36.300
If it's socket.connect, we're saying we're attempting to connect to this URL.

09:36.300 --> 09:37.520
Do you want to continue?

09:38.060 --> 09:43.880
On the right side, we have the C version, which you can use to compile a new Python executable.

09:43.880 --> 09:55.200
Notice it's considerably longer, and personally, I never really learned C 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:00.740
So here's a list of audit events that are currently implemented in Python 3.8.

10:00.740 --> 10:13.000
You can actually pull this from the documentation itself, but what I have highlighted are some of the things that you may really be interested in if you see these events occurring.

10:13.000 --> 10:33.780
Now, keep in mind that you have to modify this for your particular environment, but some things that you may find interesting are when if a script uses the compile function or the exec function or maybe some of these winreg functions on the right, things that you may not normally see in your environment,

10:33.780 --> 10:37.740
you have to be able to adjust your audit hooks appropriately.

10:38.400 --> 10:48.580
Pty.spawn, I don't know a single case of anyone using pty.spawn outside of trying to get a shell after they've already compromised your system.

10:48.640 --> 10:55.600
So things like that may be of interest to you, and you may want to capture those events as they occur.

10:55.600 --> 10:59.640
But again, the focus is on detection and not prevention.

10:59.680 --> 11:14.960
So we wanted to make sure that we are aware of these things that are happening, but we don't want to actually prevent them from happening unless you have a really good use case or use scenario to not let a particular event happen.

11:15.560 --> 11:16.620
I mean, you can totally do.

11:16.620 --> 11:18.160
You can block all events if you want to.

11:18.160 --> 11:21.620
It's not like a fake Python just sitting there.

11:21.620 --> 11:23.820
I don't know, honeypot Python or whatever.

11:24.620 --> 11:27.000
So bypassing audit hooks for Windows.

11:27.000 --> 11:28.920
So first we'll talk about how auditing works.

11:29.180 --> 11:38.120
So when an action is or when an audit hook is signaled, Python determines whether the action matches an audit rule.

11:38.120 --> 11:41.420
And that's kind of maybe not the best explanation.

11:41.420 --> 11:51.940
What happens is it will run through every single audit event or audit hook that you have, but you have to write your audit hooks to only care about the events that you want.

11:52.020 --> 11:56.440
So for example, in this network prompt hook, we only care about socket events.

11:56.440 --> 12:03.280
If it doesn't equal socket, then we're going to return because we don't want the rest of the hook to go through all that logic.

12:03.520 --> 12:11.180
So when you write your audit hooks, you have to make sure that you only process the events that you care about.

12:11.600 --> 12:19.320
Also, you probably shouldn't do anything like a while loop, some unbreakable condition, which is not a good idea.

12:19.820 --> 12:23.800
So the other thing to know is that all audit hooks are processed for all events.

12:23.800 --> 12:35.340
So even if I open a file , which is an audit hook event open, the open function, it's still going to get passed to this network prompt hook, but it won't do anything because I set a condition to only care about socket.

12:37.060 --> 12:41.860
So what's in the source code to figure out how these things work?

12:41.860 --> 12:45.940
So on the left side, we have the PySys add audit hook function.

12:46.480 --> 12:52.860
And what it does when it adds an audit hook, it literally just adds the hook to the end of a linked list.

12:53.860 --> 13:08.040
When the hook is called in the middle or the upper right, it will actually just go through each hook that has been defined and just goes through all those functions and determines whether or not it needs to do anything.

13:08.820 --> 13:16.380
Or based on... it will... you decide whether or not it needs to do anything, but it will just run all those functions at once.

13:18.340 --> 13:20.120
In order, I should say.

13:20.120 --> 13:23.480
On the right side, we also see that tracing is disallowed.

13:23.480 --> 13:30.020
Now, what I originally thought this meant was that I could not see into what an audit hook looks like.

13:30.020 --> 13:35.520
But it turns out that that tracing set to false is only for the Python debugger.

13:35.520 --> 13:46.060
So if you're using the Python debugger, for whatever reason, maybe in your IDE or maybe on the command line, you actually can't trace into the audit hooks.

13:46.060 --> 13:58.760
But if you're using an external debugger, so let's say maybe you grabbed a copy of a modified Python off of a system and you took it back to your station and you opened up a debugger, you can actually see what's going on.

13:58.840 --> 14:00.200
So here's an example, right?

14:00.200 --> 14:02.940
The same example where we had the socket.getaddress info.

14:02.940 --> 14:06.800
I can actually look into this audit hook to determine what it's doing.

14:06.860 --> 14:12.200
I'm not the greatest at assembly, and so I know what this says simply because I know what the code says.

14:12.200 --> 14:14.080
But you can still get a general idea.

14:14.080 --> 14:24.620
You can see the comparison strings, the compare, spython.string compare, and you can see that it's looking for certain audit hooks to occur before moving into some other logic.

14:25.180 --> 14:31.160
And you can also see the strings there attempt to resolve the URL that it tried to resolve.

14:32.960 --> 14:35.640
So here are some ideas for bypassing.

14:36.720 --> 14:40.400
By implementation, audit hooks can't be cleared once they're set.

14:40.400 --> 14:45.380
So there is a function, I think, to clear audit hooks, I think, but it doesn't actually work.

14:45.380 --> 14:46.480
You also can't overwrite.

14:46.480 --> 14:51.280
You can't, like, reload the sys module in order to clear all the audit hooks either.

14:51.880 --> 15:00.000
So one thing we could do is maybe overwrite the head of the linked list to point to null, but it might be possible.

15:01.080 --> 15:05.580
And attempting to locate the start of a linked list in memory might be a little bit difficult.

15:06.140 --> 15:09.700
But I'm actually really new to exploit development, so it might be possible.

15:09.700 --> 15:13.580
So if someone knows something I don't, please tell me, and maybe we'll figure that out.

15:13.700 --> 15:20.600
But in the meantime, maybe we can just overwrite the bytes in memory that calls the hook functions.

15:21.400 --> 15:24.800
And that smells like PowerShell, right?

15:24.800 --> 15:31.000
Everybody knows PowerShell and AMSI bypassing, and we can actually apply the same concept here.

15:31.040 --> 15:34.140
So there's plenty of resources to draw inspiration from.

15:34.440 --> 15:45.440
Adam Chester over at MDSec has plenty of articles about bypassing AMSI, so does RastaMouse, and there's a lot of inspiration just to draw just by reading their blogs.

15:46.820 --> 15:54.940
And it's also significantly easier to determine the address that you need to overwrite with these new bytes that you want to overwrite with.

15:55.400 --> 15:57.000
So let's go that way.

15:57.700 --> 16:14.440
So if I take the Python 3.8 DLL, and I open it up, or if I run Python, rather, this modified Python, and I go check out the Python 3.8 DLL, I can go find the address for PySysAudit, and we can see this is the 32-bit version.

16:14.680 --> 16:18.120
We can see the address and set a breakpoint on it.

16:19.360 --> 16:29.760
If I scroll further down, I'll get to a point where a function is called, and under call EAX, it has the name of the hook that is being called, right?

16:29.760 --> 16:35.880
So if I were to step into that call, I would actually see the logic that I showed you earlier.

16:36.260 --> 16:53.480
So the offending, I say offending, but the interesting call that we want to overwrite is where EIP is currently pointing, and that is call EAX for 32-bit, and call keyword pointer for 64-bit.

16:57.370 --> 16:59.410
So, again, it's very similar to PowerShell.

16:59.410 --> 17:09.510
We need to load the Python 3.8 DLL, we need to find the git address for PySysAudit, we need to change the memory permissions, and we need to just overwrite those bytes.

17:10.730 --> 17:20.590
And I learned this week, a couple of days ago, that I really need to go through all these versions of Python and grab their offsets.

17:20.590 --> 17:36.170
So I went through the 32 and 64-bit versions of Python, from 3.8 to 3.85, and even just for poops and laughter, I went to 3.9.0 Beta 5, just to see if it still worked, and it does.

17:36.170 --> 17:44.170
So we just have to set these offsets, and we can change the memory permissions and overwrite those offsets.

17:45.210 --> 17:47.050
Let's talk about Linux real quick.

17:48.470 --> 17:58.990
So I have never done any sort of, like, major Linux exploitation other than, like, a buffer overflow, right, that you find in, like, CTFs.

17:58.990 --> 18:09.370
I've never done any patching, so I didn't fully understand at the time how the Python packages worked on Linux.

18:10.170 --> 18:21.410
So there are no DLLs, right, because it's not Windows, and there was no sysmodule.so library that I thought was going to be there, and it just wasn't.

18:21.410 --> 18:24.550
So it's like the same, though, right?

18:24.550 --> 18:28.490
You can find the PySysAudit address by searching the Python executable itself.

18:28.490 --> 18:32.690
So I guess it's statically compiled into the binary, and you can just pull it out.

18:32.690 --> 18:49.630
So if you use the readelf, we can use readelf to figure out what the address of PySysAudit is, and we're going to cheat a bit, because I actually don't know how to parse ELF files.

18:49.630 --> 18:59.850
So in my code, I just subprocess readelf and grab the output, because I actually have no idea how to do it otherwise.

19:00.410 --> 19:03.090
And then we have to locate this address in memory.

19:03.650 --> 19:08.370
So once we grab that address here, it's 53007 foxtrot 0.

19:08.370 --> 19:12.950
We need to figure out where it's located and what region of memory that it's mapped to.

19:13.310 --> 19:21.910
And in this picture, we can see that it's under... it falls in the range of F230 and 6AE.

19:22.790 --> 19:25.530
So we need to modify those permissions.

19:26.070 --> 19:29.410
And we can do that, of course, with libc and mprotect.

19:29.430 --> 19:35.270
So we need the write execute permissions to change and to read write execute permissions.

19:35.710 --> 19:39.650
And sometimes, yes, sometimes it just be like that.

19:40.170 --> 19:42.370
I am watching the Discord, by the way.

19:42.370 --> 19:49.010
So what this looks like is we load the DLL for libc.

19:49.010 --> 19:50.790
Or we load libc, rather, I should say.

19:50.790 --> 19:52.550
And then we find the readelf.

19:52.550 --> 19:54.870
We find the address of pysis audit.

19:54.870 --> 19:59.910
We find the location of the memory mapped files.

19:59.910 --> 20:03.030
And then we determine in what region it falls under.

20:03.190 --> 20:05.630
And then we change the permissions.

20:05.750 --> 20:13.290
And then we just overwrite the the bytes in memory with these nops.

20:13.770 --> 20:22.370
So one thing I didn't go too far in depth in Linux is because every build for Python in Linux is different.

20:22.870 --> 20:27.330
So it's... I don't have the time of the day.

20:27.330 --> 20:28.590
Like, I'm stuck at home.

20:28.590 --> 20:31.250
I only go out to the supermarket if I need to.

20:31.250 --> 20:37.390
But I still don't have the time in the day to go through all these Linux versions and try to find these offsets.

20:37.390 --> 20:42.950
So later today I'll probably put this code up on GitHub.

20:42.950 --> 20:55.590
And if you have a version of Linux where you want to bypass audit hooks for 3.8, then you can send a pull request and I will happily accept once it's been tested.

20:58.400 --> 21:00.040
Let's talk about detection.

21:01.120 --> 21:13.120
While everything that I've mentioned works, everything up until how... up until you patch the binaries... the bytes, rather, can be detected.

21:13.120 --> 21:16.580
So importing C types can be detected, importing platform, importing sys.

21:16.580 --> 21:27.660
Every single function call that you make can be detected until you hit that men move and you overwrite the bytes.

21:28.320 --> 21:33.420
The way to defeat this is ironically better audit hook logging.

21:33.420 --> 21:50.540
Because if you can detect that these things are occurring and you send them off and you ship them to your seam or however you... your Windows event logs or however you collect your logs and you see that these are occurring, well, then you have a better chance of understanding what's going on with your Python executable on a random given station.

21:51.100 --> 22:00.740
So, yes, it's sort of like... it's like running AMSI bypass in PowerShell where you might get caught.

22:00.800 --> 22:02.280
It's the same thing.

22:02.520 --> 22:07.320
So that is something you should probably implement better audit hooks.

22:07.400 --> 22:10.940
And it might be harder for someone who doesn't program in C to implement, right?

22:10.940 --> 22:15.160
I don't think most sys admins have a lot of C knowledge.

22:15.160 --> 22:15.940
I could be wrong.

22:15.940 --> 22:17.060
I could be naive.

22:17.140 --> 22:18.440
I've never been a sys admin.

22:18.440 --> 22:19.460
I have no idea.

22:19.740 --> 22:30.140
But it's probably not something that you come across in the general resume where you need 10 years of Adobe XD that came out last year.

22:30.660 --> 22:41.860
So it would be great if somebody came up with a template of audit options and an action to help sys admins compile those custom Python executables.

22:41.860 --> 22:44.840
But as I mentioned, I don't have the time.

22:44.920 --> 22:46.320
So it's not me.

22:46.880 --> 22:50.600
So I want to give a demo real quick.

22:57.770 --> 23:01.850
Hopefully we can all still see the screen that I have up.

23:01.890 --> 23:04.790
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:27.620
Thank you.

23:27.740 --> 23:28.800
Great.

23:28.800 --> 23:33.420
So I have this executable here.

23:33.420 --> 23:36.080
This sPython that's been compiled.

23:36.680 --> 23:39.660
And I'm just going to enter it directly.

23:40.160 --> 23:46.940
And I'm going to try to run this code.

23:46.940 --> 23:55.560
And all I want to do is get the status code of the Red Team Village.io.

23:55.560 --> 23:56.600
I just want to make sure it's up.

23:56.600 --> 23:58.800
Because Red Team Village has been doing a great job.

23:58.820 --> 24:00.800
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:07.500
Do you want to continue?

24:07.540 --> 24:08.740
Yes, I do want to continue.

24:08.840 --> 24:10.580
Yes, I do want to handle the socket event.

24:10.580 --> 24:11.980
Yes, I do want to connect to this.

24:11.980 --> 24:15.380
And finally, I can get the HTTP status code.

24:15.580 --> 24:16.600
That's great.

24:17.220 --> 24:20.200
But I don't want to have to go through all that again.

24:20.260 --> 24:23.000
So maybe I'll just do this audit bypass.

24:24.100 --> 24:25.440
And we'll see.

24:25.440 --> 24:26.280
We're getting the handle.

24:26.280 --> 24:29.380
I'm printing out all this arbitrary stuff that doesn't really need to be printed out.

24:30.100 --> 24:31.680
We get the audit address.

24:31.680 --> 24:32.820
We find the protections.

24:32.820 --> 24:36.660
Using the offsets, we find the region of memory that we need to overwrite.

24:36.660 --> 24:38.660
And we successfully change the memory.

24:38.660 --> 24:44.900
And if you ever played NBA Jam, like the video game of your life, this is the point in time where I say, boom, shakalaka!

24:44.900 --> 24:46.920
And we bypass the audit hooks.

24:46.920 --> 24:52.720
So now when I run the same code, again, we just get status code 200.

24:53.060 --> 24:55.740
And yay!

24:55.740 --> 24:56.080
Right?

24:56.080 --> 24:56.860
We win.

24:56.860 --> 25:01.020
We don't have to worry about the audit hooks and going through all that stuff again.

25:01.020 --> 25:03.680
So that's great.

25:06.680 --> 25:08.440
Let's get back to these slides.

25:10.060 --> 25:11.100
Whoops.

25:12.900 --> 25:14.640
I lost my cursor.

25:15.220 --> 25:16.500
There we go.

25:18.920 --> 25:21.360
So let's talk about bypassing Python.

25:21.360 --> 25:30.760
Because as I mentioned, everything up until that point where you overwrite those bytes can be detected in memory.

25:30.760 --> 25:32.900
Or detected by an audit hook.

25:33.060 --> 25:34.240
And that could be bad.

25:34.240 --> 25:34.560
Right?

25:34.560 --> 25:36.420
So let's take this a step further.

25:36.760 --> 25:46.440
So since the bypass is easy to detect, are there any other methods that we can use to overwrite or to bypass Python in a sense?

25:46.540 --> 25:46.880
Right?

25:46.880 --> 25:49.240
So there are other ways to load and patch DLLs.

25:49.240 --> 25:53.960
I've had conversations with smart people about Python internals.

25:54.440 --> 25:56.980
And they've suggested other ways to do it.

25:57.180 --> 26:04.940
So, you know, just loading DLLs to kernel 32 and virtual protect and all that stuff is one way to do it.

26:04.940 --> 26:06.480
But there are other ways to do it.

26:07.720 --> 26:13.440
But everything that you do that way has to be run in the context of the Python interpreter.

26:13.680 --> 26:19.940
So no matter what I do, or no matter what you do, rather, you can be detected.

26:20.080 --> 26:21.580
No matter how you do it.

26:21.940 --> 26:26.920
So the question is, can we run Python without running the Python executable?

26:26.920 --> 26:28.520
And obviously, the answer is yes.

26:28.520 --> 26:32.580
Because I wouldn't be asking you this question and wasting your time with that bit of the slide.

26:32.600 --> 26:37.200
So there's a package called Python net, which is a legitimate package.

26:37.200 --> 26:38.060
It's great.

26:38.060 --> 26:43.960
And it uses it allows you to access the .NET CLR from normal Python.

26:44.060 --> 26:46.260
It's not Iron Python, right?

26:46.260 --> 26:47.440
It's not Iron Python at all.

26:47.440 --> 26:56.060
Iron Python is an implementation of Python that is, as far as I'm aware, not even MSIL compliant.

26:56.060 --> 27:00.700
But the syntax is still similar to Iron Python.

27:01.760 --> 27:06.740
And as of May 2020, it supports Python 3.8.

27:06.760 --> 27:07.760
That's great.

27:08.500 --> 27:10.020
And my camera just went out.

27:10.020 --> 27:12.460
So I'm going to turn my camera off, if that's okay.

27:14.900 --> 27:17.960
So we have the here's some syntax, right?

27:17.960 --> 27:23.500
We can import CLR from collections that we import counter.

27:23.500 --> 27:29.340
And then we can access the CLR the same way that we do it in sort of Iron Python-ish ways, right?

27:29.340 --> 27:36.920
Where from system.diagnostics, we can import process, and we can actually list all the processes in a CLR manner.

27:38.280 --> 27:39.920
CLR-like syntax.

27:39.920 --> 27:41.980
Or .NET-like syntax, I should say.

27:43.200 --> 27:47.940
You can actually also embed Python in your .NET executable.

27:48.140 --> 27:52.280
So Python.NET comes with a .NET assembly called Python.runtime.dll.

27:52.280 --> 27:55.600
And that can be used with .NET languages to run Python.

27:56.160 --> 28:03.640
The Python types for the basic primitive types are actually written in C sharp and compiled as part of this assembly.

28:04.160 --> 28:09.660
The only requirement is that your path variable must include a directory with the Python runtime installed.

28:09.660 --> 28:12.440
So in this case, we're looking for Python 3.8.dll.

28:13.700 --> 28:16.340
So here's an example of what it looks like in C sharp.

28:16.340 --> 28:25.620
We have this main function, and then we are using the Python gill, and we can import modules that Python knows about, such as numpy.

28:25.860 --> 28:34.940
And when we run this function, we get this output to the right, and it's pretty cool that we can actually run Python inside of .NET.

28:36.980 --> 28:40.020
And we can use C sharp to write this, but I like boolang.

28:40.160 --> 28:46.360
The reason I like boolang is because of Bytebleeder and the Silent Trinity project, that C2 framework that he has.

28:46.360 --> 28:47.340
It's pretty dope.

28:47.340 --> 28:53.480
I've contributed to it in the past, and I really like boolang because it's very Pythonic.

28:53.540 --> 28:58.540
If you 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 --> 28:59.780
It's boolang.

29:00.080 --> 29:04.460
Also, support Bytebleeder in his open source sponsorship program that he has.

29:04.460 --> 29:13.300
It's definitely worth investing in people who have helped you in your corporate endeavors for pentesting and retting.

29:13.780 --> 29:23.440
So what we need to do, as I mentioned before, is point the path variable to a Python installation folder, which gives me an idea, right?

29:23.440 --> 29:31.540
Because the path variable can be set to any folder on the disk or on a network share.

29:31.760 --> 29:33.860
So you can already see where I'm going with this, hopefully.

29:35.080 --> 29:35.960
Hmm.

29:36.180 --> 29:39.320
I tried to find a black one, but there are no black emojis.

29:39.840 --> 29:46.300
So we can do Python across a network share by simply by pointing the Python path to it.

29:46.300 --> 29:50.880
So in this case, I had a share called winwork slash Python 3.7.

29:51.180 --> 29:55.440
And this is 3.7 because at the time I was testing it with 3.7 before 3.8 came out.

29:55.560 --> 29:58.060
It still works with 3.8, so ignore that.

29:58.600 --> 29:59.980
And that's what it looks like, right?

29:59.980 --> 30:08.620
So the path variable can be set to the server slash share, just as long as that share points to a Python 3x installation.

30:09.820 --> 30:11.600
And this is what it looks like.

30:11.640 --> 30:20.260
So you can see all this SMB traffic, and you can clearly see that I'm loading Python 3.7 across a share.

30:20.260 --> 30:23.800
Now, this isn't like an abuse of anything, right?

30:23.800 --> 30:29.320
You can actually install Python when you install Python normally on a network share and have everybody access it.

30:29.320 --> 30:33.880
It's slower because you have to load those resources remotely, but you can actually do that.

30:34.160 --> 30:40.000
But if we can use SMB, that probably means we can also use WebDAV.

30:41.380 --> 30:51.160
And sure enough, we can actually load Python across WebDAV by setting the server and the port.

30:51.680 --> 30:58.660
So whack whack server at port, and SSL is optional.

31:00.380 --> 31:12.040
There's a registry setting in Windows that determines how you can authenticate with WebDAV, and you may have to set that in some environments.

31:12.240 --> 31:19.140
I was having issues with this earlier today, or earlier, I guess it was today, or last night, whatever.

31:19.400 --> 31:22.900
And something broke, and I couldn't figure out what it was.

31:22.900 --> 31:27.260
So I can't show you what it looks like going across a WebDAV server.

31:27.700 --> 31:29.180
But it works.

31:29.180 --> 31:30.100
Here are the packets.

31:30.200 --> 31:31.600
It worked at some point in time.

31:31.600 --> 31:33.080
I just need to figure out what happened.

31:35.340 --> 31:39.300
So we can also host our own repo to remotely load packages, right?

31:39.300 --> 31:47.680
We don't need to pip install anything on the target box, assuming that we need extra modules that aren't already there on the Python 3.8 installation.

31:47.820 --> 31:54.340
Empire did this by implementing a custom import hook that allows you to modify the logic and how modules are loaded.

31:54.340 --> 32:10.200
So it allows you to load things remotely, and if you want more information on that, you can check out Chris Ross's blog on in-memory Python imports, or you can check out the Sulink's GitHub repo for remote importer, and you can check that out.

32:10.200 --> 32:11.280
It's really interesting stuff.

32:11.660 --> 32:18.880
There's also another module out there called HTTP importer, or something like that, that allows you to import modules over HTTP directly.

32:20.040 --> 32:23.480
And of course, Empire was written a long time ago.

32:23.480 --> 32:31.040
I know a new company is picking up Empire, I think it was BC Security, and I think they recently released a tool called Starkiller that seems really interesting.

32:31.040 --> 32:32.360
You should probably go check that out.

32:34.100 --> 32:38.860
If you want to load a package remotely, you need to just have it in the standard format.

32:38.860 --> 32:43.340
So here's the request module, and I want to just import this.

32:43.340 --> 32:44.420
This is what it needs to look like.

32:44.420 --> 32:46.200
It needs to be the zip file.

32:46.260 --> 32:54.900
Inside it, you need to have the name of the package, and then inside a folder with the name of the package, and inside of that, you need to have all the files that you need for the package.

32:56.340 --> 32:58.360
Let's do a demo of that real quick.

33:00.860 --> 33:02.340
All right.

33:08.880 --> 33:14.820
So in this folder, I have some code called scarysnake.boo.

33:14.820 --> 33:20.340
Everybody who writes C Sharp tools likes to call their tools Sharp something, but no, this is Boo.

33:20.340 --> 33:23.560
It's about ghosts, so we're doing scarysnake.

33:23.980 --> 33:26.420
And so we can actually compile this.

33:26.420 --> 33:34.420
I wrote a little thing using the compiler that comes with Boo, and we can just compile it real quick.

33:34.540 --> 33:42.820
We'll also use ILRepack to package the the additional assemblies that we need.

33:42.820 --> 33:47.620
That includes the Boo.lang assembly and some other additional things.

33:47.620 --> 33:51.620
Most importantly, the Python runtime.dll that you see here.

33:51.620 --> 33:53.820
That's the most important one, right?

33:54.120 --> 34:02.500
And now we have this tool called scarysnake, and it tells me that the Python path must be set.

34:02.900 --> 34:03.500
All right?

34:04.940 --> 34:07.160
So I'm going to set this Python path

34:14.620 --> 34:17.120
by just passing it as an argument.

34:17.320 --> 34:18.640
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:27.500
And this is the output that we get.

34:27.500 --> 34:33.740
It's a little, well, it's basically, I tried to imitate the same output that Python gives you when you start the interpreter.

34:34.220 --> 34:36.240
It shows us where the path is.

34:36.240 --> 34:40.480
And again, if you were doing this remotely, it would be over WebDAV.

34:40.480 --> 34:50.480
I think I had set up in the past, I had set up a server in Azure, and the load time was about 30 seconds just to run Python.

34:50.480 --> 34:58.340
But if it takes that much time to further escalate or do whatever you need to do with the Python and not get caught, I think it's worth the investment.

34:58.340 --> 35:05.540
So we can do things like, here we go again.

35:06.460 --> 35:11.460
And we're going to print out the status code, of course, from red team village.io.

35:11.780 --> 35:28.440
And without changing the normal Python executable, we basically brought our own interpreter to the system, and we were able to run Python, using the Python that's on the system, be able to run Python without worrying about all the hooks.

35:28.440 --> 35:32.520
Once again, boom, shakalaka, I'm on fire.

35:33.240 --> 35:35.140
I'm not literally on fire.

35:36.480 --> 35:38.180
Don't call the fire department.

35:39.120 --> 35:43.780
So some of the other options that are here include the local file.

35:43.780 --> 35:45.500
So you can run a local file.

35:45.500 --> 35:47.100
You can also run a remote file.

35:47.500 --> 35:51.180
I forgot to actually test this this morning, so I don't know if it works.

35:51.180 --> 35:53.060
So I'm not going to actually do it.

35:53.140 --> 35:55.320
Because that would be embarrassing.

35:55.540 --> 35:59.040
But it has worked in the past of trying to run a remote file.

35:59.040 --> 36:08.900
So you basically download the Python file, right, from GitHub or wherever you have it hosted, and you just sort of run it in memory.

36:11.000 --> 36:14.060
So let's try something real quick, right?

36:15.700 --> 36:17.860
I forget which system I'm on sometimes.

36:22.370 --> 36:23.590
There we go.

36:24.290 --> 36:25.330
Windows terminal.

36:25.510 --> 36:26.330
Split pane.

36:26.330 --> 36:27.190
Great.

36:27.630 --> 36:31.610
So I'm going to try doing this, right?

36:31.610 --> 36:34.330
I want to import the request module.

36:35.110 --> 36:37.630
But I don't have the request module.

36:38.770 --> 36:52.550
Just to show you that it works, that I'm actually using the actual Python that's hosted on the system, I'm going to Python-npip.

36:52.550 --> 37:00.350
Which, by the way, you should always do Python-npip because if you have several different Python versions, you don't want to confuse yourself.

37:00.630 --> 37:06.090
So you should always specify the version of Python that you're using when you run things like pip.

37:06.110 --> 37:07.590
It's just good practice.

37:08.730 --> 37:11.010
And we're going to just pip install request.

37:14.540 --> 37:20.200
And if I've installed request, and now I can import it with no problem.

37:20.200 --> 37:28.260
So, again, you can definitely use the Python libraries that are on the system to do it.

37:28.340 --> 37:35.240
Or you can host your own remotely and sort of just pull from a remote repo without actually touching anything on the disk.

37:35.240 --> 37:39.600
It's really up to you and what's in the best case scenario for your immediate situation.

37:39.720 --> 37:46.120
But I think it's great that we can actually just do this in the first place.

37:46.120 --> 38:01.620
So, so far, just to reiterate, we've talked about how to bypass autohooks for Windows, how to bypass autohooks for Linux, and how to not run Python on a system without running the Python executable whatsoever.

38:02.820 --> 38:06.600
For detections, it's a little bit difficult because everything is in .NET.

38:06.600 --> 38:14.140
So you have the same problem of .NET detections that you would find in any other .NET binary.

38:14.180 --> 38:19.740
If you're collecting ECW logs somehow and you have the space for that, wow, that's impressive.

38:19.920 --> 38:23.100
But if you have that space, you'll actually see that the Python runtime is loaded.

38:23.100 --> 38:27.000
So if you're in an environment where you don't normally see that, that could be something you detect.

38:27.000 --> 38:35.940
In this case, because I wrote it in Boo, it actually also detects the Boo.lang binary.

38:35.940 --> 38:38.980
But you could write this in C Sharp if you really wanted to.

38:38.980 --> 38:43.860
If you wanted to be a conformist and write all your tools in C Sharp, you could do that.

38:44.300 --> 38:47.800
And obviously, you wouldn't have the Boo.lang.

38:48.940 --> 38:55.120
So in conclusion, Python autohooks are great when they're implemented correctly.

38:56.000 --> 39:01.880
You should protect your custom Python executables with some OS-level protections.

39:02.260 --> 39:04.620
You need to do something, right?

39:05.320 --> 39:10.700
The same way you do any protection of any other binaries on your device.

39:10.740 --> 39:14.120
You need to make sure that your Python executables are protected.

39:14.120 --> 39:28.560
Because if I can pull your executable off of the system and you have autohooks enabled, I can debug it and get a pretty good idea of what autohooks you've implemented just by looking at the code or looking at the assembly.

39:29.760 --> 39:35.060
And of course, that ties in with don't rely solely on Python to protect your Python.

39:35.180 --> 39:45.840
So even if you had all these autohooks, I can pull your executable off and do some other nonsense with it and figure out how to bypass your autohooks.

39:46.560 --> 39:51.400
So thanks to everyone who has had some influence on this talk.

39:51.440 --> 39:59.640
Steve Dower, who works at Microsoft, he's a Python core dev, and he's actually the one who's done most of the implementation work for autohooks.

40:00.000 --> 40:11.600
Bytebleeder, who got me into Boo.lang and embedded scripting, Chris Ross over at SpecterOps, worked on Empire, and Adam Chester.

