36C3 preroll music Herald Angel: On my left, I have Omer Gull, and Omer Gull is gonna tell us - A SELECT code_execution FROM * USING SQLite. Omer, Omer Gull, that's your talk, your stage, have fun! Omer Gull: Thank you. 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 Omer Gull. I'm a vulnerability researcher from Tel Aviv. I've been working in Check Point 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 will 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 Programing", or QOP, 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, Mac OS, 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 credentials, 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 loot. 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 ourself: "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 write 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 sqlite3_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 hardcoded 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 sqlite3_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, just 100 bytes. And thirdly, it was already fuzzed to death by AFL. So it's probably not a very promising path to pursue. But the SEL- the sqlite3_query might be a bit more interesting, because using SQLite author's 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 sqlite3_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, sqlite3LocateTable will actually verified that all the relevant objects that you are querying actually exist, and will 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 its type, like whether it's a table or 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 plaintext within the file. So, let me show you an example. Here I opened 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 sqlite3LocateTable 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 is 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 ". 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 indexes, 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-packaged 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 when 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, and each column is named just the same way, and you can see that now every column can have any sub query 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 cola and colb, 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's own implementation of printf. That's right, they have all these really surprising features and capabilities. So, let's see that from the supposedly - from the target side. So, anyone trying to select out of these column, 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 - maybe we can read and write to 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's huge potential, 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. And because, as I mentioned earlier, every - every database is just a file, so this is somewhat of an arbitrary file, right, 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 meterpreter.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", Michal 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 suite. 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 - with the knowledge and comfort of JavaScript exploitation, right. The JavaScript interpreter exploitation that we got, we got pretty good over the years. So there have been really several really impressive research that were published regarding WebSQL from really low hanging fruits like CVE-2015-7036, that was an untrusted pointer dereference in the fts3_tokenizer(), to some more complex exploit as presented in Blackhat 2017 by the awesome Chaitin team, that found a type confusion in the FTS optimizer, to the very recent Magellan bugs 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 what 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's "like Google for your SQlite databases". So virtual tables allow for some pretty cool functionality in SQLite, whether it's this free text search or a virtual table module called RTREE, 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. Yet 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. So - and obviously they're all 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_segdir. 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 proved 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 RTREE virtual table module. So, RTREE 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 RTREE 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 RTREE 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 RTREE interfaces, rtreenode at the very bottom, you see that we got this crash, this out- of-bounds read on the heap. 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 stealer, and will cause it to segfault. 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, like, logic statements, like if and and loops and stuff like that. However, we do vaguely recall hearing somewhere that SQL is turing 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 pwning in your past, you must be familiar with really common tasks like unpacking 64-bit pointers and doing some pointer arithmetics, right? Because we had an info leak, we converted - we read this pointer, and it's a little endian and so we need to flip it, and now we want to calculate, let's say where's the base of libsqlite so we can find some more functions maybe. So we need some pointer arithmetics. 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 Programing, or QOP. And to demonstrate QOP, we are going to exploit the unfixed CVE-2015-7036. And you might ask yourself "What? 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 WebSQL. So it was mitigated accordingly, right. It is blacklisted unless SQLite is compiled with is 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 on most of the Internet, and iOS and MacOS 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 tokanizer, that is named "simple", just split the strings by whitespaces. 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 fts3_tokenizer() in an SQL query. This is a bit where it's all repeated slowly. You pass a row 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. audience laughing clapping So fts3_tokenizer() is actually an overloaded function, and if you call it with one argument that is the 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 info leak to libsqlite3. Now, because it's little endian, so it's the other way around, so we need to reverse it, but this is already pretty cool. If you call fts3_tokenizer() with two arguments, the first one being a name of a tokenizer, and the second one, again, is a row 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, right. 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, right? 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 follows: 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, an info leak to libsqlite. So, you already know how to do it, right? We've seen fts3_tokenizer(), but we still have a tiny problem of the little-endian pointer. So we need to flip it. Now, surely we can use the SUBSTR function and read this pointer two characters at a time in a reverse fashion, and then simply concatenate everything throughout the pointer. So, we get a SELECT query that looks the following, but now we have our pointer. This is great. What about an info leak 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 RTREE 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 does many things, but behind the scenes, 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 will 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 WebSQL, 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 the 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 fts3_tokenizer() function. Now I create another view on top of it, and this one is called "leak", and it's flipping it using the SUBSTR trick that you know from before. But notice how I referred to the first view, I referred 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 building 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 and in reverse fashion using SUBSTR. And to get the value of this hex character, we're going to use INSTR, that is just like strchar, and using the following string we'll get the value of the hex character. Now, because it is one- based, you have on the right the minus one. Then I need to have some shifting, like, dark magic, and then I go and just 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 arithmetics, 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 sub-queries. 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, can make some a bit more reliable. So, eventually, what I'm ending up with is the libsqlite base in 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-byte 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 sub-queries. So this is the python function that I've used and you can see that there's a very simple for loop going from 0 to FF and just inserting values to a table called "hex_map" with its key-map value. And now our conversions are using sub-queries. 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 like shifting and modulo dark magic, but eventually, what I'm ending up with is a packed version of our libsqlite base. Now it's, we have a packed little-endian 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, right? All the cool kids are doing it and it's a pretty powerful primitive. And if you actually recall, we have to do it because fts3_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 "iVersion", that's an integer at the beginning, we don't really care about it, but following it are three function pointers. We have "xCreate", which is the constructor of the tokenizer, and "xDestroy", 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, right. So by now, I've used a fair share of my SQL knowledge, right. But I do have one more trick up my sleeve and that's JOIN queries, right? Because I learned about it at a time in the past. So we're now going to create a fake tokenizer view and we're going to concatenate a bunch of "A"'s and then, using a JOIN query, we'll concatenate it with a pointer to simple_create and a pointer to simple_destroy and then a bunch of "B"'s. 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 "A"'s followed by a pointer to simple_create, followed by a pointer to simple_destroy, and a bunch of "B"'s. So, we're 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, right. And ideally this would be some repetitive form of our fake object primitive. So we've thought about repeat, but sadly SQLite did not implement it like mySQL. So, like anyone else, we went to Stack Overflow, and we found this really elegant solution. So we're going to use the zeroblob function that simply returns a blob of N zeros. And then we will replace each of that zeros with our fake tokenizer. And we're going to do it ten thousand times, as you can see above. Again, let's verifiy it with a debugger. So, you see a bunch of "A"'s 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 hex bytes. So we created a 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 stealer. And at the bottom, you can see that it's trying to SELECT to extract the secrets by selecting a column called "BodyRich" from a table called "Notes". So, we're going to prepare a little surprise for him, right? We're going to create a view that is called "Notes". And it has three sub-queries in a column called "BodyRich". And each of these sub-queries is actually a QOP chain on its own. So if you remember my exploitation game plan, we're 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 QOP chain that utilizes our heap spraying capabilities, right. We are spraying ten thousand instances of our fake tokenizer, that is a JOIN 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 libsqlite_base plus some constant. And this goes back to the unpacked leak version, right, minus some constant. So again, we're using our pointer arithmetics capabilities. We can continue with u64_leak being derived from the almost initial leak. And we'll wrap up by showing how the leak is actually derived from the initial vulnerability using fts3_tokenizer. And this was just one out of three QOP chains that we used in this exploit. And by now, every time that I described 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 QOP.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 pwntools 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 stealer backend running the latest PHP 7. So obviously that's a model that we created with the leaked sources and you can see all the infected victims, right. Cool. Now we'll try to go to our webshell, 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 QOP.py, that will generate a malicious database. Let's see, the database was created. Now we're going to emulate an infection. We're going to send our malicious database to the C2 server as if we were infected by a password stealer. 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 webshell 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 stealer backend. We go to p.php and we got 200. Now, let's execute some code on it. whoami. www-data. And obviously we need to go for /etc/password. Yay. applause So, what just happened is that we've shown that, given just the query to our malicious a 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 a 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 is no need to sign them. And iOS and MacOS are both compiled with ENABLE_FTS3_TOKENIZER. 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 that 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 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 override the default organizer "simple" to a bunch of "A"'s. 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 it 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 others. Now we reboot and voila, we get the following CVE and secure boots was bypassed. And if you pay close attention, this is really cool, because we see that the crash happened at 0x414141...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 is more. We get a bonus here. Because the contacts DB is actually used by many, many different processes. So Contacts and Facetime and Springboard and WhatsApp and Telegram and XPCProxy 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 contacts database. Actually, any shared database can be used. All we need is something to be writeable by a weak user and then being queried by a stronger user, right. And obviously all these techniques and bugs were reported to Apple and they're all fixed. And these are the CVEs 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 CVE numbers. I want it to be the following. Querying it database might not be safe, whether if 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 programing, 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 WebSQL. And we truly think that this is just the tip of the iceberg. So far, SQLite, super popular, yet it was only assessed from the very narrow lands of WebSQL, and as much as browser pwning is exciting, SQLite has so much more potential. So we do have some thoughts about possible future 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, like you've seen, but actually if you used the internal function of SQLite, like, if during the exploitation you use functions like sqlite3_version(), you ask the interpreter, what version are you, what compile options where you compiled with, you can dynamically build these QOP chains 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 escalate 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 queried 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. Applause Herald Angel: Omer Gull, 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 gonna start with microphone number two. Microphone two: Yeah. So the question is regarding the hijacking of the CREATE something. So you mentioned that at the beginning of the research was assuming that CREATE was the first order of checking and then a 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 larger attack surface then. Well, most of the other bugs. So I just wonder if they changed. And I mean, what basically what was the mitigation if and if that was all of it? Omer Gull: Yeah. So the escalate people, we're 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 is 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. Microphone two: Good for them and good luck in the future. And that was the question. Herald Angel: All right. We head over to microphone one, please. Microphone one: Did you, maybe by accident, attack a server which was used for password stealing stealing? Omer Gull: No, obviously, I would never do that. I would never attack anyone. This is just in our lab on POC. Right. Microphone one: Thank you. Omer Gull: Your passwords are safe with the Stealers. laughter Herald Angel: Right. Nobody is queueing anymore. Do we have questions from the net? Nothing over there. Well, here we go. Omer Gull: Thank you. Herald Angel: Omer Gull, thank you very much. applause 36c3 outro music subtitles created by c3subtitles.de in the year 2019. Join, and help us!