 going to tell us. Selected code execution from everything using SQLite. Omar, that's your talk, your stage, have fun. Thank you. Hello, everyone. Welcome to my talk. Select code execution from just about anything using SQLite, where we will gain code execution using malicious SQLite databases. So my name is Omar Gull. I'm a vulnerability researcher from Tel Aviv. I've been working in checkpoint research for the past three years, and I've recently moved on to a new startup called Hunters AI. Our agenda for today. So we'll start with a little motivation and the backstory for this research. Then we'll have a brief SQLite introduction, and we'll examine the attack surface given to a malicious database. Then we'll discuss some previous work done in the field of SQLite exploitation, and think about exploiting memory corruption bugs using nothing but pure SQL. We'll then demonstrate our own innovative technique called query-oriented programming, or COP, and take it for a spin in a couple of demos. We'll wrap things up with some future work possibilities and some conclusion. So the motivation for this research is quite obvious. SQLite is one of the most deployed pieces of software out there. Whether it's PHP 5, PHP 7, Android, iOS, macOS, it is now built into Windows 10. It's in Firefox and Chrome. This list could continue forever. Yet, querying an SQLite database is considered safe. Hopefully, by the end of this talk, you will realize why this is not necessarily the case. So it all began with password stealers, which is pretty strange, and there are many, many password stealers in the wild, but the story is usually the same. First of all, a computer gets infected. Then some malware collects the storage credential as they are maintained by various clients. Now, some of these clients actually store your secrets within SQLite databases. So the malware just ships these SQLite databases to its C2 server, where the secrets are extracted and stored within a collective database with the rest of the loop. So one day, my colleague and I, Omri, we were looking at the leaked sources of a very well-known password stealers. Then we thought to ourselves, these guys are just harvesting a bunch of our databases and parse them in their own backend. Can we actually leverage the load and query of an untrusted database to our advantage? And if we could, this could have much bigger implications, just because SQLite is used in countless scenarios. And so began the longest CTF challenge of my life so far. So SQLite, unlike most SQL databases, SQLite does not have that client-server architecture. Instead, it simply reads and writes files directly to the file system. So you have one complete database with multiple tables and indices and triggers and views, and everything is contained within the single file. So let's examine the attack surface given to a potentially malicious SQLite database. So again, this is a snippet of code of a very well-known password stealer. And we have two main points of interest here. First of all, we have SQLite open, where our potentially malicious database is loaded and some parsing is going on. And obviously, we have the query itself, right, the select statement. Now, do note that we have no control over that statement, right? It is hard-coded within our target. It tries to extract the secrets out of our database. Yet we do control the content, so we might have some effect on what's going on there, right? So starting with the first point, the SQLite open. This is just a bunch of setup and configuration code. Then we move on to really straightforward header parsing. And the header itself is not that long. It's just 100 bytes. And sadly, it was already fuzz to death by AFL. So it's probably not a very promising path to pursue. But the SQLite query might be a bit more interesting, because using SQLite authors' words, the select statement is the most complicated command in the SQL language. Now, you might be aware that behind the scenes SQLite is a virtual machine. So every query must first be compiled to some proprietary bytecode. And this is also known as the preparation step. So SQLite prepare would walk and expand the query. So for example, every time you select an asterisk, it simply rewrite this asterisk as all column names. So SQLite locate table will actually verify that all the relevant objects that you are querying actually exist and we locate them in memory. Where does it locate them? So every SQLite database has a table called SQLite master. And this is actually the schema that is defining the database. And this is its structure. So for every object in the database, you have an entry. So it's type, like whether it's a table or a view and its name. And at the very bottom, you can see something called SQL. And SQL is actually the DDL that is describing the object. And DDL stands for data definition language. And you can sort of look at it like header files in C. So they are used to define the structures and names and types of the objects within the database. Furthermore, they appear in plain text within the file. So let me show you an example. Here I open the SQLite interpreter. I create a table and I insert some values into it. Then I quit the interpreter. And now I hex dump the file that was created. And you can see highlighted in yellow the DDL statement that is part of the master schema. And at the very bottom, you can also see the values. So let's go back to query preparation. We have SQLite locate table that attempts to find the structure that is describing the table that we are interested in querying. So it goes on and reads the schema available in SQLite master that we just described. And if it's the first time that it's doing so, it has some callback function for every of these DDL statements. The callback function would actually validate the DDL. And then it will go on and build the internal structures of the object in question. So then we thought about the concept of DDL patching. What if I simply replace the SQL query within the DDL? So it turns out that there is a slight problem with it. And this is the callback function that I mentioned earlier. And as you can tell, the DDL is first verified to begin with create space. And only if it does, then we continue with the preparation. So this is definitely a constraint, right? Our DDL must begin with create. Yet it does leave some room for flexibility. Because judging by SQLite documentation, many things can be created. We can create index and tables and triggers and views and something we still don't quite understand called virtual tables. So then we thought about create view. Because view is simply a pre-packed select statement. And views are queried very similarly to tables. So selecting a column out of a table is semantically equivalent to selecting a column out of a view. Then we thought about the concept of query hijacking. We are going to patch SQLite master DDL with views instead of tables. Now, our patched views can actually have any select subquery that we wish. And now with this subquery, I can suddenly interact with the SQLite interpreter. And this is a huge step forward. We just turned an uncontrollable query to something that we have some control over. So let me show you query hijacking by example. So let's say that some original database had a single table. And this is the DDL that is defining it. So it's called dummy and it has two columns. So obviously any target software would try to query it in the following way. It would just try to select these columns out of the table, right? Yet the following view can actually hijack this query. I create a view. It has just the same name and just the same amount of columns. Each column is named just the same way. And you can see that now every column can have any subquery that I wish highlighted in blue at the bottom. So again, let me show you a practical example of it. Here I created a view called dummy with call A and call B. And the first column is utilizing the SQLite version function. And that's a built-in function that simply returns the SQLite version, obviously. The second column is utilizing SQLite own implementation of printf. That's right. They have all these really surprising features and capabilities. So let's see that from the target side. So anyone trying to select out of these columns is suddenly executing our functions. So at the left you can see the SQLite version. And on the right you can see the printf that was executed on the target side. So again, this is a huge step forward. We just gained some control over that query, right? And the question is, what can we do with this control? Does SQLite have any system commands? Can maybe we can read and write some other files on the file system? So this was a good point to stop and look at some previous work done in the field. Because obviously we are not the first to notice SQLite huge potentials in terms of exploitation. A reasonable place to start is SQLite injection. Because this is sort of a similar scenario, right? Someone malicious has some control on an SQL query. So there are a couple of known tricks in SQL injection with SQLite. The first one has something to do with attaching another database and then creating a table and inserting some strings into it. Because as I mentioned earlier, every database is just a file. So this is somewhat of an arbitrary file write on the file system. Yet we do have this constraint, if you remember, that we can't attach because our DDL must begin with create. Another cool trick is abusing the load extension function. And here you can see how you can potentially load a remote DLL. In this case, the interpreter DLL. But obviously this very dangerous function is disabled by default. So again, no go. What about memory corruption in SQLite? Because SQLite is really complex and it's all written in C. So in his amazing blog post, finding bugs in SQLite the easy way, Michael Zalewski, the author of AFL, described how he found 22 bugs in just under 30 minutes of fuzzing. And actually, since then, since that was version 3.8.10, that was in 2015, SQLite actually started using AFL as an integral part of the remarkable test suit. Yet these memory corruption bugs all proved to be really difficult to exploit without some convenient environment. Yet the security research community soon found the perfect target. And it was called WebSQL. So WebSQL is essentially an API for storing data in databases. And it is queried from JavaScript. And it has an SQLite backend. Also, it is available in Chrome and Safari. So here you can see a very simple example of how to interact with WebSQL from JavaScript. But in other words, what I'm hearing here is that we have some untrusted input to SQLite and it is reachable from any website on the Internet in two of the world's most popular browsers. And suddenly, these bugs, these memory corruptions, can now be leveraged with the knowledge and comfort of JavaScript exploitation, the JavaScript interpreter exploitation, that we got pretty good over the years. So there have been several really impressive researchers that were published regarding WebSQL. From really low-hanging fruits like CVE 2015-7036, that was an untrusted pointer to reference in the FTS tokenizer to some more complex exploit as presented in Black Hat 2017 by the awesome Chai team that found the type confusion in the FTS optimizer to the very recent muggle-unbugs found and exploited by Tencent that found an integer overflow in the FTS segment reader. And if you are paying even a tiny bit of attention by now, you must see an interesting pattern arises. All these vulnerable functions start with FTS. So while it is FTS, I have never heard of it and actually Googling it just left me more confused. After some time, I came to the realization that FTS stands for full text search. And it is something called a virtual table module. And it allows for some really cool textual search on a set of documents. Or like the SQLite authors described it, it is like Google for SQLite databases. So virtual tables allow for some pretty cool functionality in SQLite. Whether it is this free text search or a virtual table module called R3 that does some really clever geographical indexing or a virtual table called CSV that lets you treat your database as a CSV file. And these virtual tables are actually queried just like regular tables. Behind the scenes, some dark magic happens. And after every query, there is some callback function that is invoked and it works on something called shadow tables. Now, shadow tables would be best explained by example. So let's say that I create a virtual table using that FTS virtual table module. And I insert a string into it. Now, obviously, to allow for some efficient search, I need to have some metadata, right? I need to have some offsets or indexes or tokens or stuff like that. And obviously, the raw text, right? So that one virtual table is actually it's raw text and metadata is stored among three shadow tables. So the raw text would go to VT content and the metadata would go to VT segments and VT segment. And in time, these shadow tables actually have interfaces passing information between them, right? Because the metadata is storing all these pointers. So you need to pass them between each other. And these interfaces prove to be really, really trusting in their nature. And it makes them a really fertile ground for bug hunting. So let me show you a bug that I found in the archery virtual table module. So archery virtual table module is available now in MacOS and iOS. And it's really cool because now it's also built into Windows 10. And as I've mentioned, it does some really clever geographical indexing. Now, the DDL is supposed to be the following. Any archery virtual table is supposed to begin with ID that needs to be an integer. Then you have some X and Y coordinates. So obviously, every archery interface would expect ID to be an integer. But if I create a virtual table and I insert into ID something that is definitely not an integer, then I use one of these archery interfaces, archery node at the very bottom, you see that we got this crash, this out of bound read on the hip. And this example is a crash in Windows 10. So that's pretty good. We have established that virtual table has bugs. And now using query hijacking technique, we can suddenly trigger these bugs on our target, which is a C2 of the password sealer. And we'll cause it to segfold. And this is nice. But actually gaining flow control over our target requires us to have some form of scripting, right? We want to bypass ASLR and do all these crazy things. Yet we don't have JavaScript. We don't have JavaScript arrays and variables and logic statements like if and loops and stuff like that. However, we do vaguely recall hearing somewhere that SQL is touring complete. So we decided to put it to the test from exploitation perspective. And we started creating our own primitive wish list for exploitation. So if it would create a full exploit, exploiting memory corruption bugs with nothing but SQL, what capabilities do we want? So obviously, to bypass ASLR and these kind of things, we want to leak some memory. We need to have an info leak. And if you've done any punning in your past, you must be familiar with really common tasks like unpacking 64-bit pointers and doing some pointer arithmetic, right? Because we had an info leak, we converted, we read this pointer, and it's a little Indian, so we need to flip it. And now we want to calculate, let's say, where's the base of lib SQLite so we can find some more functions, maybe. So we need some pointer arithmetic. Obviously, after reading pointers and manipulating them, we want to pack them again and write them somewhere. Obviously, writing a single pointer is never enough. We want to create fake objects in memory, like more complex objects than this one pointer. And finally, we would like to heap spray because this might be really useful. So the question remains, can all this exploitation be done with nothing but SQL? So the answer is yes, it is. And I proudly present to you query-oriented programming, or COP. And to demonstrate COP, we are going to exploit the unfixed CVE 2015-7036. And you might ask yourself, how come a four-year-old bug is still unfixed? And this is a great point to our argument. This CVE was only ever considered dangerous in the context of untrusted web SQL. So it was mitigated accordingly, right? It is blacklisted unless SQLite is compiled with a certain flag. So obviously, browsers are not compiled with this flag anymore. But let me show you who is compiled with this flag. So we have PHP 5 and PHP 7 in charge of most of the internet and iOS and Mac OS and probably so many other targets that we just didn't have the time to go over. So let's explain this vulnerability a little bit. I've mentioned that it's in the FTS tokenizer. So a tokenizer is just a set of rules to extract terms from documents or queries. And the default tokenizer that is named simple, just split the strings by white spaces. However, if you like, you can register your own custom tokenizer. You can just pass a C function. And you actually register this custom tokenizer with the function FTS tokenizer in an SQL query. This is a bit weird, so I'll repeat it slowly. You pass a raw pointer to a C function in an SQL query. This is absolutely insane. To be honest, after studying this for quite a while, I still don't understand how to use this feature outside of my exploit. So FTS tokenizer is actually an overloaded function. And if you call it with one argument that is a name of a tokenizer, you get back the address of that tokenizer and to make it a bit more human-readable, or somewhat human, we'll use the hex decoder. And you can now see that we actually got an infolik to lib SQLite. Now, because it's little Indian, so it's the other way around, so we need to reverse it, but this is already pretty cool. If you call FTS tokenizer with two arguments, the first one being a name of a tokenizer, and the second one, again, is a raw pointer, this is absolutely insane, you rewrite the address of that tokenizer. So now, whenever someone will try to use a virtual table so it will instantiate our default tokenizer, it will crash and burn. And this is pretty amazing. Let's have a short recap. So we've established that SQLite is a wonderful one-shot for many targets. It's absolutely everywhere. And it is a complex machine that is written in C. Now, with query hijacking, we can start triggering these bugs. And we aim to write a full exploit implementing all necessary primitives using SQL queries. Our exploitation game plan is as follow. We will leak some pointers and then we'll calculate some function addresses. We'll then create a fake tokenizer object with some pointer to system. We will override the default tokenizer and trigger our malicious tokenizer. Then something will happen. And obviously, by the end of the process, we should be able to profit somehow, right? So starting with memory leak. And infolake to Lib SQLite. So you already know how to do it, right? We've seen FTS tokenizer. But we still have the tiny problem of the little endian pointer. So we need to flip it. Now, surely, we can use the subSDR function and read this pointer, two characters at a time, in reverse fashion. And then simply concatenate everything throughout the pointer. So we get a select query that looks as following. But now we have our pointer. This is great. What about an infolake to the heap? We want to know where the heap is located. So to do that trick, I'm going to do something pretty similar to the archery bug that we've found. So again, I'm going to confuse some shadow table interface. So we created a virtual table and inserted some values into it. And now we're about to confuse the match interface. So the match interface, it does many things, but behind the scene, it just finds, it serves the pointer in memory to where the text is located, right? It's this metadata cool things that virtual table has. So we're going to confuse it. And instead of passing it to another virtual table interface, we'll simply pass it to the hex decoder. So we'll decode this raw pointer. And you can see that, again, in little endian, but now we have a link to the heap. We can cross that off the list. And before we go on to unpacking this pointer, we have a very basic problem. How do we even save these things? Because unlike browser web SQL, we don't have JavaScript variables or arrays to use and then abuse them later. But we need to create some complex logic, right? We need to calculate function address and create things in memory. But how can we do it? Obviously, with SQLite, when you want to save some values, you need to have insert statements. But we can only create tables and views and index and triggers. Then we thought about chaining this view together to use them sort of like a pseudo variable. So again, let me show you an example. Here, I create a view. It's called little endian leak, right? And again, I abuse the FTS tokenizer function. Now I create another view on top of it. And this one is called leak. And it's flipping it using the subSDR trick that you know from before. But notice how I refer to the first view. I refer to little endian leak. So again, I do it throughout the pointer. And eventually, what I have is a pseudo variable that's called leak. And when I select from it, I get the expected result. So now we can really move forward. Now we can start and build some more complex things based on this logic. And now we can go to unpacking the pointers. So we want to calculate a base of an image, for example, or maybe find the beginning of the heap. So first of all, we want to convert our pointers to integers. So again, we're going to start and read these pointers one character at a time in a reverse fashion using subSDR. And to get the value of this hex character, we're going to use inSDR. This is just like SDR char. And using the following string, we'll get the value of the hex character. Now, because it is one base, you have on the right the minus one. Then I need to have some shifting, like dark magic. And then I go and just like concatenate everything throughout the pointer. So the result is this monster query. But eventually, when all of this is done, I get an integer that is the unpacked version of our initial leak. So I successfully unpacked this pointer, and we now have integers at hand. So we can cross that off the list as well. We know how to convert pointers to integers. Now, pointer with mathematics, right? Because we want to have the addresses of some functions in memory. And actually, with integer, this is super simple. All we need to do is use some more subqueries. So on the left, you can see that I'm referring to the now pseudo variables that we have the unpacked leak. And on the right, I can either have, like, subtract a silly constant like I did here. Or I can actually use another pseudo variable to make it a bit more dynamic and a bit more reliable. So eventually, what I'm ending up with is the lib SQLite base at an integer form. So we've read some pointers, and we manipulated them. Now, it's a good time to write them back. And obviously, we're all used to char being the exact opposite of hex. And you can see that it works fairly well on most of the values. But bigger integers were actually translated to their two bytes code points. So this was a huge obstacle for us. So after bashing our head against the documentation for quite a while, we suddenly had the strangest epiphany. We realized that our exploit is actually a database. And if I want any conversion to take place, I can simply create ahead of time this key value map and simply query it to translate whatever value I want to another value with subqueries. So this is the Python function that I've used. And you can see that it's a very simple for loop going from zero to ff and just inserting values to a table called hex map with its key map value. And now our conversions are using subqueries. So again, let me show you by example. You can see that I'm selecting val from hex map, this key value map, where int is equal to, and then I go and then doing some more shifting and modular dark magic. But eventually what I'm ending up with is a packed version of our lib SQLI place. Now we have a packed Lidl-Andian pointer. So we can cross that off the list as well. As I've mentioned, writing a single pointer is definitely useful. But it's not enough. We want to be faking complete objects. All the cool kids are doing it. And it's pretty powerful, primitive. And if you actually recall, we have to do it. Because FTS tokenizer requires us to assign a tokenizer module. Now, what is a tokenizer module? How does it look? So this is the beginning of its structure. And there is an i version that's an integer at the beginning. We don't really care about it. But following it are three function pointers. We have x create, which is the constructor of the tokenizer. And x destroy, which is the destructor. We need to have both of them valid so we don't crash during our exploitation. The third function pointer is really interesting because this is what actually tokenizes the string. So we have a function pointer that we are passing a controllable string into. This would be a perfect place to put our system gadget. So by now, I have used a fair share of my SQL knowledge. But I do have one more trick up my sleeve. And that's join queries. Because I learned about it at the time at the past. So we are now going to create a fake tokenizer view. And we are going to concatenate a bunch of As. And then using a join query, we will concatenate it with a pointer to simple create and pointer to simple destroy and then a bunch of Bs. Now, let's verify it from a low level debugger. And you can actually see that at some place in memory, we have a bunch of As followed by a pointer to simple create, followed by a pointer to simple destroy, and a bunch of Bs. So we are almost done. But we need one more primitive for this exploit. And this is because we already have our malicious tokenizer. And we know where the heap is located. But we are not quite sure where our tokenizer is. So this is a great time for some heap spraying. And ideally, this would be some repetitive form of our fake object primitive. So we thought about repeat. But sadly, SQLite did not implement it like my SQL. So like anyone else, we went to Stack Overflow. And we found this really elegant solution. So we are going to use the zero blob function that simply returns a blob of n zeros. And then we will replace each of that zeros with our fake tokenizer. And we are going to do it 10,000 times as you can see above. Again, let's verify it with a debugger. So you see a bunch of As, and it's kind of hard to see because these are really bad colors. But we also got perfect consistency because these structures repeat themselves every 20 hexabytes. So we created pretty good heap spraying capabilities. And we are done with our exploitation primitive wish list. So we can go back to our initial target. Again, this is the code snippet of a very well-known password sealer. And at the bottom, you can see that he is trying to extract the secrets by selecting a column called body rich from a table called notes. So we are going to prepare a little surprise for him, right? We are going to create a view that is called notes. And it has three subqueries in a column called body rich. And each of these subqueries is actually a cop chain on its own. So if you remember my exploitation game plan, we are going to start with heap spray, and then we will override the default tokenizer, and then we will trigger our malicious tokenizer. And you might ask yourself, what is heap spray? Obviously, heap spray is a cop chain that utilizes our heap spraying capabilities, right? We are spraying 10,000 instances of our fake tokenizer that is a joint query of a bunch of A's and then some pointers like p64 simple create. And the party goes on because p64 simple create is actually derived from u64 simple create, right? With our pointer packing capabilities. And this is just turtles all the way down, because you have u64 simple create that is derived from lib SQLite base plus some constant, right? And this goes back to the unpacked leak version, right? Minus some content. So again, we are using our pointer, our thematics capabilities. We can continue with u64 leak being derived from the almost initial leak. And we will wrap up by showing how the leak is actually derived from the initial vulnerability using FTS tokenizer. And this was just one out of three cop chains that we used in this exploit. And by now, every time that I describe this exploit, this is how I must look. And to be honest, this is how I feel. But luckily for you guys, you don't have to look and feel like me, because we created cop.py. And it is available on Checkpoint Research GitHub. And suddenly, these crazy long chains can now be created with four easy lines of Python. And it feels like pond tools, if you're familiar with it. So you can go ahead and play with it and not look crazy on stage. So we'll go to our first demo. We'll own a password sealer back end running the latest PHP 7. So obviously that's a model that we created. Then you can, with the leaked sources, and you can see all the infected victims, right? Cool. Now we'll try to go to our web shell, to p.php. Obviously, it still does not exist. We get a 404. Moving to the attacker's computer. So we see that we have two scripts here. First, we're going to use cop.py that will generate malicious database. Let's see that out of the base was created. Now we're going to emulate an infection. We're going to send our malicious database to the C2 server if we're infected by a password sealer. And because this process takes a bit of time, we can look at all the cool DDL statements, right? So you see that we started with some bin leak and heap leak, and then we unpacked them. And at the very bottom, you can see that our end gadget is echoing the simplest web shell to p.php, right? And this is the same page that we just tried to reach. So hopefully, yeah. Great. It's done. Now we go back to the password sealer backend. We go to p.php, and we got 200. Now, let's execute some code on it. Who am I? Wwwdata. And obviously, we need to go for ATC password. Yeah. So what just happened is that we've shown that given just a query to our malicious SQLite database, we can execute code on the querying process. Now, given the fact that SQLite is so popular, this really opens up the door to wide range of attacks. Let's explore another use case that is completely different. And our next target is going to be iOS persistency. So iOS uses SQLite extensively. And persistency is really hard to achieve because all executable files must be signed. Yet SQLite databases are not signed, right? They're data only. There's no need to sign them. And iOS and macOS are both compiled with enable FTS token. That's the dangerous compile time flag. So my plan is to regain code execution after reboot by replacing an arbitrary SQLite DB. And to do this, I'm going to target the contacts database. And its name is addressbook.sqlite. So these are two tables in the original database. They have no significant meaning, right? Just for example. And I'm going to create a malicious contacts DB. And I'm going to start with two malicious DDL statements that you're already familiar by now. First of all, we'll overwrite the default tokenizer. Simple to a bunch of As. Then our second DDL statement would actually instantiate this malicious trigger. So it will actually crash the program, right? Because every time you create a virtual table module, someone is trying to go to the constructor of the tokenizer. So then it will crash. Now, what I'm doing next is I'm going to go over each and every of the original table and rewrite them using our query hijacking technique, right? So instead of the columns that is expected, we are going to redirect the execution to the override statement and the crash statement. So we did it for one table, we also do it for the other. Now, we reboot and voila. We get the following CVE and secure boot was bypassed. And if you pay close attention, this is really cool because we see that the crash happened at 41, 41, 41, 41, 49. And this is exactly what we expected to happen, right? Because this is where our constructor should be at an offset of eight after the first integer of the version. But actually, there's more. We get a bonus here because the context is actually used by many, many different processes. So contacts and FaceTime and Springboard and what's up and Telegram and XPC proxy and so many others. And a lot of these processes are way more privileged than others. And we've established that we can now execute code on the process that queries our malicious database. So we also got a privilege escalation in this process, right? And this is really cool. And there's nothing special about the contact database. Actually, any shared database can be used. All we need is something to be writable by a weak user and then being queried by a stronger user. And obviously, all these techniques and bugs were reported to Apple and they were fixed. And these are the CVs if you want to go and read about it later. Now, to wrap things up, if you'll take anything away from this talk, I don't want it to be the crazy SQL gymnastics or a bunch of CV numbers. I want it to be the following. Quering a database might not be safe. Whether it's across reboots or between users or processes, querying a database might not be safe with query hijacking. And now, with query hijacking and query-oriented programming, these memory corruptions that we can trigger can actually be reliably exploited with nothing but SQL. We don't need JavaScript anymore. We don't need web SQL. And we truly think that this is just the tip of the iceberg. So far, SQLite is super popular, yet it was only assessed from the very narrow lands of web SQL. And as much as browser pawning is exciting, SQLite has so much more potential. So we do have some thoughts about a huge possible future of work with this. Obviously, something really cool to do would be expanding our primitives to some stronger primitives, right? We want to gain things like absolute read and write. And my sketchy POC exploit was pretty silly because it had many constants in it, right? Like you've seen. But actually, if you used the internal function of SQLite, like, if during the exploitation you used function like SQLite version, you asked the interpreter, what version are you? What compile option were you compiled with? You can dynamically build these cobchains as you go and actually target the specific target environment that you are exploiting. Like actually utilizing the fact that our exploit is a database, we can create this really cool exploit polyglot. And we think that these techniques can be used to privilege SQLite so many situations because the developers never had it in mind that now we can take any database that is writeable by a weak user and query by a strong user and suddenly we can try to privilege our escalation. Another cool thing to do would be to note that many of the primitives that I've shown you are not exclusive to SQLite, right? The pointer packing and unpacking and heap spraying and all these things are not exclusive to SQLite. So it would be really interesting to take these primitives and see if we can go ahead and exploit other memory corruption bugs in different database engines. Thank you very much. Thank you very much. That gives us a lot of time for questions. So we do have three microphones here in the hall. Number one, number two, and number three. If you have questions, please line up. Do we have some questions from the net already? No. All right. Then we're going to start with microphone number two. Yeah. So the question is regarding the hijacking of the create something. So you mentioned that the beginning of the research was assuming that create was the first word you were checking and then space following the create word and then it could create all of other things. Now my question is if that was changed following your report because this seems like a way to expose a way larger attack surface than, well, most of the other bugs. So I just wonder if it changed. I mean, what basically what was the mitigation and if that was part of it? Yeah. So the SQLite people were really more concerned with specific bugs and not exploitation techniques. And this is really sad because we all know that you can kill bugs but exploitation techniques at what sticks. So no, they didn't change this verification. And actually, this validation of create space was actually added not so long ago. Before that, you can have any DDL statement that you want. So the situation is not really good over there. Good for them and good luck in the future. All right. Then we head over to microphone one, please. Did you maybe by accident attack on the server which was used for password stealing? No, obviously I would never do that. I would never attack anyone. This is just in our lab on POC, right? Thank you. Your passwords are safe with the stealers. Right. Nobody is queuing anymore. Do we have questions from the net? Nothing over there. Well, here we go. Thank you. All the good. Thank you very much.