Relocation Bonus: Attacking the Windows Loader Makes Analysts Switch Careers
Cano, Nick
The arbiters of defense wield many static analysis tools; disassemblers, PE viewers, and anti-viruses are among them. When you peer into their minds, these tools reveal their perilous implementations of PE file parsing. They assume PE files come as-is, but the Windows Loader actually applies many mutations (some at the command of the PE itself) before execution ever begins. This talk is about bending that loader to one's whim with the Relocations Table as a command spell. It will demonstrate how the loader can be instrumented into a mutation engine capable of transforming an utterly mangled PE file into a valid executable. This method starts with multiple ASLR Preselection attacks that force binary mapping at a predictable address. It then mangles the PE file, garbling any byte not required prior to relocation. Finally, it embeds a new Relocations Table which, when paired with a preselected base address, causes the loader to reconstruct the PE and execute it with ease. This isn't a packer or a POC, it is a PE rebuilder which generates completely valid, stable, and vastly tool-breaking executables. This talk will show you how this attack twists the protocols of a machine against the controls meant to protect it. It flexes on tools with various look-what-I-can-break demonstrations and, if you write similar tools, it'll make you rethink how you do it.
 Okay, cool. So I'm a bit nervous now following the monkey comedy hour. It's going to be hard to follow that up. But this is relocation bonus. Attacking the windows loader makes analysts switch careers. So quick introduction. I am Nick Cano. I'm 25 and I'm a senior security architect at Silance. I have written a book on hacking games. Sorry I didn't know a clap was coming. I've written a book on hacking games. I'm also a plural site author just had a course published like a month ago on C++ secure coding practices. And what does this talk about? Well this talk in essence is about the windows portable executable header and how it can be weaponized to destroy parsers and other static analysis tools, things like disassemblers and such. And that all culminates into like a PE rebuilder for 32 bit windows binaries. So you might ask why are you attacking relocations? So this is a disassembly of a game. A game that I make a bot for. Disassembly to get rid of a jump call. Just put some knobs in there. And then I saved my new binary to disk. I ran it and it crashed. I was like what the heck is going on here? And I looked at the code and my knobs were now changed. The code was different from what I had patched. And I know what you guys are thinking. What the heck? What's going on here? So as it turns out there are these things called relocations. So at first I was like oh man this is broken for no reason. Then I was like okay relocations corrupted the patched code. Then I was like maybe I shouldn't patch code that is relocated. But then I thought I can use relocations to hide my arsenal in the bowels of the machine. So before we dive into the nitty gritty technical details, let's restate what we're doing here. The windows loader has this piece in it that applies relocations. Relocations exist because binaries will have pointers in there that are sensitive to the position of the binary and where it is loaded. So if the binary is loaded at a different base address than the compiler expected, those things need to be relocated. So if we can weaponize that, if we can instrument that and make it modify things for a different purpose, we can use that for obfuscation. And we can get an obfuscator such that there's no de-obfuscation code. The windows loader does it all for us and then it's kind of a mystery if it's the first time seeing this. You're like this thing is a mess on disk and then in memory it's fine before anything executes. So that's what we're going to talk about. So first we need to say, well, what are relocations? They exist to enable ASLR and dynamic mapping. That's not exactly true. It's a bit more nuanced than that but for the sake of executables, they exist to enable ASLR. And let's look at an example. So this is the same binary loaded at two different base addresses and I'm specifically looking at a function table. And as you can see, the function tables in the two different loads, one on the left and one on the right, the addresses are different, at least that upper part of them. And on the left we have what is that, O133 and on the right we have O117. That is because they're at a different base address. We can see that actually here outlined in blue is kind of reflected by the address of where this function table is in the binary's memory and we can see that the values are actually changed here in the bytes of the function table. So let's take a sidebar really quick to talk about the PE header. By the way, this is not my diagram. I got it from Corkami if you don't know what that is. Check it out. A really good resource. So there are three things we're worried about and the first thing is image base. Image base is a part in the PE header. Well, so the PE header is kind of the description of a binary in Windows. What needs to be loaded? What parts of memory need to have? What access rights? What functions are imported? Stuff like that. But we're specifically interested in image base, which is a value you can use to say what base address you want your binary loaded at and if ASLR is not on, that will be followed. Throughout the talk I might refer to this as desired base. There's also DLL characteristics. Now this is just a two byte value and it's like a flags value. We're specifically interested in the bit 0x40, which if it's set means ASLR is on and if it's not set, ASLR is off. And ASLR is just the part of the loader that says okay, I'm going to put this binary at a different address for security purposes. And finally there's the data directories and specifically there's an offset from the data directories called image base entry reloc. So the data directories is basically just an array of pointers and sizes to different pieces of data that the loader needs and we're only worried right now about the reloc index which points to the relocations table. And the relocations table looks something like this. At the top here you have the data directories and then we have this little gray block and then we have what the reloc blocks actually look like. In the data directory there's a pointer to these blocks to the first block and then all the blocks should be contiguous after that. And there's also a size which refers to the size of all of the blocks in bytes. Each block ends in an 0x000, just two byte zero to say this is the end of the block. There's another block coming so that the loader knows that it needs to look for a new relocation center. So each block looks something like this. First there's a virtual address. That virtual address is sort of like a base so instead of encoding every single address that needs to be relocated you encode a virtual address and then offsets relative to that virtual address that are less than four bytes long and that just saves space. So this virtual address is the base which we will offset from in each entry. There's also size of block and that is the size of the entire block. That is including the size of size of block and the size of virtual address. So those are both four bytes so four plus four and then in this case we have four entries which are each two bytes so then plus two four times and you get the size of the block. So each entry is 16 bits. Four of those bits or half of a byte are the relocations type which we'll talk about in a second and then the 12 remaining bits are used for the offset. So you get up to 4096 from the virtual address. So you can have virtual address and then you can have as many entries as you want within 4096 bytes of that to be relocated. And this optimization it might not seem like much but when you have a function table where you have like 30 things to be relocated back to back it makes sense why it's done this way. So this code kind of describes how the loader does relocations. This is just pseudo code. Obviously it's not written in modern C++. But this gives you the gist and we're going to talk about this. Specifically what we care about is the first line where we say delta is equal to base. That's the base that we're actually mapped at minus desired base. That's a base that we asked for in the PE header. And then the first if statement inside the loop is the type of relocation we're going to use. So if we were to look at that first if statement we would see that a plus equal operation is used during relocations and the right hand side of that is delta. So essentially everything that is relocated is the value that's there plus equals delta which is derived from the base address we're given minus the base address we wanted. And that's how everything gets fixed up to point to the right place. So the conclusion that we can come to is that if we want to abuse relocations or if we want to be able to control what they're writing base must be pre-selected or we must have some way to take base and make it be something we know that isn't the base that we asked for. There needs to be a difference there. And desired base is the only means of controlling ASLR so that's a bit tricky. And delta is also dependent on desired base. So we know we need to use desired base somehow to pre-select the base but it's not clear how. And I thought maybe we'll just try invalid stuff. So I tried negative one or max int or whatever you want to call it and it didn't work. PE fails to load. Well what I know is those final four Fs actually have to mask and be zero because of alignment. So I tried all zeros. PE fails to load invalid header. So then I combined the two and PE loads at base OX1 with four zeros after it consistently every time. So we're getting a base address every time that is different than the one we asked for and always the same. As I later learned, Korkami already had all of this documented. So this has been known for a few years as well. So let's talk about the loading process. It's pretty complex and this is not all of it but this is sort of what the Windows loader does. So you don't really have to read this whole thing. What we really care about is that the things in blue are things that happen before relocations are applied by the loader. And the things in orange are things that happen after relocations are applied. So if we want to obfuscate things, we might want to obfuscate things that happen after relocations are applied or we might break something. So if we just take out everything that isn't relevant, this is when we do our attack, this is what it will look like. This is the flow chart we'll go through. Now you might notice there's a red arrow coming from ASLR enabled which means we're actually going to have ASLR set to off and the attack happens here. So it turns out we're not tricking ASLR but when the allocator is being asked to locate something at FF, FF, 0000, it's saying yeah, I can do that but it's spitting back 0x1, 0000 for whatever reason. So our targets for relocation obfuscation, well we know import table is loaded post relocations. So we can obfuscate the entire import table. That is all of the libraries and functions that are imported from DLLs can be obfuscated so on disc they look like trash but the loader fixes them up at load time. Additionally, even though sections are mapped pre-reloak, they're not used until execution which is post-reloak. Additionally, their memory protections aren't set properly until after relocations in case read-only stuff has to be relocated. Also entry point isn't used until post-reloak so we could obfuscate the entry point making it that much harder. The problem is that is right protected before relocations are done so you can only do this if the target machine doesn't have data execution prevention turned on. So and the conclusion is pretty much what I just said. We can hit imports, code and resource sections and optionally the entry point with this attack. And that is this little green area you can see highlighted. Now you might look at this and say okay that's only like half of the binary but what you have to realize is this is a minified PE and that is actually most of the important stuff and in like a real binary it's going to be somewhere like above 90% or 95% of what's actually there and what's actually important content. So the final attack looks something like this. We'll load the target PE file, the PE file that we want to obfuscate. Then we'll apply the original relocations because remember relocations are there for a reason 0x10000 is probably not where that binary wanted to be mapped so it's going to break unless we apply the original relocations and say okay fix everything up for that address. Once we do that we flip ASLR off because remember even though we plan to trick ASLR we're actually tricking the allocation function. Then we set desired base to that tricky FFFF000 and then we loop over all of the data that we want to obfuscate that is sections, import table, stuff like that in D word or U32 size chunks and we decrement them by what we expect delta to be. That decrement is because the relocations part of the windows loader doesn't increment so we just do the opposite of that and that is kind of our obfuscation. So every integer in the stuff we're obfuscating is going to be messed up a bit which is good enough. Then we discard the original relocations table because we've already applied them on disk. We generate a new relocations table reflecting the positions of every single decrement that we've done so that the loader can fix them up. We save the new PE file to disk and profit, we're done. We now have a PE that is completely mangled on disk. It has a huge relocations table and then when it runs it maps perfectly fine. So let's jump into some demos. So here I'm trying to load in an IDA. You can see the messed up DLL name IDA thinks is an import. Okay IDA throws some weird errors. On older versions of IDA it was crashing sometimes. I can't get newer versions of IDA to crash. You can see IDA lights up red. It doesn't know what it's looking at. It only sees one subroutine and even the assembly code there is a bunch of invalid instructions and if you look at the strings they look like trash. Everything looks like trash. Binge is going to be mostly the same. It's going to see two subs but it's got question marks there. It doesn't know what it's looking at. So next is CFF explorer. This one actually crashes somewhat consistently. I think it's this one that reaches out on the network and does a bunch of out of band scans that come back a few minutes later and those do cause crashes more than any other tool. There's no crashes in these demos because one you have to get lucky. They're not even consistent across the same binary and also you have to get a binary that happens to cause them. I haven't looked more into it. I'm really worried about as you can see here the import names are all messed up. Some of them look kind of right because remember we're doing it in four byte chunks. So for strings we're only messing up like one byte at a time. Depends just completely jokes. It has no clue what it's looking at. And then resource hacker just throws an error. It says you don't have a valid resource table because a resource section is encrypted or obfuscated. And even PE bear you can see it just lights up red. Imports look like trash. If we tab over to the disassembly we'll see it says okay these are invalid instructions. We'll also see a bunch of jumps into locations that can't possibly be jumped to. And finally PE view it's going to be the same thing. It's just going to maybe pop some errors and then imports are going to be messed up. Codes are going to be messed up. Okay. And that is that one. Okay. So we've shown that this attack is viable and that it works and we can do something with it. Oh wait what am I doing? I have another video to show. I'm pretty dumb. We've seen it break stuff but we haven't seen it work. What am I talking about? So let's do that. Okay. So here you can see me clicking on the binary and it runs. This is an encrypted obfuscated binary with this method it's running. And it's just process explorer that I've obfuscated. It's going on over there. It's just process explorer that I've obfuscated so that's why you saw process explorer launch. I did this because this is a relatively complex application. It's got a lot of stuff in its resource section and it's got a lot of non-trivial code. So I figure if it works on this it'll work. What you see right now is me searching for invalid strings in the image to show you that this is actually obfuscated. Some of the strings are not obfuscated because they're in the dot r data section which I didn't obfuscate for this demo but if you look here you'll see a bunch of file paths and a bunch of like error strings that almost look right but are actually gibberish when you try to read them. And it also kind of messes up like a ton of AVs so here's a malicious sample. We're going to go ahead and pack that. And now you see it's generated a new sample sorry. And we're going to drop them both on virus total and see what happens. The first one is going to light up already. And then we're going to load it. And now the second one even though it's effectively the same binary that's just obfuscated by the Windows loader I guess we only get two reds. Okay so we know the proof of concept works. We should probably test it on multiple platforms. Windows 7 it works. That's what I originally developed it on. Windows 8 no one uses Windows 8 come on. Windows 10 aw fuck it doesn't work. Yeah I get it. Okay I'm done sorry. Okay no but really we have to find a new attack for Windows 10. What ends up happening is asking for 4f's 4 zeros does not yield that one with 4 zeros after it. It actually lets ASLR go through the process even if the ASLR bit is off and then we get a random base address so no good. So I thought maybe I can embed multiple PE copies for all possible base addresses and then use some kind of reloc tricks to point to the right one which is something Corkami has actually shown as possible but that would be way too big it would inflate the binary even more than using relocations already does which is actually pretty significant. So then I thought maybe I can tweak ASLR configuration because I know there was a major change on Windows 10 in how all of that works and that actually does work. So if you notice the demos were on Windows 10 and this is what I used to demo on Windows 10 we set mandatory ASLR to on and bottom up ASLR to off and if we do that with this dot red file for specific executables it works but I don't really like it. As soon as you start touching these registry keys everything is going to light up AVs are going to light up, EDR is going to light up. Monitoring is just going to say hey this is bad if it doesn't just block this entirely so I wanted something else. And then I did a lot of playing with it. I was trying to see what I can do to control base addresses and I noticed this. So this is just a file. I have multiple instances running and I'm basically just looking at the base address of this file and even though I have multiple processes running of the same file I noticed that they all have the same base address. There is something in Windows that is caching that base address or reusing it on loads maybe it has something to do with copy on write not exactly sure. And then I noticed okay if I take this file I copy it to another file. I delete the original and then cat the copy into the name of the original. It sounds crazy but it's a way of copying a file to itself without the file system being able to track that it's the same file and then I launch it again. I invalidate that base address. So they all have the same base address again but it's different from what it was before. So this tells me there's something in the file system that invalidates that base address that's being reused. So I can at least brute force and get a base address that I want by just invalidating it every time. So we're going to go over a pretty complex flow chart. So really quick here's a flow chart on how to read flow charts. Okay I actually put it in the deep fryer so let's just talk for a sec. So what I figured out is that if you take a file and you memory map it then you close the mapping and you launch that file it will be matched with a different base address than last time you launched it. So if you do this in a loop you launch a file check the base address if it's not what you want memory map the file close the mapping launch it again you can keep getting a new base address and you can brute force and pre-select whatever base you want. And that attack looks like this. So you'll have a root process and a drone process effectively where the drone process is just launching and throwing back a specific return code based on whether or not it has the base address that it wants and then you have your root process which in a loop is doing that mapping stuff which is highlighted in yellow. So here in orange is really the main part of the attack within the root process we're highlighting the loop that's going in and saying okay I'm going to keep looping until I see the right base address which is signaled from the drone process by an exit code of OX what did I use bad beef yeah. So if OX bad beef is the exit code of the drone process it knows that it didn't get the right base address and it will keep doing this loop. Now these are actually the same file so effectively we want to put that embed this attack in the binary we want to obfuscate it will make a copy of itself to be the drone process so that it can map that copied file to invalidate its base address and launch it over and over and over again but effectively there's the same code which is why you see at the top we create a mutex and check it if it exists and that's how we know if we're the root process or the drone process. So to weaponize this the tool must create a new section with enough room for this brute forcing code embed the code inside of this new section and make that code aware of the original entry point then it needs to point entry point within the PE header to the embedded code so effectively it's a typical parasitic infection and for this to work this ASLR pre-selection code this brute force attack must be position agnostic because we're just throwing in a binary wherever you can it must be generically embeddable in any PE and yeah that's basically it. So to do that I made a bunch of pre-processor macros so I could do everything in line this you don't need to read these this is just showing a bunch of variadic macros and once I had all of these in place it looks something like this so like some of the weird stuff I had to do like some of these macros take a string in and then they emit that string as like assembly bytes and then jump over those bytes and then use like a call and then a ret to get the address and like move it into a variable really weird stuff but this is how I made all that work now you might be looking at this big block at the top which is doing a bunch of function resolution and you might say Nick why didn't you resolve get module handle and get proc address and then do the rest normally and so my answer to that is this is like more obfuscated it's really indirect this code actually looks like a mess so it's harder to reverse that answer is a lie I got really carried away writing the macros and then I had resolved all of my functions by the time I realized I could have done that so yeah and it worked so this actually worked so there's some caveats because this attack is actually running code we are sorry I had my bullets wrong in my head come on make your shit together so it can be slow it takes about 200 iterations to land on the base address that we want so depending on what machine you're running it on it can happen in five seconds or a minute because you're launching this executable hundreds of times and you're mapping it to memory in between so also the size of the executable matters size matters and imports can't be obfuscated right because what ends up happening is the binary has to get mapped into memory properly for this attack to start running and if imports are obfuscated on the times where we don't hit the right base address the windows loader is just going to dope out and it's going to say okay this is wrong I can't resolve any of this and it's just going to error so that's a bit of a problem but there are some advantages because first we don't really need to use that one with 4 0's after it before if you saw the request of F F F F 0 0 0 0 and you're like a parser you know it's going to map this address but with this attack we can pre-select any address within the realm of possibility and the side effect of this is some form of symbolic execution or manual analysis is now needed to determine what base address is going to be obtained so it's harder to just take this and do the relocations like inside of a parser to fix up the binary so yeah we can't do imports which was actually really cool to see but at the same time it's harder for automated analysis and let's see what this looks like. So you see process monitor on the right and you're going to see that just showing a bunch of process launches this is a brute force going you can see on the left it copied itself to just its name 2.exe it launched itself a bunch of times looking for the base address showing that in process monitor and eventually it created that binary and then eventually it launched which we see down here we see that it actually loaded and launched process explorer which is what we had obfuscated. If we go and we look we see okay we had our first thing it copied the second thing it launched it that got mapped properly and then that our process explorer actually then drops a 64 bit file and launches that which is why you see an extra process but that's just showing that we've actually obfuscated resources and they got deobfuscated so that process explorer could dump another binary out of them perfectly without messing it up. Yeah and so what can we really do with this we've seen it work we know it's an interesting attack we've got the windows loader deobfuscating stuff for us but what can we do with it? So you can annoy analysts with this it's going to be annoying like maybe the first time or first few times you see this but if you see this a lot it's actually not that annoying once you know what's going on. We can break a lot of automated static analysis systems as we saw before a lot of PE parsers just choke on it. We can break a lot of tools those two are kind of one in the same but we can break maybe some AV parsers I haven't looked too much into it but I imagine there's at least one out there that's choking on this but there are a lot of improvements we can make as well such as more obfuscations now this might mean new targets so instead of just doing like a few sections and imports we might be able to find and identify other things that can be obfuscated or we might be able to do multiple passes so we do right now each d-word in the file consecutively in any of our targets but what if we do that and then we start over offsetting by one and then we start over offsetting by two and then offsetting by three instead of getting like one or like one and a half bytes that are messed up for every four bytes everything's obfuscated the thing is your relocations table is going to blow up if you do this but I mean if you really want to make it hard to analyze it's possible we might also be able to do header scrambling so we might be able to embed things in the relocations table that say these things need to be relocated that aren't things that we've done anything to things that are needed by the loader but not needed during execution so then the loader will go and it will relocate things that were already correct then corrupting them in memory so not only would you have something that looks corrupt on disk and fine in memory you could selectively corrupt things to make dynamic analysis hard because right now this doesn't really do anything against dynamic analysis and we might even be able to combine this with runtime packers and that would just be an extra level of annoyance now support for 64 bit binaries is definitely possible the pre-selection attack it seems like there would be a much larger search space but as far as I can tell Windows does kind of prefer to load it around a base address that is like the maximum possible user space address that's available minus the size of the binary it tries to load it around there at first so if you do that you might be able to narrow down the search space though I haven't exactly tried it and the FF FF 000 trick works similarly in 64 bit but not exactly the same might also be able to support DLLs I actually have no evidence for that I just think it's probably possible with the right modifications and then you could also do selective obfuscation so rather than obfuscating the entire binary which just throws up red flags right away you might like have an IP address embedded in a section and you only use this to change one byte in that IP address so now the analyst is like okay this is the CNC because this is a string this is a host name or an IP address so then they try to connect to that and they're like okay the CNC is dead but they were looking at it in IDA which isn't doing relocations at load time you relocate just one byte in that address and now you're good to go like and it's it's kind of confusing it won't trick a lot of people for too long but it will add an extra annoying bit there and that's the end. So we have a bunch of links here this QR code I believe goes to the slides so here's what's going to happen with the code release we've decided to do it like this because while we're not dropping any O-day or anything that's like super bad we are breaking some stuff so we want to give people time to catch up with that so in two weeks we're going to drop some samples so if you're working on fixing this if you write a parser you work for an AV company you write a PE tool you can go ahead and start thinking about this the slides are already up so all the information is there in two weeks you'll have samples to test on and exactly one week after that full source code release everything will be dropped and anyone can play with this make changes commit the source code is in C++ visual studio 2017 so I think if you hit the relocation bonus get hub repo I've posted a timeline on there for now that will eventually point to the code full timelines on there but I think it's September 3rd for the full code release thank you.
 Okay, cool. So I'm a bit nervous now following the monkey comedy hour. It's going to be hard to  follow that up. But this is relocation bonus. Attacking the windows loader makes analysts switch  careers. So quick introduction. I am Nick Cano. I'm 25 and I'm a senior security architect at  Silance. I have written a book on hacking games. Sorry I didn't know a clap was coming. I've written  a book on hacking games. I'm also a plural site author just had a course published like a month  ago on C++ secure coding practices. And what does this talk about? Well this talk in essence is  about the windows portable executable header and how it can be weaponized to destroy parsers and  other static analysis tools, things like disassemblers and such. And that all culminates into  like a PE rebuilder for 32 bit windows binaries. So you might ask why are you attacking  relocations? So this is a disassembly of a game. A game that I make a bot for. Disassembly to get  rid of a jump call. Just put some knobs in there. And then I saved my new binary to disk. I ran  it and it crashed. I was like what the heck is going on here? And I looked at the code and my  knobs were now changed. The code was different from what I had patched. And I know what you guys  are thinking. What the heck? What's going on here? So as it turns out there are these things  called relocations. So at first I was like oh man this is broken for no reason. Then I was like  okay relocations corrupted the patched code. Then I was like maybe I shouldn't patch code that is  relocated. But then I thought I can use relocations to hide my arsenal in the bowels of the  machine. So before we dive into the nitty gritty technical details, let's restate what we're  doing here. The windows loader has this piece in it that applies relocations. Relocations  exist because binaries will have pointers in there that are sensitive to the position of the  binary and where it is loaded. So if the binary is loaded at a different base address than the  compiler expected, those things need to be relocated. So if we can weaponize that, if we can  instrument that and make it modify things for a different purpose, we can use that for obfuscation.  And we can get an obfuscator such that there's no de-obfuscation code. The windows loader does  it all for us and then it's kind of a mystery if it's the first time seeing this. You're like  this thing is a mess on disk and then in memory it's fine before anything executes. So that's  what we're going to talk about. So first we need to say, well, what are relocations? They  exist to enable ASLR and dynamic mapping. That's not exactly true. It's a bit more nuanced than  that but for the sake of executables, they exist to enable ASLR. And let's look at an example. So  this is the same binary loaded at two different base addresses and I'm specifically looking at a  function table. And as you can see, the function tables in the two different loads, one on the  left and one on the right, the addresses are different, at least that upper part of them. And  on the left we have what is that, O133 and on the right we have O117. That is because they're at a  different base address. We can see that actually here outlined in blue is kind of reflected by the  address of where this function table is in the binary's memory and we can see that the values  are actually changed here in the bytes of the function table. So let's take a sidebar really  quick to talk about the PE header. By the way, this is not my diagram. I got it from Corkami if  you don't know what that is. Check it out. A really good resource. So there are three things we're  worried about and the first thing is image base. Image base is a part in the PE header. Well, so  the PE header is kind of the description of a binary in Windows. What needs to be loaded? What  parts of memory need to have? What access rights? What functions are imported? Stuff like that. But  we're specifically interested in image base, which is a value you can use to say what base  address you want your binary loaded at and if ASLR is not on, that will be followed. Throughout the  talk I might refer to this as desired base. There's also DLL characteristics. Now this is just a  two byte value and it's like a flags value. We're specifically interested in the bit 0x40, which  if it's set means ASLR is on and if it's not set, ASLR is off. And ASLR is just the part of the  loader that says okay, I'm going to put this binary at a different address for security purposes. And  finally there's the data directories and specifically there's an offset from the data directories  called image base entry reloc. So the data directories is basically just an array of pointers and  sizes to different pieces of data that the loader needs and we're only worried right now about the  reloc index which points to the relocations table. And the relocations table looks something like  this. At the top here you have the data directories and then we have this little gray block and then  we have what the reloc blocks actually look like. In the data directory there's a pointer to these  blocks to the first block and then all the blocks should be contiguous after that. And there's also  a size which refers to the size of all of the blocks in bytes. Each block ends in an 0x000, just  two byte zero to say this is the end of the block. There's another block coming so that the loader  knows that it needs to look for a new relocation center. So each block looks something like this.  First there's a virtual address. That virtual address is sort of like a base so instead of  encoding every single address that needs to be relocated you encode a virtual address and then  offsets relative to that virtual address that are less than four bytes long and that just saves  space. So this virtual address is the base which we will offset from in each entry. There's also  size of block and that is the size of the entire block. That is including the size of size of block  and the size of virtual address. So those are both four bytes so four plus four and then in this  case we have four entries which are each two bytes so then plus two four times and you get the size  of the block. So each entry is 16 bits. Four of those bits or half of a byte are the relocations  type which we'll talk about in a second and then the 12 remaining bits are used for the offset.  So you get up to 4096 from the virtual address. So you can have virtual address and then you can  have as many entries as you want within 4096 bytes of that to be relocated. And this optimization  it might not seem like much but when you have a function table where you have like 30 things to be  relocated back to back it makes sense why it's done this way. So this code kind of describes how  the loader does relocations. This is just pseudo code. Obviously it's not written in modern C++.  But this gives you the gist and we're going to talk about this. Specifically what we care about is  the first line where we say delta is equal to base. That's the base that we're actually mapped at  minus desired base. That's a base that we asked for in the PE header. And then the first if statement  inside the loop is the type of relocation we're going to use. So if we were to look at that first  if statement we would see that a plus equal operation is used during relocations and the right  hand side of that is delta. So essentially everything that is relocated is the value that's  there plus equals delta which is derived from the base address we're given minus the base address we  wanted. And that's how everything gets fixed up to point to the right place. So the conclusion that  we can come to is that if we want to abuse relocations or if we want to be able to control what  they're writing base must be pre-selected or we must have some way to take base and make it be  something we know that isn't the base that we asked for. There needs to be a difference there. And  desired base is the only means of controlling ASLR so that's a bit tricky. And delta is also  dependent on desired base. So we know we need to use desired base somehow to pre-select the base  but it's not clear how. And I thought maybe we'll just try invalid stuff. So I tried negative one or  max int or whatever you want to call it and it didn't work. PE fails to load. Well what I know is  those final four Fs actually have to mask and be zero because of alignment. So I tried all  zeros. PE fails to load invalid header. So then I combined the two and PE loads at base OX1 with  four zeros after it consistently every time. So we're getting a base address every time that is  different than the one we asked for and always the same. As I later learned, Korkami already had  all of this documented. So this has been known for a few years as well. So let's talk about the  loading process. It's pretty complex and this is not all of it but this is sort of what the  Windows loader does. So you don't really have to read this whole thing. What we really care about is  that the things in blue are things that happen before relocations are applied by the loader. And  the things in orange are things that happen after relocations are applied. So if we want to obfuscate  things, we might want to obfuscate things that happen after relocations are applied or we might  break something. So if we just take out everything that isn't relevant, this is when we do our  attack, this is what it will look like. This is the flow chart we'll go through. Now you might  notice there's a red arrow coming from ASLR enabled which means we're actually going to have  ASLR set to off and the attack happens here. So it turns out we're not tricking ASLR but when  the allocator is being asked to locate something at FF, FF, 0000, it's saying yeah, I can do that but  it's spitting back 0x1, 0000 for whatever reason. So our targets for relocation obfuscation, well we  know import table is loaded post relocations. So we can obfuscate the entire import table. That is  all of the libraries and functions that are imported from DLLs can be obfuscated so on disc  they look like trash but the loader fixes them up at load time. Additionally, even though  sections are mapped pre-reloak, they're not used until execution which is post-reloak. Additionally,  their memory protections aren't set properly until after relocations in case read-only stuff has to  be relocated. Also entry point isn't used until post-reloak so we could obfuscate the entry point  making it that much harder. The problem is that is right protected before relocations are done so  you can only do this if the target machine doesn't have data execution prevention turned on. So and  the conclusion is pretty much what I just said. We can hit imports, code and resource sections and  optionally the entry point with this attack. And that is this little green area you can see  highlighted. Now you might look at this and say okay that's only like half of the binary but what  you have to realize is this is a minified PE and that is actually most of the important stuff and  in like a real binary it's going to be somewhere like above 90% or 95% of what's actually there and  what's actually important content. So the final attack looks something like this. We'll load the  target PE file, the PE file that we want to obfuscate. Then we'll apply the original relocations  because remember relocations are there for a reason 0x10000 is probably not where that binary  wanted to be mapped so it's going to break unless we apply the original relocations and say okay  fix everything up for that address. Once we do that we flip ASLR off because remember even though  we plan to trick ASLR we're actually tricking the allocation function. Then we set desired base  to that tricky FFFF000 and then we loop over all of the data that we want to obfuscate that is  sections, import table, stuff like that in D word or U32 size chunks and we decrement them by what  we expect delta to be. That decrement is because the relocations part of the windows loader doesn't  increment so we just do the opposite of that and that is kind of our obfuscation. So every  integer in the stuff we're obfuscating is going to be messed up a bit which is good enough. Then we  discard the original relocations table because we've already applied them on disk. We generate a  new relocations table reflecting the positions of every single decrement that we've done so that  the loader can fix them up. We save the new PE file to disk and profit, we're done. We now have a  PE that is completely mangled on disk. It has a huge relocations table and then when it runs it  maps perfectly fine. So let's jump into some demos. So here I'm trying to load in an IDA. You can see the  messed up DLL name IDA thinks is an import. Okay IDA throws some weird errors. On older versions of  IDA it was crashing sometimes. I can't get newer versions of IDA to crash. You can see IDA lights  up red. It doesn't know what it's looking at. It only sees one subroutine and even the assembly  code there is a bunch of invalid instructions and if you look at the strings they look like trash.  Everything looks like trash. Binge is going to be mostly the same. It's going to see two subs but  it's got question marks there. It doesn't know what it's looking at. So next is CFF explorer. This  one actually crashes somewhat consistently. I think it's this one that reaches out on the network and  does a bunch of out of band scans that come back a few minutes later and those do cause crashes  more than any other tool. There's no crashes in these demos because one you have to get lucky.  They're not even consistent across the same binary and also you have to get a binary that happens to  cause them. I haven't looked more into it. I'm really worried about as you can see here the  import names are all messed up. Some of them look kind of right because remember we're doing it in  four byte chunks. So for strings we're only messing up like one byte at a time. Depends just  completely jokes. It has no clue what it's looking at. And then resource hacker just throws an error.  It says you don't have a valid resource table because a resource section is encrypted or  obfuscated. And even PE bear you can see it just lights up red. Imports look like trash. If we  tab over to the disassembly we'll see it says okay these are invalid instructions. We'll also see a  bunch of jumps into locations that can't possibly be jumped to. And finally PE view it's going to be  the same thing. It's just going to maybe pop some errors and then imports are going to be messed  up. Codes are going to be messed up. Okay. And that is that one. Okay. So we've shown that this  attack is viable and that it works and we can do something with it. Oh wait what am I doing? I  have another video to show. I'm pretty dumb. We've seen it break stuff but we haven't seen it  work. What am I talking about? So let's do that. Okay. So here you can see me clicking on the  binary and it runs. This is an encrypted obfuscated binary with this method it's running. And it's  just process explorer that I've obfuscated. It's going on over there. It's just process explorer  that I've obfuscated so that's why you saw process explorer launch. I did this because this is a  relatively complex application. It's got a lot of stuff in its resource section and it's got a  lot of non-trivial code. So I figure if it works on this it'll work. What you see right now is me  searching for invalid strings in the image to show you that this is actually obfuscated. Some of  the strings are not obfuscated because they're in the dot r data section which I didn't obfuscate for  this demo but if you look here you'll see a bunch of file paths and a bunch of like error strings  that almost look right but are actually gibberish when you try to read them. And it also kind of  messes up like a ton of AVs so here's a malicious sample. We're going to go ahead and pack that. And  now you see it's generated a new sample sorry. And we're going to drop them both on virus total  and see what happens. The first one is going to light up already. And then we're going to  load it. And now the second one even though it's effectively the same binary that's just  obfuscated by the Windows loader I guess we only get two reds. Okay so we know the proof of  concept works. We should probably test it on multiple platforms. Windows 7 it works. That's  what I originally developed it on. Windows 8 no one uses Windows 8 come on. Windows 10 aw fuck it  doesn't work. Yeah I get it. Okay I'm done sorry. Okay no but really we have to find a new attack  for Windows 10. What ends up happening is asking for 4f's 4 zeros does not yield that one with  4 zeros after it. It actually lets ASLR go through the process even if the ASLR bit is off and  then we get a random base address so no good. So I thought maybe I can embed multiple PE  copies for all possible base addresses and then use some kind of reloc tricks to point to the  right one which is something Corkami has actually shown as possible but that would be way too big  it would inflate the binary even more than using relocations already does which is actually  pretty significant. So then I thought maybe I can tweak ASLR configuration because I know there  was a major change on Windows 10 in how all of that works and that actually does work. So if you  notice the demos were on Windows 10 and this is what I used to demo on Windows 10 we set  mandatory ASLR to on and bottom up ASLR to off and if we do that with this dot red file for  specific executables it works but I don't really like it. As soon as you start touching these  registry keys everything is going to light up AVs are going to light up, EDR is going to light up.  Monitoring is just going to say hey this is bad if it doesn't just block this entirely so I  wanted something else. And then I did a lot of playing with it. I was trying to see what I can  do to control base addresses and I noticed this. So this is just a file. I have multiple  instances running and I'm basically just looking at the base address of this file and even though  I have multiple processes running of the same file I noticed that they all have the same base  address. There is something in Windows that is caching that base address or reusing it on  loads maybe it has something to do with copy on write not exactly sure. And then I noticed okay  if I take this file I copy it to another file. I delete the original and then cat the copy into  the name of the original. It sounds crazy but it's a way of copying a file to itself without the  file system being able to track that it's the same file and then I launch it again. I invalidate  that base address. So they all have the same base address again but it's different from what it  was before. So this tells me there's something in the file system that invalidates that base  address that's being reused. So I can at least brute force and get a base address that I want by  just invalidating it every time. So we're going to go over a pretty complex flow chart. So really  quick here's a flow chart on how to read flow charts. Okay I actually put it in the deep fryer  so let's just talk for a sec. So what I figured out is that if you take a file and you memory map it  then you close the mapping and you launch that file it will be matched with a different base  address than last time you launched it. So if you do this in a loop you launch a file check the  base address if it's not what you want memory map the file close the mapping launch it again you  can keep getting a new base address and you can brute force and pre-select whatever base you want.  And that attack looks like this. So you'll have a root process and a drone process effectively  where the drone process is just launching and throwing back a specific return code based on  whether or not it has the base address that it wants and then you have your root process which  in a loop is doing that mapping stuff which is highlighted in yellow. So here in orange is  really the main part of the attack within the root process we're highlighting the loop that's  going in and saying okay I'm going to keep looping until I see the right base address which is  signaled from the drone process by an exit code of OX what did I use bad beef yeah. So if OX bad  beef is the exit code of the drone process it knows that it didn't get the right base address and  it will keep doing this loop. Now these are actually the same file so effectively we want to  put that embed this attack in the binary we want to obfuscate it will make a copy of itself to be  the drone process so that it can map that copied file to invalidate its base address and launch it  over and over and over again but effectively there's the same code which is why you see at the top  we create a mutex and check it if it exists and that's how we know if we're the root process or the  drone process. So to weaponize this the tool must create a new section with enough room for this  brute forcing code embed the code inside of this new section and make that code aware of the  original entry point then it needs to point entry point within the PE header to the embedded code  so effectively it's a typical parasitic infection and for this to work this ASLR pre-selection code  this brute force attack must be position agnostic because we're just throwing in a binary  wherever you can it must be generically embeddable in any PE and yeah that's basically it. So to do  that I made a bunch of pre-processor macros so I could do everything in line this you don't need  to read these this is just showing a bunch of variadic macros and once I had all of these in  place it looks something like this so like some of the weird stuff I had to do like some of these  macros take a string in and then they emit that string as like assembly bytes and then jump over  those bytes and then use like a call and then a ret to get the address and like move it into a  variable really weird stuff but this is how I made all that work now you might be looking at this  big block at the top which is doing a bunch of function resolution and you might say Nick why  didn't you resolve get module handle and get proc address and then do the rest normally and so my  answer to that is this is like more obfuscated it's really indirect this code actually looks like a  mess so it's harder to reverse that answer is a lie I got really carried away writing the  macros and then I had resolved all of my functions by the time I realized I could have done that so  yeah and it worked so this actually worked so there's some caveats because this attack is  actually running code we are sorry I had my bullets wrong in my head come on make your  shit together so it can be slow it takes about 200 iterations to land on the base address that  we want so depending on what machine you're running it on it can happen in five seconds or a  minute because you're launching this executable hundreds of times and you're mapping it to  memory in between so also the size of the executable matters size matters and imports can't be  obfuscated right because what ends up happening is the binary has to get mapped into memory  properly for this attack to start running and if imports are obfuscated on the times where we  don't hit the right base address the windows loader is just going to dope out and it's going to say  okay this is wrong I can't resolve any of this and it's just going to error so that's a bit of a  problem but there are some advantages because first we don't really need to use that one with 4  0's after it before if you saw the request of F F F F 0 0 0 0 and you're like a parser you know  it's going to map this address but with this attack we can pre-select any address within the  realm of possibility and the side effect of this is some form of symbolic execution or manual  analysis is now needed to determine what base address is going to be obtained so it's harder to  just take this and do the relocations like inside of a parser to fix up the binary so yeah we  can't do imports which was actually really cool to see but at the same time it's harder for  automated analysis and let's see what this looks like. So you see process monitor on the right and  you're going to see that just showing a bunch of process launches this is a brute force going  you can see on the left it copied itself to just its name 2.exe it launched itself a bunch of  times looking for the base address showing that in process monitor and eventually it created  that binary and then eventually it launched which we see down here we see that it actually loaded  and launched process explorer which is what we had obfuscated. If we go and we look we see okay we  had our first thing it copied the second thing it launched it that got mapped properly and then  that our process explorer actually then drops a 64 bit file and launches that which is why you see  an extra process but that's just showing that we've actually obfuscated resources and they got  deobfuscated so that process explorer could dump another binary out of them perfectly without  messing it up. Yeah and so what can we really do with this we've seen it work we know it's an  interesting attack we've got the windows loader deobfuscating stuff for us but what can we do  with it? So you can annoy analysts with this it's going to be annoying like maybe the first time  or first few times you see this but if you see this a lot it's actually not that annoying once  you know what's going on. We can break a lot of automated static analysis systems as we saw  before a lot of PE parsers just choke on it. We can break a lot of tools those two are kind of  one in the same but we can break maybe some AV parsers I haven't looked too much into it but I  imagine there's at least one out there that's choking on this but there are a lot of improvements  we can make as well such as more obfuscations now this might mean new targets so instead of  just doing like a few sections and imports we might be able to find and identify other things  that can be obfuscated or we might be able to do multiple passes so we do right now each  d-word in the file consecutively in any of our targets but what if we do that and then we start  over offsetting by one and then we start over offsetting by two and then offsetting by three  instead of getting like one or like one and a half bytes that are messed up for every four bytes  everything's obfuscated the thing is your relocations table is going to blow up if you do  this but I mean if you really want to make it hard to analyze it's possible we might also be  able to do header scrambling so we might be able to embed things in the relocations table that  say these things need to be relocated that aren't things that we've done anything to things that  are needed by the loader but not needed during execution so then the loader will go and it will  relocate things that were already correct then corrupting them in memory so not only would you  have something that looks corrupt on disk and fine in memory you could selectively corrupt  things to make dynamic analysis hard because right now this doesn't really do anything against  dynamic analysis and we might even be able to combine this with runtime packers and that would  just be an extra level of annoyance now support for 64 bit binaries is definitely possible the  pre-selection attack it seems like there would be a much larger search space but as far as I  can tell Windows does kind of prefer to load it around a base address that is like the maximum  possible user space address that's available minus the size of the binary it tries to load it  around there at first so if you do that you might be able to narrow down the search space  though I haven't exactly tried it and the FF FF 000 trick works similarly in 64 bit but not  exactly the same might also be able to support DLLs I actually have no evidence for that I  just think it's probably possible with the right modifications and then you could also do  selective obfuscation so rather than obfuscating the entire binary which just throws up red  flags right away you might like have an IP address embedded in a section and you only use  this to change one byte in that IP address so now the analyst is like okay this is the CNC  because this is a string this is a host name or an IP address so then they try to connect to  that and they're like okay the CNC is dead but they were looking at it in IDA which isn't doing  relocations at load time you relocate just one byte in that address and now you're good to go  like and it's it's kind of confusing it won't trick a lot of people for too long but it will add  an extra annoying bit there and that's the end. So we have a bunch of links here this QR  code I believe goes to the slides so here's what's going to happen with the code release we've  decided to do it like this because while we're not dropping any O-day or anything that's like  super bad we are breaking some stuff so we want to give people time to catch up with that so in  two weeks we're going to drop some samples so if you're working on fixing this if you write a  parser you work for an AV company you write a PE tool you can go ahead and start thinking about  this the slides are already up so all the information is there in two weeks you'll have  samples to test on and exactly one week after that full source code release everything will be  dropped and anyone can play with this make changes commit the source code is in C++ visual  studio 2017 so I think if you hit the relocation bonus get hub repo I've posted a timeline on  there for now that will eventually point to the code full timelines on there but I think it's  September 3rd for the full code release thank you.
DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_0_311.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_312_2051.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_2052_2687.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_2688_8015.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_8016_15599.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_15600_20399.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_20400_20471.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_20472_20555.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_20556_20639.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_20640_20963.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_20964_21395.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_21396_21491.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_21492_21587.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_21588_22367.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_22368_22907.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_22908_23291.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_23292_23579.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_23580_23843.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_23844_24215.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_24216_24359.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_24360_24575.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_24576_26243.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_26244_26495.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_26496_26543.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_26544_26687.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_26688_26891.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_26892_27875.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_27876_32999.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_33000_35627.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_35628_35867.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_35868_36071.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_36072_36467.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_36468_36635.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_36636_40151.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_40152_40403.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_40404_41111.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_41112_41183.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_41184_41303.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_41304_41675.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_41676_41747.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_41748_47063.png DEF_CON_26_-_Nick_Cano_-_Relocation_Bonus_Attacking_the_Windows_Loader_SseZ_t9DToo_47064_48883.png
Okay, cool. So I'm a bit nervous now following the monkey comedy hour. It's going to be hard to follow that up. But this is relocation bonus. Attacking the windows loader makes analysts switch careers. So quick introduction. I am Nick Cano. I'm 25 and I'm a senior security architect at Silance. I have written a book on hacking games. Sorry I didn't know a clap was coming. I've written a book on hacking games. I'm also a plural site author just had a course published like a month ago on C++ secure coding practices. And what does this talk about? Well this talk in essence is about the windows portable executable header and how it can be weaponized to destroy parsers and other static analysis tools, things like disassemblers and such. And that all culminates into like a PE rebuilder for 32 bit windows binaries. So you might ask why are you attacking relocations? So this is a disassembly of a game. A game that I make a bot for. Disassembly to get rid of a jump call. Just put some knobs in there. And then I saved my new binary to disk. I ran it and it crashed. I was like what the heck is going on here? And I looked at the code and my knobs were now changed. The code was different from what I had patched. And I know what you guys are thinking. What the heck? What's going on here? So as it turns out there are these things called relocations. So at first I was like oh man this is broken for no reason. Then I was like okay relocations corrupted the patched code. Then I was like maybe I shouldn't patch code that is relocated. But then I thought I can use relocations to hide my arsenal in the bowels of the machine. So before we dive into the nitty gritty technical details, let's restate what we're doing here. The windows loader has this piece in it that applies relocations. Relocations exist because binaries will have pointers in there that are sensitive to the position of the binary and where it is loaded. So if the binary is loaded at a different base address than the compiler expected, those things need to be relocated. So if we can weaponize that, if we can instrument that and make it modify things for a different purpose, we can use that for obfuscation. And we can get an obfuscator such that there's no de-obfuscation code. The windows loader does it all for us and then it's kind of a mystery if it's the first time seeing this. You're like this thing is a mess on disk and then in memory it's fine before anything executes. So that's what we're going to talk about. So first we need to say, well, what are relocations? They exist to enable ASLR and dynamic mapping. That's not exactly true. It's a bit more nuanced than that but for the sake of executables, they exist to enable ASLR. And let's look at an example. So this is the same binary loaded at two different base addresses and I'm specifically looking at a function table. And as you can see, the function tables in the two different loads, one on the left and one on the right, the addresses are different, at least that upper part of them. And on the left we have what is that, O133 and on the right we have O117. That is because they're at a different base address. We can see that actually here outlined in blue is kind of reflected by the address of where this function table is in the binary's memory and we can see that the values are actually changed here in the bytes of the function table. So let's take a sidebar really quick to talk about the PE header. By the way, this is not my diagram. I got it from Corkami if you don't know what that is. Check it out. A really good resource. So there are three things we're worried about and the first thing is image base. Image base is a part in the PE header. Well, so the PE header is kind of the description of a binary in Windows. What needs to be loaded? What parts of memory need to have? What access rights? What functions are imported? Stuff like that. But we're specifically interested in image base, which is a value you can use to say what base address you want your binary loaded at and if ASLR is not on, that will be followed. Throughout the talk I might refer to this as desired base. There's also DLL characteristics. Now this is just a two byte value and it's like a flags value. We're specifically interested in the bit 0x40, which if it's set means ASLR is on and if it's not set, ASLR is off. And ASLR is just the part of the loader that says okay, I'm going to put this binary at a different address for security purposes. And finally there's the data directories and specifically there's an offset from the data directories called image base entry reloc. So the data directories is basically just an array of pointers and sizes to different pieces of data that the loader needs and we're only worried right now about the reloc index which points to the relocations table. And the relocations table looks something like this. At the top here you have the data directories and then we have this little gray block and then we have what the reloc blocks actually look like. In the data directory there's a pointer to these blocks to the first block and then all the blocks should be contiguous after that. And there's also a size which refers to the size of all of the blocks in bytes. Each block ends in an 0x000, just two byte zero to say this is the end of the block. There's another block coming so that the loader knows that it needs to look for a new relocation center. So each block looks something like this. First there's a virtual address. That virtual address is sort of like a base so instead of encoding every single address that needs to be relocated you encode a virtual address and then offsets relative to that virtual address that are less than four bytes long and that just saves space. So this virtual address is the base which we will offset from in each entry. There's also size of block and that is the size of the entire block. That is including the size of size of block and the size of virtual address. So those are both four bytes so four plus four and then in this case we have four entries which are each two bytes so then plus two four times and you get the size of the block. So each entry is 16 bits. Four of those bits or half of a byte are the relocations type which we'll talk about in a second and then the 12 remaining bits are used for the offset. So you get up to 4096 from the virtual address. So you can have virtual address and then you can have as many entries as you want within 4096 bytes of that to be relocated. And this optimization it might not seem like much but when you have a function table where you have like 30 things to be relocated back to back it makes sense why it's done this way. So this code kind of describes how the loader does relocations. This is just pseudo code. Obviously it's not written in modern C++. But this gives you the gist and we're going to talk about this. Specifically what we care about is the first line where we say delta is equal to base. That's the base that we're actually mapped at minus desired base. That's a base that we asked for in the PE header. And then the first if statement inside the loop is the type of relocation we're going to use. So if we were to look at that first if statement we would see that a plus equal operation is used during relocations and the right hand side of that is delta. So essentially everything that is relocated is the value that's there plus equals delta which is derived from the base address we're given minus the base address we wanted. And that's how everything gets fixed up to point to the right place. So the conclusion that we can come to is that if we want to abuse relocations or if we want to be able to control what they're writing base must be pre-selected or we must have some way to take base and make it be something we know that isn't the base that we asked for. There needs to be a difference there. And desired base is the only means of controlling ASLR so that's a bit tricky. And delta is also dependent on desired base. So we know we need to use desired base somehow to pre-select the base but it's not clear how. And I thought maybe we'll just try invalid stuff. So I tried negative one or max int or whatever you want to call it and it didn't work. PE fails to load. Well what I know is those final four Fs actually have to mask and be zero because of alignment. So I tried all zeros. PE fails to load invalid header. So then I combined the two and PE loads at base OX1 with four zeros after it consistently every time. So we're getting a base address every time that is different than the one we asked for and always the same. As I later learned, Korkami already had all of this documented. So this has been known for a few years as well. So let's talk about the loading process. It's pretty complex and this is not all of it but this is sort of what the Windows loader does. So you don't really have to read this whole thing. What we really care about is that the things in blue are things that happen before relocations are applied by the loader. And the things in orange are things that happen after relocations are applied. So if we want to obfuscate things, we might want to obfuscate things that happen after relocations are applied or we might break something. So if we just take out everything that isn't relevant, this is when we do our attack, this is what it will look like. This is the flow chart we'll go through. Now you might notice there's a red arrow coming from ASLR enabled which means we're actually going to have ASLR set to off and the attack happens here. So it turns out we're not tricking ASLR but when the allocator is being asked to locate something at FF, FF, 0000, it's saying yeah, I can do that but it's spitting back 0x1, 0000 for whatever reason. So our targets for relocation obfuscation, well we know import table is loaded post relocations. So we can obfuscate the entire import table. That is all of the libraries and functions that are imported from DLLs can be obfuscated so on disc they look like trash but the loader fixes them up at load time. Additionally, even though sections are mapped pre-reloak, they're not used until execution which is post-reloak. Additionally, their memory protections aren't set properly until after relocations in case read-only stuff has to be relocated. Also entry point isn't used until post-reloak so we could obfuscate the entry point making it that much harder. The problem is that is right protected before relocations are done so you can only do this if the target machine doesn't have data execution prevention turned on. So and the conclusion is pretty much what I just said. We can hit imports, code and resource sections and optionally the entry point with this attack. And that is this little green area you can see highlighted. Now you might look at this and say okay that's only like half of the binary but what you have to realize is this is a minified PE and that is actually most of the important stuff and in like a real binary it's going to be somewhere like above 90% or 95% of what's actually there and what's actually important content. So the final attack looks something like this. We'll load the target PE file, the PE file that we want to obfuscate. Then we'll apply the original relocations because remember relocations are there for a reason 0x10000 is probably not where that binary wanted to be mapped so it's going to break unless we apply the original relocations and say okay fix everything up for that address. Once we do that we flip ASLR off because remember even though we plan to trick ASLR we're actually tricking the allocation function. Then we set desired base to that tricky FFFF000 and then we loop over all of the data that we want to obfuscate that is sections, import table, stuff like that in D word or U32 size chunks and we decrement them by what we expect delta to be. That decrement is because the relocations part of the windows loader doesn't increment so we just do the opposite of that and that is kind of our obfuscation. So every integer in the stuff we're obfuscating is going to be messed up a bit which is good enough. Then we discard the original relocations table because we've already applied them on disk. We generate a new relocations table reflecting the positions of every single decrement that we've done so that the loader can fix them up. We save the new PE file to disk and profit, we're done. We now have a PE that is completely mangled on disk. It has a huge relocations table and then when it runs it maps perfectly fine. So let's jump into some demos. So here I'm trying to load in an IDA. You can see the messed up DLL name IDA thinks is an import. Okay IDA throws some weird errors. On older versions of IDA it was crashing sometimes. I can't get newer versions of IDA to crash. You can see IDA lights up red. It doesn't know what it's looking at. It only sees one subroutine and even the assembly code there is a bunch of invalid instructions and if you look at the strings they look like trash. Everything looks like trash. Binge is going to be mostly the same. It's going to see two subs but it's got question marks there. It doesn't know what it's looking at. So next is CFF explorer. This one actually crashes somewhat consistently. I think it's this one that reaches out on the network and does a bunch of out of band scans that come back a few minutes later and those do cause crashes more than any other tool. There's no crashes in these demos because one you have to get lucky. They're not even consistent across the same binary and also you have to get a binary that happens to cause them. I haven't looked more into it. I'm really worried about as you can see here the import names are all messed up. Some of them look kind of right because remember we're doing it in four byte chunks. So for strings we're only messing up like one byte at a time. Depends just completely jokes. It has no clue what it's looking at. And then resource hacker just throws an error. It says you don't have a valid resource table because a resource section is encrypted or obfuscated. And even PE bear you can see it just lights up red. Imports look like trash. If we tab over to the disassembly we'll see it says okay these are invalid instructions. We'll also see a bunch of jumps into locations that can't possibly be jumped to. And finally PE view it's going to be the same thing. It's just going to maybe pop some errors and then imports are going to be messed up. Codes are going to be messed up. Okay. And that is that one. Okay. So we've shown that this attack is viable and that it works and we can do something with it. Oh wait what am I doing? I have another video to show. I'm pretty dumb. We've seen it break stuff but we haven't seen it work. What am I talking about? So let's do that. Okay. So here you can see me clicking on the binary and it runs. This is an encrypted obfuscated binary with this method it's running. And it's just process explorer that I've obfuscated. It's going on over there. It's just process explorer that I've obfuscated so that's why you saw process explorer launch. I did this because this is a relatively complex application. It's got a lot of stuff in its resource section and it's got a lot of non-trivial code. So I figure if it works on this it'll work. What you see right now is me searching for invalid strings in the image to show you that this is actually obfuscated. Some of the strings are not obfuscated because they're in the dot r data section which I didn't obfuscate for this demo but if you look here you'll see a bunch of file paths and a bunch of like error strings that almost look right but are actually gibberish when you try to read them. And it also kind of messes up like a ton of AVs so here's a malicious sample. We're going to go ahead and pack that. And now you see it's generated a new sample sorry. And we're going to drop them both on virus total and see what happens. The first one is going to light up already. And then we're going to load it. And now the second one even though it's effectively the same binary that's just obfuscated by the Windows loader I guess we only get two reds. Okay so we know the proof of concept works. We should probably test it on multiple platforms. Windows 7 it works. That's what I originally developed it on. Windows 8 no one uses Windows 8 come on. Windows 10 aw fuck it doesn't work. Yeah I get it. Okay I'm done sorry. Okay no but really we have to find a new attack for Windows 10. What ends up happening is asking for 4f's 4 zeros does not yield that one with 4 zeros after it. It actually lets ASLR go through the process even if the ASLR bit is off and then we get a random base address so no good. So I thought maybe I can embed multiple PE copies for all possible base addresses and then use some kind of reloc tricks to point to the right one which is something Corkami has actually shown as possible but that would be way too big it would inflate the binary even more than using relocations already does which is actually pretty significant. So then I thought maybe I can tweak ASLR configuration because I know there was a major change on Windows 10 in how all of that works and that actually does work. So if you notice the demos were on Windows 10 and this is what I used to demo on Windows 10 we set mandatory ASLR to on and bottom up ASLR to off and if we do that with this dot red file for specific executables it works but I don't really like it. As soon as you start touching these registry keys everything is going to light up AVs are going to light up, EDR is going to light up. Monitoring is just going to say hey this is bad if it doesn't just block this entirely so I wanted something else. And then I did a lot of playing with it. I was trying to see what I can do to control base addresses and I noticed this. So this is just a file. I have multiple instances running and I'm basically just looking at the base address of this file and even though I have multiple processes running of the same file I noticed that they all have the same base address. There is something in Windows that is caching that base address or reusing it on loads maybe it has something to do with copy on write not exactly sure. And then I noticed okay if I take this file I copy it to another file. I delete the original and then cat the copy into the name of the original. It sounds crazy but it's a way of copying a file to itself without the file system being able to track that it's the same file and then I launch it again. I invalidate that base address. So they all have the same base address again but it's different from what it was before. So this tells me there's something in the file system that invalidates that base address that's being reused. So I can at least brute force and get a base address that I want by just invalidating it every time. So we're going to go over a pretty complex flow chart. So really quick here's a flow chart on how to read flow charts. Okay I actually put it in the deep fryer so let's just talk for a sec. So what I figured out is that if you take a file and you memory map it then you close the mapping and you launch that file it will be matched with a different base address than last time you launched it. So if you do this in a loop you launch a file check the base address if it's not what you want memory map the file close the mapping launch it again you can keep getting a new base address and you can brute force and pre-select whatever base you want. And that attack looks like this. So you'll have a root process and a drone process effectively where the drone process is just launching and throwing back a specific return code based on whether or not it has the base address that it wants and then you have your root process which in a loop is doing that mapping stuff which is highlighted in yellow. So here in orange is really the main part of the attack within the root process we're highlighting the loop that's going in and saying okay I'm going to keep looping until I see the right base address which is signaled from the drone process by an exit code of OX what did I use bad beef yeah. So if OX bad beef is the exit code of the drone process it knows that it didn't get the right base address and it will keep doing this loop. Now these are actually the same file so effectively we want to put that embed this attack in the binary we want to obfuscate it will make a copy of itself to be the drone process so that it can map that copied file to invalidate its base address and launch it over and over and over again but effectively there's the same code which is why you see at the top we create a mutex and check it if it exists and that's how we know if we're the root process or the drone process. So to weaponize this the tool must create a new section with enough room for this brute forcing code embed the code inside of this new section and make that code aware of the original entry point then it needs to point entry point within the PE header to the embedded code so effectively it's a typical parasitic infection and for this to work this ASLR pre-selection code this brute force attack must be position agnostic because we're just throwing in a binary wherever you can it must be generically embeddable in any PE and yeah that's basically it. So to do that I made a bunch of pre-processor macros so I could do everything in line this you don't need to read these this is just showing a bunch of variadic macros and once I had all of these in place it looks something like this so like some of the weird stuff I had to do like some of these macros take a string in and then they emit that string as like assembly bytes and then jump over those bytes and then use like a call and then a ret to get the address and like move it into a variable really weird stuff but this is how I made all that work now you might be looking at this big block at the top which is doing a bunch of function resolution and you might say Nick why didn't you resolve get module handle and get proc address and then do the rest normally and so my answer to that is this is like more obfuscated it's really indirect this code actually looks like a mess so it's harder to reverse that answer is a lie I got really carried away writing the macros and then I had resolved all of my functions by the time I realized I could have done that so yeah and it worked so this actually worked so there's some caveats because this attack is actually running code we are sorry I had my bullets wrong in my head come on make your shit together so it can be slow it takes about 200 iterations to land on the base address that we want so depending on what machine you're running it on it can happen in five seconds or a minute because you're launching this executable hundreds of times and you're mapping it to memory in between so also the size of the executable matters size matters and imports can't be obfuscated right because what ends up happening is the binary has to get mapped into memory properly for this attack to start running and if imports are obfuscated on the times where we don't hit the right base address the windows loader is just going to dope out and it's going to say okay this is wrong I can't resolve any of this and it's just going to error so that's a bit of a problem but there are some advantages because first we don't really need to use that one with 4 0's after it before if you saw the request of F F F F 0 0 0 0 and you're like a parser you know it's going to map this address but with this attack we can pre-select any address within the realm of possibility and the side effect of this is some form of symbolic execution or manual analysis is now needed to determine what base address is going to be obtained so it's harder to just take this and do the relocations like inside of a parser to fix up the binary so yeah we can't do imports which was actually really cool to see but at the same time it's harder for automated analysis and let's see what this looks like. So you see process monitor on the right and you're going to see that just showing a bunch of process launches this is a brute force going you can see on the left it copied itself to just its name 2.exe it launched itself a bunch of times looking for the base address showing that in process monitor and eventually it created that binary and then eventually it launched which we see down here we see that it actually loaded and launched process explorer which is what we had obfuscated. If we go and we look we see okay we had our first thing it copied the second thing it launched it that got mapped properly and then that our process explorer actually then drops a 64 bit file and launches that which is why you see an extra process but that's just showing that we've actually obfuscated resources and they got deobfuscated so that process explorer could dump another binary out of them perfectly without messing it up. Yeah and so what can we really do with this we've seen it work we know it's an interesting attack we've got the windows loader deobfuscating stuff for us but what can we do with it? So you can annoy analysts with this it's going to be annoying like maybe the first time or first few times you see this but if you see this a lot it's actually not that annoying once you know what's going on. We can break a lot of automated static analysis systems as we saw before a lot of PE parsers just choke on it. We can break a lot of tools those two are kind of one in the same but we can break maybe some AV parsers I haven't looked too much into it but I imagine there's at least one out there that's choking on this but there are a lot of improvements we can make as well such as more obfuscations now this might mean new targets so instead of just doing like a few sections and imports we might be able to find and identify other things that can be obfuscated or we might be able to do multiple passes so we do right now each d-word in the file consecutively in any of our targets but what if we do that and then we start over offsetting by one and then we start over offsetting by two and then offsetting by three instead of getting like one or like one and a half bytes that are messed up for every four bytes everything's obfuscated the thing is your relocations table is going to blow up if you do this but I mean if you really want to make it hard to analyze it's possible we might also be able to do header scrambling so we might be able to embed things in the relocations table that say these things need to be relocated that aren't things that we've done anything to things that are needed by the loader but not needed during execution so then the loader will go and it will relocate things that were already correct then corrupting them in memory so not only would you have something that looks corrupt on disk and fine in memory you could selectively corrupt things to make dynamic analysis hard because right now this doesn't really do anything against dynamic analysis and we might even be able to combine this with runtime packers and that would just be an extra level of annoyance now support for 64 bit binaries is definitely possible the pre-selection attack it seems like there would be a much larger search space but as far as I can tell Windows does kind of prefer to load it around a base address that is like the maximum possible user space address that's available minus the size of the binary it tries to load it around there at first so if you do that you might be able to narrow down the search space though I haven't exactly tried it and the FF FF 000 trick works similarly in 64 bit but not exactly the same might also be able to support DLLs I actually have no evidence for that I just think it's probably possible with the right modifications and then you could also do selective obfuscation so rather than obfuscating the entire binary which just throws up red flags right away you might like have an IP address embedded in a section and you only use this to change one byte in that IP address so now the analyst is like okay this is the CNC because this is a string this is a host name or an IP address so then they try to connect to that and they're like okay the CNC is dead but they were looking at it in IDA which isn't doing relocations at load time you relocate just one byte in that address and now you're good to go like and it's it's kind of confusing it won't trick a lot of people for too long but it will add an extra annoying bit there and that's the end. So we have a bunch of links here this QR code I believe goes to the slides so here's what's going to happen with the code release we've decided to do it like this because while we're not dropping any O-day or anything that's like super bad we are breaking some stuff so we want to give people time to catch up with that so in two weeks we're going to drop some samples so if you're working on fixing this if you write a parser you work for an AV company you write a PE tool you can go ahead and start thinking about this the slides are already up so all the information is there in two weeks you'll have samples to test on and exactly one week after that full source code release everything will be dropped and anyone can play with this make changes commit the source code is in C++ visual studio 2017 so I think if you hit the relocation bonus get hub repo I've posted a timeline on there for now that will eventually point to the code full timelines on there but I think it's September 3rd for the full code release thank you.
essence is about the windows portable executable parsers and other static analysis tools, things culminates into like a PE rebuilder for 32 bit at the code and my knobs were now changed. The bowels of the machine. So before we dive into the this piece in it that applies relocations. if the binary is loaded at a different base a sidebar really quick to talk about the PE interested in image base, which is a value you binary loaded at and if ASLR is not on, that will of those bits or half of a byte are the and then you can have as many entries as you want kind of describes how the loader does that we can come to is that if we want to abuse Windows loader does. So you don't really have to out everything that isn't relevant, this is when file, the PE file that we want to obfuscate. Then done so that the loader can fix them up. We save We now have a PE that is completely mangled on an error. It says you don't have a valid resource the binary and it runs. This is an encrypted in the image to show you that this is actually then you close the mapping and you launch that file it will be matched with a different base can keep getting a new base address and you can in the binary we want to obfuscate it will make a but with this attack we can pre-select any execution so then the loader will go and it will with the code release we've decided to do it like this if you write a parser you work for an AV