DEF CON SG1 - CSIT Village - Ernest Ang - From Chrome Renderer, To Mouse To System
Watch on YouTubeVideo summary
Ernest Ang opens his presentation by outlining a complex browser-to-system exploitation chain that begins within the Chrome renderer process and escalates to full kernel-level access on Windows. He clarifies that while the vulnerabilities discussed have been patched in modern versions, the demonstration relies on older Chromium builds to illustrate a theoretical attack path that is technically intricate but not representative of current "zero-click" threats used by advanced persistent groups. The core of his research focuses on the V8 JavaScript engine, specifically targeting a bug in its parser where unused variables are mistakenly treated as used during the lazy parsing phase. This flaw allows an attacker to generate incorrect bytecode, which can be further manipulated through garbage collection cycles to create inconsistent code generation and ultimately achieve arbitrary read-write access within the V8 heap, bypassing standard sandbox protections like Uber cage by exploiting misconfigurations in the external pointer table.
Once code execution is established within the untrusted renderer process, Ang demonstrates how to escalate privileges by leveraging a logical vulnerability in Chrome's drag-and-drop API. By manipulating the `startDragging` function, he can synthesize arbitrary mouse events and force the user's cursor to click on malicious executables downloaded via JavaScript, effectively bypassing browser sandbox boundaries to achieve medium integrity code execution. This step is crucial because it bridges the gap between the restricted renderer environment and the more privileged browser process. However, Ang notes that this chain requires significant engineering, including patching Chrome functionalities to call internal APIs like `startDragging` directly from a payload, as well as handling memory corruption issues related to heap allocator decompression algorithms that initially caused crashes during execution.
The final stage of the attack involves exploiting a race condition in the CRFS Windows kernel driver to achieve system-level privilege escalation. By manipulating handle counts and reference counts for file objects, Ang triggers a use-after-free vulnerability where a dangling FS context object is replaced with a controlled pipe buffer before it can be cleaned up. This allows him to inject arbitrary values into specific registers, including the instruction pointer (RIP), enabling control over kernel execution flow. With this level of access, he can modify security tokens to gain full administrative rights, though he emphasizes that the entire chain remains unstable in practice due to Windows Defender SmartScreen protections and Endpoint Detection and Response (EDR) tools, making it an educational proof-of-concept rather than a deployable weapon for modern systems.
Read the full video transcript
And now to close off day one, we start
off deceptively simple but escalate
quickly from Chrome renderer to mouse to
system. Ernest Ang will share how what
begins as a simple click turns into full
system compromise, chaining multiple
vulnerabilities from the Chromium
renderer all the way to kernel level
access. It's technical, it's creative,
and yes, it's a little bit terrifying.
Let's welcome our final speaker for
today.
>> [applause]
>> Okay. So, a very good evening to
everyone. Thank you for making the time
to come for my sharing. Uh, I'll try to
make it as brief as possible so we don't
take up the full 1 hour. Uh, yeah. So,
today I'll be sharing a browser full
chain exploitation from Chroman renderer
to actually the Chrome browser, not the
mouse. Uh, to eventually obtain system
level privilege on the Windows operating
system.
So just a bit of caveats. Number one,
all the vulnerabilities that I'll be
going through are all patched. So don't
worry about me or anyone controlling
your mouse after this. Uh and as a
result, we'll be looking at older
versions of Chromium and Windows. And
last but not least, this is strictly an
unrealistic expert chain as you will see
later on. So if you are looking for
something which is AP grade such as the
one used a few months ago by UNCC 386
against our telos uh you will not find
it here. But if you are looking for
something interesting and something to
learn more about complex systems about
browsers and the kernel um do stay to
the end of the presentation and uh you
get to see something funny in 30 to 40
slides or more.
Okay. Okay, so a little bit about
background about sorry who am I? Uh I've
been with CSIT for 3 years under the
mobile security department and we mainly
deal with VAP of mobile apps which is
used throughout CSIT and Mindv and from
time to time we are tasked to do
evaluation of uh mobile applications
which are used throughout the whole of
government of Singapore. Aside from that
we also do a bit of esoteric uh research
against the Android operating system. So
things like the uh binder kernel zygote
and a bit of the mitigations which are
implemented there and as a byproduct of
it we also have to do a bit of product
and tooling development here and there.
Okay. So some motivations of why browser
food chain well um I think browsers are
used every day and everywhere. I think
even my grandmother uses browsers. Uh
yet they under research due to its
complex nature. Right.
Okay, never mind. So, due to its complex
nature, sorry. So, um yeah, and you will
see that there are a lot of weird bugs
that come from browsers and which leads
to even weirder exploitation techniques.
Also, there are not a lot of full chain
proof of concepts available. So, I
thought why not be one of the few that
manage to demonstrate one. It'll be hard
to dive into each and every component.
So, but I'll still try nonetheless.
Okay. Uh and then as of why did I choose
Chrome? Well, it has the biggest market
share uh today. And apart from that, I
think personally for me uh one click
exploit chains has always been very
fascinating. And if we look at the
trends of other exploit chains such as
the zero click used in operation um
triangulation I think on the iOS
operating system. Um there is also the
element of browser
and uh also three years ago I think
theory also did a full chain but their
full chain was from from a renderer
process in a guest VM and they escaped
and managed to add system level
privileges in host. So I thought this
was also quite interesting and uh I
thought of doing something similar at a
smaller scale.
Okay. So now um since we are going to be
targeting Chrome, I think it'll be good
to share a bit of context on how Chrome
works. So every time you click that
green, blue, red, yellow icon um a lot
of processes run underneath the hood. We
will skip the utility, GPU and plug-in
process and just focus on the renderer
and browser process because these are
the two processes which we'll be
exploiting. So the renderer process is
in charge of two things, executing your
JavaScript and rendering your web
content.
And usually it runs with the lowest
amount of privileges because of the fact
that um there's a lot of attack surface
there. And then on the other end of the
spectrum we have the browser process
which is the most privileged process uh
in the Windows operating system. And it
is in charge of performing the more
privileged actions such as read write to
this network connections and stuff like
that. And as with every multipprocess
architecture there is the concept of IPC
which is interprocess communication and
the IPC for Chrome is module.
Okay. So now with some context let's
start with the first bug. Uh this is an
issue within the V8 parcel which
eventually gives us code execution with
untrusted integrity. Okay. So what is
V8? For the uninitiated V8 is basically
the JavaScript execution engine for
Chromium. It is highly complex and uh it
in charge of taking your JavaScript
passing it interpreting it optimizing it
deoptimizing it so and so forth and
eventually uh well executing it. Uh
there's many many different components
and with each component there is a tech
surface. So but the thing is that the
issue is within the vessel. So we'll be
looking at the V passer only and this
diagram by is also quite outdated. I
think it's uh done in 2017. The VA today
is highly complex and has more
components such as part plug maclo tool
uh scanner and then the optimizer to has
been superseded by turbos shaft.
Okay. So what is the TLDDR of this bug?
We can basically trick the passer into
thinking that this is being used by a
parent function when it is actually
unused and so we'll wrongly generate a
and bite code for it. So in this example
here you can see that the function I do
not use this doesn't use this but we can
basically trick the VA engine into
thinking that I do not use this uses
this. Uh there are some caveats to
triggering this bug. Uh you need to use
error functions. You need to use classes
which are statically initialized and use
this in the static initialization.
And we also need to make sure that the
whole function can be lazily passed but
uh more on that later on.
Okay. And so now we have a very weird
bug which is that we can generate bite
code that is not supposed to be there.
So how do we basically escalate our
privilege and upgrade it so that we get
something stronger. So to do so we will
need to understand two concepts. The
first concept is the concept of b
flushing which is the fancy term for
bico garbage collection. So what is
garbage collection is just in computer
science or complex computer systems. Uh
garbage collection is a system which
helps program save space via automated
memory reclaim and uh is triggered in V8
is triggered when the B code B codes age
gets too old and when your bite codes
get flashed uh you will need to
recompile it and repass it.
Okay. So the next concept is the concept
of um lazy passing or pre-passing. So to
understand lazy passing or and
pre-passing you have to understand full
passing first. So it comes to full
passing uh your what the parser will do
is that it will check your code for
grammatical errors and this is not the
same grammar as your English language
grammar your was this those that it is a
programming language grammar right it
checks your code for syntax errors which
is specific to your uh programming
language and then once it's
grammatically correct we'll generate a
when it comes to prepassing we only
check for syntax errors and the rest of
passing is done by the pre-passer when
the function is called or use. Okay. So
when you are prepassing a function the
your function will use that uses
variables um the metadata of them will
be saved and will be saved during
prepassing and then loaded when the
function is called. So some of these
important metadata which is relevant to
the bug is things like the is used field
since we are dealing with a
vulnerability which can trick your fun
which can trick the passer into thinking
that something is used when it is
actually unused. So this is quite
important for the vulnerability and
exploitation of this bug.
Okay. So looking at V8 um we come across
this function known as pass function
literal. And this function within the V8
passer is in charge of well doing
passing doing function passing. And if
we dive deeper into this function
eventually the the passer will determine
if this function that we are currently
passing can be prepassed and if it is
lazy. And if it's lazy and it can be
prepassed, we should prepassseass it.
And if we should repass it, we'll call
skip function. And as you can guess,
skip function will basically mark the
current function that it is passing that
that VA passer is passing because it is
identified that this function meets the
requirements for prepassing and it would
set certain metadata or flex within the
function to indicate that yes, this
function is prepassing. do prepass it
and it do so using the set must use
prepass data flag. Okay, so putting
everything together we can control a
simple P uh which involves code that is
written by our trigger function that we
have written a few slides ago. We'll
call the trigger function with the
vulnerable passing code. We'll flush his
bite code and then we'll trigger it
again and let's see what happens.
Okay, so initially uh pass function lit
is called on our vulnerable function and
the passer determines that this function
can be repass and it is lazy and hence
we should repass it and
skip function technically will be called
on this function and you can see that
after this the rest of passing stops
which confirms our hypothesis right when
it comes to pre-passing we only check
for syntax errors and the rest of the
passing and the generation of bite code
is skipped until we call the trigger fun
until we call our function. And when we
call our function, you can see that on
this instance um the passer detects that
uh we have to that this function is
marked for pre-passing and then it
restores all the metadata of all the
variables uh that he has saved during
prepassing. And over here you can see
the vulnerability in the flesh right
that even though this trigger function
doesn't use this the prepasser
mistakenly thinks that it is using this
and hence um you'll see what happened
later on. Okay. So after this we'll
flush his white code and then we call
the function again. And what happens
here in this instance of in this
instance of second passing this function
uh the passer determines that this
function can't be prepassed anymore
because it has already been prepassed
and hence it doesn't prepass. Okay. And
then this leads to a very interesting uh
primitive right. So on the first
instance of passing this function we can
see that um the bite code because the
fun because the passer thinks that this
is being used by the function it will
generate bite code for it and on the
second instance after we flush its bite
code on the second instance of passing
this function again
the bite code is not generated right
because of the fact that in this
instance on this second passing of this
function uh pre-passing is not done and
hence no prepassing means no wrong bite
code generated.
So over here you can see that we have
upgraded our primitive actually right we
have upgraded our primitive from a
incorrect bite code uh generation to a
inconsistent bite code generation
because for the same exact function that
we are passing there are two different
set of by codes that are generated
and the thing is that we can further
upgrade this primitive because a VA will
think that the bite code is still there
when it is actually not. So this sort of
constructs some form of outbound rewrite
that we can further exploit.
And to further further prove our
hypothesis, what we can do is that we
can generate a simple patch which just
forces V8 to not prepass any function.
And let's see what happens. You can see
that the moment we don't prepassse uh
any function which includes our valable
function um the by code is never wrongly
generated. And if we can never wrongly
generate bite code then we can never
inconsistently generate bite code and we
can never outbound rewrite. Yes.
Okay. So
after we upgraded our primitive again to
outbound rewrite the sort of holy grail
or the golden path to getting code
execution in well the chrome render
process is try to upgrade it to a V8
rewrite. That means a rewrite throughout
the vap space and I'll very briefly show
you how. So typically what we will do is
that we'll allocate two JavaScript
arrays. So uh we have the target array
and victim array on your right here or
left. So and on your left or right you
see how the arrays look like in memory.
There's four important fields map
properties elements and length. We'll
skip the map and property few for now
because even though it is quite
important when you're exploiting other
types of bugs such as turban bugs.
So the element pointer is actually the
pointer to the backing store of the
array and backing store in browser
research very briefly refers to the
memory location where your array stores
its data. So in this case every when you
allocate your target array first what
happens is that VA will allocate the
packing store to the target array which
is 1.1 1.21 2 1.3 and 1.4 and after that
you will locate the actual array object.
Then uh you will allocate the backing
store for your victim array which is 2.1
2.2 2.3 then you'll allocate the actual
victim object.
So and typically the backing store is
usually allocated before the actual
object and then for the length field
length just refers to the length of your
backing store.
So what we can do is that using our
outbound readr and some V8 heat
manipulation on massaging we can out
read write to the exact location of the
length field of the first array and we
can corrupt it to make it bigger than it
actually is. So then because of this um
the V8 engine will also be tricked into
thinking that the target array can read
and write out of bounds of the backing
store. Right? So after we do this, we
can actually corrupt out of bounds of
the backing store and even corrupt the
elements of the second
arrays backing store. So what we can do
is that we can update the second element
to 4.4. And in JavaScript, the second
index of the victim array would actually
be updated as well. But more
insidiously, what we can do is that we
can corrupt the elements pointer of the
second uh array and make it point to any
arbitrary location inside the V8 heat.
And this will give us a V8 rewrite. But
unfortunately this readwrite is
sandboxed within the V8 tip. And this is
due to the V8 sandbox which is uh code
name as Uber cage. So what is Uber cage?
Okay. So to understand what is Uber cage
uh let's take a step back because this
is a mitigation which was implemented
quite uh not so recent actually. This
was implemented quite a while ago I
think in 2021. So in the past before
2021 or before this V8 sandbox um there
were a lot of trusted pointers which
were allocated inside the V8 heat and
once you obtain V8 rewrite throughout
this region you can basically corrupt
this trust pointer and then you can get
code execution quite quickly. Right. So
the objective of Uber cage is to isolate
these pointers outside of the vit. So
things like your pointer to your bite
code, pointer to your DOM node and
pointer to g code. We isolate these
pointers out of the vit and instead of
leaving these pointers, we will just
leave text and these texts are to be
converted into indices to the pointer
table which will then store the actual
pointer to the trust object. And
obviously all of the pointer tables are
also allocated outside the VA.
And this will effectively render V8 box
as useless unless you have a V8 sandbox
escape primitive which we so happen to
have for this version of V8.
So for this version of V8 uh we will
actually abuse one of the pointer tables
which were not implemented correctly
known as the external pointer table.
Okay. So before talking about the
external pointer table, let's talk about
blink. So previously I mentioned that
the renderer process is in charge of
executing JavaScript and rendering web
content. So V8 handles JavaScript and
Blink handles um web content right and
there are two main he allocators for
blink. Number one is partition allocator
and oil pen. So oil pen is in charge of
the memory license for DOM objects. So
it is quite relevant here. And every
time you allocate a DOM object, the
actual object is allocated inside the
bling oil pan heat. Uh but there will be
a V8 representation of that same object
allocated inside the V8 heat. And the
reason why Chroma developers probably
did this is to allow it is to make a way
for us to be able to manipulate DOM
objects from JavaScript,
right? And in the past these four
pointers, so the bling red pointer and
the bling type array op pointer these
were all inside the VAT. And
the EP or the external pointer table
aims to isolate the bling pointers out
uh by storing the pointers inside this
table instead of the vit. However, the
tag remains inside the V8 heat and in
this version of V8 um the tag
there's no strict strict type checking
for the DOM objects. So what we can do
is that using our V rewrite primitive we
can corrupt the tech of DOM object to
create a DOM object type confusion. And
in this case what we do is that we will
type confuse the DOM red object with a
DOM type array object by writing the DOM
by writing the EP tag of the DOM type
array to the DOM rate object.
And then after this subsequent
operations on the DOM rack through
JavaScript will not write to the DOM
object in the blink but it will write to
the
DOM type array object in the blink. And
what we can do is that the DOM type
array will also have a backing store and
we can corrupt the packing store and
this will give us arbitrary rewrite
throughout not just the vit but the
whole chromium renderer process.
Okay. So from here um I think if you
were there from Kenoon's talk I think
code execution is quite straightforward
and it's quite generic throughout
browsers. We will allocate a Wom object
and because all when every time you
allocate a Wom object the Wom function
the WM function associated with the WOM
object is mapped by RWX memory. So it's
readable, writable and executable. So
what you can do is that using your
arbitrary write you can write in your
shell code inside the WOM space and then
you can get code execution. But just to
take note from 2023 onwards the RWX
memory associated with resm instances
has also been isolated out of the VA and
into the trusted space. So it's
the trusted pointer table. So you will
have to repeat the steps that we did for
the EP on the TPT. But as I've already
shown that uh I will not do it here.
Yes. Okay. Okay. So with this we have
coexecution in untrusted integrity.
Okay. So but untrusted integrity uh
doesn't give us a lot of uh space to do
a lot of actions. So we will need to
escape the renderer process and exploit
bugs in other processes in chromium
which are more privileged and get
stronger code execution privileges. So
what we do is that using our renderer
bug, we will trigger this bug which is a
logical bug in the Chrome browser
process which basically allows us to
control and click the user's mouse at
any arbitrary coordinate. So what we
would do is that we would download an
executable using JavaScript. We'll force
the user to click on it and uh we'll get
code execution with integrity level of
the user which we will just assume as
medium.
Okay. So the vulnerable function starts
from here which is start dragging which
is a modum API that is exposed on the
renderer process end and this function
will eventually call this function in
the browser process which is start drag
and drop and if we specify the field if
we spec if we can set the fuel source of
the start drag and drop function as K
touch which we can because um this value
is derived from drag event source info
pointer.
Um we will be able to call start touch
drag um with arbitrary coordinates and
then start touch drag will call send
mouse event and send mouse event will
eventually call send input which is a
native windows API
which synthesizes keystrokes mouse
motions and button clicks. So basically
we'll be able to control the user's
mouse
and at no point from the renderer
process to the browser process will
there any bounce check and even if there
was a bounce check the bounce checks
needs to be implemented at the browser
process end because if you have render
code execution and you implement checks
on the renderer process end you can just
patch out the checks.
Okay. So all we need to do is to call
this function with arbitrary arguments
and then we can trigger the bug. So as
this function is found in the local
framework proxy object, we need a
pointer to the local framework proxy
object which we can get if we can get a
pointer to the local frame object and we
can and to get the local frame object,
we will need a pointer to the document
object which is very convenient because
um in JavaScript the VA representation
of this DOM object is
um found in the window object. We just
need to do the conversion which I will
very quickly do.
So,
[clears throat]
so first we get the address of the V8
representation of the document object
and then we find the tag for this
object. We convert the tag to index and
then we index the EP table. Then we can
find the bling pointer to the DOM object
and then we do some shifting and then we
can find the object
and then um you can just implement it in
C or C++ code depending on your
preference
and after this uh you can just call the
function right because all of as you
have seen in the previous slides all of
the arguments to this function are
static values so you can just craft them
in C or C++ code And the last thing you
need is the address to the start
dragging function which you can get
because on Windows Chromium they release
the symbols. So you can just download
the symbols and find the address to the
function.
And then we crash unfortunately
one second. Okay. So let's analyze why
we crash, right? And to understand why
we crash just let's take a step back and
and know that every heap allocator in
chromium implements some form of pointer
compression in order to save memory
space. In so in V8 the compressed
pointers are an offset from the V8 heat
base whereas in oil pen uh the
compressed pointers function more of an
index which are converted to an offset
from the oil pen base address. It's all
pen heat base address. And you can see
here in the first orange square that
actually this is the all pen
decompression algorithm. And as part of
the decompression algorithm you will
need the base address of the all pen
heat. But the thing is that this value
uh has a symbol inside the chrome.dl
binary and it is updated at runtime. But
the thing is that um for our sb payload
we also have a symbol which stores the
base address of this open heat. But
because we are not running our sbx
payload at runtime uh it was remain
uninitialized. So the fix for this is
just you can just find that str
reference to pointer and update our
reference to the open uh heatbased
address and then we'll be able to call
the function but there's no process
continuation because when we try to
return from the function and continue we
crash and this is because of a stack
frame issue. I think when we return from
the function the stack frame was sort of
corrupted and to uncorrupt it you just
shift the stack upwards and downwards.
Okay. So at this point um assuming that
we can do all the steps all the
downloading of the exe controlling his
mouse to click on the exe uh we
basically have SBX because we can launch
any executable and and you'll see what
exe executable we launch later but I
would like to take a few minutes to
share about the concept of compromise
renderer. So
majority of the SBX triggers cannot be
called from JavaScript, right? As have
as you have seen in this example whereby
the vulnerable function is start
dragging and start dragging can't be
called from JavaScript. So we will
require some form of native code
execution
and sometimes we may also require to
modify the Chrome renderer process. So
in our context we patched various Chrome
functionalities to be able to call start
dragging in an arbitrary and control
fashion. Right? So usually you will need
a renderer code execution but sometimes
researchers who are doing SBX uh they
don't have access to a renderer bug. So
what they do is that they shortcut this
process by generating a patch file which
would simulate what they would have done
if they had a renderer bug. But because
we are trying to constru
of sort of shortcut this and that's why
you encounter all the errors that you
have seen or all the issues that we have
seen in the previous few slides.
Okay. So, okay. So, how do we chain this
bug with the previous buck? What we do
is that we'll compile this payload as a
DL. Then we convert the DL to shell code
using any generic open source framework.
We store the shell code as an array
buffer using fetch and then we find the
SBX shell code in memory. We map it as
executable and then we execute and then
we execute it. Yes.
Okay. So now we have coexecution with
medium integrity. Let's try to escalate
it to system level privilege. So we'll
be exploiting a bug in the CRFS driver
which is a race condition leading to a
use after free of the FS context to
object.
Okay. So to understand vulnerabilities
uh I think patch diffing is a very
common technique. So uh in the
vulnerable in the unpatched version of
the driver we basically call the release
function which eventually frees the fs
context to object in the cleanup driver
routine and in the patch version of this
uh driver. We will shift the releasing
of this or the free of the fs context to
object to the closed um clfs driver
routine. And then if we look at release,
it also reveals certain information of
where this FS context to object is
allocated, right? Which is in this case
the non-page pool.
Okay. So what is the difference between
the cleanup driver routine and the
closed driver routine. So every time you
call create file and you retrieve a
handle to a kernel file object, uh it
represents an open instance of the file
or the device. And basically when the
handle count of this object reaches zero
then the sort of cleanup driver routine
is called and then when the reference
count of that same file object reaches
zero which means that this object is no
longer being referenced by any uh user
mode handles or references then the
close driver routine is called. So TLDDR
when your hand when your handle count
reaches zero you'll call cleanup and
when your pointer count reaches zero
you'll call close.
So in the vulnerable version of this
driver we are basically freeing the FS
context to object when the handle count
of the file object associated with the
FS context to object reaches zero. But
there could be some system references to
that file object. And means that that
file object which is now referencing the
dangling FS context to object could
still be used between cleanup and close.
And then this and if we can use it, we
if we can use it and use the dangling FS
context to object in the process, we can
achieve some use after free of this
object, right? And in starabs actually
did a very good uh deep dive into this
bug and in their PC they opted for
rearchive metadata which is a driver
routine which would free the already
free FS context to object and what
happens is that the Windows kernel
catches this double free attempt and it
just bs this.
Okay. But in our context we are not
looking for a proof of concept. We are
looking to get system level privilege
and actually the object itself is quite
useless and undocumented. So we need to
find creative ways to build stronger
primitive. So arbitary rewrite is the
holy grail and is the best but quite
hard to find and uh arbitrary call was
more possible in my context but uh there
are a few obstacles to overcome such as
KCFG and to exploit arbitrary call you
will need some for register control.
Okay. So after searching the driver uh
we come across this function which seems
to be a good candidate for arbitrary
call and uh it is actually the read
notification function which is a async
device IO control function and it's
reachable using the 8007702A
op code and as long as we satisfy three
other caveats we will be able to call
this function which is at number one. Um
we the input buffer length needs to be
more than zero and the output buffer
length needs to be more than hex 18 and
basically at offset 108 of the object
which is the FS context to object it
needs to be a non uh it needs to be a
non-zero value.
So assume that you can free the FS
context to object in the cleanup.
Replace it, win the race, call read
notification on the replace FS context
to object. You should kind of look like
this, right? Where now our FS context to
object is replaced by a pipe buffer and
then offset 108 is a pointer which is a
pointer to values that we can control.
Okay. So to reach the part of the
driver function which allows us to
control the RIP value all we need to do
is that we need to construct a fake
object chain. So at offset 108 we set it
to a pointer but this pointer will
actually point to another object which
points to another object which points to
another object which points to another
another object which points to the last
object which eventually uh allows us to
control the RP value which would look
something like this where we will
control RAX uh we control RBX we RBX is
quite useless to us but more importantly
we also control the value of RCX right
so we have we basically can control the
value of RIP which is the instruction
pointer and we have one register control
which is nice because um there is a
publicly publicly known technique to
exploit this sort of um bugs or this of
conditions which is RTL set orbits. So
this is a legitimate kernel function but
this legitimate kernel function will
give us write fff fff n number of times
at any location that we want as long as
we control rcx right and which we do if
you can see in the previous few slides.
So
um what we do is that we will set the
location of where we are writing to to
the token property of the current er and
then we will write fff three number of
times. So this will enable all
privileged bits on our current E process
and then uh at this point getting system
is uh I think it's quite trivial but
take note that we are still subjected to
the security model of the Windows
operating system. So we are still
operating from medium. We still have to
we still need to abide by deco cycle
security descriptors and so forth. But
since we have all privileges um you can
abuse SE impersonate, you can abuse SE
debug or even SE load driver uh
privilege and then to get our system.
And if we put everything together, so
our solution kind of looks like this. We
have a main thread which starts off with
some heat manipulation in in it. We'll
create thread one and thread two in
suspender mode and then we'll start and
then we'll pin each of the threads to
different CPUs. Then we'll start both
threads. So the first thread okay we
talk about thread two first. So what
thread two will do is that we'll close
the handle to the CL to the CL FS driver
and then this will free the FS context
to object and then we'll try to we will
try to quickly replace this object using
write file and then hopefully uh thread
one will call read notification which
would uh win the race and which would
use the replace FS context to object and
then uh after that thread one and thread
two will do some form of cleanup and
then the main thread will check if we
have all privileges or we have a certain
privilege page that's not supposed to be
there. If we have it, we exploit. If
not, we just restart.
Okay. So, coming to the end of the
presentation. Um, so now we need to
chain this bug with our previous bug.
So, to do so, what we do is that we
compile the LP as a .exe because the
victim is going to click on it
forcefully. But in reality, uh, where
you have a SBX which deals with memory
corruption, you will need to abuse some
form of shared memory. So share memory
between the renderer process and the
browser process. So you need to transfer
your PE from the renderer to the
browser. And after that you also need to
find the location of the PE inside the
browser process virtual address space.
And then there are certain logic bugs
such as the one used in operation form
control that can make the chaining
easier. But uh in general you I I think
you will have to do the first option.
Okay. So now we'll we'll do a demo which
is a video and you'll see why it's a
video in the next few slides.
Okay, over here I'm just showing that
I'm exporting a chromium from the Google
store or the Google website, not a
Chromium that I built myself. So then
now I'm going to serve oh oops okay
never mind
after which I will serve my renderer
payload which will give me code
execution in the renderer process which
is over here now okay after which I will
run my sbx payload which will control
the mouse to click on the LP I'll run my
LP and then I'll get system level
privilege
Right.
Okay. So, some reflections. Is this
realistic? Well, not really. The SBX
pillow was actually quite unstable. I
managed to only get it up to a 30%
success rate. And the POC also doesn't
deal with mark of the web. So, in real
in real life, when you do the clicking
on the popup, there should be a smart
screen defender thing that will come up.
And this is due to mark of the web. And
as I've said before, SBX involving
Chrome browser or the other Chrome
process is that involve memory capture
are more common.
Next, the LP runs with a visual queue
due to the SBX. So, and the LP will take
time to run and has a 5% chance of dying
and also the LP will not survive under
EDR. So, you'll need to build stronger
primitive to disable the EDRs.
Okay. But I think personally I learned a
lot from uh writing this exploit chain.
Uh I got to dive into the Chrome source
code uh and compilation study the V
passer and some of the Windows kernel in
Windows kernel internals and also deal
with a lot of weird export engineering
issues where in theory I thought
something was possible but actually it
is not so easy or not so
straightforward.
Yeah. And with that I come to the end of
my talk. Uh thank you for tolerating me
and I hope that uh you had the first
great day at Defcon and you and I hope
to see you tomorrow and the day after.
Thank you.
[applause] Thank you Ernest for that
wild ride and a strong finish to day
one. And that brings us to the end of
day one here at CSIT's C517 village
stage. We've explored hidden
assumptions, invisible attack surfaces,
and the subtle flaws that can lead to
major compromises. But we're just
getting started at Defcon Singapore. So
join us again for day two and three for
even more insights, even more research,
and even more opportunities to learn,
connect, and get inspired.