*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!