weaponizing Windows shell codes as modern 32 bit shell code. We have a lot of content to cover so we are going to try to go a little bit more quickly than we would prefer. Uh my co-speaker is Tarek. He is a former graduate assistant of mine. He is currently an offensive security engineer at 23andMe. And he enjoys um shell coding. My name is Dr. Bramo Brizendine. I am the director of the former director of the Verona Lab. And uh that deals with exploitation research. I'm also the creator of the Jop Rocket which deals with jump oriented programming. And I am a current professor at the University of of um Alabama in Huntsville. And I have a PhD in cyber operations. So our talk really concerns Windows shell code. And typically the traditional way in which we invoke malicious functionality in shell code is with what we call Windows APIs. And it's a very familiar process. We do uh some peb walking and then we traverse the PE file format. Um typically our shell code will be utilized for exploitation as well as for malware. Our research deals with utilizing Windows sys calls and shell code. And that's something that's very, very different from Linux sys calls if you are at all familiar with that. So we're looking specifically at 32 bit shell code. And that's shell code running on WoW 64. So WoW 64 is a mechanism by which we can do 32 bit applications on a 64 bit architecture. And our goal is to try to uh achieve 100 percent functionality through pure sys calls. And it's something that has only very rarely been done in shell code. A quick history on sys call usage in uh uh Windows 64. Uh Windows 64 is a very, very, very, very, very, very um shell code, so egg hunters are very well known. They've been around 15, 20 years and traditionally we use an egg hunter to check and see if memory is valid and then try to locate a tag such such as starfall or woot woot. And typically that uses just one uh sys call. Now the single exception to that and we looked long and hard to try to find uh something that was more complex was a sys call shell code from 2005. And that uses a single exception to that and we looked utilized four syscalls as a mechanism to establish persistence. And it did that by using a hard-coded value for the syscall pushed into EAX. Now, a problem with syscalls is a problem of portability. So, syscalls, uh, traditionally, um, in the Windows environment, at least, uh, there's a great deal of variance. So, Windows 10, for instance, there's more than 13 different OS builds or releases. And for each of these, there are different syscall values for the same syscall. And that can make it extremely difficult to use a particular syscall simply because we don't know the correct value. So, it's not always reliable. And that's why people typically have not used that in shellcode. Um, so, a little, uh, quick history of shellco- shellco- syscalls and the modern error. So, there was a report by Hod at Gavriel in 2018 about, uh, the very sharp uptick in direct syscall usage and malware. And that included even dual loading of NTDLL, and also additionally, used something that was kind of like a variation on, uh, Hell's Gate. It would look for the move opcode and then parse the syscall value. And so that then led directly to several syscall tools, the first being Dumpert, which was a PLC tool that, uh, used pre-computed syscall tables to uh determine the the correct syscall values for the OS build. That was followed up very quickly by uh syswhispers and that generated a 64 bit uh header and assembler files that could be used to work in a um Visual Studio project. Uh so that's very well known and it uses a 64 bit path to determine the OS build. Hell's Gate came about later on and that was a way to dynamically uh determine the OS build um re- as opposed to using pre-computed syscall values from existing syscall tables. Uh it worked well for a period of time but the problem is EDR is able to detect that and to overwrite certain critical parts of um the the the syscall functions. Hell's Gate is a refinement and response to that that allows us to extrapolate and to figure out what a modified syscall is by looking at the preceding or the following syscall and adding and subtracting one. And that uses a technique developed by a guy called Elephant Seal. He figured out the syscalls if you sort them by function addresses then you can deduce the s- the system or the syscall ID by incrementing by one. So he developed his tool Freshie Calls to implement that uh about a month later we had syswhispers two that uh integrated um a similar technique and so that's how we did it. So that is extremely well known, very famous tool. Uh syswhispers three is a recent variation that has some additional functionality. So one of the kind of secret sauces of sorts for these is this idea that uh the syscalls are gonna be incrementing by one. So if that if that works then great but the problem is that underlying assumption is now wrong because Microsoft has introduced mitigation. And I found this myself by creating some syscall tables for recent OS builds of Windows 10 and Windows 11. What they've done is we can look at Windows 7. So Windows 7 uh three, four, five, six. Clearly incrementing by one. But we look at Windows 10 and we have four, five, six, seven but oh look lo and behold somebody has inserted some other bytes in front of that. So it's no longer incrementing by one. Which is tragic. And it repeats itself dozens of times throughout um modern OS builds. So what does it mean? Well it means we gotta get a little bit more creative. So what you could do is you could dual load NTDLL and then you could do a modified hells gate on that. The problem though is that some EDR can detect hells gate being loaded. Um but fundamentally uh it's important to bear in mind here that uh with the modern uh OS builds uh syscalls are not necessarily the same as Windows 10. They're no longer incremental by one. So some of the, some of the elements are no longer incremental by one. So any of the tools that utilized the uh sorting by address technique are no longer a hundred percent reliable. They may work 60, 70% of the time but then they will fail at other times because some of them are just simply not incrementing by one. So that effects uh Freshly Calls, that affects syswhispers too and Hellos gate among others. So lets talk about reverse diverse engineering Windows syscalls. We can look at Windows 7 and this is inside NTDLL. So NTDLL function for NT allocate virtual memory and it's loading 15 into EAX. So 15 into EAX is the value for syscall value for NT allocate virtual memory for one particular OS build. We then make a dereference call to FSC zero. FSC zero points to a far jump and that far jump has what's called a 33 selector in front of it. So that means it's going to transition from 32 bit mode to 64 bit mode. So we actually have 64 bit code inserted into a 32 bit process. And this is something we couldn't directly follow in a debugger if we wanted to. So FSC zero is important to remember here. Windows 10, we switched things up a bit. So now we have a NTDLL off. That will then point to our syscall, a function called wow 64 transition and that will lead us then to the far jump to transition from 32 bit to 64 bit mode and then eventually to the real underlying syscall. The good news, though, is we don't need to use that. We could instead just use the FSC zero method. That still works. In fact, it's actually easier to use. Windows 11, well, at least from the user mode perspective, it's virtually identical, some different function names, but it's pretty much doing the same thing. And guess what? The Windows 7 backwards compatibility method of invoking it with the FSC zero, that also works too. So let's figure out how we can build some syscall shellcode. So we need to determine first the OS release. Because if we don't have the correct OS build, then everything else that we do will fail because we'll be calling the incorrect functions or syscalls. So we can figure this out through introspection and we can do that by a process called walking the peb. We get that from FS 30. And then in this case, we're going to go to the OS build number, which will be offset AC. And then that can allow us to determine conclusively the OS build will give us a hex value. We don't actually need to determine the OS major version or minor version. We can do that. But we don't actually need to determine the OS major version. We can if we like. But if we're using Windows 10 and 11 or Windows 7 separately, then it's just not necessary because the OS build itself is unique. However, if we are combining them, meaning we're using Windows 10 and 11 as well as Windows 7, then we do absolutely need to determine the OS major version. So turning it into shellcode, we just simply use the get the offset of the peb. So FS 30, the process environment block. And then we can do the offset AC. Which will then enable us to get the OS build, which in this case is 21H2. It's the latest version of Windows 10. So when we actually call the syscall, we need to create a small little function. And this is a function that will be part of the assembly. So Windows 7 method is a little bit more convoluted. The Windows 10 and 11 is more succinct. And if we're combining both of them, then we need to programmatically determine which we are going to use. And we can do that fairly simply by looking at the OS build. So we're going to use the OS major version. So what we are doing here is we're using what we call a syscall initializer. So we will capture the OS build. And then we will determine, we will create some space on the stack for what we call a syscall array. And then we check to see which version it is. We then load the appropriate values for that into our syscall array. And then we can then dereference the elements from the syscall array through EDI. So EDI will be a persistent version of the OS build. And the system will then take the function that it is called, put the parametries into the system, and we need at the end that we have a consistent pointer to the syscall array. And if we're combining 10 and 11, then we also will check the OS major version. And that will be located at EDI minus 4. And so once we complete that process, then we will have the syscall array. And then that will allow us to dereference it from EDI. So EDI plus 4, plus 8, plus C. Can enable us to access those values at runtime that will correspond to the correct OS build. So there's an example of how we can do that. So no hard coded values, just simply dereferencing EDI. And so by doing this, then we will have the correct values, whether it's Windows 7, 10, or 11. The pointer to the syscall array is something we need to maintain. So we'll push EDI out of the stack before pushing our parameters. We'll do our syscall, and then we'll do a stack cleanup, and then we'll pop the pointer back into EDI. And so, therefore, we can maintain the integrity of the syscall array pointer. So one tool that I made, and this is kind of a bonus thing, because we didn't advertise it, is this called Shell Wasp, and it's available on our GitHub. Be well, Shell Wasp. And it will also be in the slides you can download. And so this automates building a template for syscall shell code, and it supports nearly all user mode syscalls. It addresses the portability problem, and builds out the syscall array. And it's very simple to utilize and select syscalls. You can do that through the user interface, through a config file. You can rearrange it. You can select the desired OS releases as well. And then the final result here is we can see that it does indeed create a template. So these are initialized as zeros. And that's kind of your job to go through and to figure out the values to put on them. It's something that Tarek will be talking about very briefly or very shortly here. But that also provides the parameter names and types. And it takes care of managing the syscall array and all of that. So if you're doing something very complex, all of that is taken care of for you. And without further ado, we'll then introduce Tarek, who is going to give us an excellent example. Hello, everyone. So here I'm going to talk to you about process injection shell code using system calls. So here I have a list of all the APIs I'm going to use in this shell code. We're going to start with the first one, which is NT query system information. So the purpose of this API is to give all the necessary information about the running processes in the background. So the reason we do this is we need to get the process ID or the process number of the targeted process that we are using to inject our shell code into. So this API has a system information class and it has many other classes. But the system information class is telling the API what kind of information you need from the operating system. So here I'm using system process information, which is equivalent to five, the value in hex. So this is telling the operating system we're only interested in information about running processes. So once we get that, so we can parse through each process, and we can get the process name and the process ID. So this is what the system process information structure looks like. The first element is called next entry offset, which is pointing to the next process. And the other one is called image name, which is the process name. And that's the one that we are interested in because we know the process ID is changing, but the process name, it doesn't change. So we need to look for a process. By looking up its name. And the last one at the bottom, it's called unique process ID or the process ID. And this is the one that we're doing all of that to get. Here we have the system information, system process information. This is just an example. A process called system. It's a Unicode process name. So make sure when you're doing this, Unicode string has null bytes between each character. At the top, the next process is at offset zero. This is what we use to go through each process in the data that we get. Offset 3C has the process name and offset 44 has the process ID. So the way this works is we don't know how much data all the processes running in the background would be. So we can allocate arbitrary value using something like NT allocate virtual memory. And we call the NT query system information. That might not give us a success value as a return. So what we do here is we allocate more memory, which is like let's say 2,000 in hex or something. And then we call the same API again. Once we get a successful value back, that means we have all the data that we want that we can parse through each process using the same thing that I just discussed here. The process ID and the name and all of that. Once we have the process ID, we can move forward with the NT open process. Very straightforward. It takes four arguments. One of them is called object attributes. The object attributes here is another structure that we're not kind of like we're not using most of it. We just need the length. Everything else should be zeros. Like the real directory, object name, all of that could be used for other stuff, but not for this case. And we also have the client ID or PC client ID here. This is kind of like a process ID we just obtained. So we will use that. We also have the desired access, which is all access, one on five Fs in hex. And the variable at the top, the process handle, this is to receive the handle for the process. Once we are done and we open the process, we have a handle. We can keep going with the NT allocate virtual memory. This API is like the godfather of the virtual alloc and all its variants. It's very similar to virtual alloc. It's very similar to virtual alloc. The only difference here is the base address, like the second argument, it's in and out. Like this variable will be used to get back the allocated address in memory. And we will use that later to write our shell code. The process handle, the one that we just got before, and the zero bits is just zeros. It's kind of optional here. So we have the region size, which is the size of the shell code. We have the allocation type and the protect. Very similar to virtual alloc. This is just an example, what it looks like. The first one is the protect, like the page execute, read and raw, what kind of permissions you need to get. And the type, the reserve or commit or both. And the rest of the things here, like the size of the shell code and what I just discussed. And finally we invoke the system call to make it happen. Next we allocate us the memory in the remote process. Now we need to write our data there. We need to write the shell code, which is I'm using the interpreter shell code here in this case. So here, very straightforward, same thing. We have the process handle. We have the base address, which is the pointer of the allocated space in the remote process. We have the buffer, which is a pointer to the shell code that we have in our C program, like the interpreter shell code. And the size of the shell code. It's a number of bytes to write. And the last one is optional. This is the number of bytes you... get back. Like how many bytes you were able to successfully write to the remote process. Once we're done, this is like a shell code, same thing. We invoke the system call to make it happen and write the interpreter shell code to the remote process. And here, we... once we have the shell code written to the remote process, we spawn a thread from that... from the process using Inti create thread, the extended version of Inti. And here we have the shell code written to the remote process. Into the remote process, we want to archive security versions of it. In this API, yeah, it takes 11 arguments, but that actually... the last six arguments are zeros. They're optional. And we don't need them in this case. We're only interested in the first five. The P handle of the thread. This is like a handle, like a variable to receive a handle for a thread, which is not really going to be used. The second one is the desired axis. Has we're using all axis, which is one in five Fs and HECs. They shot in six times.иной is the three advantages of or And we have the object attributes, same thing as the NT open process. We're only using the length, and we're not using anything else in this structure. And then we have the process handle that we keep using all the time. And then we have the LP start address, which is a metropeter shellcode address that we were able to write to the remote process. This is what it looks like in shellcoding. This is an example. So the shellcoded pointer to the, I'm using a tool called crypttool.exe. You can use anything else you want. Just make sure when you're doing this, if you compiled your file using 32-bit, and you want to inject something into another process, make sure to say the process has 32-bit architecture, otherwise you will get access violation. So now we have two separate shellcodes. One is for system code shellcode, and the other one is the metropeter shellcode, that is hiding in your C program. But now, we need to combine those shellcodes together so we can use them if you're leveraging a vulnerability or something, and if you want to execute them at once. So here at the top, I have the green part at the top. This is just used to get the beginning of the metropeter shellcode. I'm using the cold pop technique here just to get the address of the metropeter shellcode, and then I jump down, all the way down to the green part at the bottom, which is the start of the system call shellcode. So now once you have this large shellcode, a combination between system call shellcode and the metropeter is sandwiched between them, we need to execute it somehow. So you can execute this in memory if you have a vulnerability, or you can just have a C program if you're running a malware, and you can use a shellcode tester if you want, but if you do that, you will still use some kernel 32 APIs. So I wrote this execution skeleton to do that, so you can avoid kernel 32 APIs. It's equivalent to the one that you can use virtual alloc and then mem copy. This one's still using the same system calls. You're still using NT allocate and NT write to write the shellcode to memory, and then the last line at the bottom is jump to this place and start executing. So it's demo time. So this is the tool I just started, and then now I'm going to start a debugger. Just to show you the stuff, how it works in the debugger. You can't see it. Oh. Why it doesn't? I'm just going to try. Okay, guys, you've got like one minute left. I'll let this run. Okay, it's ready? Okay. So here I'm using the CryptTool.exe, the one that I just mentioned. And this is a debugger, X32 debug. So the first breakpoint is where we get the actual CryptTool.exe. Like we found the process here at the right side. And then now we can parse through the process. And we get the process ID. This is just to verify the process ID that I got. The dot is the same using task list. Convert the hex to a decimal so we can get just to verify it's the same process. The second breakpoint will be when we allocate the memory into the remote process. Here we have the allocated space at 18E40s. We open process hacker to make sure it's the same. It's there. We have the read, write, execute permissions. I'm sorry. And the last part here is just showing the Windows Defender is up and running. And we run it and we get my interpreter shell back. And as you can see here, Windows Defender was just taking a nap. It's not doing anything. Thank you.