From Zero to Zero Day
CC Attribution 4.0 International:
You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal purpose as long as the work is attributed to the author in the manner specified by the author or licensor.
Jacobi, Jonathan
In this talk I will share my story of how in a little over a year, a high school student with almost zero knowledge in security research found his first RCE in Edge.
 Jonathan, wonderful good morning. You're going to tell us about the last 12 months and what you have done. So your talk is called from zero to zero day. This is Jonathan J from the MSRC in Israel, a division of Microsoft, and the stage is yours and that is your applause. Thank you. So hi everyone. My name is Jonathan and I'm a security researcher in MSRC in Israel. On my free time I play CTFs with Team Perfect Blue and I also do research on my own. I'm just an 18 year old and I started doing security related stuff last year. So the first part of this talk is going to be about what I learned in the past year and how I learned it. So if you're just starting off in security and you want to learn what to pick up what I did and learn from what I did, this could be helpful for you. And for the more experienced security researchers in the audience, the second part of this talk is going to be about a zero day I found in Chakra, a jit type confusion. So we're going to dive deep into that. And even if you're just starting in security, a basic understanding in programming is probably going to be enough to follow up. There's going to be a lot of code, but it's fine. It's not too complex. And last but not least, we're going to finish up with a demo, hopefully working one. So yeah, let's start off. So why vulnerability research? Vulnerabilities are for me some sort of riddles. There is some sort of very challenging riddles that we have to find some flaws that the developers did not consider. This is very challenging and very interesting for me at least. And I found it awesome to do. So what is a vulnerability? There are a lot of definitions to it. And when you want to understand something, you started your job on Wikipedia. There are a lot of definitions. As you can see, it's some more weird, like the probability that an asset will be unable to resist the actions of a threat agent. I don't know what that means. Yeah, for me, a vulnerability is some sort of flaw in a program that allows you to change the intended flaw of the program. That's what I consider as a vulnerability. And the definitions we've just seen do not really tell me how I find them. So how do we find vulnerabilities? So when I started, I had some knowledge in programming. I wasn't the best developer out there. I was just a decent one. An under good level in C and assembling and always internals to understand how things actually work in Python to actually write some code. So I wasn't the best developer, but I had some knowledge. For example, I read the C programming language, which is a great book, and we'll give you all the C knowledge you need to know in order to actually get into vulnerability research. The next thing I did was to explore myself through vulnerability research basics. So I read online about some basic vulnerabilities like classic stack buffer overflows, like integer overflows and so on. And then I tried to practice my knowledge. And that was through war games. War games are some challenges that are offline and you can try to solve them. They're security-related challenges like you have to find some vulnerability and exploit it. So that's what I did next. And at first I failed, like, badly, but it's okay because I read the solutions, I read the write-ups, and I learned how to actually approach the solutions and actually how to solve them. So it comes with time and it's okay to fail because we all do. The next thing I did was to explore myself through CTFs. Now CTFs are capture the flag contests and they're basically competitions where you have to solve some challenges and compete against other teams. This is a team effort and you play with other players, your friends. And this is how I met my teammates. So we met through CTFs through IRC and we played together. And sometimes we fail, like, miserably, but sometimes we actually do well. And we got to qualify to some finals and we actually got to travel the world because when you qualify to finals, they sometimes pay for the trip. So we actually got to see some cool places and, yeah, that was awesome. And I actually think CTFs are a great way to get into security. So the next thing I did was I dove into the dipwater. So once you know the basics, it's important to not stay there for too long. You have to expose yourself to harder things. I was afraid to see and try to solve the harder challenges at first, but with time I managed to understand that nothing too bad would happen even if I fail. So I try to solve challenges and I failed, but with time I managed to pick up the tricks and the ways to solve the harder challenges. And I think this is very important to not be afraid to solve things because even if you fail, you learn from other people's solutions and it's fine, it's totally okay. A great tweet by LiveOverflow, which also should be in the audience or in the conference, and basically says what I really believe in, which is move away from the basics as soon as possible and expose yourself to harder things which you don't understand. This will make you understand things that you thought you understood, but you actually didn't. And also it's important to learn from different resources. This way you learn different tricks and different ways to approach problem solving, and it's very important in my opinion. So, yeah, LiveOverflow also has a great YouTube channel which I also watch and he talks about vulnerability and security research, and I really recommend watching his videos as well. So after I had some knowledge in CTFs and wargames, I practiced and I practiced a lot. So it's really important to practice and solve challenges on your own because this way you actually get to pick up those tricks that you have to understand in order to solve some challenges. So some vulnerabilities have patterns in them. And the way you catch on those patterns is by seeing them quite a few times, and a great way to do that and to pick that up is by solving a lot of challenges. This is where I also expose myself to actual real-world vulnerabilities. I did that through a lot of websites. There's Project Zero, Bug Shackery, which you can read the vulnerabilities about exploits and so on. So this is where I also expose myself to real-world vulnerabilities. And I also realized that there is a great connection between vulnerabilities in CTFs and in real-world. So vulnerabilities are essentially bugs and they exist both in CTFs and in real life. And the main problem that you think you have when you actually try to do real-world research is that you think that the code base is huge and stuff like that. But even if the code base is huge, bugs are still out there. So don't be afraid to look at it because the odds of vulnerabilities running out just when you're starting to look at something are very low. So you can start to try and solve things. And by solving things, I mean looking at actual code bases. After doing some practice, I realized that how do we find vulnerabilities? And once we actually start and repeat the process of solving challenges and practicing, we notice that vulnerability research is about identifying bugs. And we do that by looking at code. So looking at code is essentially understanding the code and we have to find vulnerabilities in it, right? Because we want to find them. And that's through actually auditing the code. And that comes with practice. So practice is really important in vulnerability research. And once we master that, even though I'm not a master and I'm not nearer that, I still think that practice is important. So how do we actually find vulnerabilities? So as I said before, vulnerabilities have patterns in them. And patterns are something that you catch up with time. And I wasn't doing that for so long. I'm doing it just for a year. So how did I catch it up? That was basically through a practice. As I said before, practice can cover the time that you're not doing that for so long. So I managed to notice that vulnerabilities have patterns in them. For example, programming errors like integer overflows and sign this issue. These are bugs that actually exist because people make mistakes and we're all humans. And humans make mistakes. We're not perfect. A great example for such a mistake is the following code. So on the third line, we have an integer overflow, which a lot of developers know about the existence of this vulnerability, yet they still make this mistake. As I said before, it's because we are all humans and humans make mistakes. So don't be afraid to look at actual code bases because these kind of bugs actually exist in actual code bases, not only in CTFs. So don't be afraid to look at actual vulnerabilities. You can find simple ones as well. So there's a great difference between CTFs and real-world vulnerabilities. In CTFs, usually when you find the vulnerability, you know what you need to do with it. You know what you have to progress with it. You know what you need to exploit. How you need to exploit it. So sometimes you have a stock buffer overflow, so you need to override some variable, the return address, or you can override a function pointer. Mostly when you see a vulnerability in CTF, you know what you need to do with it. In real-world, you usually have a set of weird states, a set of primitives that you have. Primitives are essentially capabilities we have as attackers. So you usually have some primitives that we can chain together to do something greater and that can later lead to vulnerability. And as such example of that will be the vulnerability I found in Chakra, which is basically a bunch of primitives together, chain together to make an actual vulnerability. So this is basically everything I knew about vulnerability research and security research before I started looking in Chakra. So yeah, let's dive into some JavaScript. So JavaScript engines. I didn't say I learned JavaScript because I didn't. And JavaScript is a very readable language. Once you have learned a few programming languages, reading JavaScript would probably be more smooth, a more smooth process. So doing that would not be too hard. Now JS engines are basically what's capable of running the code that you write as a JavaScript developer. They have a lot of parts to them and the most important one for us is the JIT compiler. Now JIT stands for just-in-time compiler. What it essentially does is when some function gets hot and it's being called a lot of times, it compiles this function to machine code to improve performance. Now this compilation is-this JIT compiler is also incapable of-is also responsible of doing optimizations for the code. It has a lot of assumptions about the code and doesn't want assumptions to be broken. So we'll see just how that works later and about JIT compiler vulnerabilities. So about JavaScript basics. So JavaScript is a dynamically-tipped language and it's fairly readable. You can make arrays in different ways. You can have arrays with different types of elements and you can have console logs to actually print stuff. So JavaScript has prototypes. Prototypes are essentially something that you can-someway you can inherit features from other objects. So for example, in our case, we have a parent object and a child object. So the child object, if the prototype of it is the parent object, then it will inherit some features. In this case, the properties X and Y. So this actually is fairly important to us in the vulnerability I found. And you can modify the prototype by accessing the proto property. So JavaScript also has something called proxy. Now, our proxy is an object that can wrap another object and redefine basic operations like getters and setters. So in our case, we redefined the getter. So we redefined the getter in which we're supposed to get X to be 1, 3, 3, 7. But due to the fact that the proxy redefines the behavior, what happens is the getter handler is actually being called. So we redefined the basic operation of the getter. And in this way, we actually tracked the function called to the getter of this object. So JavaScript has arrays, as we've seen, but Chakra has arrays with types. So the first tab we're going to talk about is called the JavaScript native interay. And it is the way Chakra calls integer array. Basically, it's an array that stores integers, which are four bytes per element, and they're basically integers. JavaScript also has a native float array, which is essentially floats. And unlike the C definition for float, this float is actually eight bytes. And they store floats in their eight byte pile elements. And we have JavaScript arrays, which are objects, they store objects, essentially pointers, and they are also eight bytes per element. So let's see about how we can convert between the types. So if we start off with an array of integers and we add a float to it, what happens is we convert the array to a float array. And if we add an object to it, the conversion that takes place is a conversion to a JavaScript array. If we have both floats and integers, the outcome is a float array. So if we have both floats and integers and objects and mixed array, what happens is this array is considered to be a JavaScript array. And the most weird conversion that takes place is this one. And you usually don't see this when you see about conversions in JavaScript engines, but this is very crucial for our talk. So when you have two arrays and you assign one of them to be the prototype of another one, the prototype one is directly converted to a JavaScript array. Now, this is going to be very important to us later, so keep that in mind. And this basically, the conversion takes place right when the assignment takes place. So let's see about how array layout actually looks like in memory. So when we have an array, for example, in this case, a JavaScript array, it has some flags which indicate some things about the array. And it's called array flags. We have the length of the array and we have the head. Now, a head is a pointer essentially to a segment, and a segment is a contiguous memory region which has elements in this. So it has some of the elements in the array and it stores another pointer to the next segment. So what comes out is essentially a length list of segments that start off with the head pointer in the JavaScript array. So let's see how that actually looks in memory. We have this simple example where we have two values, in this case integers, and we have an array with two values. So let's see how it actually looks like in memory. So in red, we have the JavaScript array properties. So we can see the initial array value, which is the array flags value. And we have in green, we have the actual segment. So we can see the segment has a length and a size. And in blue, we can actually see the memory layout of the segment. And if you notice on the right bottom, we have the two elements we defined. So we actually can see this in memory and this is how it looks like. So what is the array flag that we're just talking about? So array flags basically some flags that indicate some things about the array. In our case, it's defined as an enum. And the interesting field for us is there has no missing values flag. And so if we, for those of you who noticed, the initial array value that was defined as our array flags is actually composed of two different flags. It's composed of the array object array flags tag and it has no missing values tag. So the first one is not really important for our talk. So we'll focus on the second one, which basically means that the array doesn't have any holes in it. Now, what does the whole mean? So we can create an array and have some values between the, like we have, in this case, we have three elements, but the middle one is missing. So it's important to say that the values I put there are the way they're represented in memory is by those constants. And I chose them because it will be easier for us to see them to actually notice them when we see the memory layout as we've seen before. So as you can see, this one has a hole and the array flags doesn't have, there has no missing values on, which means we have a hole in the array. And things actually make sense. But when we look at the memory layout, we see some weird thing. So as you can see, we have the dead beef and the four one, but between them where the missing value where the hole was, we have some magic constant. Now, it makes sense that this constant would represent a missing value or a hole in the array. But as we've seen before, there's already something that tells us when we have holes in array. It has no missing values flag. So we know that there is one way to indicate holes in array and it's the flag we've seen before. But it seems like there is another way to tell if there's holes in arrays. So that's kind of weird. That raises a lot of questions. We have, basically have here, potentially can have here two things that indicate, should indicate the same state. We have the flag of the array, which is the has no missing values flag. And we have the content of the array, which we've seen before and is the FFF constant. So if we can, what happens if they're like mismatched? What happens if we can do that? It has no missing values to be set to true and therefore says that we don't have any holes, but we actually have a missing value in the array. Also, we mix some sort of mixed data and metadata together because if the constant is being used in a control flow, that could be interesting for us if we can fake it. And it actually is very interesting and we can, it turns out that we can actually fake a missing value. And this was a vulnerability find by Loki hard and sort of my bad and got CVE for it. So basically what they did is they faked a missing value by putting the constant we've just seen before into a float array. So we could beforehand, the value was not the same one that we've seen and it was something that you could actually represent as a valid float number. So you could actually put it in and this could be turned to a vulnerability. Now this would mitigate it in a few ways and the constant changed so you couldn't represent it as is. And what also happened is they added a few more checks to harden things. So we'll talk about how this can be turned into, this weird state can be turned into a vulnerability and how we can actually exploit it. But before that, we actually want to talk about something interesting. So as I just said, the exploit, the vulnerability sort of my bad and Loki hard found was in native float array. So apparently JavaScript arrays don't store floats as native as actual floats. They first do something called boxing. So they first XOR these values with this constant you can see here. So before you put the float into the array, this value is being XORed with the float you're trying to put in. So the question that comes up next is whether we can actually have missing values in JavaScript array. And if so, does the constant change because as we see here, we change the way we represent values and therefore we can represent new values. So theoretically, we should be able, theoretically, the engine should change the constant. Otherwise, we could potentially get there and actually represent it. And it turns out that the constant does not change. Therefore, we can actually represent it by first boxing it. So what we did is we first boxed it and XORed the constant with the FFF we've just seen here. So that in turn gives us the ability to have the constant as the original constant value. Why are you wondering? Because this property of XOR, so when you have XOR of three elements and two of them are the same, then they cancel out and they give you the other one. So if we have two of them to be this value and they want them to be the magic value, what happens is we actually get in turn the magic value and therefore we actually can represent it. And this is exactly the vulnerability I found, which is truly heavily relies on the fact that we have basic understanding of JavaScript engines. This boxing is one of the first things we learn when we learn about JavaScript engines. So we use this idea of boxing things and turned in supposedly unexploitable state to be a vulnerability. So basically what we did is, as you can see here, we first box it and then we put it into an array that is a JavaScript array and not a float array. So how do we turn this weird state into a vulnerability? So what happens first thing we need to understand is how vulnerabilities look like. So JIT stands for just in time as I already explained. JIT bugs are usually what we see today, are usually type confusions. So type confusions are essentially confusion between two types that the program thinks something is of one type but actually of the other type. So JIT type confusions, the most common ones are due to side effects. Now side effects are some things that take place in the JIT code. Now JIT code I refer to the machine code that is emitted when the JIT compiler compiles the hot function. So in our case, the usual case I mean, what happens is you have some function, let's call it foo, which changes a type of some object, let's think of it as an array, and then the function that got jitted doesn't know about this conversion. So it thinks that everything is still the same and nothing has changed, so it's going to work with it as it did before. And this leads to a type of fusion. So let's look at an example. So first thing we do when we have such vulnerability is we make the function hot. As I explained before, to make the function be compiled into machine code, we first have to make it hot. We do that by calling the function a lot of times, and this can be done by a simple loop. And the next thing, okay, so also it's important to say that the JIT compiler has a lot of assumptions. These assumptions are on some types, on some things that take place during the execution of the function. So for instance, when we call foo, and it thinks that foo doesn't do anything, it knows that it doesn't need to check things again, because if foo doesn't change any state of the program, why would we check something again? Because that would make the function way more slower, and JIT compilers are all about making things faster. So it has a lot of assumptions that are there to improve performance. So once we do that, and we have something that breaks the assumption, we can eventually get to a weird state, weird states, and from there we can turn this into a vulnerability. So in this case, once we call the function, and we make it be compiled into machine code, and we actually call the function again, but this time we want to trigger the type confusion. So what happens here is foo will change the type of the array, and therefore the assumption that foo doesn't change anything breaks. That gives us the ability to have the JIT compiler think that nothing happened, but we actually change something. This is very important because JIT compiler bugs are very complex sometimes, and this one is not too complex. What happens here? We essentially made the JIT compiler forget to not know about the conversion that happened. If foo converts array to a JavaScript array, something happened, but the JIT compiler doesn't know about it. So it's very crucial to understand that, and the assumption essentially breaks because we change something in the function that we call. So this is basically about JIT type confusions. We essentially have some function to make it hot, and then we have another function which gets emitted to machine code, and in that function we call some other function which will have side effects. Side effects are things that the JIT compiler is not aware of. So let's actually move on to my vulnerability. To turn the mismatch between the flags, if you remember we had a flag that indicates it has no missing values, and we also had the array content. So once we have the mismatch and we actually managed to fake a missing value, to turn that into a vulnerability, it's quite interesting. So the way Luckyheart and so on my bed did that was basically through calling this function concat, which relied on has no missing values and also relied on the content of the array. This allowed the mismatch to get us into a weird state which shouldn't happen because in normal case, why would we have the missing values and the content of the array to have not a mismatch between them? So in our case, in Luckyheart's case, they got the array flags to be mismatched with the content of the array, and they called concat. So the way concat was called was once we make a fake array, let's call it buggy, and the buggy has a fake missing value in it, but the flag that indicates if there are any missing values is not set to true. So we set this fake array and we call concat with it. Now the first thing that happens when we call concat is we get to this code. Now this is the part where we have a lot of code. Now bear with me, it's not too hard, it's just a lot of code. So first thing we get is we get to this function. This function essentially is called concat args, and a item in this case is the buggy we have. We invoke isFillFromPrototypes and we would essentially want fillFromPrototypes to return false in order to get into that if statement. Now isFillFromPrototypes essentially checks the following things. First thing it checks is that there's only one segment in the array and that check is through the fact that it checks that the next segment of the header head segment is null. And another thing that it checks is that the length of the array is equal to the length of the segment. Therefore, the segment is the only segment in the array. So this is the first thing that it checks and the second thing that it checks is that the flag has no missing values is set to true. Now this can be easily bypassed because we've already seen that we can fake a missing value and set the flag to true. By doing that, we can actually get isFillFromPrototypes to return false and we can actually successfully get into the if statement. Next thing we reach is this else statement which calls copy array elements with our array as an argument. As you can see, it's the p-artm array. Once we reach copy array elements, it's essentially copying an internal function and which does the following. Now this part is very interesting for us. So first thing that happens is they make an enumerator out of the array. An enumerator will enumerate every single element in the array and will increase the counter every time it engages a new element. Now let's see how this enumerator is actually implemented. So this is a code for the moveNext function and as we can see on the red box, when we encounter a value that is actually a missing value, we actually skip it. So what happened here is that every time we got into the, sorry, every time we got into the while loop and there was a missing value, it skipped it. Therefore, the count that we had there is not equal to the amount of elements in the array. This is because every time we get, we find a missing value, it doesn't increase the counter. And therefore, we can actually get into this statement and call internal fill from prototypes. Now this also is a very interesting function for us because it basically, first thing it does is loop through the prototype chain. Now if you remember, the prototype chain is basically some object that we inherit features from. So we can have our prototype to have another prototype and therefore we forge a prototype chain. So this thing, this function first loops through the prototype chain and then what it does, it calls this long function name with our prototype as an argument. So if you remember, prototype is actually directly converted into a JavaScript array when assigning a prototype. Remember from the conversion slide. So essentially what happens here, it knows that the prototypes should know that a prototype is a JavaScript array and we loop through it. So every time we get in here, what happens is the prototype is being passed through the object argument and is then converted to a JavaScript array and then it is passed through the insurance native array. So as you can guess, insurance native array basically ensures the array is not of a native type. Therefore, it's a JavaScript array and this is the interesting part for us. So what we've seen so far is, so to quick recap, okay, so what we have happened so far is we managed to get concat to convert some array into a JavaScript array and there is nothing that indicates this conversion. It's important to know that once we fake an missing value and we call the prototype, we call the concat, all of the prototype chain is being converted to JavaScript array. Now, as we've seen before, prototypes in the prototype chain are not native types, so we shouldn't be able to put there any type because it should be JavaScript array. So it turns out that we actually can do that. So remember proxy. Proxy can wrap an existing functionality and redefine its functionality. In our case, we can, and it's pretty fun that we can do that, is we can redefine the functionality of get prototype. So what this allows us is whenever we call get prototype, we redefine a behavior. So remember that prototype chain? So what we do here is basically we redefine this prototype chain loop and by doing so, we allow us to return to call an arbitrary function. Now, if we call an arbitrary function of our choice, for example, in any function, what happens is the JIT compiler checks if this function is marked as having side effects or not. If the function is marked as having side effects, the whole function that got jitted is marked as having side effects and it's not good for us because we don't want the JIT compiler to know that some side effect took place because we want to get a tag on a fusion. So what we do is we use an existing function which is called value off and is marked as not having side effects. This basically allows us to return any object in the prototype chain by calling get prototype. So let's see an example for that. This is the proof of concept code and we'll go through every single part of it and I'll explain everything. So first thing we do, as I explained before, we loop through this first loop to make the function hot and therefore it gets compiled to machine code. Next thing we do is redefine two arrays. Bug array, which is going to be the array we fake a missing value inside of it, and an array which is called a-r and is going to be our target for a tag confusion. Now it's important to notice that the target which will be converted to a JavaScript array is not buggy, it's array. So notice that. Next thing we do is we redefine the get prototype off. So get prototype off is basically the get prototype function and by redefining it to be value off, whenever the function get prototype is called on a-r, we redefine the implementation and get it to return the a-r itself. This allows us to return a-r whenever get prototype is called. This is huge because if we define a proxy, what happens is we essentially made the get prototype loop to return us a JavaScript native flow array. So if you notice on the line before, whenever get prototype is called, we call value off. And if we loop through the prototype chain and value off is the get prototype, the return value is going to be a-r, which is a JavaScript native flow array as you can see here. This allows us to essentially have a native flow array even though there shouldn't be any of those in the prototype chain. Next thing we do is we redefine the missing value constant and by doing so, we still have the has no missing values flag for buggy set to true. This is the interesting part because this is where the mismatch takes place. So we have a missing value but we don't have the flag set to true. So next thing we do is we call the jitter function. So this is the key part here. So when we call this function, what happens is this function is, this code is being ran. So first thing we do is we define the temporary and we define the first element of the array object, the array array to be a float. This makes the jit know that whenever it uses array and no conversions happened, it still thinks it's a JavaScript float array. And then we call the temp, we call the concat function and we give it the buggy as an argument. This will essentially loop through all of the prototype chain and one of the prototype in the prototype chain is the proxy as we can see here. And when we call the get prototype for the proxy, we get the aorr and when we call the get prototype for the aorr, we get, sorry, when we call the prototype for the buggy, we get proxy. When we call the get prototype for the proxy, we invoke the get prototype for aorr, which will return us the aorr. And therefore, what happens is aorr is converted to a JavaScript array whenever the prototype chain loop takes place. Afterwards, in the last line of the jit function, we basically write some value into the array and therefore make write a float into some array that was converted to JavaScript, a float JavaScript array. So array is the same array that is in the prototype chain and therefore we still overwrite, we overwrite some pointers with a float, which in our case is one, two, three, four. So last thing we do is call console log and we crash because we have a object which is defined for this address and it's not mapped. So this is essentially the proof of concept code and what we do here is fairly simple. The first thing we do is we make a function hot and by doing so, we make it be compiled into machine code. Next thing we do is we redefine the behavior of the get prototype function and we allow it to return an arbitrary object whenever we call get prototype. Then we invoke the jit, then we fake a missing value and we call the jit, jitted function, which in turn allows us to convert in all of the prototype chain to be some JavaScript arrays. Now, even though most of them are JavaScript arrays, one of them, the array one, is not because we redefined the implementation for get prototype. This in turn allows us to have a JavaScript float array converted to a JavaScript array and we can still act with it, still operate with it as if it were a JavaScript float array and therefore the last line here allows us to write a number as a pointer and therefore we have what we call in the beginning a type confusion. So we essentially had a type confusion between a float and a pointer because the jit did not understand that some conversion took place. Now, this is essentially debug and to exploit it, we actually had to do a few things. First thing we did was we targeted a fake object primitive. Now, what is a fake object primitive? The thing we've just seen was that we faked an object at an arbitrary address. In our case, it was faked in data one, two, three, four as seen here, but the thing is we actually can fake an object at an arbitrary address. The object we decided to fake was a data view object which has some fields of it which are a pointer you can read from and write to. This in turns gives us a read write primitive and from there what we did was to use a known trick which is to find a stack address and override it to run to execute our code in Rope. So to do all that, we used a great library which is called Ponges and we had to fix a few things but it did most of the work. What it did was basically do what we explained. It requires us to have a read write primitive and then what it does is to scan the program to find a stack pointer. From there, it overwrites the stack with whatever we want because we got an arbitrary read write primitive. And then what happens is we can execute code in Rope. Now, we first exploited it on edge but to actually execute our own code. When we actually executed our own code, we executed it in the sandbox context. So it wasn't any nice for a demo because it would not allow us to pop a calculator or something. So what we did was we actually compiled Chakra Core for Linux and we exploited it for Linux. The exploit for Linux was similar because we still overwrote this stack with some things we controlled as we had a read write primitive. And from there, we basically executed code in Rope. So that's basically what we did to get a POC, a crashing proof of concept to actually execute our own code. So let's see a demo for that. Let's hope it actually works. One second. You can see my screen. Sorry. Second. Oh. No. So let's hope everything works. So basically, we have Chakra Core compiled for Linux and this is how it looks like. So to execute it, we actually have to give it a source file. So in our case, we give it an exploit. So notice this is going to be very fast exploit because Chakra is super fast and it has super optimizations because why not? So it's going to be super fast. So beware. And therefore, we have a shell. So to prove them, we actually got code execution. Let's run a calculator. Oops. Oops. And we have a calculator as well. So we actually can actually execute code and everything works as expected. So a few things about let's get back to the talk. Oops. Wait. Sorry. So what we've seen here was basically we had some code. Okay. So the demo shows us a very simple thing. We first did what we did in the proof of concept. We got the JIT compiler to not know that some side effect took place. This side effect in our case was some conversion that allowed us to convert some array into a JavaScript array and then in turn allows us to write floats as pointers. And this basically gave us a top confusion primitive which we exploited by faking an object which gives us a read write primitive and therefore we continue to write our own. So I really hope that my talk was helpful for those of you who want to get into security research. And I hope it was entertaining for those of you who just wanted to hear about the technical stuff. And I want to thank everyone who helped me out with the talk and with the slides. And for you to come for you for coming here and seeing my talk. I know there's a great competition and there's way cool talks probably, but you still came to mind. So thank you for that. And yeah, thanks for coming. Jonathan, do we have some time for questions? Yeah, sure. Okay. Was it the first talk you held for such a big audience? Yeah, I never spoke more for more than ten people. A shock for Christ's sake. That was good. We have a couple of microphones over here. So if you have questions for Jonathan, please line up over there and shoot the questions. We could start with one from the internet. Do we have one? No, we don't. Okay. Don't we have any questions over here? People are good. Where's here? Number five, here we go. So thank you for the excellent talk. I was surprised to see that the JavaScript engine for Edge was open source. So is this something that the moving towards more open source components? I think that the open sourcing is cool, but I don't know how actually how the future will be if we will open source more things. I actually don't know. Then we continue with microphone number two. Hey, thanks for your very interesting talk. So obviously, Jet Compilers are a pretty in-depth subject, so my question is, when you got started with all of this, how much prior domain knowledge of Jet Compilers and dynamic language runtimes did you have, and when you then got to Chakrakor, how much did you have to just read through the whole thing before you could actually start looking for a vulnerability in there? So what I did was when I started to get into Chakra, I read a lot of by reports, and I've seen that there's plenty of vulnerabilities there. So I read most, not every one of them, but I read a lot of them, and I tried to understand what they do and why they exist, and this is basically what got me into looking for my own bug as well. So basically, I had understanding of JavaScript engines in general, and then I read about how Chakra works and read about specific vulnerabilities in Chakra. Thank you. And over to microphone one, please. You mentioned the process in Windows runs in a sandbox, so you weren't able to put up a calculator. Would this stop the exploit in the wild, or is there a way this could be dangerous even in the sandbox? So to actually execute your arbitrary code, you have to escape the sandbox. There's things you can do with the sandbox, but they're pretty limited. I didn't exploit, I didn't find a sandbox escape yet, I hope I will find one day, but to actually execute interesting stuff, you need to do a sandbox escape because there's plenty of mitigations nowadays, and specifically for Edge does a lot, and I don't know specifically if you could do anything interesting without a sandbox escape, but you could actually run code, so you could do stuff, I'm not sure if it's interesting enough to actually do something in the wild. And over to microphone number two. Great research and work. How much time did you work on it? So I worked on Chakra for about, I think, from July, I think. When I started, I started reading about Chakra in July, but I had some knowledge about JavaScript engines before, for about like two months, I think. So not enough, I mean, the more important part for me was to actually read about examples and see examples, more than spending a lot of time on it, like, I think it comes with practice, as I explained before, what I do believe in is practice, and my practice was through reading a lot of research and a lot of internal stuff about Chakra. So it's about, I think, from July and maybe two months before. All right, nobody's queuing up on the microphone, so have I overseen someone? Jonathan. Excellent. Thanks. That's it. That's your applause. That was Jonathan Jay from Zero to Zero Day. Thank you very much.
 Jonathan, wonderful good morning. You're going to tell us about the last 12 months and what you have done.  So your talk is called from zero to zero day. This is Jonathan J from the MSRC in Israel, a division of Microsoft, and the stage is yours and that is your applause.  Thank you.  So hi everyone. My name is Jonathan and I'm a security researcher in MSRC in Israel. On my free time I play CTFs with Team Perfect Blue and I also do research on my own.  I'm just an 18 year old and I started doing security related stuff last year. So the first part of this talk is going to be about what I learned in the past year and how I learned it.  So if you're just starting off in security and you want to learn what to pick up what I did and learn from what I did, this could be helpful for you.  And for the more experienced security researchers in the audience, the second part of this talk is going to be about a zero day I found in Chakra, a jit type confusion.  So we're going to dive deep into that. And even if you're just starting in security, a basic understanding in programming is probably going to be enough to follow up.  There's going to be a lot of code, but it's fine. It's not too complex. And last but not least, we're going to finish up with a demo, hopefully working one.  So yeah, let's start off. So why vulnerability research? Vulnerabilities are for me some sort of riddles. There is some sort of very challenging riddles that we have to find some flaws that the developers did not consider.  This is very challenging and very interesting for me at least. And I found it awesome to do.  So what is a vulnerability? There are a lot of definitions to it. And when you want to understand something, you started your job on Wikipedia.  There are a lot of definitions. As you can see, it's some more weird, like the probability that an asset will be unable to resist the actions of a threat agent.  I don't know what that means. Yeah, for me, a vulnerability is some sort of flaw in a program that allows you to change the intended flaw of the program.  That's what I consider as a vulnerability. And the definitions we've just seen do not really tell me how I find them. So how do we find vulnerabilities?  So when I started, I had some knowledge in programming. I wasn't the best developer out there. I was just a decent one.  An under good level in C and assembling and always internals to understand how things actually work in Python to actually write some code.  So I wasn't the best developer, but I had some knowledge. For example, I read the C programming language, which is a great book,  and we'll give you all the C knowledge you need to know in order to actually get into vulnerability research.  The next thing I did was to explore myself through vulnerability research basics.  So I read online about some basic vulnerabilities like classic stack buffer overflows, like integer overflows and so on.  And then I tried to practice my knowledge. And that was through war games.  War games are some challenges that are offline and you can try to solve them. They're security-related challenges like you have to find some vulnerability and exploit it.  So that's what I did next. And at first I failed, like, badly, but it's okay because I read the solutions, I read the write-ups,  and I learned how to actually approach the solutions and actually how to solve them. So it comes with time and it's okay to fail because we all do.  The next thing I did was to explore myself through CTFs. Now CTFs are capture the flag contests and they're basically competitions where you have to solve some challenges and compete against other teams.  This is a team effort and you play with other players, your friends. And this is how I met my teammates.  So we met through CTFs through IRC and we played together. And sometimes we fail, like, miserably, but sometimes we actually do well.  And we got to qualify to some finals and we actually got to travel the world because when you qualify to finals, they sometimes pay for the trip.  So we actually got to see some cool places and, yeah, that was awesome. And I actually think CTFs are a great way to get into security.  So the next thing I did was I dove into the dipwater. So once you know the basics, it's important to not stay there for too long.  You have to expose yourself to harder things. I was afraid to see and try to solve the harder challenges at first, but with time I managed to understand that nothing too bad would happen even if I fail.  So I try to solve challenges and I failed, but with time I managed to pick up the tricks and the ways to solve the harder challenges.  And I think this is very important to not be afraid to solve things because even if you fail, you learn from other people's solutions and it's fine, it's totally okay.  A great tweet by LiveOverflow, which also should be in the audience or in the conference, and basically says what I really believe in, which is move away from the basics as soon as possible and expose yourself to harder things which you don't understand.  This will make you understand things that you thought you understood, but you actually didn't. And also it's important to learn from different resources.  This way you learn different tricks and different ways to approach problem solving, and it's very important in my opinion.  So, yeah, LiveOverflow also has a great YouTube channel which I also watch and he talks about vulnerability and security research, and I really recommend watching his videos as well.  So after I had some knowledge in CTFs and wargames, I practiced and I practiced a lot.  So it's really important to practice and solve challenges on your own because this way you actually get to pick up those tricks that you have to understand in order to solve some challenges.  So some vulnerabilities have patterns in them.  And the way you catch on those patterns is by seeing them quite a few times, and a great way to do that and to pick that up is by solving a lot of challenges.  This is where I also expose myself to actual real-world vulnerabilities. I did that through a lot of websites.  There's Project Zero, Bug Shackery, which you can read the vulnerabilities about exploits and so on.  So this is where I also expose myself to real-world vulnerabilities. And I also realized that there is a great connection between vulnerabilities in CTFs and in real-world.  So vulnerabilities are essentially bugs and they exist both in CTFs and in real life.  And the main problem that you think you have when you actually try to do real-world research is that you think that the code base is huge and stuff like that.  But even if the code base is huge, bugs are still out there. So don't be afraid to look at it because the odds of vulnerabilities running out just when you're starting to look at something are very low.  So you can start to try and solve things. And by solving things, I mean looking at actual code bases.  After doing some practice, I realized that how do we find vulnerabilities?  And once we actually start and repeat the process of solving challenges and practicing, we notice that vulnerability research is about identifying bugs.  And we do that by looking at code. So looking at code is essentially understanding the code and we have to find vulnerabilities in it, right?  Because we want to find them. And that's through actually auditing the code. And that comes with practice.  So practice is really important in vulnerability research. And once we master that, even though I'm not a master and I'm not nearer that, I still think that practice is important.  So how do we actually find vulnerabilities? So as I said before, vulnerabilities have patterns in them.  And patterns are something that you catch up with time. And I wasn't doing that for so long. I'm doing it just for a year.  So how did I catch it up? That was basically through a practice. As I said before, practice can cover the time that you're not doing that for so long.  So I managed to notice that vulnerabilities have patterns in them. For example, programming errors like integer overflows and sign this issue.  These are bugs that actually exist because people make mistakes and we're all humans. And humans make mistakes. We're not perfect.  A great example for such a mistake is the following code. So on the third line, we have an integer overflow, which a lot of developers know about the existence of this vulnerability, yet they still make this mistake.  As I said before, it's because we are all humans and humans make mistakes. So don't be afraid to look at actual code bases because these kind of bugs actually exist in actual code bases, not only in CTFs.  So don't be afraid to look at actual vulnerabilities. You can find simple ones as well.  So there's a great difference between CTFs and real-world vulnerabilities. In CTFs, usually when you find the vulnerability, you know what you need to do with it.  You know what you have to progress with it. You know what you need to exploit. How you need to exploit it.  So sometimes you have a stock buffer overflow, so you need to override some variable, the return address, or you can override a function pointer.  Mostly when you see a vulnerability in CTF, you know what you need to do with it.  In real-world, you usually have a set of weird states, a set of primitives that you have. Primitives are essentially capabilities we have as attackers.  So you usually have some primitives that we can chain together to do something greater and that can later lead to vulnerability.  And as such example of that will be the vulnerability I found in Chakra, which is basically a bunch of primitives together, chain together to make an actual vulnerability.  So this is basically everything I knew about vulnerability research and security research before I started looking in Chakra.  So yeah, let's dive into some JavaScript. So JavaScript engines. I didn't say I learned JavaScript because I didn't.  And JavaScript is a very readable language. Once you have learned a few programming languages, reading JavaScript would probably be more smooth, a more smooth process.  So doing that would not be too hard. Now JS engines are basically what's capable of running the code that you write as a JavaScript developer.  They have a lot of parts to them and the most important one for us is the JIT compiler.  Now JIT stands for just-in-time compiler. What it essentially does is when some function gets hot and it's being called a lot of times,  it compiles this function to machine code to improve performance.  Now this compilation is-this JIT compiler is also incapable of-is also responsible of doing optimizations for the code.  It has a lot of assumptions about the code and doesn't want assumptions to be broken.  So we'll see just how that works later and about JIT compiler vulnerabilities.  So about JavaScript basics. So JavaScript is a dynamically-tipped language and it's fairly readable.  You can make arrays in different ways. You can have arrays with different types of elements and you can have console logs to actually print stuff.  So JavaScript has prototypes. Prototypes are essentially something that you can-someway you can inherit features from other objects.  So for example, in our case, we have a parent object and a child object.  So the child object, if the prototype of it is the parent object, then it will inherit some features. In this case, the properties X and Y.  So this actually is fairly important to us in the vulnerability I found.  And you can modify the prototype by accessing the proto property.  So JavaScript also has something called proxy. Now, our proxy is an object that can wrap another object and redefine basic operations like getters and setters.  So in our case, we redefined the getter. So we redefined the getter in which we're supposed to get X to be 1, 3, 3, 7.  But due to the fact that the proxy redefines the behavior, what happens is the getter handler is actually being called.  So we redefined the basic operation of the getter.  And in this way, we actually tracked the function called to the getter of this object.  So JavaScript has arrays, as we've seen, but Chakra has arrays with types.  So the first tab we're going to talk about is called the JavaScript native interay.  And it is the way Chakra calls integer array.  Basically, it's an array that stores integers, which are four bytes per element, and they're basically integers.  JavaScript also has a native float array, which is essentially floats.  And unlike the C definition for float, this float is actually eight bytes.  And they store floats in their eight byte pile elements.  And we have JavaScript arrays, which are objects, they store objects, essentially pointers, and they are also eight bytes per element.  So let's see about how we can convert between the types.  So if we start off with an array of integers and we add a float to it, what happens is we convert the array to a float array.  And if we add an object to it, the conversion that takes place is a conversion to a JavaScript array.  If we have both floats and integers, the outcome is a float array.  So if we have both floats and integers and objects and mixed array, what happens is this array is considered to be a JavaScript array.  And the most weird conversion that takes place is this one.  And you usually don't see this when you see about conversions in JavaScript engines, but this is very crucial for our talk.  So when you have two arrays and you assign one of them to be the prototype of another one, the prototype one is directly converted to a JavaScript array.  Now, this is going to be very important to us later, so keep that in mind.  And this basically, the conversion takes place right when the assignment takes place.  So let's see about how array layout actually looks like in memory.  So when we have an array, for example, in this case, a JavaScript array, it has some flags which indicate some things about the array.  And it's called array flags.  We have the length of the array and we have the head.  Now, a head is a pointer essentially to a segment, and a segment is a contiguous memory region which has elements in this.  So it has some of the elements in the array and it stores another pointer to the next segment.  So what comes out is essentially a length list of segments that start off with the head pointer in the JavaScript array.  So let's see how that actually looks in memory.  We have this simple example where we have two values, in this case integers, and we have an array with two values.  So let's see how it actually looks like in memory.  So in red, we have the JavaScript array properties.  So we can see the initial array value, which is the array flags value.  And we have in green, we have the actual segment.  So we can see the segment has a length and a size.  And in blue, we can actually see the memory layout of the segment.  And if you notice on the right bottom, we have the two elements we defined.  So we actually can see this in memory and this is how it looks like.  So what is the array flag that we're just talking about?  So array flags basically some flags that indicate some things about the array.  In our case, it's defined as an enum.  And the interesting field for us is there has no missing values flag.  And so if we, for those of you who noticed, the initial array value that was defined as our array flags is actually composed of two different flags.  It's composed of the array object array flags tag and it has no missing values tag.  So the first one is not really important for our talk.  So we'll focus on the second one, which basically means that the array doesn't have any holes in it.  Now, what does the whole mean?  So we can create an array and have some values between the, like we have, in this case, we have three elements, but the middle one is missing.  So it's important to say that the values I put there are the way they're represented in memory is by those constants.  And I chose them because it will be easier for us to see them to actually notice them when we see the memory layout as we've seen before.  So as you can see, this one has a hole and the array flags doesn't have, there has no missing values on, which means we have a hole in the array.  And things actually make sense.  But when we look at the memory layout, we see some weird thing.  So as you can see, we have the dead beef and the four one, but between them where the missing value where the hole was, we have some magic constant.  Now, it makes sense that this constant would represent a missing value or a hole in the array.  But as we've seen before, there's already something that tells us when we have holes in array.  It has no missing values flag.  So we know that there is one way to indicate holes in array and it's the flag we've seen before.  But it seems like there is another way to tell if there's holes in arrays.  So that's kind of weird.  That raises a lot of questions.  We have, basically have here, potentially can have here two things that indicate, should indicate the same state.  We have the flag of the array, which is the has no missing values flag.  And we have the content of the array, which we've seen before and is the FFF constant.  So if we can, what happens if they're like mismatched?  What happens if we can do that?  It has no missing values to be set to true and therefore says that we don't have any holes, but we actually have a missing value in the array.  Also, we mix some sort of mixed data and metadata together because if the constant is being used in a control flow, that could be interesting for us if we can fake it.  And it actually is very interesting and we can, it turns out that we can actually fake a missing value.  And this was a vulnerability find by Loki hard and sort of my bad and got CVE for it.  So basically what they did is they faked a missing value by putting the constant we've just seen before into a float array.  So we could beforehand, the value was not the same one that we've seen and it was something that you could actually represent as a valid float number.  So you could actually put it in and this could be turned to a vulnerability.  Now this would mitigate it in a few ways and the constant changed so you couldn't represent it as is.  And what also happened is they added a few more checks to harden things.  So we'll talk about how this can be turned into, this weird state can be turned into a vulnerability and how we can actually exploit it.  But before that, we actually want to talk about something interesting.  So as I just said, the exploit, the vulnerability sort of my bad and Loki hard found was in native float array.  So apparently JavaScript arrays don't store floats as native as actual floats.  They first do something called boxing.  So they first XOR these values with this constant you can see here.  So before you put the float into the array, this value is being XORed with the float you're trying to put in.  So the question that comes up next is whether we can actually have missing values in JavaScript array.  And if so, does the constant change because as we see here, we change the way we represent values and therefore we can represent new values.  So theoretically, we should be able, theoretically, the engine should change the constant.  Otherwise, we could potentially get there and actually represent it.  And it turns out that the constant does not change.  Therefore, we can actually represent it by first boxing it.  So what we did is we first boxed it and XORed the constant with the FFF we've just seen here.  So that in turn gives us the ability to have the constant as the original constant value.  Why are you wondering? Because this property of XOR, so when you have XOR of three elements and two of them are the same,  then they cancel out and they give you the other one.  So if we have two of them to be this value and they want them to be the magic value,  what happens is we actually get in turn the magic value and therefore we actually can represent it.  And this is exactly the vulnerability I found, which is truly heavily relies on the fact that we have basic understanding of JavaScript engines.  This boxing is one of the first things we learn when we learn about JavaScript engines.  So we use this idea of boxing things and turned in supposedly unexploitable state to be a vulnerability.  So basically what we did is, as you can see here, we first box it and then we put it into an array  that is a JavaScript array and not a float array.  So how do we turn this weird state into a vulnerability?  So what happens first thing we need to understand is how vulnerabilities look like.  So JIT stands for just in time as I already explained.  JIT bugs are usually what we see today, are usually type confusions.  So type confusions are essentially confusion between two types that the program thinks something is of one type but actually of the other type.  So JIT type confusions, the most common ones are due to side effects.  Now side effects are some things that take place in the JIT code.  Now JIT code I refer to the machine code that is emitted when the JIT compiler compiles the hot function.  So in our case, the usual case I mean, what happens is you have some function, let's call it foo,  which changes a type of some object, let's think of it as an array, and then the function that got jitted doesn't know about this conversion.  So it thinks that everything is still the same and nothing has changed, so it's going to work with it as it did before.  And this leads to a type of fusion.  So let's look at an example.  So first thing we do when we have such vulnerability is we make the function hot.  As I explained before, to make the function be compiled into machine code, we first have to make it hot.  We do that by calling the function a lot of times, and this can be done by a simple loop.  And the next thing, okay, so also it's important to say that the JIT compiler has a lot of assumptions.  These assumptions are on some types, on some things that take place during the execution of the function.  So for instance, when we call foo, and it thinks that foo doesn't do anything, it knows that it doesn't need to check things again,  because if foo doesn't change any state of the program, why would we check something again?  Because that would make the function way more slower, and JIT compilers are all about making things faster.  So it has a lot of assumptions that are there to improve performance.  So once we do that, and we have something that breaks the assumption, we can eventually get to a weird state,  weird states, and from there we can turn this into a vulnerability.  So in this case, once we call the function, and we make it be compiled into machine code,  and we actually call the function again, but this time we want to trigger the type confusion.  So what happens here is foo will change the type of the array, and therefore the assumption that foo doesn't change anything breaks.  That gives us the ability to have the JIT compiler think that nothing happened, but we actually change something.  This is very important because JIT compiler bugs are very complex sometimes, and this one is not too complex.  What happens here? We essentially made the JIT compiler forget to not know about the conversion that happened.  If foo converts array to a JavaScript array, something happened, but the JIT compiler doesn't know about it.  So it's very crucial to understand that, and the assumption essentially breaks because we change something in the function that we call.  So this is basically about JIT type confusions.  We essentially have some function to make it hot, and then we have another function which gets emitted to machine code,  and in that function we call some other function which will have side effects.  Side effects are things that the JIT compiler is not aware of.  So let's actually move on to my vulnerability.  To turn the mismatch between the flags, if you remember we had a flag that indicates it has no missing values,  and we also had the array content.  So once we have the mismatch and we actually managed to fake a missing value, to turn that into a vulnerability, it's quite interesting.  So the way Luckyheart and so on my bed did that was basically through calling this function concat,  which relied on has no missing values and also relied on the content of the array.  This allowed the mismatch to get us into a weird state which shouldn't happen because in normal case,  why would we have the missing values and the content of the array to have not a mismatch between them?  So in our case, in Luckyheart's case, they got the array flags to be mismatched with the content of the array,  and they called concat.  So the way concat was called was once we make a fake array, let's call it buggy,  and the buggy has a fake missing value in it, but the flag that indicates if there are any missing values is not set to true.  So we set this fake array and we call concat with it.  Now the first thing that happens when we call concat is we get to this code.  Now this is the part where we have a lot of code.  Now bear with me, it's not too hard, it's just a lot of code.  So first thing we get is we get to this function.  This function essentially is called concat args, and a item in this case is the buggy we have.  We invoke isFillFromPrototypes and we would essentially want fillFromPrototypes to return false in order to get into that if statement.  Now isFillFromPrototypes essentially checks the following things.  First thing it checks is that there's only one segment in the array and that check is through the fact that it checks that the next segment of the header head segment is null.  And another thing that it checks is that the length of the array is equal to the length of the segment.  Therefore, the segment is the only segment in the array.  So this is the first thing that it checks and the second thing that it checks is that the flag has no missing values is set to true.  Now this can be easily bypassed because we've already seen that we can fake a missing value and set the flag to true.  By doing that, we can actually get isFillFromPrototypes to return false and we can actually successfully get into the if statement.  Next thing we reach is this else statement which calls copy array elements with our array as an argument.  As you can see, it's the p-artm array.  Once we reach copy array elements, it's essentially copying an internal function and which does the following.  Now this part is very interesting for us.  So first thing that happens is they make an enumerator out of the array.  An enumerator will enumerate every single element in the array and will increase the counter every time it engages a new element.  Now let's see how this enumerator is actually implemented.  So this is a code for the moveNext function and as we can see on the red box, when we encounter a value that is actually a missing value, we actually skip it.  So what happened here is that every time we got into the, sorry, every time we got into the while loop and there was a missing value, it skipped it.  Therefore, the count that we had there is not equal to the amount of elements in the array.  This is because every time we get, we find a missing value, it doesn't increase the counter.  And therefore, we can actually get into this statement and call internal fill from prototypes.  Now this also is a very interesting function for us because it basically, first thing it does is loop through the prototype chain.  Now if you remember, the prototype chain is basically some object that we inherit features from.  So we can have our prototype to have another prototype and therefore we forge a prototype chain.  So this thing, this function first loops through the prototype chain and then what it does, it calls this long function name with our prototype as an argument.  So if you remember, prototype is actually directly converted into a JavaScript array when assigning a prototype.  Remember from the conversion slide.  So essentially what happens here, it knows that the prototypes should know that a prototype is a JavaScript array and we loop through it.  So every time we get in here, what happens is the prototype is being passed through the object argument and is then converted to a JavaScript array  and then it is passed through the insurance native array.  So as you can guess, insurance native array basically ensures the array is not of a native type.  Therefore, it's a JavaScript array and this is the interesting part for us.  So what we've seen so far is, so to quick recap, okay, so what we have happened so far is we managed to get concat to convert some array into a JavaScript array  and there is nothing that indicates this conversion.  It's important to know that once we fake an missing value and we call the prototype, we call the concat,  all of the prototype chain is being converted to JavaScript array.  Now, as we've seen before, prototypes in the prototype chain are not native types,  so we shouldn't be able to put there any type because it should be JavaScript array.  So it turns out that we actually can do that.  So remember proxy.  Proxy can wrap an existing functionality and redefine its functionality.  In our case, we can, and it's pretty fun that we can do that, is we can redefine the functionality of get prototype.  So what this allows us is whenever we call get prototype, we redefine a behavior.  So remember that prototype chain?  So what we do here is basically we redefine this prototype chain loop and by doing so,  we allow us to return to call an arbitrary function.  Now, if we call an arbitrary function of our choice, for example, in any function,  what happens is the JIT compiler checks if this function is marked as having side effects or not.  If the function is marked as having side effects, the whole function that got jitted is marked as having side effects  and it's not good for us because we don't want the JIT compiler to know that some side effect took place  because we want to get a tag on a fusion.  So what we do is we use an existing function which is called value off and is marked as not having side effects.  This basically allows us to return any object in the prototype chain by calling get prototype.  So let's see an example for that.  This is the proof of concept code and we'll go through every single part of it and I'll explain everything.  So first thing we do, as I explained before, we loop through this first loop to make the function hot  and therefore it gets compiled to machine code.  Next thing we do is redefine two arrays.  Bug array, which is going to be the array we fake a missing value inside of it,  and an array which is called a-r and is going to be our target for a tag confusion.  Now it's important to notice that the target which will be converted to a JavaScript array is not buggy, it's array.  So notice that.  Next thing we do is we redefine the get prototype off.  So get prototype off is basically the get prototype function and by redefining it to be value off,  whenever the function get prototype is called on a-r, we redefine the implementation and get it to return the a-r itself.  This allows us to return a-r whenever get prototype is called.  This is huge because if we define a proxy, what happens is we essentially made the get prototype loop  to return us a JavaScript native flow array.  So if you notice on the line before, whenever get prototype is called, we call value off.  And if we loop through the prototype chain and value off is the get prototype,  the return value is going to be a-r, which is a JavaScript native flow array as you can see here.  This allows us to essentially have a native flow array even though there shouldn't be any of those in the prototype chain.  Next thing we do is we redefine the missing value constant and by doing so,  we still have the has no missing values flag for buggy set to true.  This is the interesting part because this is where the mismatch takes place.  So we have a missing value but we don't have the flag set to true.  So next thing we do is we call the jitter function.  So this is the key part here.  So when we call this function, what happens is this function is, this code is being ran.  So first thing we do is we define the temporary and we define the first element of the array object,  the array array to be a float.  This makes the jit know that whenever it uses array and no conversions happened,  it still thinks it's a JavaScript float array.  And then we call the temp, we call the concat function and we give it the buggy as an argument.  This will essentially loop through all of the prototype chain and one of the prototype in the prototype chain is the proxy as we can see here.  And when we call the get prototype for the proxy, we get the aorr and when we call the get prototype for the aorr,  we get, sorry, when we call the prototype for the buggy, we get proxy.  When we call the get prototype for the proxy, we invoke the get prototype for aorr,  which will return us the aorr.  And therefore, what happens is aorr is converted to a JavaScript array whenever the prototype chain loop takes place.  Afterwards, in the last line of the jit function, we basically write some value into the array and therefore make write a float into some array  that was converted to JavaScript, a float JavaScript array.  So array is the same array that is in the prototype chain and therefore we still overwrite, we overwrite some pointers with a float,  which in our case is one, two, three, four.  So last thing we do is call console log and we crash because we have a object which is defined for this address and it's not mapped.  So this is essentially the proof of concept code and what we do here is fairly simple.  The first thing we do is we make a function hot and by doing so, we make it be compiled into machine code.  Next thing we do is we redefine the behavior of the get prototype function and we allow it to return an arbitrary object whenever we call get prototype.  Then we invoke the jit, then we fake a missing value and we call the jit, jitted function, which in turn allows us to convert in all of the prototype chain  to be some JavaScript arrays.  Now, even though most of them are JavaScript arrays, one of them, the array one, is not because we redefined the implementation for get prototype.  This in turn allows us to have a JavaScript float array converted to a JavaScript array and we can still act with it, still operate with it  as if it were a JavaScript float array and therefore the last line here allows us to write a number as a pointer and therefore we have what we call in the beginning a type confusion.  So we essentially had a type confusion between a float and a pointer because the jit did not understand that some conversion took place.  Now, this is essentially debug and to exploit it, we actually had to do a few things.  First thing we did was we targeted a fake object primitive.  Now, what is a fake object primitive?  The thing we've just seen was that we faked an object at an arbitrary address.  In our case, it was faked in data one, two, three, four as seen here, but the thing is we actually can fake an object at an arbitrary address.  The object we decided to fake was a data view object which has some fields of it which are a pointer you can read from and write to.  This in turns gives us a read write primitive and from there what we did was to use a known trick which is to find a stack address and override it to run to execute our code in Rope.  So to do all that, we used a great library which is called Ponges and we had to fix a few things but it did most of the work.  What it did was basically do what we explained. It requires us to have a read write primitive and then what it does is to scan the program to find a stack pointer.  From there, it overwrites the stack with whatever we want because we got an arbitrary read write primitive.  And then what happens is we can execute code in Rope.  Now, we first exploited it on edge but to actually execute our own code.  When we actually executed our own code, we executed it in the sandbox context.  So it wasn't any nice for a demo because it would not allow us to pop a calculator or something.  So what we did was we actually compiled Chakra Core for Linux and we exploited it for Linux.  The exploit for Linux was similar because we still overwrote this stack with some things we controlled as we had a read write primitive.  And from there, we basically executed code in Rope.  So that's basically what we did to get a POC, a crashing proof of concept to actually execute our own code.  So let's see a demo for that.  Let's hope it actually works.  One second.  You can see my screen.  Sorry.  Second.  Oh.  No.  So let's hope everything works.  So basically, we have Chakra Core compiled for Linux and this is how it looks like.  So to execute it, we actually have to give it a source file.  So in our case, we give it an exploit.  So notice this is going to be very fast exploit because Chakra is super fast and it has super optimizations because why not?  So it's going to be super fast.  So beware.  And therefore, we have a shell.  So to prove them, we actually got code execution.  Let's run a calculator.  Oops.  Oops.  And we have a calculator as well.  So we actually can actually execute code and everything works as expected.  So a few things about let's get back to the talk.  Oops.  Wait.  Sorry.  So what we've seen here was basically we had some code.  Okay.  So the demo shows us a very simple thing.  We first did what we did in the proof of concept.  We got the JIT compiler to not know that some side effect took place.  This side effect in our case was some conversion that allowed us to convert some array into a JavaScript array and then in turn allows us to write floats as pointers.  And this basically gave us a top confusion primitive which we exploited by faking an object which gives us a read write primitive and therefore we continue to write our own.  So I really hope that my talk was helpful for those of you who want to get into security research.  And I hope it was entertaining for those of you who just wanted to hear about the technical stuff.  And I want to thank everyone who helped me out with the talk and with the slides.  And for you to come for you for coming here and seeing my talk.  I know there's a great competition and there's way cool talks probably, but you still came to mind.  So thank you for that. And yeah, thanks for coming.  Jonathan, do we have some time for questions?  Yeah, sure.  Okay. Was it the first talk you held for such a big audience?  Yeah, I never spoke more for more than ten people.  A shock for Christ's sake. That was good.  We have a couple of microphones over here.  So if you have questions for Jonathan, please line up over there and shoot the questions.  We could start with one from the internet.  Do we have one?  No, we don't.  Okay.  Don't we have any questions over here?  People are good. Where's here?  Number five, here we go.  So thank you for the excellent talk.  I was surprised to see that the JavaScript engine for Edge was open source.  So is this something that the moving towards more open source components?  I think that the open sourcing is cool, but I don't know how actually how the future will be  if we will open source more things.  I actually don't know.  Then we continue with microphone number two.  Hey, thanks for your very interesting talk.  So obviously, Jet Compilers are a pretty in-depth subject, so my question is,  when you got started with all of this, how much prior domain knowledge of Jet Compilers  and dynamic language runtimes did you have, and when you then got to Chakrakor,  how much did you have to just read through the whole thing before you could actually start looking for a vulnerability in there?  So what I did was when I started to get into Chakra, I read a lot of by reports,  and I've seen that there's plenty of vulnerabilities there.  So I read most, not every one of them, but I read a lot of them,  and I tried to understand what they do and why they exist,  and this is basically what got me into looking for my own bug as well.  So basically, I had understanding of JavaScript engines in general,  and then I read about how Chakra works and read about specific vulnerabilities in Chakra.  Thank you.  And over to microphone one, please.  You mentioned the process in Windows runs in a sandbox, so you weren't able to put up a calculator.  Would this stop the exploit in the wild, or is there a way this could be dangerous even in the sandbox?  So to actually execute your arbitrary code, you have to escape the sandbox.  There's things you can do with the sandbox, but they're pretty limited.  I didn't exploit, I didn't find a sandbox escape yet, I hope I will find one day,  but to actually execute interesting stuff, you need to do a sandbox escape  because there's plenty of mitigations nowadays, and specifically for Edge does a lot,  and I don't know specifically if you could do anything interesting without a sandbox escape,  but you could actually run code, so you could do stuff, I'm not sure if it's interesting enough to actually do something in the wild.  And over to microphone number two.  Great research and work. How much time did you work on it?  So I worked on Chakra for about, I think, from July, I think.  When I started, I started reading about Chakra in July, but I had some knowledge about JavaScript engines before,  for about like two months, I think.  So not enough, I mean, the more important part for me was to actually read about examples and see examples,  more than spending a lot of time on it, like, I think it comes with practice, as I explained before,  what I do believe in is practice, and my practice was through reading a lot of research  and a lot of internal stuff about Chakra.  So it's about, I think, from July and maybe two months before.  All right, nobody's queuing up on the microphone, so have I overseen someone?  Jonathan. Excellent.  Thanks.  That's it. That's your applause.  That was Jonathan Jay from Zero to Zero Day. Thank you very much.
35c3-9657-from_zero_to_zero_day_0_59.png 35c3-9657-from_zero_to_zero_day_60_1067.png 35c3-9657-from_zero_to_zero_day_1068_1295.png 35c3-9657-from_zero_to_zero_day_1296_2639.png 35c3-9657-from_zero_to_zero_day_2640_4847.png 35c3-9657-from_zero_to_zero_day_4848_8399.png 35c3-9657-from_zero_to_zero_day_8400_9827.png 35c3-9657-from_zero_to_zero_day_9828_10931.png 35c3-9657-from_zero_to_zero_day_10932_11519.png 35c3-9657-from_zero_to_zero_day_11520_15935.png 35c3-9657-from_zero_to_zero_day_15936_20903.png 35c3-9657-from_zero_to_zero_day_20904_22559.png 35c3-9657-from_zero_to_zero_day_22560_59351.png 35c3-9657-from_zero_to_zero_day_59352_60407.png 35c3-9657-from_zero_to_zero_day_60408_60587.png 35c3-9657-from_zero_to_zero_day_60588_64931.png 35c3-9657-from_zero_to_zero_day_64932_65195.png 35c3-9657-from_zero_to_zero_day_65196_65555.png 35c3-9657-from_zero_to_zero_day_65556_65759.png 35c3-9657-from_zero_to_zero_day_65760_66743.png 35c3-9657-from_zero_to_zero_day_66744_70979.png 35c3-9657-from_zero_to_zero_day_70980_71651.png 35c3-9657-from_zero_to_zero_day_71652_72155.png 35c3-9657-from_zero_to_zero_day_72156_72551.png 35c3-9657-from_zero_to_zero_day_72552_72703.png