 I'm Nick Harbour, I'm an incident responder and consultant with Mandiant. My primary Jamauer analysis also do a lot of research and development. So let's get cracking. Oh, there's also supposed to be questions and answers or something, they want me to keep me hostage in there for two hours. I'm going to have to bail on that, as you can imagine, that doesn't sound too entertaining. I did compete in the Race to Zero competition, so I'd kind of like to get over there and see if I won or not. So I apologize for not having an extended two hour QA session. So let's start with what we're going to talk about a little bit today. I'm going to go over some basics on Packers and exactly what the current state of the field is today and a little bit of theory behind them, and then I'm going to talk about my tool that I'm releasing called PE Scrambler and how it's different from what's out there today. And I'm going to rant a little bit about the challenges and headaches I faced when writing the tool. It was kind of a pain. And I'm going to discuss piecewise some of the techniques that it's using and go into the theory on exactly how they work, why they work, what the flaws might be in modern disassembly techniques and things like that. The future avenues of development such as the polymorphic code replacement. And then I have a tool for the good guys at the end, which is a malware detection tool that uses a disassembler to detect packed binaries in malware. So releasing two tools today. This is a talk that's going to encompass both of them. All right, so let's talk a little bit about Packers and where they're going today. A Packer, if you don't know what it is, is a tool to compress or encrypt a binary so that it still has its original functionality. There's two sides of the coin on how they want to be used. Compression or protection. And there's usually a trade-off between the two. So I plotted on a graph like this. So you have Packers that are really good at compression that don't particularly care too much about being hard to reverse engineer or thwarting the reverse engineer. Then on the other end of the scale you have Packers which actually make the binary larger but are very difficult to reverse engineer. So my PE Scrambler tool, it's certainly going to be on the armoring end, not on the compression end. It's going to make binaries probably almost twice as big. So a little bit about the traditional Packer design, which is going to encompass most of the easy common Packers that are on the compression end of things. As a reverse engineer and malware analyst, I don't usually lose sleep about those kind of Packers. They're easy to defeat. And the reason they're easy to defeat is they all work on the same basic model. The Packer program compresses or encrypts the original binary in its complete intact state. It appends a small unpack or stub program. And then when the executable runs that unpack or stub launches, unencrypts or uncompresses the original binary in its complete state and then just jumps into it. So if you catch it with its pants down, you have the original binary pretty much intact. So here's how the process works when the actual Packer compresses the original binary. We have an original binary here which has three sections in it, .txt, .data, and then a resources section. I'm just picking on UPX. It's a pretty good basic model of a Packer. All that stuff in the .txt and the .data, which is executable instructions and any type of data needed by the program, is all compressed and put into one of these sections in the Packed binary, usually the UPX1 section. And that's also where you get the stub program put right in that same thing. And there's a UPX0 section, which is just kind of an empty placeholder. And we'll see how that loads. Whenever you launch the Packed binary, it looks quite a bit different in memory than it did on disk. You have that UPX0 section which was empty in the actual file that becomes much larger in memory and actually comes pretty much the size of all the original data. So the entry point in the Packed binary launches. The stub program unpacks the original code out of its section and populates it into the UPX0 section, and then it just jumps right into it. And most traditional Packers are kind of just theme and variations on this very basic model. You have a Packed payload. You have kind of a landing zone where you want to put it. The Unpacker stub, through any type of gyration and mechanism, unpacks it and it's complete original state. So the binary has to get back to a point where it was originally compiled to run from. There's not a whole lot of moving that you can do on where that thing is. So like I said, they're easy to defeat. There's many ways you can use to detect these basic Packers. We can detect when code is being overwritten. We can detect when that section jump occurs, like when the Unpacker stub jumps into something that didn't used to be code. And the main problem is that it uncompresses the original binary into memory. So if you catch it while it's running, you have it and you can easily reverse engineer it. Nothing's been changed with that binary. So stepping it up a notch, some more sophisticated Packers do some things, mainly with actually modifying that original binary. At no point do you actually have a complete original intact binary that you can easily reverse engineer and IDA-PRO. So Thamida and VM Protect are pretty good examples. They actually translate the instructions in the original binary into like a virtual machine language that executes. That's particularly tricky, but it also, as you can imagine, might have some problems and compatibility and whatnot. Those are commercial tools and they cost quite a bit of money to run. And it's always going to use at your own risk. Not that my tool isn't, but I work on a slightly different model. So how my little Packer works is I manipulate the x86 instructions for the purpose of anti-reverse engineering. So I have a disassembler, so I basically disassemble the whole binary, and based on all that analysis, I can move around chunks of code, I can wrap chunks of code, I can do all sorts of things. I can hijack function calls, wrap chunks of code in anti-reverse engineering, and later on we'll get into some polymorphic stuff that I can do. So some of the challenges and headaches I've had when writing this is there's no margin of error for bad disassembly. So since I'm in the business of finding instructions and manipulating them and moving them around, if I do that on something that wasn't actually instructions to begin with, potentially serious problems with the binary, it's not going to work. So IDA-PRO can get away with that and it's disassembly because whatever, it disassembles something that wasn't real, you know, the user can go in there and mess with it and it's fine. But for me, I have a realistic set standard on what needs to be proper disassembly. It simply will bomb out my whole program if it isn't perfect. So I spent most of this project actually refining the disassembler, reducing false positives and getting the code really to a true disassembly, which is pretty interesting. There's a lot into that. It's pretty amazing that pretty much almost any arbitrary data structure can look like Intel assembly if you disassemble it, even though it completely isn't. So my disassembly is very pessimistic, and I'm going to use this same disassembler engine and the other tool I'm going to show you at the end of the talk, which I can use to find malware. All right, so let's get into the first little trick I can do. It's called, I just called it function call dispatching. So basically, with my disassembly, I'm going to find every function call in the program, whether it's to an internal function or to something from the import table, like an external DLL, I can find each one of those instructions and actually remap them to a tiny little 65-byte function that I insert into the binary. And this function is basically then going to call whatever it was supposed to call. So if you look at an original call tree of a function, so we have function A that calls B and C, and then each one of those calls functions of its own. If you dispatch all these with, and you remap them all to point to the dispatcher, the call tree is completely obliterated. It all looks like everything is just calling one function. It's pretty sweet. And once we have the ability to do that, we can do further avenues of research and like function level packing and things like that. So how this thing works, it works based off the return pointer. So I'm just going to cover a quick primer on the return pointer for anybody that's a little bit new to this. So here's a quick snippet of C code, prints out two things. When this gets compiled, set of assembly instructions, notice those two call instructions. That's the real important guys that we're looking at here. So we see offsets being pushed to the printf and whatnot. So I'm going to step through this and watch how it executes. First the push. So you see the string hello world pushed on the stack or really a pointer to it. Now when the call instruction executes, this is the key point here. The thing that was pushed on the stack is what's called the return pointer. So when the printf function is done, it knows where it needs to get back to in your program. And we can continue stepping through. And the same thing happens for every single function call. And in most binaries, that return pointer is never going to change. For every return pointer, I should know based on disassembly what function was supposed to have been called by the previous call instruction. It's pretty basic. So the function called dispatcher, all it does has a table of return pointers and then a table of targets. Whenever it gets called, it checks the return pointer off the stack and sees what it's supposed to call. It sets up the stack and jumps to it. It's beautiful. So the next concept here is code chunking. So it's going to kind of open the avenue for a lot more advanced areas of research because the main problem you have with trying to add anti-reverse engineering code into a binary is that you don't have the ability to just wedge in extra bytes into a binary and hope that it works. It simply doesn't work that way. It's going to fail pretty miserably. That's what I tried when I was young. I found it to be a lot more of a problem, but once you have the disassembly, you can know how to intelligently move things around. So that's exactly what I do. I find sequences of instructions that I can easily relocate and I do things like I substitute a jump there and put the instruction somewhere else and then I jump back when I'm done. It seems pretty basic, but once you have the ability to move things around, the code becomes more position independent and I can insert code everywhere I want. So I can weave in anti-reverse engineering code to that. I can make it also more difficult to reverse engineer because that's going to obscure the flow control of the program. One basic thing I just call scrambling. That's where the term PE scrambler comes from. So here's four functions, and if I break them up into little relocatable sections, I just jumble them up. It's pretty simple. Now, unfortunately, this isn't going to be enough. If you're looking at it in a static linear disassembler, this might kind of beat you for a while. It's like, ah, what's going on? But IDA Pro with its graph mode is still going to kind of reconstruct the functions appropriately. This isn't quite good enough. So I mentioned the relocation technique with a simple jump, which is the programming equivalent of, like, go-to if you guys are old, like, basic programmers. But there's other advanced things I can do. I can do things like find instructions that produce known conditional flags, and I can put in a conditional jump. Which, to a human, we know that's basically unconditional. It's like, you know, if the moon is made out of cheese, go here. To a disassembler, it doesn't know that the moon is made out of cheese, but we know. So let me start with the basic jump relocation, just in case I didn't quite catch that. So here we have a set of instructions on the left and how it's going to get translated by P.E. Scrambler on the right with basic jump relocation. So we have a call instruction, and then after that, I'm going to relocate everything from the test instruction down. So basically breaking this off into two sections. And it's just simple. I just replace the test instruction with a jump, and everything I moved it that it was pointing to is moved to somewhere else in the file. This is basically position independent, so there's no real problems with this. But it's also pretty easy to detect. Ida Pro is going to show you basically what you see on the right, where it's going to show you function chunks, and one just flows right into the other. No real advantage to doing this if you're looking in modern Ida Pro. But Ida Pro isn't very good at these, the known conditional stuff like I was talking about. So the whole idea here is we're going to find a flag and substitute a conditional jump. Later on, insert conditionals, which are kind of fake. So here's a fake conditional jump. I'm going to target here the XOR instruction. So XOR EAX EAX. That sets EAX to zero. Since it's zero, the zero flag is going to be set. And then I do a jump, JZ. That means jump if the zero flag is set. So to us, we know that the zero flag is set. I can tell you that zero flag will always be set at that point in the program. The disassembler doesn't know. So it thinks that other stuff can happen. So I have a point right here where I say fake code right here. We'll get into that in a second. But this is a completely unconditional jump in reality. We can also insert some fake conditionals, like I was mentioned, to kind of trip up the automated analysis process. Things that the human knows. So right here, in most Windows processes, I mean, this is pretty ridiculous. You'll never see ESP being, you know, 8000 or greater. I mean, that's absolutely insane. So I can insert an instruction right here that says, compare ESP with that and then jump if less than or equal to. So that's basically an unconditional jump in the Windows process. A disassembler, there's no way a disassembler is going to know that. It's not going to keep track of what ESP should be, what are reasonable values or not. It simply doesn't have that level of intelligence. But we, as humans, do. Someone must do. Just kidding. So the whole point of doing these fake conditionals and everything like that and trying to trip up these conflicting sets of disassembly. This is how you defeat a disassembler. So most disassemblers, when they come to a conditional branch, they trust the false branch first because they disassembled the false branch first. Whatever they disassemble first, they trust the most. So what we want to do is basically get the false branch executing wrong code and have that truth branch executing from a slightly different offset so that it actually doesn't match up. When the disassembler does go to process that truth branch, it's like, I already have instructions here. I'm good to go. I'm going to bail. So here's an example. We have three instructions, a push, a move, an XOR. They take up five bytes. If you start at the 5.5, they produce that exact set of instructions. If, for whatever reason, the disassembler started one byte earlier and we planted an E8, then it's going to disassemble a call instruction. It's pretty basic. And the disassembler is never going to go back and produce two overlapping instructions. They just don't. So what we do here, like with these false conditionals that I was mentioning, we have the truth branch point one byte in, then we have the false branch point to one byte before. So if it's doing that false branch first, like I mentioned, it's going to actually disassemble the exact wrong instruction. So the truth branch is going to be hidden, essentially. And like I said, since we have the ability to move around sections of code and change the size of things, I have the ability to add these types of little nuggets in there all throughout the program. Because I have free reign to add code as needed. So here's the basic rule. So now we're going to get into some impossible disassembly. This is a common one. It's fairly well known. We have a jump instruction, which is two bytes. Oops. Jump actually jumps from the end of the instruction. So the argument here is negative one. So it's actually going to jump into itself, which seems kind of ridiculous. But it actually executes perfectly. So it jumps into itself, and then starting at the second byte of itself, which is increment EAX, then decrement EAX. This is kind of like a know-op, a know-op that will fool a disassembler. All right, moving on. I've come up with a lot of these over the years. They're kind of fun. It's like a puzzle. How do you completely ruin a disassembler for life? So here's another one. A move instruction, XOR, and then a jump zero to negative seven. So I'm going to show you the flow of how this actually goes. So going from the end of the jump of zero instruction, it points back here to the second instruction. And if you look at what that actually is, it's part of that move instruction. So it's actually the operand to the move instruction. And what is that? It's a jump at five. It jumps right there. So notice the jump of zero to negative seven. I said on a conditional, it takes the false branch first. The false branch, I planted an E8. So it's going to disassemble that stuff as a call instruction. And then the truth branch is going to produce a disassembly. You're jumping to embedded instructions within embedded instructions. I don't think a disassembly is going to get this. You guys might have a disassembly that will. I don't. Then we can get into polymorphic replacement. This isn't quite as going to be as advanced as some of the viruses and whatnot out there, but even at a basic level, this can be pretty mean for signature detection. So what I'm going to do is seek out small little clusters of instructions that I can replace with equivalent instructions but I didn't have the ability to do this until we had the ability to break apart the binary which allowed us to add more code as needed. And also these types of polymorphic replacements can be randomized. So basically, given an original binary, every time I run this tool, I could essentially randomize the binary, the compiled code, which is pretty neat. So here's a few of them. I have a push instruction which is really simple. The push modifies the stack. It puts a variable on the stack. As well as a move relative to the stack. So I manually adjust the stack pointer and then I manually move data onto it. It's the same thing as a push. Same thing with call. Call is just like a jump, only it puts that return pointer on the stack. So I can manually put the return pointer on the stack and then do a jump. Fairly simple. You get the idea. With the push and the call, I'm just showing you an example of actually just randomly inserting useless or subtract from the stack pointer. It's completely useless code. But if you had an antivirus or something that had a signature for a sequence of instructions and you added code in between it, the signature's broken. So one thing that most compilers do is this is what the first thing on the top is called the function prolog. It sets up the stack frame, that kind of thing. You can do this with one instruction, but most compilers don't for efficiency. So I can actually find the function prologs in almost every function replacing with, like, Enter. Or there's any number of ways you can imagine to set up a stack frame, which can be easily substituted based on disassembly. Jump instructions. All the return instruction is it takes the value off the stack and jumps to it. So I can substitute a jump for a push and a return pretty easy. As you can kind of see where I'm going with this, there's any number of instructions which have any number of equivalent mappings. There's a lot that can go on here. On the last one, I actually substitute a logical comparison with an equivalent one. So let me show you an example of this tool. It's only on the screen, so we have to press my typing. All right, so in this directory, I have pescrambler.exe and I have notepad.exe. So pescrambler.exe, I run it, get the command line options there. It just takes a dash i input file dash o output file. There's really nothing to it. So, let's specify dash i notepad dash o new notepad. And once you see the list of things it does, disassembling, generating cross-references, remapping call instructions, armoring code, that's all the other things we talked about. And now that should work. Let me get a directory listing here. In the directory listing, you can actually see that it actually added $30k to the binary, so it's actually much bigger. It blacked my screen. Yeah. Hold on. My screen is black. I don't know what's going on. Anyway, you have to trust me it did. There we go. Bear with me. Yeah. All right, so there is notepad launching right there. That's the actual new modified notepad that we were playing with, everything. I don't know what's going on with my laptop. It's been a pretty solid laptop up until now. Let's take a look at these in disassembly. I can show you some of the things we're going on here. Here's the original notepad. This is before I modified it in IDA. You can see IDA shows you a nice graph of all the instructions and all that. The main thing I want you to look at here is the actual, this is basically the map of the binary. Blue means code, gray means data. That's all you need to know. Take a look at the pscramble binary. Take a look at the map up there. Not quite the same, is it? Yeah. So now, here's what I was talking about with the function called chunking. You can see it's actually translated what we had into just a series of these little jumps and whatnot. We can still kind of get some logic here going. So this call instruction right here, this is the actual function called dispatcher. You can read through the logic if you want. I'm not going to spend much time on it. If you look at crossrefs2, we're going to get a nice little map. This is every function that's calling our little function called dispatcher. And as you zoom in, you're going to find it's quite flat. Every single call instruction in the entire binary is calling the same thing. Oh, and that's not the whole story. Let's do the equivalent. Open subviews. Trackpad. Anybody have any ideas on what this is going to do? If you guessed jack shit, you got it right. The reason is Ida doesn't know exactly how my function called dispatcher works, so it doesn't know that this thing actually calls every other function call in the whole binary. So you could reverse engineer this, figure out how it works and all that. It's pretty simple, just a little bit of shell code. So, some other cool bits. I think, that's pretty much it. Let me show you what a normal call graph looks like in a normal program in case you haven't seen it. Oops. So I'm going to go up here to the top. Graphs. Function calls. Here's a normal call graph of an entire program, and you can see through reverse engineering you can get inferences here. So if I see a function that calls a bunch of registry functions, maybe that's something that does registry stuff. If I wax all this stuff with the function call redirector, it's good to go. So, just some ideas there on what's going on. Moving on. All right. I don't need to go any further. I was just going to show you one more tool. Oh, shit. We're way ahead of time. Okay, one other tool. Let me get this back to the top. Find evil. So I have a tool here. It's a GUI. So I'm going to try to launch another GUI. And I did it again. It's completely blacked. Damn it. All right, so how Find Evil works. I'm going to go talk about what was in the slides. How Find Evil actually works is I disassemble the binary. And based on disassembly, I know how many instructions were legitimate. My disassembly is very pessimistic, so it doesn't trust anything that it doesn't absolutely think is code. So if there's any anti-disassembly tricks, like what I've shown you, it completely fakes it out, and it won't disassemble very much code at all. Oh, shit. So what I do here to find malware is I'm going to disassemble every binary on the system. I'm just trying to go malware hunting. Disassemble every binary and see which ones don't have very many instructions that I was able to disassemble. Typically in a large binary, I should have a whole bunch of instructions that were able to be disassembled. Whereas packed binaries, I'm only going to disassemble maybe a little bit of the unpacker stub. Okay, here's Find Evil. Oh, shit. Now I'm up over here, but you're not up over there, so coming up, coming up. There we go, okay. Hooray. See, I saw that I'm running a little quickly, so I'm purposely doing this to stall. Okay, so here's Find Evil. We just do a new scan. If you've ever used the other tool I wrote called Red Curtain, the interface may seem a little familiar, but vastly different technologies underneath. Red Curtain did, like, entropy analysis on all the binaries to try to find binaries that had a lot of random data in them, which means usually, like, compressed data, which can be used to detect packers. The problem with Red Curtain, though, is you've got a lot of false negatives on some packers. If they had really weak compression, or they didn't do compression, they just X-word, the entropy score would be quite low. So I didn't want something that's going to give me false negatives on a packer. And this technique, I've never found a false negative. Just throwing that out there. Oops, not my documents. I'm going to scan just a little demo folder I have with some malware in it. Find Evil malware. It scans. If you notice, that actually disassembled pretty quick. If you ever loaded a binary and IDA-PRO, it takes a while to disassemble. I go much faster. One of the reasons I go much faster is for this purpose, I just capped the disassembly at 10K. So once I've hit 10K, I'm assuming I'm probably not packed. I bail. This is code to disassembly ratio, even though it's kind of fuzzy on that screen. If it detected there's not enough instructions for the size of the sections that are marked code, I'm going to flag that. If I detected, if it disassembled 10K or more, I just marked it as green and you can move on. So that's the main field. And I've never seen a truly packed binary that actually did not score red in that column. So there's also a yellow field, but it's a little bit more rare. There are other columns in here of interest in case, for whatever reason, that didn't quite work right, file to code. So if I have a really small file, and then I go through the PE file format and I see that actually when the thing's in memory, there should be a whole lot more code that kind of keys me off to the fact that maybe it's actually compressed in the file and it's going to be uncompressed in memory. So I flagged that. That's also a pretty dang good indicator. I've never seen a false positive on that one either. Some of the other columns, you can take your pick, but this is a good way. If you have a hard drive or whatever, you can just run this tool against it and if there is a packed binary on there, it will be red. So this only has the problem of a few false positives maybe, but no false negatives that I know of. And both of these are available on the website. Let me skip to the end. So the source code is not currently available. Yeah. Truth be told, my company might take action on me for even releasing the binary. They seriously did not like it, but I did develop this completely on my own time. No company affiliation officially slides don't say my company, so we'll see what happens. I like to put stuff out there, so you might see code, you might not. Don't hold your breath. You can get them at rnicrosoft.net. That is not an M, that is an R-N. So it's all up there. And thanks a lot. Anybody have any questions? Are you in the blue? It's just the Shannon entropy curve. So we go through the binary, find every section that's marked as code and the image count code characteristic and then do entropy on those. Next down here. Yeah, interesting you mentioned that. Well, I haven't looked into multiple dispatchers because the one seems to do enough job, but with the function called dispatching, I'm looking into actually, that's where I'm going to intercept things, maybe be able to change the functionality of any function that I want to remap or just wrap it around with some anti-reverse engineering code, things like that. There's a lot of options you can do. Rescramble it twice. Not really because my disassembler is very pessimistic, so if there's any anti-reverse engineering tricks, it just shits the bed. I mean, it fails. So whenever I try to go to rescramble it, it's just going to get like three bytes in and be like, dude, you know, seriously. Yeah. So, good point. The next tool I want to write is just, I don't know what's going to be called, but based on my disassembly, I'm going to look into ways to dynamically pack and unpack strings as needed based on, so weave that code right in instead of unpacking all the strings up front. It can be done with disassembly, I believe. So I think you've been in the back and had your hand up for a while. I would like to say no comment, but yeah, I did want to do STAG with this, and it can be done, and it may be being done, and maybe not. No official comment. Mandiant is not responsible for things said on this stage. Down here? Oh, well, you saw a notepad running, right? I actually surprisingly, I thought with the function call dispatcher, where I'm intercepting everything and adding like, you know, tons of instructions and tons of loops and whatnot to every single function call, I thought it would be a lot slower. Surprisingly not. You said, if I looked into more calculating, oh, yeah, more complicated programs, but I did a pro, and it worked. So, you know, I think that's somewhat sophisticated. Over here? It's normally, I'm putting it in, whenever I relocate sections of code, I usually put it at the beginning and just wrap it. Or even just the simple jump, the conditional jump trick works fairly well. There is anti-debugging code I can put in. It's not in the free tool. It's on the web right now. And that gets, it's really tough to debug this tool, as I'm writing it, because I'm writing it for the specific purpose of anti-reverse engineering, and you have to reverse engineer the output to figure out why it's failing. So, like I said, this has been a nightmare project, but it's workable, it's usable, and that's phenomenal. Any other question back there? Yeah, so how often does this thing just generate nonsense and garbage? Unfortunately, more than I would like right now, it's probably on the neighborhood of 20 to 25%. That's just going to be bug fixing. It's still kind of alpha prototype kind of stuff, but when it works, it's pretty good, but that's just going to be continuing development. Oh, God, Steve Lawler. Interesting you add that. I don't actually obfuscate the V-table, but I do, I mean, the V-tables do get run through, because I have the ability with my disassembly to run through every single potential pointer in the binary, which is something Ida can't do, because it's too trusting and forgiving. So I can run through every four bytes in the binary and try to find anything that looks like it might be a pointer value. A V-table dispatcher. Version two. Cool. Yeah, I haven't tried it on a wide variety, and I don't have any good tools for instrumenting that kind of stuff, but for like malware purposes, that's mainly not that anybody writes malware. So I would really be kind of sparing if performance is that much of an issue with you. I might look for things that, anti-reverse engineering technologies that you can integrate in your source code so you can turn it on and off as needed and put it in sporadically. The whole advantage of this tool is that you don't need source code. You can just run it against the compiled binary. So people who don't have the source code can use it, and it does things in a way that the source code stuff won't. If performance is that big of an issue and your timings come out to be shot after you run it, I don't think there's any way I'm going to get around that. It's a fundamental issue. So, any other questions? Well, it's interesting you mentioned dead code because it's going to be tough to determine it to be dead code. Dead code would be, at least typically in my thought, is like functions that don't get called, things that are left out, looked over. The instructions that I insert are not dead. They're executed frequently. So, I mean, you could do some runtime optimization and say, well, these instructions aren't needed. That would be an incredibly sophisticated tool, though, so that's actually probably a lot harder than anything I've developed and anything I'm going to develop. Well, it's not really based on variables, like the conditional jump trick and whatnot. That's just a flag register. Theoretically, I mean, something you come out to predict it. But I can probably write the anti-reverse engineering stuff quicker than people can write the code to actually detect what I'm doing. It's a losing game for detection because this stuff is easy. Like, getting the code to mix stuff up and all that was tough. The actual technique to defeat disassemblers was easy. There's nothing to it. Any other questions? All right, well, thanks. Hopefully.