[01:05.720 --> 01:06.680] Hello, everyone. [01:07.120 --> 01:11.380] Welcome to the next talk in our series of talks today in Track 3. [01:11.640 --> 01:13.240] A couple of housekeeping issues. [01:13.940 --> 01:16.560] Again, I'm sure some of you have heard this many times before. [01:17.460 --> 01:19.980] Thank you for conforming the mask policy. [01:20.140 --> 01:21.000] We really appreciate it. [01:21.120 --> 01:24.740] It helps keep us all feel comfortable being here in this space. [01:25.540 --> 01:29.780] If you feel uncomfortable, remember you can always go outside and take them off, masks are not required outside the building. [01:30.400 --> 01:31.060] Stay hydrated. [01:31.260 --> 01:33.620] I know we're getting later in the day, so it's not quite so important now. [01:33.780 --> 01:38.280] But it's always important so you can be healthy and enjoy the rest of the talks in the conference. [01:38.760 --> 01:40.820] Please mute your phone for these sessions. [01:41.000 --> 01:45.280] The audio is super sensitive, so it picks up almost any sound other than... [01:45.280 --> 01:47.080] And we'd like to keep the focus on the talks. [01:47.660 --> 01:53.340] There is an unscheduled fourth talk track for anyone who wasn't able to get into any of the three principal tracks. [01:53.340 --> 01:55.060] You can go to the information desk. [01:55.320 --> 01:58.460] If you have an idea for a talk you want to give, you can go there and you can request. [01:58.620 --> 02:03.340] And they'll find a time for you to be able to participate in the unscheduled track, which is in the coffeehouse. [02:04.640 --> 02:06.060] Hacker Karaoke tonight at 10. [02:07.280 --> 02:08.520] Hopefully some of you will participate. [02:09.660 --> 02:10.960] I'm sure it will be a lot of fun. [02:11.460 --> 02:12.720] More volunteers are welcome. [02:12.980 --> 02:14.120] We can always use volunteers. [02:14.260 --> 02:17.860] There's all kinds of activities that we need help with, especially in terms of security. [02:17.860 --> 02:22.840] So if you're interested, stop by room 301 or go to volunteer.hope.net and sign up. [02:23.200 --> 02:31.100] And lastly, the first late-night block of mature content programming starts at 9:30 in the video room, which is 406, fourth floor, all the way over. [02:32.400 --> 02:35.600] Tonight's video room late-night theme is humor and horror. [02:35.880 --> 02:38.120] So expect the unexpected is what they tell me. [02:38.780 --> 02:46.280] For the talk, Novel Exploitation Tactics in Linux User Space, One-Byte OOB Write to ROB Chain by Sammy Hellman. [02:52.690 --> 02:54.190] Is everything Joe and stuff? [02:54.570 --> 02:54.830] No. [03:01.520 --> 03:02.280] All right. [03:02.420 --> 03:02.680] Hi. [03:03.020 --> 03:07.280] Welcome to Novel Exploitation Tactics in Linux User Space with Runtime Loader. [03:07.840 --> 03:15.840] We're going to be utilizing the Runtime Loader to exploit some vulnerabilities when these vulnerabilities are considered weak and the security checks are considered high. [03:15.840 --> 03:17.260] So let's get right in. [03:17.880 --> 03:19.200] First off, who am I? [03:19.680 --> 03:20.940] I'm Sammy Hachamid. [03:21.120 --> 03:23.920] I go by Pepsipu on Twitter, my handles, stuff like that. [03:24.260 --> 03:25.940] I'm a student at Troy High School. [03:26.480 --> 03:30.140] I'm also the founder and reverse engineer at B-Swap Labs. [03:30.400 --> 03:34.600] But I also do blockchain security auditing and vulnerability research at OtterSec. [03:35.700 --> 03:38.000] I also play CTFs for fun. [03:38.460 --> 03:40.140] Dicegang CTF and Redpone CTF for my team. [03:40.300 --> 03:40.720] Hi, Dicegang. [03:40.820 --> 03:41.600] See you in the free part row. [03:42.040 --> 03:42.360] All right. [03:42.540 --> 03:43.840] So let's get right into the talk. [03:43.840 --> 03:46.140] First off, what's this presentation about? [03:46.560 --> 03:51.280] Well, the process of turning vulnerabilities into exploits is hard. [03:51.640 --> 03:54.980] There's a lot of protections which exist to make this even harder. [03:55.680 --> 04:00.320] And it's complicated, but the runtime loader can make it a lot easier. [04:01.060 --> 04:04.260] First, to show this, we're going to start with a weak vulnerability. [04:04.600 --> 04:07.960] And this vulnerability is going to be under extremely powerful protection. [04:08.200 --> 04:09.300] So we'll talk about those protections later. [04:09.300 --> 04:13.060] And through that, we're still going to get arbitrary code execution. [04:13.580 --> 04:14.800] So let's get right in. [04:16.240 --> 04:16.660] All right. [04:16.920 --> 04:18.780] Vulnerability exploitation made easy. [04:19.020 --> 04:19.440] Houses. [04:20.060 --> 04:25.560] Houses are a sort of pre-baked series of steps to turn a vulnerability into a full-fledged exploit. [04:26.240 --> 04:28.480] Generally, this is kind of sort of a helping process. [04:28.680 --> 04:30.740] You know, turning vulnerabilities into exploits is hard. [04:30.740 --> 04:34.580] And houses kind of give you a roadmap to do that process. [04:34.960 --> 04:37.100] There's a lot of popular houses. [04:37.340 --> 04:37.720] You're right. [04:37.760 --> 04:40.000] You can see a list of the how-to-heap houses. [04:40.440 --> 04:45.220] And there's also some unlisted, like House of Red, House of Husk, House of Corrosion. [04:45.380 --> 04:49.120] All very popular and well-respected houses in TTF and exploitation. [04:49.720 --> 04:53.540] But the one aspect that all these houses share is that they're portable. [04:53.540 --> 05:02.880] See, the series of steps to turn the vulnerability into the exploit should be roughly the same as long as the vulnerabilities are roughly the same. [05:03.260 --> 05:12.540] For example, two programs with the same vulnerability, let's say TopChunkHeaderOverwrite, should both be exploitable by using House of Force. [05:13.720 --> 05:23.480] And even if those two programs are Minecraft and Chrome, as long as they share that core vulnerability, they can kind of share that core process of turning the vulnerability into an exploit. [05:24.020 --> 05:25.800] So, how do programs do this? [05:26.620 --> 05:28.200] Well, the answer is libraries. [05:28.660 --> 05:35.060] Instead of leveraging the vulnerability against the program, houses use the vulnerability against a shared library. [05:35.440 --> 05:42.780] The overwhelming majority of houses that vulnerability is leveraged against is Libc, or the GNUC library. [05:43.040 --> 05:46.340] It's the most overwhelmingly popular C library. [05:46.340 --> 05:51.400] It's really the de facto standard, and it's used in almost 100% of Linux programs. [05:51.760 --> 05:55.960] It provides everything from printing to memory management, file operations, you name it. [05:56.280 --> 06:02.580] And so, if you can find a way to use your vulnerability against Libc, you'll be able to have a portable house. [06:05.560 --> 06:07.140] So, let's take an example. [06:07.840 --> 06:10.080] Let's say we have a buffer overflow on the heap. [06:10.360 --> 06:13.620] Let's say we allocate two chunks, chunk one and chunk two. [06:13.620 --> 06:16.900] For those who don't know, these chunks end up on the heap. [06:17.120 --> 06:21.620] It's sort of a free use location of memory where programs can put their data. [06:22.200 --> 06:25.820] Now, let's say we have a buffer overflow in chunk one. [06:26.040 --> 06:28.900] This means that we're writing data outside the bounds of chunk one. [06:29.360 --> 06:37.240] Is it a better idea to A, overwrite the chunk two header, or B, overwrite the chunk two program data? [06:37.500 --> 06:39.000] I've just seen this little diagram right here. [06:39.440 --> 06:45.940] Well, you can make a case for both, but in the case for houses, it's a lot easier to say, let's overwrite the chunk two header. [06:46.260 --> 06:52.500] This is because the allocator's behavior, in Libc anyways, behavior is very well studied and well documented. [06:52.820 --> 06:59.920] And there exist multiple pre-baked steps, houses, to turn this chunk two header overwrite into remote co-execution. [07:00.100 --> 07:03.960] So, you can kind of use somebody else's work by overwriting the header. [07:04.240 --> 07:07.840] The program data, on the other hand, is different from each program to program. [07:07.840 --> 07:09.940] So, you'd have to roll your own solution. [07:10.360 --> 07:13.720] Just like that, you can kind of see the power of houses, that they're portable. [07:15.420 --> 07:17.140] Alright, how do I find a house? [07:17.600 --> 07:19.460] Well, finding houses is hard. [07:19.860 --> 07:21.320] Mostly because there's a lot of eyes. [07:21.500 --> 07:28.060] Not only from hackers looking for new houses, but Libc maintainers patching all those houses that are getting generated. [07:28.560 --> 07:30.100] But, that doesn't mean it's impossible. [07:30.100 --> 07:38.040] If you can look at an attack surface in a new way, or perhaps find a new one entirely, you can still get a really cool house out of it. [07:39.240 --> 07:41.680] So, let's talk about the surface I decided to look at. [07:42.140 --> 07:42.660] Exit. [07:43.480 --> 07:45.680] Programs tend to exit pretty frequently. [07:46.080 --> 07:53.780] I mean, not only calling the exit function, but returning from main or tossing errors tends to call exit under the hood. [07:53.780 --> 07:59.900] And I don't mean errors just in your program, but Libc errors as well may cause an exit. [08:00.380 --> 08:04.020] Now, forcing exits with vulnerabilities is pretty simple. [08:04.180 --> 08:09.860] I mean, it's very easy to corrupt some sort of Libc state and cause an exit by throwing an error. [08:10.180 --> 08:16.640] So, if you can find some sort of process or inject your own code into that exit process, you could potentially get a house out of it. [08:18.620 --> 08:21.560] So, we've seen this kind of done before. [08:21.560 --> 08:24.080] I mean, it has a pretty good track record. [08:24.640 --> 08:37.280] If you take a look at, for example, the Libc at exit hook overwrite, it was a kind of a cool tactic where you could just kind of write a function pointer to a location memory called Libc at exit hook, and that would be called when the program decided to exit. [08:37.640 --> 08:47.440] There's another one called House of Orange, which would exploit the IO cleanup process and would look up a piece of memory, and it would call the function pointer at that piece of memory. [08:47.860 --> 08:54.000] Of course, both of these both have mitigations, and the Libc at exit hook has very strong mitigation, pointer encryption. [08:54.340 --> 09:00.760] So, it's kind of not super viable anymore, but you can kind of see that it still is a cool attack surface to look at. [09:01.700 --> 09:02.240] All right. [09:02.580 --> 09:03.580] Let's take a deeper look. [09:04.540 --> 09:04.900] Okay. [09:05.080 --> 09:08.400] So, right off the bat, you can see at the top, you have the code for exit. [09:09.000 --> 09:13.280] It calls one function, run exit handler, with a list of exit functions. [09:14.140 --> 09:19.180] It's a little complicated, but when you break it down, it comes down to, I guess, four separate steps. [09:19.360 --> 09:21.760] The first three looking potentially exploitable. [09:22.180 --> 09:25.320] The first step is to run TLS detours. [09:25.640 --> 09:28.360] We don't know what that is for now, but let's just keep that in the back of our head. [09:28.920 --> 09:36.220] Then, we call the exit handlers, sort of clean up processes that are registered by the program or libc or something, and those are run. [09:36.680 --> 09:38.980] And finally, we call it libc at exit. [09:39.580 --> 09:43.660] Then, we call the actual exit syscall, which terminates the program. [09:43.780 --> 09:45.000] That's the underscore exit at the bottom. [09:45.700 --> 09:49.400] Now, it seems like a complicated process, but we can break it down, step by step. [09:50.920 --> 09:53.180] First off, let's take a look at the TLS detours call. [09:53.600 --> 09:55.700] These are also known as thread destructors. [09:56.260 --> 10:01.100] Basically, they kind of are a list of functions to call to clean up when a thread terminates. [10:01.100 --> 10:06.980] Now, if we could somehow inject our own thread destructor, we might be able to get remote code execution. [10:08.000 --> 10:10.200] Unfortunately, it turns out we can't do this. [10:10.480 --> 10:18.040] If you take a look at the commits, Florian Weimer introduced a hardening of the TLS detour calling seven years ago. [10:18.340 --> 10:22.240] And no longer can you write an arbitrary function pointer and get it called. [10:22.480 --> 10:30.320] Now, it's pointer mangled, meaning you need to know lots of secrets about the program and other things that makes writing just a function pointer completely inviolable. [10:30.320 --> 10:31.760] So you need a very, very strong vulnerability. [10:32.140 --> 10:34.820] And it's the same difficulty as just overwriting libc at exit. [10:35.060 --> 10:37.040] So no dice with a weak vulnerability. [10:38.620 --> 10:39.120] All right. [10:39.320 --> 10:40.840] What about the exit function handlers? [10:41.380 --> 10:45.920] Well, it's sort of just a linked list of exit functions, and they all have their own flavor. [10:46.260 --> 10:51.640] For example, you can see this big switch statement in the code, which kind of tells you what types of exits you could have. [10:52.260 --> 10:58.100] Although they do vary from flavor to flavor, all of them implement pointer mangling or pointer encryption, whatever you want to call it. [10:58.100 --> 11:07.040] And so unfortunately, you cannot inject your own exit function handler without knowing memory leaks or knowing a little bit more information about the program. [11:07.160 --> 11:08.260] You can't use a weak vulnerability. [11:08.960 --> 11:10.400] So that's really unfortunate. [11:10.860 --> 11:13.480] At this point, you might be stumped, truly lost even. [11:15.220 --> 11:16.900] Unfortunately, that's just how it is sometimes. [11:17.180 --> 11:20.880] And I had given up and I said, hey, maybe the exit attack surface was dry. [11:20.880 --> 11:20.900] Okay. [11:21.380 --> 11:22.300] And so I took a break. [11:23.000 --> 11:26.780] However, I was playing a CTF not too long, and then I remembered a trick in CTF. [11:26.920 --> 11:27.880] It's called the FeenyArrayOverwrite. [11:28.880 --> 11:30.980] And then I was like, wait, how does this trick work? [11:31.940 --> 11:32.980] Let me give you a crash course. [11:33.300 --> 11:34.040] The FeenyArray trick. [11:34.800 --> 11:37.880] Basically, a common tactic in CTF is called a GOTOverwrite. [11:38.380 --> 11:43.340] Basically, the table, the global offset table, contains a list of function pointers. [11:44.060 --> 11:51.260] Overwriting one of these function pointers with your own function or own function pointer would cause that function to get called instead of the function it's supposed to get called. [11:51.580 --> 11:57.020] For example, you could see in the image, if we have GOT, the first entry, zero entry, is string copy. [11:57.020 --> 12:03.680] If we overwrite that with something else, like, for example, deadbeef, then you would end up calling deadbeef when string copy is supposed to be called. [12:04.220 --> 12:07.080] Now, sometimes overwriting the GOT isn't always available. [12:07.680 --> 12:09.700] So there's sort of an extension of the GOT attack. [12:10.000 --> 12:13.740] Very close to the GOT exists this variable called FeenyArray. [12:14.160 --> 12:20.560] Now, if you overwrite FeenyArray and write your own function pointer to FeenyArray, then you can call whatever's there. [12:20.720 --> 12:21.960] It'll get called when you exit. [12:22.560 --> 12:23.820] But hold on for a second. [12:23.900 --> 12:24.520] Isn't that a little weird? [12:24.520 --> 12:25.980] Didn't we take a look at the exit code? [12:26.120 --> 12:28.000] And we saw nothing calling FeenyArray. [12:28.260 --> 12:30.100] So how does FeenyArray work? [12:33.020 --> 12:34.280] Well, it's a good question. [12:34.420 --> 12:36.920] And the answer is dlfeeny. [12:37.180 --> 12:41.320] It's an extremely complicated function, but it is super cool because there's a lot of complexity inside of it. [12:41.400 --> 12:42.800] Maybe complexity we could exploit. [12:43.880 --> 12:47.380] Now, this is the shared library unloader function. [12:48.260 --> 12:52.700] It's the one that cleans up all the shared library, unloads everything, calls all the destructors, stuff like that. [12:52.700 --> 12:56.780] So it's kind of just like the memory cleaner upper for all the shared libraries you have. [12:57.360 --> 12:58.880] Now, it's really interesting, right? [12:58.960 --> 13:03.620] Because this is a complicated function that not only has complexity, but 18-year-old code. [13:03.840 --> 13:10.360] So it could be some attack vector we could take advantage of here to get potentially, like, remote co-execution or something like that. [13:11.340 --> 13:16.040] And so, that's exactly what I did in a house called House of Blindness. [13:17.080 --> 13:25.320] Basically, it's kind of taking advantage of this dlfeeny function to call arbitrary functions without ever needing memory leaks. [13:25.760 --> 13:28.440] Now, the way it works is you take a standard vulnerability. [13:28.560 --> 13:30.420] Remember, houses turn vulnerabilities into exploits. [13:30.420 --> 13:35.160] And the vulnerability that House of Blindness takes is out-of-bounds heap write. [13:35.480 --> 13:45.460] What it does is it takes the out-of-bound heap write and turns that into leakless function arbitrary co-execution, which means you can call whatever function you want without needing to know ASLR base or any of those things. [13:45.680 --> 13:52.980] And so it's kind of like a new leakless take on the classic pheny array exploit, which is since kind of a little deprecated. [13:53.180 --> 13:54.380] It's not very useful anymore. [13:55.620 --> 13:58.140] Anyways, it's all written in this little document right here. [13:58.140 --> 13:58.760] It's linked. [13:58.940 --> 14:00.560] I'll post the slides at the end if you want to check it out. [14:01.320 --> 14:03.940] But I'd like to read you guys a little synopsis of what House of Blindness is about. [14:04.820 --> 14:08.240] House of Blindness isn't a conventional overwrite pheny-inarray style exploit. [14:08.580 --> 14:16.580] Rather, by a few clever partial overwrites and interesting runtime dynamic loader properties, we can trick the runtime loader into miscalculating where pheny is in memory. [14:16.900 --> 14:18.720] The only primitive we need is relative write. [14:19.180 --> 14:20.800] Did I mention we can do this completely leaklessly? [14:21.160 --> 14:23.980] So basically, we can call arbitrary functions without memory leaks. [14:24.240 --> 14:26.740] And how exactly does this work? [14:26.740 --> 14:29.700] Well, I'll first have to tell you something about link maps. [14:30.140 --> 14:33.820] Link maps are basically information about a shared object. [14:34.300 --> 14:39.040] Those shared objects could include libc, the runtime loader, the program, stuff like that. [14:39.420 --> 14:40.560] It has some important fields. [14:40.880 --> 14:44.240] For example, l-addr is where that shared library is loaded into memory. [14:44.580 --> 14:50.100] But it also has l-info, which is where all the data structures about that link map is. [14:50.180 --> 14:52.440] For example, the location of the global offset table. [14:52.440 --> 14:55.940] Location of the destructor functions, dt-pheny, is there. [14:56.140 --> 14:59.060] And there's also things like the constructor functions and all that stuff. [14:59.180 --> 15:02.780] It's where all the information about the shared object is. [15:03.980 --> 15:12.840] And so you might think, hey, if we can somehow corrupt this link map with something like an out-of-bounds write vulnerability, maybe we can get some interesting behavior out of it. [15:12.840 --> 15:14.900] And that's exactly what House of Blindness did. [15:16.460 --> 15:18.920] Basically, we caused the pheny function to be miscalculated. [15:19.680 --> 15:22.320] And this is done through something called an lsb overwrite. [15:22.580 --> 15:29.100] In a normal link map, as you can see in this diagram, generally, l-addr points at where the shared object is in memory. [15:29.480 --> 15:36.280] And then it uses the dt-pheny array, points to some offset from that start address, this l-addr. [15:36.280 --> 15:45.820] And so, for example, in this program, the dt-pheny function would be located at the start of the program, plus 0x451 bytes. [15:46.440 --> 15:53.320] Now, by using our out-of-bounds write, we may be able to corrupt this link map and cause the dt-pheny function to be miscalculated. [15:53.880 --> 16:00.920] In this specific case, instead of using an lsb overwrite, we can cause dt-pheny to point at an entirely different dynamic entry. [16:01.120 --> 16:03.020] In this case, dt-debug. [16:03.020 --> 16:09.280] Now, dt-debug is useful because instead of having an offset, r-debug is an actual pointer. [16:09.560 --> 16:15.140] And so what you end up doing is you end up adding this pointer to the start of the program base, which, as you know, is not a valid pointer. [16:15.420 --> 16:16.860] However, we can fix this. [16:17.040 --> 16:23.620] By overwriting the l-addr with some arbitrary offset, we can basically call any function from an offset from r-debug. [16:24.040 --> 16:29.260] Now, r-debug is located in roughly the same memory region as libc. [16:29.260 --> 16:30.440] It's adjacent in memory. [16:30.440 --> 16:38.940] Which means that we could call any libc function by specifying our offset in l-addr, and then calling that using house of blindness. [16:39.340 --> 16:42.800] So we can arbitrarily call any libc function, which is very useful. [16:43.380 --> 16:45.800] And I thought this was pretty cool, a little curious. [16:46.980 --> 16:49.600] But that was just the tip of the iceberg, and I didn't know it at the time. [16:50.160 --> 16:57.180] I had compiled all this research about DL Feeney and house of blindness and all that into this document called Issues in Exit Town. [16:57.660 --> 16:59.440] I didn't really publish it or anything. [16:59.560 --> 17:02.440] I just kind of shared it around with my CTF friends and got some feedback on it. [17:02.620 --> 17:05.700] But it really was the tip of the iceberg of what you can do with the runtime loader. [17:06.220 --> 17:07.160] So let's keep going. [17:08.500 --> 17:12.100] So even now I had thought that, you know, ah, I was done. [17:12.280 --> 17:14.440] I had finally got my house mission complete, right? [17:15.600 --> 17:18.720] But DICE CTF 2022 was rolling around. [17:18.780 --> 17:20.940] And I decided there probably was more to be found. [17:21.520 --> 17:23.200] And so that's exactly what I did. [17:23.340 --> 17:25.980] I decided to go back in and do some more. [17:26.500 --> 17:29.120] In this case, I created the Nightmare CTF challenge. [17:29.460 --> 17:39.080] Which basically, instead of giving you the out-of-bounds and the heap required for houses of blindness, instead, you got one byte out-of-bound write and a ton of security protections. [17:39.700 --> 17:42.920] The most important security protection here is no leak. [17:43.240 --> 17:48.520] This means that you have absolutely no memory leak and you don't know where any addresses are in memory. [17:48.700 --> 17:51.500] And so it can be a lot more complicated to exploit like this. [17:52.180 --> 17:53.940] But the program itself... we'll talk about that more later. [17:53.940 --> 17:56.340] But the program itself is pretty simple. [17:56.580 --> 17:59.540] If you look at the code, it reads an offset in a byte from the user. [17:59.760 --> 18:02.080] And then it does the write. [18:02.300 --> 18:05.320] It writes that byte to that offset for some allocated chunk. [18:05.720 --> 18:07.320] After that, it exits. [18:07.840 --> 18:10.640] And so that seems like a very simple program. [18:10.660 --> 18:13.220] But it actually can get pretty complicated. [18:14.020 --> 18:18.140] And we can learn a lot about the runtime loader by going through this program. [18:19.300 --> 18:22.560] But first, let's talk about the security protections. [18:22.560 --> 18:29.240] The first security restriction, and by far the most severe and restrictive, is there is no two-way communication. [18:29.680 --> 18:33.300] Although you can communicate with the program, the program cannot communicate with you. [18:33.660 --> 18:41.000] This means that there's absolutely no way to leak ASLR base or somehow figure out a memory leak or something. [18:41.240 --> 18:44.520] It's just not possible because the program cannot communicate that information to you. [18:44.520 --> 18:48.000] This means the entire time you are working blind. [18:48.240 --> 18:51.860] You don't know where any pointers are in memory, but you still have to somehow build a ROP chain. [18:52.460 --> 18:59.700] And we'll talk about this more later, but the issue is that there really isn't a current known way to build a ROP chain without memory leak. [18:59.700 --> 19:07.620] By definition, you need to be able to write arbitrary pointers in a sort of a list, right, to create a ROP chain, but that requires knowing memory leaks. [19:07.800 --> 19:11.880] And since we don't know any memory leaks here because there's no two-way communication, it's kind of a bust. [19:12.400 --> 19:15.180] However, we will be able to beat this, but we'll talk about that later. [19:15.940 --> 19:20.040] The second restriction, built on the first, it's preventing brute force. [19:20.040 --> 19:27.360] In a lot of blind challenges where the users don't know memory leaks and can't defeat ASLR, often you have to use some brute force. [19:27.880 --> 19:30.060] Generally, this brute force is either 4 to 12 bits. [19:30.340 --> 19:36.000] For example, the unsorted bin attack and house of corrosion both require, at minimum, guessing 4 bits of entropy. [19:36.280 --> 19:39.300] And so this is a brute force that could potentially cause your program to fail. [19:39.540 --> 19:43.360] And if it's like 4 bits, you know, 1.16 chance, so it can get messy, right? [19:45.160 --> 19:48.920] So, no brute force, which makes this program explaining it a lot harder. [19:49.940 --> 19:52.080] Finally, there's a sandbox using seccomp. [19:52.380 --> 19:58.180] It prevents you... it really disallows everything except the bare minimum to read the flag and get the program running. [19:58.760 --> 20:02.900] And so, you can't really just, you know, just call system and get a shell or something. [20:03.200 --> 20:08.400] And this is supposed to be kind of like emulate a sandbox process, which is like, you know, supposed to just do computation, right? [20:09.200 --> 20:12.580] And so, it sounds like a little difficult, right? [20:12.740 --> 20:17.040] Because how are you supposed to get a rough chain with all these protections? [20:17.040 --> 20:18.160] But it's possible. [20:18.480 --> 20:23.940] And to do it, we're going to be using three new houses, all leveraging the runtime loader, and get these primitives. [20:24.780 --> 20:31.540] And the last primitive will achieve something that has never been gotten before with a house, which will allow us to finally get ROP. [20:32.100 --> 20:35.120] So, let's get right into it with our preliminary analysis. [20:35.640 --> 20:36.840] How do we get more bytes? [20:37.480 --> 20:41.820] Well, going from one byte write to infinite byte writes is probably the move here. [20:42.100 --> 20:49.420] As you can imagine, encoding an entire ROP chain in a remote code execution cycle with just one byte isn't really possible. [20:49.600 --> 20:50.560] It's only eight bits of entropy. [20:50.560 --> 20:52.960] You can't encode an entire ROP chain with that. [20:52.960 --> 20:59.400] So, our first, you know, course of action should probably be to get more byte writes. [20:59.740 --> 21:01.920] And this was sort of a warm-up for the users. [21:02.100 --> 21:06.920] It's supposed to show them a little bit about the runtime loader and kind of get them warmed up with it. [21:07.500 --> 21:09.260] But it's worth noting, it's very contrived. [21:09.700 --> 21:11.880] It's an interesting puzzle, but it doesn't have any real-world bearing. [21:12.000 --> 21:16.640] There's not really a time where you're supposed to, you know, go from one byte write to infinite byte writes in the real world. [21:16.640 --> 21:17.700] It does not really make sense. [21:17.840 --> 21:21.820] But I think it's a good puzzle for competitors to get used to the runtime loader. [21:22.820 --> 21:27.360] And so, you can kind of ask, okay, so how do we get infinite byte writes? [21:27.740 --> 21:31.160] And the first course of action is to choose a surface to attack. [21:31.920 --> 21:36.620] But as you look at the program, you might notice there's not really any attack surfaces here. [21:37.000 --> 21:40.840] Every function in the code is all just syscalls. [21:41.160 --> 21:43.340] The read is a syscall, write is a syscall. [21:43.400 --> 21:45.200] Malik happens before, so that's not really relevant. [21:45.200 --> 21:47.940] But the read, write, and exit are all syscalls. [21:48.480 --> 21:53.200] Be warned, the exit here is not the exit we've been talking about with the, you know, calling exit handlers. [21:53.440 --> 21:56.480] But rather, it's just a thin wrapper over the exit syscall. [21:56.680 --> 22:00.160] So there's not really anywhere where our one byte write could be useful. [22:01.040 --> 22:04.060] But there actually is, and it's in between the lines. [22:04.440 --> 22:05.920] It's complexity that's out of sight. [22:06.100 --> 22:07.840] And that's the runtime loader once again. [22:08.760 --> 22:14.880] Basically, symbols such as, you know, write, exit, read, malik, are all being looked up at runtime. [22:15.560 --> 22:20.980] The program needs to know where these are located in memory, so they can call these functions like write, like read, like write, like exit. [22:21.260 --> 22:25.440] And so you have to call the runtime loader to look up these symbols for you. [22:25.800 --> 22:28.420] The function that does this is called dlfixup. [22:28.420 --> 22:31.260] And it's quite a complicated process, actually. [22:31.480 --> 22:34.700] And it could be a very useful target for a one byte write. [22:35.020 --> 22:40.280] We could potentially use the one byte write on the state of dlfixup to get infinite byte writes. [22:40.500 --> 22:42.040] And so let's try that. [22:42.900 --> 22:48.820] But first, we have to establish that if exit doesn't get called, you get infinite byte writes. [22:48.820 --> 22:51.040] This is actually due to technicality. [22:51.560 --> 22:54.360] It's because of GCC optimizations and function orders. [22:54.600 --> 22:56.000] As again, this is a little contrived. [22:56.120 --> 22:57.660] It's not actually useful in the real world. [22:57.780 --> 23:00.560] But if exit doesn't get called, you'll get infinite byte writes. [23:00.880 --> 23:06.740] And so the question is, how do we prevent exit from getting called by modifying the state of dlfixup? [23:07.400 --> 23:14.520] Well, the way I did it, anyways, was by doing an lsb overwrite on the l adder of the link map. [23:15.320 --> 23:16.560] Now, it sounds a little weird. [23:16.760 --> 23:18.560] And I guess it kind of is, right? [23:19.540 --> 23:23.300] But you can kind of learn on why exit doesn't... [23:23.300 --> 23:27.380] If exit doesn't get called, that causes infinite byte writes on the blog I linked earlier. [23:27.740 --> 23:30.240] It goes into detail, line-by-line breakdown, all that. [23:30.600 --> 23:32.060] But for now, let's just take it as a given. [23:32.420 --> 23:41.260] So to do this lsb overwrite on l adder, what we'll do is, we'll modify the least significant byte, which basically is like the same as adding a small constant to l adder. [23:41.480 --> 23:45.140] This causes the GOT location of write to be miscalculated. [23:45.820 --> 23:53.580] This, instead of writing the write function to the write location in the global offset table, we'll instead write the write function. [23:53.660 --> 23:55.500] I know it's a little confusing, because, you know, same name. [23:55.700 --> 23:59.100] But we'll write the write function to exit in the global offset table. [23:59.380 --> 24:02.580] This means that exit will not get called, and we'll call write instead of exit. [24:02.940 --> 24:04.620] That way, we'll end up looping the program. [24:06.460 --> 24:10.160] Alright, that's super cool, but what do we do once we have infinite byte rights? [24:10.560 --> 24:13.380] Well, we get leakless address call, of course. [24:13.640 --> 24:17.440] We're going to be calling arbitrary functions using dlfixup a second time. [24:17.760 --> 24:19.100] This is actually a new house. [24:19.300 --> 24:20.760] It's called houseoffixup. [24:21.320 --> 24:23.500] I don't know, that's just the name I chose. [24:23.760 --> 24:32.420] But it's a little more complicated than houseofblindness, but it uses that core structure of using rdebug to forge fake entries of the .dynamics section. [24:32.420 --> 24:38.420] For example, the two entries in the .dynamics section that we're forging here is dtsymtab and dtstringtab. [24:39.180 --> 24:43.700] These two entries specify information about how symbols should be looked up. [24:43.900 --> 24:49.820] For example, it'll tell you the name of the write function is write. [24:50.480 --> 24:56.160] And it'll also tell you other information for dlfixup to figure out where write is located in memory. [24:56.420 --> 25:04.280] By forging this, we can cause dlfixup to miscalculate where functions like write, like read, like exit, are located in memory. [25:04.580 --> 25:09.940] And in doing that, we can use rdebug to kind of make those fake symbols tables or string tables. [25:10.160 --> 25:11.380] And so it could be pretty useful. [25:11.540 --> 25:15.620] As you can see a diagram of how this is modified, it's not super important to talk. [25:15.760 --> 25:18.680] But basically, we can call arbitrary functions using fixup. [25:18.880 --> 25:20.380] That's our second usage of dlfixup. [25:20.960 --> 25:22.360] As you can see, it's a pretty cool function. [25:23.820 --> 25:24.400] All right. [25:24.600 --> 25:25.740] But from here, you may... [25:26.420 --> 25:31.120] What we also do is we use houseofixup to get houseofblindness. [25:31.340 --> 25:36.200] It's worth noting that houseofblindness is significantly more useful than houseofixup. [25:36.360 --> 25:44.140] This is because houseofblindness allows you to still call any arbitrary function, but it also lets you specify a parameter for that arbitrary function. [25:44.280 --> 25:48.380] While houseofixup is just a blank call, it doesn't let you specify any arguments for that function. [25:48.380 --> 25:52.580] So as you can imagine, it's more useful to specify an argument for any arbitrary function you're calling. [25:53.020 --> 25:54.420] And so we do this. [25:54.480 --> 25:59.680] We migrate from houseofixup to houseofblindness by calling dlfini using houseofixup. [26:01.340 --> 26:03.530] Remember, houseofblindness is only called with dlfini. [26:04.100 --> 26:06.470] So if we call dlfini, we can start using houseofblindness. [26:07.080 --> 26:11.000] And we do that with a relatively simple process, you know, using houseofixup. [26:12.220 --> 26:12.740] All right. [26:13.020 --> 26:16.920] Now that we have arbitrary calling, leaklessly, what do we do from here? [26:16.920 --> 26:24.040] Because, yeah, we can call functions, that's cool and all, and we've escalated our vulnerability from just an out-of-bounds write to calling functions. [26:24.520 --> 26:27.620] But we need ROP from here if we want to do anything like reading a flag from memory. [26:28.040 --> 26:30.900] And the issue is that ROP needs leaks. [26:31.440 --> 26:37.820] It's in order to make a ROP chain, you need to specify the functions you're going to call or return to in the ROP chain, right? [26:38.400 --> 26:43.000] However, writing these functions out in memory is impossible unless you have memory leak. [26:43.000 --> 26:44.240] So what do we do? [26:44.920 --> 26:46.880] Well, we'll need a man on the inside. [26:47.080 --> 26:52.300] We'll need someone inside the program or a piece of code in the program which can write these addresses for us. [26:52.840 --> 26:55.120] So we kind of need this ROP writer, right? [26:55.160 --> 26:59.820] We need someone who can write arbitrary pointers to arbitrary locations in memory. [27:00.860 --> 27:02.280] So what is it going to be? [27:02.420 --> 27:04.320] How are we going to get a ROP writer? [27:04.320 --> 27:07.000] How are we going to write arbitrary pointers to arbitrary locations? [27:07.680 --> 27:11.700] The answer is, of course, dlfixup for the third time. [27:11.840 --> 27:12.840] We're using dlfixup again. [27:13.180 --> 27:14.140] It's a great attack surface. [27:14.700 --> 27:16.600] And what we're going to do is... [27:16.600 --> 27:18.260] It's actually super cool how this is done. [27:19.640 --> 27:21.640] Basically, we're going to say... [27:23.120 --> 27:25.480] Basically, dlfixup is a three-step process, right? [27:25.660 --> 27:28.580] What it does is it looks up a symbol in libc. [27:28.580 --> 27:34.480] It then looks up where that symbol should be written in the GOT, and then it writes the symbol to the GOT. [27:34.780 --> 27:35.740] So it's a three-step process. [27:35.800 --> 27:39.880] It does the lookup, and then it does the where it should be written, and then it actually writes it, right? [27:39.920 --> 27:40.440] So three steps. [27:41.500 --> 27:48.140] And then you can kind of say, okay, well, how does dlfixup know where, for example, exit is in libc? [27:48.360 --> 27:54.080] And how does dlfixup know where exit should be written in the GOT? [27:54.480 --> 27:56.120] And these are two very good questions, right? [27:56.140 --> 27:57.300] How does dlfixup know that? [27:57.300 --> 28:02.440] And by asking that question, you can end up getting arbitrary symbols to arbitrary locations. [28:02.440 --> 28:08.240] Because what you can do is you can trick dlfixup into writing the wrong symbol to the wrong location. [28:08.460 --> 28:10.360] And just like that, you can write arbitrary pointers. [28:10.660 --> 28:14.900] And so it's super cool because you can kind of, you know, use that to build a ROP chain. [28:15.540 --> 28:18.160] However, this is a lot easier said than done. [28:18.280 --> 28:20.420] It required an absolutely hellish amount of legwork. [28:20.820 --> 28:33.520] It required not only several supplementary houses and exploits that are including, but not limited to, global max fast for uncontrolled pointer write, IO string overflow and IO string finish for malloc free primitives. [28:33.760 --> 28:36.460] Credits to not the ghost slash Robert Chen for that. [28:37.280 --> 28:40.880] Double freeze using the IO save base and its associated functions. [28:41.440 --> 28:45.860] Open memstream to spray pointers into a chunk, among other lesser primitives and houses. [28:45.980 --> 28:47.100] It's a complicated process. [28:47.360 --> 28:49.700] In fact, it could probably be an entire presentation by itself. [28:49.960 --> 28:53.840] And so the entire process is, of course, documented on the blog post I mentioned earlier. [28:54.400 --> 28:57.300] But it could definitely be an entire presentation itself. [28:57.920 --> 29:09.280] Anyways, this construction of, you know, tricking DL fix up with this fake link map we're crafting, using all these different supplementary houses and stuff, actually ends up in a very beautiful construction. [29:09.580 --> 29:12.100] And that construction is house of sight. [29:12.420 --> 29:14.740] That's the diagram of what that construction looks like. [29:15.360 --> 29:20.960] Basically, we build a fake link map using all those different, you know, supplementary houses and exploits I mentioned. [29:20.960 --> 29:29.800] And what we end up with is this construction which, when DL fix up is called on, ends up writing arbitrary pointers to arbitrary locations. [29:30.160 --> 29:31.600] It's a two-parameter system. [29:31.780 --> 29:36.620] So we specify where we want to write the pointer and then what we want to write by shifting around two values. [29:36.880 --> 29:42.420] We shift around the where parameter to adjust where exactly that pointer gets written. [29:42.620 --> 29:48.100] And then we can adjust the what parameter to adjust what pointer gets written. [29:48.100 --> 29:50.100] But like that, we can build a ROP chain. [29:50.600 --> 29:54.020] The process of actually calling this ROP chain is hell on its own. [29:54.240 --> 29:58.420] But it's documented in the blog post if that's a technical detail you're interested in. [29:58.740 --> 30:04.320] But the point is that all these houses using the runtime loader are very cool. [30:04.640 --> 30:10.600] And I talk about this in the presentation more, or the blog post more, but the power of offsets is really shown here. [30:11.900 --> 30:19.040] Because the DLFixUp and all the runtime loaders operate on sort of offsets, you don't really need to specify pointers. [30:19.240 --> 30:20.420] You can just specify offsets. [30:20.600 --> 30:28.400] And that has very good implications for leakless exploits, such as house of sight, house of blindness, and house of fix-up. [30:28.560 --> 30:30.920] Because all these houses don't require memory leaks. [30:31.300 --> 30:33.100] Offsets are more powerful than pointers. [30:33.720 --> 30:34.760] And that's it. [30:34.860 --> 30:36.340] That's the Nightmare Challenge. [30:36.600 --> 30:43.160] It had all these three houses and allowed us to never before write pointers to arbitrary locations without needing memory leak. [30:43.820 --> 30:46.500] It was a lot of fun because you can learn a lot about the runtime loader. [30:47.040 --> 30:48.700] You can learn a lot about houses. [30:49.040 --> 30:50.700] And it was honestly so much fun to make. [30:50.800 --> 30:54.160] And it was really fun to talk with competitors about their attempted solutions. [30:55.560 --> 30:58.600] Unfortunately, they didn't have any solutions by the end of the CTF. [30:58.800 --> 31:01.400] But later, I talked with a lot of competitors about it. [31:01.540 --> 31:03.000] And, you know, gave them the right hints. [31:03.100 --> 31:04.320] And someone actually solved it in the end. [31:04.380 --> 31:05.000] So that was super cool. [31:05.860 --> 31:07.040] But yeah, it was a lot of fun. [31:07.040 --> 31:08.780] And it was really cool to learn about. [31:09.800 --> 31:11.520] If you want, the link is up there. [31:11.800 --> 31:14.000] That tiny URL of the slides. [31:14.000 --> 31:15.700] If you want all the links in this presentation. [31:15.960 --> 31:17.680] So you might want to take a screenshot if that's something you're into. [31:18.260 --> 31:19.160] But yeah, that's it. [31:19.900 --> 31:22.060] That's the runtime loader and how it can be used. [31:22.340 --> 31:22.980] So, yep. [31:29.940 --> 31:30.580] Excellent, Daniel. [31:30.760 --> 31:33.440] So now if anyone has any questions, it's time for Q&A. [31:34.280 --> 31:35.980] Feel free to ask Daniel any questions. [31:46.600 --> 31:46.960] No? [31:47.120 --> 31:48.340] Looks like there aren't any questions. [31:48.560 --> 31:49.200] Thank you so much. [31:49.200 --> 31:51.220] Oh, there's a question back there. [31:51.260 --> 31:54.380] I'm risking a lot of, exposing a lot of ignorance here. [31:54.480 --> 31:55.920] A lot of this went a little over my head. [31:56.480 --> 32:01.400] But when you're doing a ROP chain, maybe there's a point that I missed. [32:01.520 --> 32:06.360] Because I thought usually, you know the relative offset of the functions of a standard library. [32:06.520 --> 32:08.000] But you don't know where it's loaded. [32:08.340 --> 32:10.720] And I thought that's where you needed a leak. [32:11.120 --> 32:11.520] Yeah. [32:11.640 --> 32:13.140] But you're saying that you don't need that here. [32:13.300 --> 32:17.300] So isn't that just, you can write, are we writing it to arbitrary offsets? [32:17.300 --> 32:21.540] That's not, like we don't need to calculate the offset plus the base is what you're saying? [32:21.740 --> 32:22.260] Yeah, exactly. [32:22.560 --> 32:25.440] So the entire point here is that we don't know base, right? [32:25.760 --> 32:26.900] And that could cause a lot of problems. [32:27.040 --> 32:29.320] So he said, wait, hold on a second. [32:29.940 --> 32:32.000] ROP chains require you to know base, right? [32:32.080 --> 32:36.900] You need to know the base of the, where the shared library or libc is loaded, so that way you can write those addresses. [32:36.900 --> 32:38.020] And that's very true. [32:38.100 --> 32:42.320] In a normal circumstance, you need to know the base of the shared library or libc or whatever. [32:42.640 --> 32:46.480] But in this specific case, what we're using is we can only specify the offset. [32:46.740 --> 32:59.300] And then what we have is, remember that man on the inside, that ROP chain writer, which in this case is house of sight, which allows us to write those addresses in the ROP chain by only specifying the offset without specifying the base. [32:59.300 --> 33:07.060] And the reason why not specifying the base is such a powerful tool is because getting base requires this vulnerability on its own. [33:07.360 --> 33:11.980] Remember, you need a memory leak or something like that which can tell you what exactly the libc base is. [33:12.380 --> 33:16.260] But the runtime loader allows us to not have to know that. [33:16.340 --> 33:18.440] We don't need that separate information or that separate vulnerability. [33:18.620 --> 33:22.320] We can do it without it by using dlfixup and its associated functions. [33:23.440 --> 33:24.580] So yeah, that's a good question. [33:30.510 --> 33:31.270] Yeah, what's up? [33:31.270 --> 33:32.070] Yeah, so [33:36.720 --> 33:40.900] is there like a common database or website to learn about houses? [33:41.660 --> 33:47.420] Oh yeah, so actually a lot of CTF wikis will list a lot of the different houses for specific attack surfaces. [33:47.820 --> 33:59.640] For example, I know the HowToHeap repository, which is by the Shellfish CTF team, maintains an active list of all the current and patched houses for a specific type of attack surface called the heap. [34:00.180 --> 34:03.320] But yeah, basically there's an entire list of those houses. [34:03.320 --> 34:04.080] It's big. [34:04.240 --> 34:07.060] I think it's like 28, 29 or something like that. [34:07.220 --> 34:08.680] But it's a big list of houses. [34:09.180 --> 34:11.160] So yeah, if that's something you want to learn, I'd recommend checking it out. [34:11.420 --> 34:11.740] Thanks. [34:11.960 --> 34:12.080] So [34:21.520 --> 34:27.760] he mentions that this process might only work for shared libraries, right? [34:27.840 --> 34:35.440] Because if it's statically loaded, then you don't end up doing this sort of relocation process because you already know all the addresses in memory. [34:35.590 --> 34:36.180] This is true. [34:36.420 --> 34:43.550] In fact, if the binary is statically loaded, you don't end up using functions like dlfixup because you already know the location of every address in memory. [34:43.780 --> 34:46.800] This means dlfixup is never called, so you can never really use this exploit. [34:47.110 --> 34:48.050] However, that's okay. [34:48.380 --> 34:55.200] Most times, all binaries, or majority speaking, are all dynamically loaded. [34:55.200 --> 34:56.880] So usually, that's not much of an issue. [34:57.920 --> 34:58.000] Yeah. [34:59.280 --> 35:00.200] Yeah, good point. [35:01.040 --> 35:01.800] Yeah, what's up? [35:02.070 --> 35:04.340] I'll add, because this thing tripped me up once. [35:04.920 --> 35:18.900] I think you can have, even if it's dynamically loaded, there is a setting, right, for actually calculating the memory locations of functions before runtime. [35:19.360 --> 35:20.590] Yeah, so this is true. [35:20.820 --> 35:21.800] This is called relrow. [35:22.150 --> 35:22.630] Relrow. [35:22.650 --> 35:23.150] Yeah, yeah. [35:23.150 --> 35:33.980] So he mentions that very often that the functions in the GOT are calculated before runtime, which is true. [35:34.150 --> 35:35.110] Yeah, it happens sometimes. [35:35.540 --> 35:40.400] It's not all the time, but it does happen quite frequently, where people will enable relrow in their programs. [35:40.570 --> 35:44.880] And this means that all of them were calculated beforehand, so dlfixup never gets called. [35:45.090 --> 35:58.960] In this specific case, house of binaries still applies because it's at exit time, but for the house of site and house of fixup, both of those require dlfixup, and that is only called when you have partial relrow. [35:59.110 --> 36:01.480] Because partial relrow is that, when it does it, it's called lazy loading. [36:01.480 --> 36:04.230] It does it at runtime. [36:04.820 --> 36:07.920] But as you mentioned, yeah, there's also full relrow, which does it before. [36:09.540 --> 36:10.550] Yeah, great question. [36:20.640 --> 36:21.140] All right. [36:21.220 --> 36:21.900] No more questions? [36:24.940 --> 36:25.440] All right. [36:25.520 --> 36:26.500] Well, thank you so much, Sammy. [36:26.680 --> 36:27.740] It was an excellent talk. [36:28.340 --> 36:28.620] Thank you. [36:35.220 --> 36:37.000] Stick around at 9 o'clock. [36:37.120 --> 36:39.160] The next talk will be Not Enough... [36:39.160 --> 36:41.420] Just Enough RIT Cloning to be Dangerous. [36:41.660 --> 36:43.200] So stick around for the talk. [36:43.400 --> 36:43.720] Thank you.