 We have a super interesting talk for you up next by Amro Abdel-Gawad. He's a security researcher with Immunai, sorry. My speech is getting a little slurred today. I'm not even drunk. So without further ado, I'm going to let him take off his talk. Let's get a big round of applause for him. So when I started working on the remote metamorphic engine research, the last thing I was thinking about back then was metamorphism. All right, is it OK now? So the last thing I was thinking about when I started working on the remote metamorphic engine research was metamorphism. I wasn't thinking about metamorphic at all. I was mainly trying to create unbreakable code, a piece of code that cannot be reverse engineered, cannot be tampered, cannot be analyzed. So anyone who has limited experience with reverse engineering will know that it's actually not possible to create unbreakable code. It's possible to resist reverse engineering, but it's really impossible to create a piece of code that would have high resistance to the degree that it cannot be analyzed. So I researched the subject. I read about a lot of papers. I learned about a lot of obfuscation techniques, amazing obfuscation techniques. And I applied a lot of techniques. But unfortunately, I failed. Sequence of failure attempts kept going up until the moment that I decided to simplify the problem and treat the problem as a security problem. Well, at first, that actually turned to complicate the problem rather than simplifying it. Because unfortunately, we don't know what security is. We know what security is not. And we learned about weaknesses based on knowledge of vulnerabilities. And we learned about strengths and security based on weaknesses and defined weaknesses. But we really don't know what security really is. If you ask me 15 years ago when I started my career, what is security, you would have lost your whole day receiving my response to your question. Today, if you ask me the same question, you will really make me thinkin' something amazing about security that the more we learn about it, the less likely we are capable to define it. But there's no wonder. As at a certain point in my research, I came into very satisfying view for security. That security actually meant to be undefined. That security is all about undefined expressions. Undefined expressions that aim to take probabilities out of the equation that you're trying to secure. So the remote metamorphic engine, research of resisting reverse engineering started by defining security as an undefined expression, taking the lessons learned from there and applied to the binary protection problem. And that resulted in flux binary mutation. And only then with flux binary mutation I started to have satisfying results for resisting reverse engineering. But resisting reverse engineering or resisting a reverse engineer turned to be not enough. Once you have high rates of satisfying results of resisting reverse engineering, you will then realize that the problem is much bigger than that. You need to also count automated tools. You need to count AI tools or machine learning. And then the problem is not only about the code. The problem is also about data. How are we gonna secure the data? Input, output, even the data all is being processed inside the code. So that resulted in the end by adding techniques to secure the data and the code that resulted in sort of artificial immunity. So that's the outline of the presentation today. And the remote metamorphic engine is actually the name that I've given to the approach which is a new approach for resisting reverse engineering. But it's more like a new approach rather than an actual engine. And I decided to name the approach like that because it's not possible without metamorphism or mutation and it's not possible without having the engine or the morphic engine isolated remotely or away from the reverse engineering environment. And I'm applying all these techniques using very simple, very simple techniques that I'm gonna go through with all with you today within the coming few minutes. So I define security as an undefined expression following very simple analysis flow. If we don't know what security is but we know about a lot of successful security solutions, so if we analyze these successful security solutions enough, we can then find patterns that keep repeating itself everywhere and then we can, by defining these patterns probably that will help us to better understand security and will help us to better approach security. So that's exactly what I've done. If you ever do that, you will find something pretty interesting that randomization and isolation are two major patterns that we rely on in everything. To the degree that if you take these two patterns out of the equation or out of any security solution, security is not gonna be possible. Randomization like, let me give you an example like stack buffer of fruit protection where we insert random number and then we check on the random number when the function returns. Added a space layout randomization to disable jumping into hard coded locations. Encryption, asymmetric and symmetric encryption almost everywhere you will find random numbers without random numbers we cannot apply a lot of security solutions. And on the other hand, a much stronger security pattern is isolation. And if you just treat these patterns based on their meaning, randomization and isolation you wouldn't really gain much from their meaning but analyzing them in abstract mathematics you can then be able to isolate the patterns away from their meaning and then you would be able to extend their strength and to find their ultimate strength and then you will be able to move freely with these patterns to apply it on other problems. So I've done just that and then I arrived into defining randomization in its ultimate security pattern as division by infinity in an inverse relation to probabilities where like you increase the random number as much as possible to reduce probabilities as much as possible to zero. And on the other hand, I defined isolation as a division by zero which is an undefined mathematical expression by then you can actually take probabilities totally out of the equation. So only then when I return back to solve the binary protection problem only then I started to see another dimension to the problem. From that perspective, all the researches that I read about and learned about and all the failure attempt that I went through, they were all going toward infinity in an attempt to increase the time and effort needed to disable reverse engineering or to resist reverse engineering. So how about going toward zero? In this case, and instead of increasing the time and effort needed for reverse engineering, we will just reduce the time as much as possible to zero. So allow just few milliseconds for the code to be executed. And by that, there will be no way for the code to be reverse engineered. Imagine that you are just generating a code that will be valid only for six milliseconds. There will be no way that it will be reverse engineered unless it will be saved and then analyzed and then reverse engineer will return back to try to attack the system based on knowledge of previous execution. But then if you do that, it means that the code is expired. The code is not gonna be used anymore. So you need to generate new code. And hence we need to have metamorphic engine. But not a metamorphic engine from the perspective of viruses or malware where they use metamorphism just to change the way the code looks like or to change the pattern or to change the signature of the code. We need to have actually more like mutation engines, more than just morphic engines. So I went by and I defined the unbreakable code as an unpredictable code, but the code that cannot be determined and cannot be and to keep on changing. And it cannot be expected before it gets executed. And while trying to apply the randomization on isolation techniques that we talked about, I found that the major weakness is actually the static code dynamic data, which is the model that we use to everywhere that this is the way we learn how to program. This is the way we learn how to develop our software. The code is static and the data is dynamic and that enables all sorts of reverse engineering and also enables all sorts of replicable software exploits. So I tried to change that model into the dynamic code dynamic data that the code will keep on changing and the code will keep on evolving while it's being executed and that the code will not remain the same, will not remain static. So if you look at the code now and try to analyze it, it should look totally different than the way it looked just a minute ago or a few seconds ago. So any reverse engineering attempt goes through three main stages. Locating the code, analyzing the code and then breaking the code. In order to have very high rates of RE resistance, we need to resist all these stages. We need to make the code unlocatable. So we're gonna make that by storing the code remotely and perform remote execution. And we're gonna disable analyzing the code by using flux binary mutation and by setting the lifetime of the code into a few milliseconds. And then we'll make the code unbreakable by allowing the code to know more about itself that it would detect any tampering attempt if any happened while it's being executed. So the remote metamorphic engine architecture looks as you see here, will divide the engine into two separate areas. Trusted area and untrusted area. The trusted zone here is the area where the reverse engineer has no access to. And the untrusted zone is where the reverse engineer would have access to. We'll have mutation engine stored in the trusted zone that will keep generating code and keep mutating the code and then push the code to be executed in the untrusted zone by using challenge response metamorphic protocol. So why remote? Why we have to store the engine, the morphic engine remotely, away from the reverse engineering environment? Well, if you keep the engine next to the, in the reverse engineering environment, a reverse engineer will just simply go and reverse the engine itself and will break the engine. So in order to secure the engine itself, the engine has to be stored in a secure area and an area where the reverse engineer doesn't have any access to. But you don't really have to do that. It all comes to what you're trying to defend against. If you are trying to make the code keep morphing itself and keep changing because you're trying to defend against intrusion or malware or external intrusion. So you can then have the engine in the same trusted area. But today I'm mainly focused on, or the presentation is mainly focused on resisting reverse engineering rather than using metamorphism to secure the trusted environment. So the remote metamorphic engine is based on challenge response protocol, challenge response communication protocol that is made of morphed machine code rather than data. And the protocol is pretty simple is that the trusted, the trusted area will push four bytes of code size and then will push the morphed code and then the untrusted area will just receive, will receive the code and execute the code and then respond back, respond to the engine. And trusted area and untrusted area here can be client and server. It can be kernel and user mode. It can be guest and host machine. It can be even like an oil reader in an oil field and then you need to check its integrity to make sure that it's secure. It's not tampered. And on the other hand, we also should count the offensive approach which is in this case, a malware can use the trusted area as a command and control server and untrusted area as the infected machine where it's untrusted because reverse engineers will have access to. So by doing that, we would actually split the execution flow into two different areas. An area that the reverse engineer has access to and another area where the reverse engineer doesn't have access to. And that on its own will create a lot of challenges to any reverse engineer because the reverse engineer will never see the whole picture. We'll see just parts of the code being executed and we then even determine what decision is gonna be made on the responses or return values. So here is a list of samples of the challenges that can be pushed into that protocol which is mainly, here I'm mainly focused on challenges that we'll check on the integrity of the environment such as in memory code integrity check, execution environment integrity check, detecting hooks or trying to determine if the execution environment is real or it's emulated or it's instrumented. Clock synchronization. Clock synchronized challenges is actually empty challenges. It has no functionality at all where you can just create a challenge that has to be executed to be solved and then you would push it to the untrusted area to make sure that it's not analyzed. And then detect virtual machines or detect or collect hardware IDs to check on the integrity of the hardware. So once you start to work on these challenges you will find that not all the challenges will have the same strength. Some challenges will be so easy to be broken and some challenges will be much harder to be broken from a reverse engineering perspective. The challenges, the more you're gonna rely on the CPU and the execution and the process itself that you will create challenges that will only execute inside the process this will be the most solid challenges. But if you're gonna create challenges that will communicate with the operating system or resolve or communicate with APIs in the system these are weak challenges that can be fooled easily. So that makes the approach if it's gonna be used by any malware will be weak enough to be analyzed. The only trick will be just to know that a malware is analyzing the execution environment while you're analyzing the malware and to make sure that you wouldn't slow down the malware while it's being executed so it will reveal all its functionality and you can go on and reveal its functionality. So in order to ensure, in order to ensure to secure the challenges we will use morphing techniques where we will have the function that we need to execute and then we have to mutate the function in a manner that the challenge cannot be solved unless the code is executed. And the way we will do that we will have to rely on other morphing techniques not just like the malware morphing techniques. We need to use mutation techniques that will change the functionality of the code not just changing the code structure or not just changing the code semantics. So here's how we can, here's how I'm creating these challenges in the remote metamorphic engine is getting the function and then applying morphing techniques on the function changing the function structure totally and then add a head and a tail to the function and the head is just unused in instructions and the tail is where we will perform response mutation. So every time the function is gonna get executed it will return different response. Here is the sample of the code being executed. Challenges are being generated and being sent to be executed in the untrusted area. And as you can see the encrypted response every time the challenge is being executed it will return back different return value and then the morphing engine will receive the response and then decrypted back to its original value. And the main reason why you need to do that is to make sure that no one can fool the responses or can hook into the response and then just send fake responses. So in order to perform mutation for every single challenge I'm mainly using reversible instructions and the reversible instruction what they do is that when the function returns before the function returns a set of instructions will take the return value and then mutate the return value. So the remote metamorphic engine would generate mutation key and then use that key to create dynamic encryption routine and then insert the encryption routine in the end of the function to encrypt the response. And then once the response is returned to the remote metamorphic engine it will use the same mutation key to use to generate a decryptor that will decrypt the response and return it back to its original value. So here as you can see these are samples of the mutation that we will use to mutate the response. And this mutation is actually as you can see here we're using a set of reversible instructions like addition, subtraction, XOR, bit rotation to the left or to the right and then once the response returns back you will apply the opposite instruction to reverse the response back to its original value. So once you start to use these instructions and as you can see here if you use these instructions in detail any AI or reverse engineer trying to analyze the code will be able to detect that this set of instructions is actually in the tail of the function. So in order to disable that we have to use the same exact instructions in the body of the function. We'll use that in the morphing techniques that we're gonna do to the function and also we'll use the same instructions in the head where we will insert useless instructions. So this how like anyone analyzing the function will not be able to determine the beginning of or the end or the body of the function. So here after performing the morphing techniques that I'm gonna show you now what you need to do is to disable any reverse engineer or AI trying to automated tools trying to analyze the code. You need to disable them from determining the beginning or the end or the middle of the function by mixing the same instruction sets everywhere in the function. So the mutation techniques that we will need to use to resist reverse engineering it's totally different than the mutation techniques or the morphing techniques that malware would use. Malware they use morphing or polymorphic techniques to evade antiviruses. They use polymorphic techniques simply by encrypting the code and then when the code executes it will fold memory and then decrypt and then will be in its original form. And on the other hand they use metamorphic techniques to make the code operate in the same exact way but would look totally different. I use totally different instruction sets. So no encryption is used in metamorphism. Here are some samples of what here are some techniques that are used by malware metamorphic techniques which is like all aiming to change the structure of the code or evading signatures. But what we're trying to do here is not really to evade signature. We need actually to resist reverse engineering. If you make some few changes to the code to make it look different still a reverse engineer can easily determine what's going on. But more importantly because we need to evade artificial intelligence or any automated tools we need to use morphing techniques that will make it expensive for automated tools in terms of time rather than just changing the signature. So the flux mutation goals that we're gonna have is to extend the trust. So the notion is that if you have few milliseconds of trusted execution we need to be able to extend that trust from few milliseconds to cover the whole untrusted area by checking on the integrity of the execution area. Ensure trusted execution. We need to make sure that the challenges that we're creating will not be solved unless the code get executed. And while doing that we also need to disable the code being emulated or instrumented. And we need to also detect reverse engineering or an evade reverse engineering while the code is being executed. So in order to make it expensive for any automated tool or reverse engineer trying to reverse engineer the code we need to have, we need to use morphing techniques that will require time for any automated tool to analyze the code. So I'm using here mainly structure obfuscation. So every time the code is being morphed the structure of the code will be totally different. And I'm actually changing the structure of the code not by making the structure look different but actually by making the structure look the same. So all the functions while they are being morphed though they are different functions at the end they will look all the same. And that's how you can make it harder for any automated tools to determine the difference between the functions. So we'll make it harder for any automated tool to attack the code while it's being executed. So these are actually basic blocks. So we take a function, simple function and then we would morph it into thousands of basic blocks that they all look the same. And these basic blocks will be totally disconnected. There's no edges connecting these basic blocks with one another. And then we will have to use self-modifying techniques where every single basic block when the basic block is executed it will modify itself and then connect to the next block only after it gets executed. So here as you can see reverse engineer will just receive the code as totally disconnected basic blocks. And then only when these blocks start to get executed they will start to connect to one another. And that's how we can make it more expensive for any reverse engineer or automated tool to analyze the code. In order for any automated tool to analyze that code it has to emulate the execution of these basic blocks or analyze it. So it will be expensive at the end of the day in terms of time. And then at the end you wanna make sure that in order for the challenges to be solved it will only be solved if the code gets executed only natively or even on an emulator but not on an instrumentation tool or any tool that tries to understand try to understand the code or break the code while it's being executed. So here sample of these basic blocks. Every basic block here is actually just one instruction. And the morphic techniques that I'm using is actually taking every single instruction as you can see up here this is the original instruction. And then taking the instruction and then transform every single instruction into a basic block. And that basic block will encrypt the instruction and then the only way that the real instruction will appear is by executing the basic block. So the morphic techniques that we can use to resist reverse engineering in the context of clock synchronization that you're allowing the code only to execute for few milliseconds is totally different than the morphic techniques that is used by malware to change the structure of the code or to evade signatures. So here is the list of the techniques that I found to be very helpful. We need to use metamorphic techniques plus polymorphic techniques. You cannot really rely on metamorphism only. You need to use polymorphic techniques. And why you need to use polymorphic techniques because you have to do self-modifying code. You have to generate self-modifying code to make it more expensive in terms of time for any automated tool to reverse engineer the code. And you need to make code structure obfuscation so any AI wouldn't determine which function is which because not all the challenges will have the same strength. Some challenges will be weak and some challenges will be strong. So you need to disable any reverse engineer to determine which function is which. And the way we'll do that is to make all the function look the same and all the function will have the same structure as I showed you. And then challenge response mutation which we talked about in the beginning is that you're generating code that will expire in few milliseconds so you need to actually make the code function differently because if it wouldn't function differently it can easily be faked. So every time we execute the function it should function in a different way and return different return value. So it will transform into a real challenge. And slices permutation is where if you have 100 functions if you send these functions to be executed in the same sequence that will be a weakness as well. You have to morph the sequence of the functions. So every time you send functions to be executed you will have to rearrange the sequence of these functions while being executed. And code size magnification is also very important here because if you're expiring the code in few milliseconds and you're trying to determine if the code is being executed natively or if the code is being analyzed if you're sending just few instructions it will be so hard for you to determine the difference. So you have to magnify the code. You have to magnify it enough that will enable you to determine or you will have a larger difference between if the code is being executed natively or the code is being emulated. And by saying emulated here I don't really mean like emulated CPU but rather instrumentation that the code is being instrumented while it's being executed and then a reverse engineer or an AI would patch or like would tamper the code while it's being executed. So here's a sample. This is just a very simple function that you can define in the remote metamorphic engine. This function is just checking if the debugger is connected to the process. And I've chosen this function because it's just very short and small and enough to fit into the screen. So we'll start morphic techniques by inserting useless instructions, unused instructions. And then after inserting these unused instructions and randomizing every time you would insert different set of instructions this is how you can change a little bit the structure of the code on the first stage. And then here we're also can use expansion so you can replace one instruction that does memory operation. You can change it with a different set of instructions that perform the same exact operation but in a different way and using different instructions. So the first morphing stage you will reach that code. And then in the second morphing stage because we need to make it harder for any reverse engineer to analyze the code we need actually to change the structure of the code and make it much harder for any automated tool to hook into any part of the code. So every single instruction should look totally different and should be moved into totally different position inside the function or inside the binary code. So what I'm doing here is actually I'm taking that code and then inserting a label into every single instruction and then inserting a jump after every single instruction. And by that I'm totally free to move any instruction anywhere because the sequence of execution will always be the same. So this code here that you see is exactly the same as that one. By inserting labels and inserting jumps after every instruction to the next one you are free to move any instruction anywhere. You are free to move any of these basic blocks anywhere. These two are exactly the same. Just doing trun position here. So after doing that and changing the structure of the code and doing transposition and moving every single instruction in a different location we will take every single basic block and then use polymorphic techniques to transform this basic block into self-modifying code. So that basic block, we're gonna take it and transform it into that morphed basic block which is a self-modifying basic block. As you can see here, this is a mutation, a randomly generated mutation key that I'm using to create randomly encryption and decryption routines. And here as you can see this is a randomly generated self-modifying code. So every single basic block which is actually every single instruction will be transformed into a self-modifying basic block on its own. And here's a helper function that just helps to allocate the location of the code and memory. And as you can see, these parts actually will transform into self-modifying and it will modify itself while it's being executed to unfold into the original instruction or the original basic block. And also the jump that you see here, the jump that is after the instruction is also gonna be morphed. So any AI or reverse engineer trying to understand which instruction will be next, he wouldn't be able to know or any automated tool trying to analyze the code, they wouldn't be able to know which next instruction, which instruction will be next until the code is executed and the code self-modify and decrypt itself and then we'll jump to the next basic block and then the next basic block will self-modify and decrypt itself and memory and then we'll reveal the next instruction and so on. And we need to do that because we only have few milliseconds for the code to be executed and we wanna make it very expensive for any automated tool to try to solve the code within that allowed timeframe. Like these techniques might not be that interesting if you just give the reverse engineer as much time as he want to reverse engineer the code but in the context of the code has to be executed in few milliseconds, these techniques are very helpful. And for sure it's gonna be helpful if you would add multiple layers of self-modifying. So at the end, the code will be morphed into these structures. Here as you can see, these are like three basic blocks. So every single instruction will be transformed into these randomly generated basic blocks. Here's a sample of the code being executed and self-modifying just to give you a feel. All the basic blocks will end up looking exactly the same and while being executed, they will change and they will connect and they will unfold the memory. As you can see here, this code will change now and reveal that instruction. Sorry. So these morphic techniques, if it's used normally to just to morph a piece of code it wouldn't be helpful at all. But the point is that the code will have only few milliseconds to be executed and to respond back to the remote metamorphic engine with the right response. So here as you can see, these are four different generation, four different samples of the same function being morphed. And every time the function is morphed, it will respond back with a totally different return value and then the engine will receive the return value and then decrypt it and return it back to its original value. And as you can see here, the response time is six milliseconds. I was here connecting the trusted and untrusted area, the remote metamorphic engine and the client on a same machine and a local host. If you're gonna connect it remotely it might be much longer than that. And here as you can see, every time the code is being morphed it will result in a totally different code size. So the code size will be different, the structure will be different and the instruction sets that are used will be totally different. As you can see the first time the original code actually if you just assemble it it will be around maybe 30 or 40 bytes. And here these 30, 40 bytes of the original code are being transformed into 15,000 bytes or around like 2,000 or 5,000, five or 6,000 instructions. Now, in order to determine if the code is being really executed or if the challenge really been solved without being tampered or without being reverse engineered or without being emulated or instrumented. If someone just hooked into the challenge response protocol and then tried to just set the return value into any value and trying to fool the protocol the remote metamorphic engine can easily detect that. So any immunity system in nature actually is based on knowing the self. So the remote metamorphic engine actually tries to learn and know about the self by comparing the responses or the challenges that are returned comparing it to the previous execution. So we have the same function, we would execute it maybe five times and every time the code will look different the structure will look different and the function will actually include totally different functionalities because it's mutated. And every time the function get morphed and being sent to be executed it will return a different return value but then the engine will use the randomly generated decryption or mutation key to solve the challenge and return it back to the original value. So as you can see here if anyone tries to tamper the code while it's being executed or try to tamper the response while the response is being returned by just simply hooking into a response and then sending, faking the response the engine can easily determine that by comparing the responses with the previously returned responses. So as you can see here these are seven different mutated functions of the same exact functions every time the function is being sent to be executed it will return back totally different value and then all these values should decrypt back to the same exact value. So the remote metamorphic engine can determine if there's any tampering attempts if the decrypted value will look totally different will result in a different value than all the previously generated code. On the other hand the engine can be able to determine if the code been executed natively or in a healthy way compared to being instrumented or being analyzed based on time. So we, in this challenges for example we're allowing 500 milliseconds for the code to be executed and to return back and then the engine can determine if the code is being analyzed or the code is being emulated or being instrumented if the response returns back in a higher time frame than the allowed time frame. So the way I actually said the allowed time frame I'm actually doing it manually by executing the code natively and then measuring the code the execution time of the code and then would allow because every time you send the code to be executed will return back in a different time frame so you have to enable a time frame because the code will keep on fluctuating. So I'm mainly allowing the average of the execution time multiplied by three to five factors. And this way it's like it's based on the assumption that if the code is being analyzed there must be at least two or three instructions being inserted to analyze the code or there must be at least comparison instructions. So as you can see here on these are all the same functions these are all same generation seven different generations of the same function every time it's being executed it will return back in a slightly different time frame. The first time return back in 45 milliseconds second one 65 milliseconds and then in this sample I'm allowing only 500 milliseconds for the code to be executed and then once the return value returns back in a higher time frame the engine can determine that the code is being analyzed and then it can act in a different way. In case of a malware using these techniques perhaps that would be the most tricky part of it. A malware wouldn't really be able to change the behavior because the malware will still have to communicate with API who still will have to communicate with the operating system. So still you can determine or you can signature the malware based on behavior analysis but the tricky part is that the malware can be analyzing the code or analyzing the reverse engineer while the reverse engineer is analyzing the code or the malware can analyze the execution or the instrumentation environment while the instrumentation environment is analyzing the malware. So that might eliminate or just the tricky part once you know about it it's gonna be so easy to bypass it but if you don't know about it a malware can evade the analysis and maybe act in a totally different way and in this case the functionality is even not stored in the executable file that you're analyzing all the functionalities are stored remotely and then you wouldn't be able to go to the next step in analyzing the code. So it's just this trick that the malware can use to evade reverse engineering so it's the technique is not really that wouldn't really add much to malware evading malware as much as it can be used for defensive approaches trying to eliminate the code from being reverse engineered or pushing integrity checking functions to check on the integrity of the code or the integrity of the environment you're executing the code in and this is actually the most this is the most challenging part is that how can you really trust in any nodes that you are connecting to how can you trust on the integrity of things in your network? How can you trust the integrity of the processes that are running in your network where you are relying on static code? If you're relying on static code and that static code can be hooked can be patched, can be tampered in memory and then you wouldn't really be able to determine or trust its input or output. So the remote metamorphic engine can be used to check on the integrity of connected things and to ensure that things cannot be reverse engineered or it will be much harder to be reverse engineered or get tampered. On the other hand, execution time also you can determine that the code is being analyzed or the code is being faked if the response returned back in a lower timeframe than the allowed timeframe. In this case, maybe someone is trying to fake the responses and executing it in a much faster CPU or performing any tampering attempts that would result in the code being returned very fast. So these two variants would help a lot to determine if the code is being executed natively or it's being tampered or it's being reverse engineered. So I'm running out of time. So if you have any question you can feel free to email me. I'm gonna be using this email address for the coming couple of weeks and with that, it's been an honor to be part of your day today. Thank you for joining me. Have a good day. Thank you.