Tuesday, December 10, 2019

SockPuppet: A Walkthrough of a Kernel Exploit for iOS 12.4

Posted by Ned Williamson, 20% on Project Zero

Introduction

I have a somewhat unique opportunity in this writeup to highlight my experience as an iOS research newcomer. Many high quality iOS kernel exploitation writeups have been published, but those often feature weaker initial primitives combined with lots of cleverness, so it’s hard to tell which iOS internals were specific to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit and which are generic techniques.

In this post, we’ll look at CVE-2019-8605, a vulnerability in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 iOS kernel and macOS for five years and how to exploit it to achieve arbitrary kernel read/write. This issue affected XNU as early as 2013, and was reported by me to Apple on March 2019. It was cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n patched in iOS 12.3 in May 2019 and I released cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 complete details including cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit for iOS for analysis, named “SockPuppet,” in July 2019. It was cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n discovered that this issue regressed in iOS 12.4 and was later patched in iOS 12.4.1 in late August 2019.

The primitive in SockPuppet is stronger than usual: it offers an arbitrary read and free with very little work. This makes it easier to see what a canonical iOS exploit looks like since we can skip over cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 usual work to set up strong initial primitives. I’ll begin by describing how I found my bug, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n explain how I exploited it given only a background in Linux and Windows exploitation. If you’re interested, I’ve collaborated with LiveOverflow to make a video explaining this bug. You can watch it here.

Bug Hunting

Why network fuzzing?

One technique for choosing fuzz targets is enumerating previous vulnerability reports for a given project, finding cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bug locations in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 source tree, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n picking up a component of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 project that is self-contained and contains a diverse subset of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bugs. Then by creating a fuzzer which is fairly generic but can still reproduce cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 previous finds, you are likely to find new ones. When I started to work on my fuzzer, I used two bug reports to seed my research: an mptcp_usr_connectx buffer overflow by Ian Beer of Google Project Zero and an ICMP packet parsing buffer overflow by Kevin Backhouse of Semmle. What made cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se perfect candidates was that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y were critical security issues in completely different parts of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same subsystem: one in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 network-related syscalls and one in parsing of remote packets. If I could make a fuzzer that would make random network-related syscalls and feed random packets into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 IP layer, I might be able to reproduce cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se bugs and find new ones. Those past bugs were discovered using code auditing and static analysis, respectively. As someone who primarily uses fuzzing to find memory corruption vulnerabilities, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se are highly useful artifacts for me to study, since cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y come from some of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 best practitioners of auditing and static analysis in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 industry. In case I failed to reproduce cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bugs or find any new ones, it would at least be an educational project for me. Success would validate that my approach was at least as good as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 approaches originally used to discover cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se bugs. Failure would be an example of a gap in my approach.

The first draft of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fuzzer went off without a hitch: it found Ian’s and Kevin’s bugs with actionable ASAN reports. Even better, for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ICMP buffer overflow it crashed exactly on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 line that Ian described in his email to Kevin as described on Semmle’s blog. When I saw how accurate and effective this was, I started to get really excited. Even better, my fuzzer went on to find a variant of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ICMP bug that I didn’t see mentioned publicly, but was fortunately addressed in Apple’s thorough patch for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 vulnerability.

From Protobuf to PoC

The exact details of how cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fuzzer works will be described in a future post, but some context is necessary to understand how this specific bug was found. At a high level, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fuzzer’s design is a lot like that of syzkaller. It uses a protobuf-based grammar to encode network-related syscalls with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 types of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365ir arguments. On each fuzzer iteration, it does a sequence of random syscalls, interleaving (as a pseudo-syscall) cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 arrival of random packets at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 network layer.

For example, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 syscall to open a socket is int socket(int domain, int type, int protocol). The protobuf message representing this syscall and its arguments is:

message Socket {
  required Domain domain = 1;
  required SoType so_type = 2;
  required Protocol protocol = 3;
}

enum Domain {
  AF_UNSPEC = 0;
  AF_UNIX = 1;
  AF_INET = 2;
...
  AF_MAX = 40;
}

enum SoType {
  SOCK_STREAM = 1;
  SOCK_DGRAM = 2;
  SOCK_RAW = 3;
  SOCK_RDM = 4;
  SOCK_SEQPACKET = 5;
}

enum Protocol {
  IPPROTO_IP = 0;
  IPPROTO_ICMP = 1;
  IPPROTO_IGMP = 2;
  IPPROTO_GGP = 3;
...
}

LibFuzzer and protobuf-mutator work togecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r to generate and mutate protobuf messages using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 format I defined. Then I consume cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se messages and call cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 real C implementation. The fuzzer might generate cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following protobuf message as part of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 sequence of messages representing syscalls:

socket {
  domain: AF_INET6
  so_type: SOCK_STREAM
  protocol: IPPROTO_IP
}

In cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 loop over input syscall messages, I call cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 syscall appropriately based on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 message type:

std::set open_fds;

// ...
case Command::kSocket: {
  int fd = 0;
  int err = socket_wrapper(command.socket().domain(),
                           command.socket().so_type(),
                           command.socket().protocol(), &fd);
  if (err == 0) {
    assert(open_fds.find(fd) != open_fds.end());
    open_fds.insert(fd);
  }
  break;
}

Here, you can see some of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 light manual work that is involved: I keep track of open file descriptors by hand, so I can be sure to close cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 end of one fuzzer iteration.

The fuzzer started out by simply encoding all cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 network-related syscalls into messages that had cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 correct types for each argument. To improve coverage, I refined cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 grammar and made changes to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 code under test. Because cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re is so much code to cover, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 most efficient way to find bugs is to identify suspicious-looking code manually by auditing. Given our fuzzing infrastructure, we can look at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 coverage metrics to understand how well-tested some suspicious code is and tweak cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fuzzer to uniformly exercise desired states. That may be at a higher level of abstraction than code coverage alone, but coverage will still help you identify if and how often a certain state is reached.

Now let’s see how refining cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fuzz grammar led us from a low-quality crash to a clean and highly exploitable PoC. The testcase triggering cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first crash for CVE-2019-8605 only affected raw sockets, and was cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365refore root only. Here’s cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 reproducer I submitted to Apple:

#define IPPROTO_IP 0

#define IN6_ADDR_ANY { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }
#define IN6_ADDR_LOOPBACK { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 }

int main() {
    int s = socket(AF_INET6, SOCK_RAW, IPPROTO_IP);
    struct sockaddr_in6 sa1 = {
        .sin6_len = sizeof(struct sockaddr_in6),
        .sin6_family = AF_INET6,
        .sin6_port = 65000,
        .sin6_flowinfo = 3,
        .sin6_addr = IN6_ADDR_LOOPBACK,
        .sin6_scope_id = 0,
    };
    struct sockaddr_in6 sa2 = {
        .sin6_len = sizeof(struct sockaddr_in6),
        .sin6_family = AF_INET6,
        .sin6_port = 65001,
        .sin6_flowinfo = 3,
        .sin6_addr = IN6_ADDR_ANY,
        .sin6_scope_id = 0,
    };
    connect(s, (const sockaddr*)&sa1, sizeof(sa1));
    unsigned char buffer[4] = {};
    setsockopt(s, 41, 50, buffer, sizeof(buffer));
    connect(s, (const sockaddr*)&sa2, sizeof(sa2));
    close(s);
}

As this C reproducer was modelled directly after cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 protobuf testcase, you can see how my early grammar had lots of precision for sockaddr structures. But setsockopt was horribly underspecified: it just took 2 integers and a random buffer of data. Fortunately, that was enough for us to guess 41 (IPPROTO_IPV6) and 50 (IPV6_3542RTHDR), correctly setting an IPv6 output option.

Looking at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ASAN report for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 use after free, we see cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following stack trace for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 free:

#0 0x497a3d in free _asan_rtl_:3
#1 0x7f8bbe5f42cd in in6_pcbdetach /src/bsd/netinet6/in6_pcb.c:681:3
#2 0x7f8bbe6b06d0 in rip6_detach /src/bsd/netinet6/raw_ip6.c:829:2
#3 0x7f8bbe6af680 in rip6_abort /src/bsd/netinet6/raw_ip6.c:837:9
#4 0x7f8bbe6b0795 in rip6_disconnect /src/bsd/netinet6/raw_ip6.c:848:9
#5 0x7f8bbe10132f in sodisconnectlocked /src/bsd/kern/uipc_socket.c:1792:10
#6 0x7f8bbe1028dc in soconnectlock /src/bsd/kern/uipc_socket.c:1664:15
#7 0x7f8bbe133e00 in connectit /src/bsd/kern/uipc_syscalls.c:954:10
#8 0x7f8bbe133b25 in connect_nocancel /src/bsd/kern/uipc_syscalls.c:726:10
#9 0x7f8bbe6f22b4 in connect_wrapper /src/fuzzing/syscall_stubs.c:125:7

Looking at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 function that actually calls free, we see cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following:

void
in6_pcbdetach(struct inpcb *inp)
{
    // ...
        if (!(so->so_flags & SOF_PCBCLEARING)) {
                struct ip_moptions *imo;
                struct ip6_moptions *im6o;

                inp->inp_vflag = 0;
                if (inp->in6p_options != NULL) {
                        m_freem(inp->in6p_options);
                        inp->in6p_options = NULL; // <- good
                }
                ip6_freepcbopts(inp->in6p_outputopts); // <- bad, dangling pointer
                ROUTE_RELEASE(&inp->in6p_route);
                // free IPv4 related resources in case of mapped addr
                if (inp->inp_options != NULL) {
                        (void) m_free(inp->inp_options);
                        inp->inp_options = NULL; // <- good
                }
// ...

The call to ip6_freepcbopts is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 culprit here. In my fuzzer build this function was inlined into ipc6_pcbdetach, which explains cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 backtrace we saw in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bug report. As you can see, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 developers intended for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 socket options to be reused in some cases by NULLing out each pointer after it was freed. But because cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 in6p_outputopts are represented by a pointer to anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r struct, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y are freed by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 helper function in6_freepcbopts. That function does not know cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 address of inp, so it cannot clear &inp->in6p_outputopts, as we can see cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 code in this snippet neglects to do. This bug does look straightforward upon inspection, but cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ROUTE_RELEASE on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following line, for example, is safe because it’s modifying cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 in6p_route stored inline in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 inp and correctly NULLing pointers. Older XNU revisions didn’t NULL anything, and eicá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y were all buggy or this code just wasn’t originally designed to account for reuse of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 socket.

The freed buffer was created by a call to setsockopt. This is a hint that we might be able to keep accessing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 freed buffer with more calls to getsockopt and setsockopt, which would represent read and write primitives respectively. The initial testcase looked like a really specific edge case in raw sockets, so I figured it wasn’t easily exploitable. Whenever I report a bug, I will create a local patch for it to avoid hitting it again in subsequent fuzzing. But because I wanted to find more variants of it, I just disabled raw sockets in my fuzzer with a one line enum change and left cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bug intact.

This would prove to be cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 right idea. I quickly found a new variant that let you read cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 use-after-free data using getsockopt via a TCP socket, so it worked inside cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 iOS app sandbox. Awesome! After some quick trial and error I saw that setsockopt wouldn’t work for sockets that have been disconnected. But letting cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fuzzer continue to search for a workaround for me was free, so again I worked around cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 unexploitable testcase and left cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bug intact by adding a workaround specifically for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 getsockopt case:

// HACK(nedwill): this prevents us from seeing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 trivial read UaF case
if (in6p->inp_state == INPCB_STATE_DEAD) {
    error = 0;
    break;
}
// Normal handler
error = ip6_getpcbopt(in6p->in6p_outputopts, optname, sopt);

By this point I realized that setsockopt was an important source of complexity and bugs. I updated cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 grammar to better model cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 syscall by confining cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 name argument for setsockopt to be only valid values selected from an enum. You can see cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 change below, where SocketOptName enum now specifies a variety of real option names from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 SO, TCP, IPV6, and ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r levels.

message SetSocketOpt {
   optional Protocol level = 1;
-  optional int32 name = 2;
+  optional SocketOptName name = 2;
   // TODO(nedwill): structure for val
   optional bytes val = 3;
   optional FileDescriptor fd = 4;
}

 enum SocketOptName {
+  option allow_alias = true;
+
+  /* socket.h */
+  SO_DEBUG = 0x0001;           /* turn on debugging info recording */
+  SO_ACCEPTCONN = 0x0002;              /* socket has had listen() */
+  SO_REUSEADDR = 0x0004;               /* allow local address reuse */
+  SO_KEEPALIVE = 0x0008;               /* keep connections alive */
+  SO_DONTROUTE = 0x0010;               /* just use interface addresses */
+  SO_BROADCAST = 0x0020;               /* permit sending of broadcast msgs */
...

These changes were cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 critical ones that led to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 highly exploitable testcase. By allowing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fuzzer to explore cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 setsockopt space much more efficiently, it wasn’t long before it syncá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365sized a testcase that wrote to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 freed buffer. When I looked at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 crashing input, was stunned to see this in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 decoded protobuf data:

set_sock_opt {
  level: SOL_SOCKET
  name: SO_NP_EXTENSIONS
  val: "\267\000\000\000\001\000\000\000"
  fd: FD_0
}

What is that SO_NP_EXTENSIONS option? And why did inserting this syscall into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 testcase turn it from a memory disclosure into an exploitable memory corruption? Quickly skimming through cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 SO_NP_EXTENSIONS handling in XNU I realized that we were hitting this:

#define SONPX_SETOPTSHUT 0x000000001 /* flag for allowing setsockopt after shutdown */

I think every vulnerability researcher can relate to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 moment when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y realize cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y have a great bug. This was that moment for me; that comment described cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exact scenario I needed to turn my use-after-free-read into a use-after-free-write. Transcribing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 full testcase to C yields cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following:

int s = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);

// Permit setsockopt after disconnecting (and freeing socket options)
struct so_np_extensions sonpx = {.npx_flags = SONPX_SETOPTSHUT, .npx_mask = SONPX_SETOPTSHUT};
setsockopt(s, SOL_SOCKET, SO_NP_EXTENSIONS, &sonpx, sizeof(sonpx));

// Initialize ip6_outputopts
int minmtu = -1;
setsockopt(s, IPPROTO_IPV6, IPV6_USE_MIN_MTU, &minmtu, sizeof(minmtu));

// Free ip6_outputopts
disconnectx(s, 0, 0);

// Write to ip6_outputopts
setsockopt(s, IPPROTO_IPV6, IPV6_USE_MIN_MTU, &minmtu, sizeof(minmtu));

In effect, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fuzzer managed to guess cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following syscall:

struct so_np_extensions sonpx = {.npx_flags = SONPX_SETOPTSHUT, .npx_mask = SONPX_SETOPTSHUT};
setsockopt(s, SOL_SOCKET, SO_NP_EXTENSIONS, &sonpx, sizeof(sonpx));

I was surprised to see this because I completely expected cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 use after free to be triggered in anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r way. What’s really cool about combining grammar based fuzzing with coverage feedback is that specifying cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 enums that represent cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 level and name options along with a raw buffer for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 “val” field was enough to find this option and set it correctly. This meant cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fuzzer guessed cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 length (8) of val and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 data representing SONPX_SETOPTSHUT (low bit set for each little-endian dword). We can infer that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fuzzer tried cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 SO_NP_EXTENSIONS option many times before discovering that a length of 8 was notable in terms of additional coverage. Then this set_sock_opt message was propagated throughout cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 corpus as it was mixed with ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r relevant testcases, including cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 one that triggered my original bug. Then ensuring two bits in val were set was just a 1 in 4 guess. The same setsockopt call that setup cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 buggy state was called again to trigger cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 use after free, which was anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r shallow mutation made by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 protobuf-mutator, just cloning one member of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 syscall sequence. Writing effective fuzzers involves a lot of thinking about probability, and you can see how by giving cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fuzzer manually-defined structure in just cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 right places, it managed to explore at an abstraction level that found a great PoC for this bug.

I hope you enjoyed this insight into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bug hunting process. For more background about getting started with this fuzzing approach, take a look at syzkaller and this tutorial.

Exploitation

How use after free works

The exploit I’m about to describe uses a single use after free bug to get a safe and reliable arbitrary read, defeat ASLR, do an arbitrary free, and ultimately allow us to build an arbitrary read/write mechanism. That’s a lot of responsibility for one bug, so it’s worth giving a little background into how use-after-frees work for readers who have never exploited one before. I vividly remember when I read a post by Chris Evans right here on this blog, called “What is a ‘good’ memory corruption vulnerability?” When I downloaded and ran Chris’s canonical use after free demonstration, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 “exploit” worked cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first try on my laptop, I was instantly struck by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 simplicity of it. Since cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n, I’ve written several real world use after free exploits, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y all stem from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same insight: it’s much easier than you would think to reclaim a freed buffer with controlled data. As long as a buffer is not allocated from a specialized pool (PartitionAlloc, a slab heap, etc.), objects of approximately cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same size, i.e., in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same size class, will be mixed between different callers of malloc and free. If you can cause arbitrary allocations that are cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same size from your freed object’s size class you can be pretty sure that you will reclaim cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 freed data quickly. This is by design: if memory allocators did not behave this way, applications would lose performance by not reusing cache lines from recently freed allocations. And if you can tell whecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r or not you succeeded in reclaiming your freed buffer, exploitation is almost deterministic. So cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n, what makes a good UaF bug? If you can control when you free, when you use cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 freed allocation, and can safely check whecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r you’ve reclaimed it (or can massage cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap to make reclaiming deterministic), exploitation will be straightforward. This is at least how a CTF teammate explained it to me, and it still holds today against real targets. The bug we are looking at in this post is one of those bugs, and for that reason, it’s about as “nice” as memory corruption gets.

Bootstrapping better primitives

Generally cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 end goal of binary exploitation is to get arbitrary code execution, sometimes referred to as “shellcode” when that arbitrary code spawns a shell for you on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 target system. For iOS, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 situation is slightly more complicated with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 addition of PAC, which introduces a security boundary between kernel memory R/W and kernel code execution. This means our bug will serve as an entrypoint to get kernel memory R/W using a data-based attack, with code execution left to anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r layer of exploitation.

To start cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit, I thought it would be interesting to see what primitives I could build without knowing Mach specifics. Mach and BSD are cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Yin and Yang of XNU, representing a dual view of many fundamental kernel objects. For example, a process is represented twice in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel: once as a Mach task and once as a BSD proc. My bug occurs in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 BSD half, and most exploits end up getting control of a highly privileged Mach port. This means we’ll need to figure out how to manipulate Mach data structures starting from our corruption on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 BSD side. At this point I was still only familiar with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 BSD part of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel, so I started my research cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re.

Here’s cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 inpcb containing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 dangling inp6_outputopts pointer:

Looking at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 getters and setters for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se options via [get/set]sockopt, we quickly see that fetching cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 integers for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 minmtu and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 prefer_tempaddr fields is straightforward and will let us read data directly out of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 freed buffer. We can also freely read 20 bytes from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 in6po_pktinfo pointer if we manage to reclaim it. Take a look at this snippet from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ip6_getpcbopt implementation yourself:

case IPV6_PKTINFO:
    if (pktopt && pktopt->ip6po_pktinfo)
        optdata = (void *)pktopt->ip6po_pktinfo;
    else {
        /* XXX: we don't have to do this every time... */
        bzero(&null_pktinfo, sizeof (null_pktinfo));
        optdata = (void *)&null_pktinfo;
    }
    optdatalen = sizeof (struct in6_pktinfo); // 20 bytes
    break;

case IPV6_USE_MIN_MTU:
    if (pktopt)
        optdata = (void *)&pktopt->ip6po_minmtu;
    else
        optdata = (void *)&defminmtu;
    optdatalen = sizeof (int);
    break;

case IPV6_PREFER_TEMPADDR:
    if (pktopt)
        optdata = (void *)&pktopt->ip6po_prefer_tempaddr;
    else
        optdata = (void *)&defpreftemp;
    optdatalen = sizeof (int);
    break;

ip6po_minmtu and ip6po_prefer_tempaddr are adjacent to each ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r and qword-aligned so if we manage to reclaim this freed struct with some ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r object containing a pointer we will be able to read out cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 pointer and defeat ASLR. We can also take advantage of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se fields by using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m as an oracle for heap spray success. We spray objects containing an arbitrary pointer value we choose at a location that overlaps cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 in6po_pktinfo field and a magic value in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 mintmu field. This way we can repeatedly read out cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 minmtu field, so if we see our magic value we know it is safe to dereference cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 pointer in in6po_pktinfo. It is generally safe to read cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 inp6_outputopts because we know it is already mapped, but not in6po_pktinfo as it could have been reclaimed by some ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r garbage that points to unmapped or unreadable memory. Before we talk about which object we spray to leak a pointer and how to spray arbitrary data, let’s quickly figure out what primitive we can build from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 setsockopt corruption.

Unfortunately, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 setsockopt path, unlike cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 getsockopt path, is not as easy to use as it first appears. Most of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 relevant options are root only or are highly constrained. This still leaves IPV6_2292PKTINFO/IPV6_PKTINFO as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 best option, but in testing and reading cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 code it appeared impossible to write anything but highly constrained values cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re. The ipi6_addr field, which looks perfect for writing arbitrary data, must be set to 0 to pass a check that it is unspecified. And cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 interface index has to be valid, which constrains us to low values. If cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 interface is 0, it frees cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 options. This means we can only write 16 null bytes plus a small non-zero 4 byte integer anywhere in memory. That’s certainly enough for exploitation, but what about cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 free case? As long as you pass in a pktinfo struct containing 20 null bytes, ip6_setpktopt will call ip6_clearpktopts for you, which finally calls FREE(pktopt->ip6po_pktinfo, M_IP6OPT). Remember, in6po_pktinfo is our controlled pointer, so this means we have an arbitrary free. Even better, it’s a bare free, meaning we can free any object without knowing its zone. That’s because FREE is a wrapper for kfree_addr, which looks up cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 zone on your behalf. To keep late stage exploitation generic, I opted for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 arbitrary free primitive over cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 constrained write primitive.

Implementing and testing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap spray

Now that we have an attack plan, it’s just a matter of figuring out a way to spray cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap with controlled data. Fortunately for us, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re is already a well-known way to do this via IOSurface, and even better, Brandon Azad (@_bazad) already had some code to do it! After some debugging and integration into my exploit, I had a working “stage 1” abstraction that could read and free an arbitrary address, by reclaiming and checking cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 minmtu magic value as described above. This IOSurface technique was used as early as 2016 as part of an in-cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365-wild exploit chain for 10.0.1-10.1.1.

When testing on different iOS devices and versions, I found that spray behavior was different. What was fast and reliable on one device was unreliable on anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r. Fortunately, improvements for one device generally benefited all platforms and versions, so I didn’t need to worry about maintaining multiple spray patterns per-platform. Some of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 parameters involved here are cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 number of objects to spray per attempt, how many times to retry, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 order in which to make allocations of both cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 sprayed and (use-after-)freed socket options. Understanding heap allocator internals across versions and devices would be ideal, but I found experimentation was sufficient for my purposes. This is a CTF insight; I used to solve Linux heap problems by reading glibc and carefully planning out an exploit on paper. A couple years later, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 popular approach had shifted (at least for me) to using tools to inspect cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 state of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap, and iterating quickly to check how high level modifications to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit would change cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap layout. Of course, I didn’t have such tooling on iOS. But by checking cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 minmtu value, I did have a safe oracle to test spray performance and reliability, so it was quick to iterate by hand. When iOS 12.4 regressed and reintroduced this vulnerability, I tested cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit against an iPhone XR and found that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 spray failed often. But after changing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 order in which I did sprays (creating a new dangling pointer after each spray attempt, instead of all at once in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 beginning), success became quick and reliable again. I have no doubt that keeping a good understanding of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 internals is superior, but treating this like an experimental black box is pretty fun.

What makes cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 SockPuppet exploit fast? Ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r exploits often rely on garbage collection in order to get cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365ir freed object reallocated across a zone. Because all of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 objects I used were in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same generic size-based zone, I needed fewer allocations to succeed, and I didn’t have to trigger and wait for garbage collection.

Learning about tfp0

At this point, I have stretched cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 initial bug to its limits, and that has given me an arbitrary read and an arbitrary free. With this we can now create a new use after free where cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re was never a bug in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 original code. I took a look around for any cute shallow tricks that ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365rs might have overlooked in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 BSD part of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel tree before accepting that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Mach exploitation path offers some nice facilities for kernel exploitation, and so it was time to learn it.

If you follow iOS kernel exploitation even casually, you’ve probably heard of “tfp0.” So what is it exactly? It’s a short name for task_for_pid, which returns to you a Mach port with a send right to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 task with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 given pid. When you call it with pid 0, this gives you cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel task port. A port is one of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fundamental primitives of Mach. It’s like a file descriptor that is used to represent message queues. Every such message queue in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel has one receiver, and potentially multiple senders. Given a port name, such as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 one returned by task_for_pid, you can send or receive a Mach message to that queue, depending on what rights you have to access it. The kernel_task is like any ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r task in Mach in that it exposes a task port.

What’s so great about getting access to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel task port? Just take a look at osfmk/mach/mach_vm.defs in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 XNU sources. It has calls like mach_vm_allocate, mach_vm_deallocate, mach_vm_protect, and mach_vm_read_overwrite. If we have a send right to a task port, we can read, write, and allocate memory in that process. XNU supports this abstraction for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel_task, which means you can use this clean API to manipulate memory in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel’s address space. I couldn’t help but feel that every iPhone has this “cheat” menu inside of it, and you have to pass a serious test of your skills to unlock it. You can see why this is so appealing for exploitation, and why I was so excited to try to get ahold of it! Of course, we can’t just call task_for_pid(0) from our unprivileged sandboxed app. But if we can implement this function call in terms of our memory corruption primitives, we’ll be able to pretend we did!

To understand what we need to do to simulate a legitimate tfp0 call, let’s look at how a message we send from our task to anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r task (perhaps kernel_task) actually looks, starting from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 port name (file descriptor equivalent) in userland all cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 way to message delivery.

Let’s start by taking a look at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 struct representing a message header:

typedef struct {
  mach_msg_bits_t    msgh_bits; // "disposition", e.g. MACH_MSG_TYPE_COPY_SEND
  mach_msg_size_t    msgh_size;
  mach_port_t        msgh_remote_port; // destination port name
  mach_port_t        msgh_local_port;
  mach_port_name_t   msgh_voucher_port;
  mach_msg_id_t      msgh_id;
} mach_msg_header_t;

I’ve labeled cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 important fields above. msgh_remote_port contains cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 destination port name, which will be cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel task port name if we have access to it. The msgh_bits specify a number of flags, one of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m being cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 “disposition” of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 message we’re sending for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 different port names. If we have cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 send right to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel task port, for example, we’ll set msgh_bits to tell cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel to copy cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 send right we have in our IPC space to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 message. If this sounds tricky, don’t worry. The main thing to keep in mind is that we name cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 destination of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 message in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 header, and we also mark how we want to use cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 capability we have for it stored in our IPC namespace (mach file descriptor table).

When we want to send a message from userland, we do a mach trap, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 mach equivalent of a syscall, called mach_msg_overwrite_trap. Let’s look at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 MACH_SEND_MSG case and follow along, so we find out what we’ll need to arrange in kernel memory for tfp0:

mach_msg_return_t mach_msg_overwrite_trap(
    struct mach_msg_overwrite_trap_args* args) {
  // ...
  mach_msg_return_t mr = MACH_MSG_SUCCESS;
  vm_map_t map = current_map();

  if (option & MACH_SEND_MSG) {
    ipc_space_t space = current_space();
    ipc_kmsg_t kmsg;

    mr = ipc_kmsg_get(msg_addr, send_size, &kmsg);
    // ...
    mr = ipc_kmsg_copyin(kmsg, space, map, override, &option);
    // ...
    mr = ipc_kmsg_send(kmsg, option, msg_timeout);
// ...

If we want to deliver a message to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel task port, we just need to understand how ipc_kmsg_get, ipc_kmsg_copyin, and ipc_kmsg_send work. ipc_kmsg_get simply copies cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 message from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 calling task’s address space into kernel memory. ipc_kmsg_copyin actually does interesting work. Let’s see how it ingests cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 message header through a call to ipc_kmsg_copyin_header.

mach_msg_return_t ipc_kmsg_copyin_header(ipc_kmsg_t kmsg, ipc_space_t space,
                                         mach_msg_priority_t override,
                                         mach_msg_option_t *optionp) {
  mach_msg_header_t *msg = kmsg->ikm_header;
  mach_msg_bits_t mbits = msg->msgh_bits & MACH_MSGH_BITS_USER;
  mach_port_name_t dest_name = CAST_MACH_PORT_TO_NAME(msg->msgh_remote_port);
  mach_port_name_t reply_name = CAST_MACH_PORT_TO_NAME(msg->msgh_local_port);

  mach_msg_type_name_t dest_type = MACH_MSGH_BITS_REMOTE(mbits);
  ipc_object_t dest_port = IO_NULL;
  ipc_port_t dest_soright = IP_NULL;
  ipc_entry_t dest_entry = IE_NULL;

  if (dest_name != reply_name) {
    // nedwill: this converts name to ipc_entry_t
    dest_entry = ipc_entry_lookup(space, dest_name);
    if (dest_entry == IE_NULL) {
      goto invalid_dest;
    }

    // nedwill: this converts ipc_entry_t to ipc_port_t (and checks capability)
    kr = ipc_right_copyin(space, dest_name, dest_entry, dest_type, FALSE,
                          &dest_port, &dest_soright, &release_port, &assertcnt);
    if (kr != KERN_SUCCESS) {
      goto invalid_dest;
    }

    // ...
  }

  // ...
  msg->msgh_bits =
      MACH_MSGH_BITS_SET(dest_type, reply_type, voucher_type, mbits);
  msg->msgh_remote_port = (ipc_port_t)dest_port;

  // ...
}

ipc_kmsg_copyin_header serves to convert cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 remote port name into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 port object, updating cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 msg->msgh_remote_port to point to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 actual object instead of storing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 task-specific name. This is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 BSD/Linux equivalent of converting a file descriptor into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 actual kernel structure that it refers to. The message header has several name fields, but I’ve simplified cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 code to highlight cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 destination case, since we’ll want cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel_task port to be our destination port. The ipc_space_t space argument represents cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 IPC space for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 current running task, which is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Mach equivalent of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 file descriptor table. First, we lookup cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 dest_name in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 IPC space to get cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ipc_entry_t representing it. Every ipc_entry_t has a field called ie_bits which contains cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 permissions our task has to interact with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 port in question. Here’s what cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 IPC entry struct looks like:

struct ipc_entry {
  struct ipc_object *ie_object; // pointer to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ipc_port_t
  ipc_entry_bits_t ie_bits; // our rights (receive/send/send-once/etc.)
  mach_port_index_t ie_index;
...
};

Remember that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 header of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 message we sent has a “disposition” for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 destination which describes what we want our message to do with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 capability we have for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 remote port name. Here’s where that actually gets validated and consumed:

kern_return_t ipc_right_copyin(ipc_space_t space, mach_port_name_t name,
                               ipc_entry_t entry,
                               mach_msg_type_name_t msgt_name, boolean_t deadok,
                               ipc_object_t *objectp, ipc_port_t *sorightp,
                               ipc_port_t *releasep, int *assertcntp) {
  ipc_entry_bits_t bits;
  ipc_port_t port;

  *releasep = IP_NULL;
  *assertcntp = 0;

  bits = entry->ie_bits;

  switch (msgt_name) {
    case MACH_MSG_TYPE_COPY_SEND: {
      if (bits & MACH_PORT_TYPE_DEAD_NAME) goto copy_dead;

      /* allow for dead send-once rights */
      if ((bits & MACH_PORT_TYPE_SEND_RIGHTS) == 0) goto invalid_right;

      port = (ipc_port_t)entry->ie_object;

      if ((bits & MACH_PORT_TYPE_SEND) == 0) {
        assert(IE_BITS_TYPE(bits) == MACH_PORT_TYPE_SEND_ONCE);
        assert(port->ip_sorights > 0);

        ip_unlock(port);
        goto invalid_right;
      }

      port->ip_srights++;
      ip_reference(port);
      ip_unlock(port);

      *objectp = (ipc_object_t)port;
      *sorightp = IP_NULL;
      break;
    }

    default:
    invalid_right:
      return KERN_INVALID_RIGHT;
  }

  return KERN_SUCCESS;
}

Here, I’ve reproduced cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 code for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 MACH_MSG_TYPE_COPY_SEND case. You can see where ie_bits from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 IPC entry is used to check cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 permission we have. If we want to take advantage of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 send right in this message, we can copy cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 right to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 message, and this code checks that we have cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 right in ie_bits before updating cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 relevant reference counts and finally giving us access to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 port object to which we can enqueue messages. If we don’t have cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 proper permissions according to entry->ie_bits, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 attempt to send cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 message will fail.

Now that our message is copied in, validated, and updated to contain real kernel object pointers, ipc_kmsg_send goes ahead and just adds our message to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 destination queue:

mach_msg_return_t ipc_kmsg_send(ipc_kmsg_t kmsg, mach_msg_option_t option,
                                mach_msg_timeout_t send_timeout) {
  ipc_port_t port;
  thread_t th = current_thread();
  mach_msg_return_t error = MACH_MSG_SUCCESS;
  boolean_t kernel_reply = FALSE;

  port = (ipc_port_t)kmsg->ikm_header->msgh_remote_port;
  assert(IP_VALID(port));
  ip_lock(port);

  if (port->ip_receiver == ipc_space_kernel) {
    port->ip_messages.imq_seqno++;
    ip_unlock(port);

    kmsg = ipc_kobject_server(kmsg, option);
    if (kmsg == IKM_NULL) return MACH_MSG_SUCCESS;

    /* restart cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 KMSG_INFO tracing for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 reply message */
    port = (ipc_port_t)kmsg->ikm_header->msgh_remote_port;
    assert(IP_VALID(port));
    ip_lock(port);
    /* fall thru with reply - same options */
    kernel_reply = TRUE;
    if (!ip_active(port)) error = MACH_SEND_INVALID_DEST;
  }

  if (error != MACH_MSG_SUCCESS) {
    ip_unlock(port);
  } else {
    // ...
    error = ipc_mqueue_send(&port->ip_messages, kmsg, option, send_timeout);
  }
  // ...
  return error;
}

As you can see above, if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 destination port’s ip_receiver is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel IPC space, ipc_kobject_server is called as a special case to handle cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel message. The kernel task port has cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel IPC space as its ip_receiver, so we’ll make sure to replicate that when we are arranging for tfp0.

Whew, that was a lot! Now that we see cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 essentials behind message sending, we are ready to envision our goal state, i.e., how we want kernel memory to look as if we had called tfp0 successfully. We’ll want to add an IPC entry to our IPC space, with ie_object pointing to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel task port, and ie_bits indicating that we have a send right. Here’s how this looks:

The green nodes above represent all cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 data structures that are part of our current task which is running cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit. The blue node is a fake IPC port that we’ll set up to point to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel task and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel task’s IPC table. Remember cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ie_bits field specifies cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 permissions we have to interact with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ie_object, so we’ll want to make sure we have a send right to it specified cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re.

Defeating ASLR and faking data structures

IPC systems generally need a way to serialize file descriptors and send cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m over a pipe, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel needs to understand this convention to do cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 proper accounting. Mach is no exception. Mach ports, like file descriptors, can be sent by one process to anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r with send rights attached. You can send an out of line port from one process to anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r using a special message that contains a mach_msg_ool_descriptor_t. If you’d like to send multiple ports in a single message, you can send mach_msg_ool_ports_descriptor_t, an array of ports stored out of line (OOL), meaning outside of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 message header itself. We, like many ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365rs, will be using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 OOL ports descriptor in our exploit.

What makes cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 OOL ports array so useful is that you completely control cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 size of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 array. When you pass in an array of mach port names, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel will allocate space for an arbitrary number of pointers, each of which is filled with a pointer to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ipc_port structure that we want to send. In case you didn’t notice, we can use this trick as an ASLR bypass as we can overlap an OOL descriptor array of port pointers with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 freed buffer of size 192, and simply read cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 two adjacent int fields from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 freed struct via getsockopt. At this point we can start to traverse cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel data structures with our arbitrary read.

Many exploits turn a corruption bug into a read primitive. We have cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 rare privilege of having a reliable read primitive before we do any corruption, so we use that combined with this pointer disclosure to leak all cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 relevant pointers to complete cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit, including setting up crafted data at a known address. We go ahead and do all cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 necessary traversal now as you can see below.

The green nodes above represent cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 seed values for our exploration, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 orange nodes represent cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 values we’re trying to find. By spraying a message with an OOL port descriptor array containing pointers to ipc_port structs representing our host port, we find its ipc_port which will give us ipc_space_kernel via cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 receiver field.

We repeat cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same initial trick to find cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ipc_port for our own task. From cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re we find our task’s file descriptor table and use this to find a vtable for socket options and a pipe buffer. The vtable will give us pointers into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernelcache binary. Because cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel process’s BSD representation kernproc is allocated globally in bsd/kern/bsd_init.c, we can use a known offset from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 socketops table to find it and lookup cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 address of kernel_task.

The pipe buffer is created by a call to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 pipe() syscall, and it allocates a buffer that we can write to and read from via a file descriptor. This is a well known trick for getting known data at a known address. In order to make cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fake ipc_port that we’ll inject into our IPC space, we create a pipe and send data to it. The pipe stores queued data into a buffer on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel heap, allocated via cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 generic size-based zones. We can read and write to that buffer repeatedly from userspace by reading and writing to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 relevant pipe file descriptors, and that data is stored in kernel memory. By knowing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 address of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 buffer for our pipe, we can store controlled data cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re and create pointers to it. We’ll need that to make a crafted ipc_port for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel task.

So we can now create our fake ipc_port and point it to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel_task and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ipc_space_kernel, right? I should point out now that even if we could call task_for_pid(0) and obtain a kernel_task port, we wouldn’t be able to send messages to it. Any userland task that tries to send a message to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel_task will be blocked from doing so when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel turns an ipc_port for a task into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 task struct. This is implemented in task_conversion_eval:

kern_return_t
task_conversion_eval(task_t caller, task_t victim)
{
        /*
         * Tasks are allowed to resolve cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365ir own task ports, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel is
         * allowed to resolve anyone's task port.
         */
        if (caller == kernel_task) {
                return KERN_SUCCESS;
        }

        if (caller == victim) {
                return KERN_SUCCESS;
        }

        /*
         * Only cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel can can resolve cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel's task port. We've established
         * by this point that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 caller is not kernel_task.
         */
        if (victim == TASK_NULL || victim == kernel_task) {
                return KERN_INVALID_SECURITY;
        }
// ...

I use cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 trick that many ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365rs have used, and simply created a copy of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel_task object so cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 pointer comparison cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y use won’t detect that I’m sending a message to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fake kernel_task object. It doesn’t matter that it’s not cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 real kernel_task because it’s simple to support cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 mach_vm_* functions with a fake kernel_task; we simply need to copy cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel’s kernel_map and initialize a few ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r fields. You can see in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 diagram above that we can simply pull that from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel_task, whose address we already know. We’ll store cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fake kernel task adjacent to our fake ipc_port in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 pipe buffer. For an example of this approach being used in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 wild, see this exploit writeup from Ian Beer on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 team.

Injecting our kernel_task port

We’re now going to use cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 OOL port descriptor array for anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r purpose. We send a message to ourselves containing an OOL array containing copies of our task port name, which we have cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 send right to. The send right validation happens initially when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 message is sent, so if we edit cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 array while it’s waiting to be delivered, we can overwrite one of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ipc_ports to point to our fake kernel_task ipc_port. This trick is adapted from Stefan Esser’s excellent presentation on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 subject, and has been used in several exploits. Note that an ipc_port has no notion itself of a send or receive right; those rights are tracked as part of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ipc_entry and are handled outside of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ipc_port. This makes sense, because a port encapsulates a given message queue. The rights to send or receive to that queue are specific to each process, so we can see why that information is stored in each process’s table independently.

Even though this trick of overwriting a pointer in an OOL port descriptor array is a known exploit technique, it’s up to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit developer to figure out how to actually make this corruption happen. We have an arbitrary read and arbitrary free. OOL port descriptor arrays and pipe buffers are allocated out of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 global zone. We can combine cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se facts! Earlier we noted down cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 address of our pipe buffer. So we just free cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 pipe buffer’s actual buffer address and spray OOL port descriptor arrays. We cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n read cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 pipe buffer looking for our task’s ipc_port, overwriting it with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 pointer to our fake port. Then we deliver cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 message to ourselves and check whecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r we managed to inject cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fake kernel task port.

At this point, we have tfp0. Like voucher_swap and ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r exploits, we want to use this temporary tfp0 using pipe buffer structures to bootstrap a more stable tfp0. We do this by using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel task port to allocate a page of kernel memory dedicated to storing our data, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 write primitive to write our fake task port and kernel_task cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re. We cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n change our IPC space entry to point to this new ipc_port.

We still have a pipe structure with a dangling pointer to a freed buffer. We don’t want it to double-free when we close cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fd, so we use our new stable tfp0 powers to null out that pointer. We essentially did two actions to corrupt memory: free that pointer, and use cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 new use-after-free pipe buffer to overwrite a single ipc_port pointer, so keeping track of cleanup is fairly straightforward.

If you want to read and test cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit yourself, you can grab it here.

Evaluating PAC and MTE

Because this exploit is based on a memory corruption bug, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re’s a lingering question of how it is affected by different mitigations. With cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 A12 chip, Apple brought PAC (Pointer Aucá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365ntication) to iOS, which appears to be designed to limit kernel code execution assuming arbitrary kernel read/write among ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r goals. This sounds like a pretty strong mitigation, and without any real experience I wasn’t sure how exploitation would fare. I was testing on an A9 chip, so I just hoped I wouldn’t do anything in my exploit that would turn out to be mitigated by PAC. This was cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 case. Because my exploit only targeted data structures and did not involve arbitrary code execution, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re were no code pointers to forge.

iOS 13 is beginning to introduce protections for some data pointers, so it is worthwhile to examine which pointers I would need to forge for this exploit to have worked in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 context of data PAC. PAC protects return addresses on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 stack from corruption by signing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m with a private key and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 location of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 pointer itself on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 stack as a context value. However, ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r code pointers are signed without a context value. Similarly, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 effectiveness of data PAC will likely depend on how Apple chooses to use context values.

Let’s consider cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 situation where all data pointers are protected but not signed with a context based on location. In this scenario we can copy cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m from one location to anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r so long as we manage to leak cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m. This is better known as a “pointer substitution attack,” and has been described by Brandon in his blog post about PAC.

Our read primitive remains effective in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 context of data PAC since our dangling pointer is still signed. There are several attacker-sourced pointers we ultimately need to eicá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r forge or substitute: ipc_space_kernel, kernel_map, &fake_port, and &fake_task, along with all cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 intermediate reads needed to find cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m. Recall that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 &fake_port and &fake_task are pointers to pipe buffers. For our initial entrypoint, It doesn’t matter if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 pktinfo pointer is protected, because we have to leak a real ipc_port pointer anyways via cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 OOL ports spray. This means we can collect a signed ipc_port, and do all of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 up front data structure traversals we do already, copying cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 PAC data pointers without a problem. ipc_space_kernel and kernel_map are already signed, and if pipe buffers are signed we can simply split cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fake port and task across two pipe buffers and obtain a signed pointer to each buffer. In any case, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit would not work completely out of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 box, because we do forge a pointer into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 file descriptor table to lookup arbitrary fd structures and some lookups may require reading more than 20 bytes of data. However, I’m confident that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 read primitive is powerful enough to work around cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se gaps without significant effort.

In practice iOS 13 only protects some data pointers, which paradoxically might improve end user security. For example, if pipe buffers are unprotected, simply leaking cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 address of one is unlikely to let us use that pointer to represent a fake ipc_port if pointers to ports are signed. An examination of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel cache for 17B5068e revealed that IPC port pointers are indeed not protected, but I do think cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y plan to do so (or already do so in non-beta builds) according to Apple’s BlackHat talk earlier this year. Like any mitigation combined with a bug providing strong initial primitives, it’s just a matter of designing alternative exploit techniques. Without considering cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 whack-a-mole of which pointers should be protected or unprotected, I’m hoping that in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 future, as many pointers as possible are signed with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 location as a context to help mitigate cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 effect of pointer substitution attacks. As we can see from our thought experiment, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re isn’t much to be gained with a good use-after-free based read primitive if data pointers are simply signed with a context of 0.

The ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r mitigation to consider is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Memory Tagging Extension (MTE) for ARM, an upcoming CPU feature that I believe that Apple will try to implement. There’s a nice high level summary for this mitigation here and here. In essence, memory allocations will be assigned a random tag by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 memory allocator that will be part of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 upper unused bits of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 pointer, like in PAC. The correct tag value will be stored out of line, similar to how ASAN stores heap metadata out of line. When cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 processor goes to dereference cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 pointer, it will check if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 tag matches. This vulnerability would have been mitigated by MTE, because we trigger cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 use after free many times in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit, and every time cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 freed pointer would be accessed, its tag would be compared against cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 new tag for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 freed range or that of whichever allocation reclaimed cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 buffer. Depending on what cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 CPU or kernel is configured to do when a mismatching tag is identified will affect how an exploit will proceed. I would expect that Apple configure eicá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r a synchronous or asynchronous exception to occur during tag check failure, considering cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y make an effort to trigger data aborts for PAC violations according to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365ir LLVM documentation for PAC: “While ARMv8.3's aut* instructions do not cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365mselves trap on failure, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 compiler only ever emits cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m in sequences that will trap.”

Using corrupted code pointers forged by an attacker is a rare occurrence, but invalid heap accesses happen very often in real code. I suspect many bugs will be identified using MTE, and look forward to seeing its use in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 iPhone. If it is combined with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 current PAC implementation, it will be a huge boost to security for end users.

The iOS Exploit Meta

I found it interesting to see which techniques I used are part of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 iOS exploit “meta,” that is, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 tricks that are used often in public exploits and those seen in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 wild. These were all cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 techniques I came across and how and if I incorporated cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit. As mentioned earlier, for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 closest publicly documented variant of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 approach I used, see Stefan Esser’s presentation on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 topic, which seems to be cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first to use this basket of techniques.

Summary
Benefit
Used?
IOSurface subsystem
Spray arbitrary data of controlled contents and size in kernel address space
Yes
OOL port descriptor array
Spray arbitrary multiple of 8 array containing pointers to ipc_ports with send right
Yes
Pipe buffers
Repeatable read/write from userland of malloced buffer without needing sprays
Yes
Looking around cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 host port for ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r early ports
Find cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel task port
Yes, SockPuppetV1, replaced with my own variant later
Copying kernel_task task port to a new address
Bypass kernel_task task port check for messages coming from a user task
Yes
Creating a fake task port pointing to an arbitrary “task” and reading its PID
Repeatable arbitrary read
No, already had arbitrary read directly via first stage
Triggering zone allocator garbage collection
Reclaim an object from one zone with an object from anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r
No, all relevant objects were already in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 generic size-based zones

A bug’s life

When testing my exploit on older phones, I noticed that my 32-bit iPhone 5 was still running iOS 9.2. Out of curiosity I tested cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 highly exploitable disconnectx PoC that permits corrupting memory via cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 freed buffer and was shocked to see that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 PoC worked right away. The kernel panicked when accessing freed memory (0xDEADBEEF was present in one of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 registers when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 crash occurred). After some more testing, I found that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 PoC worked on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first XNU version where disconnectx was introduced: cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Mavericks kernel included with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 release of macOS 10.9.0. The iOS 7 beta 1 kernel came soon after Mavericks, so it’s likely that iOS 7 beta 1 until iOS 12.2/12.4 was affected by this bug. September 18, 2013 was cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 official release date of iOS 7, so it appears that macOS and iOS users were broadly affected by this vulnerability for over 5 years.

Conclusion

It is somewhat surprising that a bug with such a strong initial primitive was present in iOS for as long as it was. I had been following public iOS security research since cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 iPhone’s inception and it wasn’t until recently that I realized I might be capable of finding a bug in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel myself. When I read exploit writeups during cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 iOS 7 era, I saw that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re were large chains of logic bugs combined with memory corruption. But I knew from my work on Chrome that fuzzing tools have become so effective recently that memory corruption bugs in attack surfaces that were thought to be well audited could be discovered again. We can see that this is true for iOS (as much as it is for ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r platforms like Chrome that were thought to be very difficult to break), that single bugs that are sufficient for privilege escalation existed even during cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 time when large chains were used. Attacker-side memory corruption research is too easy now: we need MTE or ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r dynamic checks to start making a dent in this problem.

I’d like to give credit to @_bazad for his patience with my questions about Mach. SockPuppet was heavily inspired by his voucher_swap exploit, which was in turn inspired by many exploit techniques that came before it. It is really a testament to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 strength of some exploitation tricks that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y appear in so many exploits. With PAC protection for key data pointers, we may see cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 meta shift again as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 dominant approach of injecting fake task ports is on Apple’s radar, and mitigations against it are arriving.

Finally, if Apple made XNU sources available more often, ideally per-commit, I could have automated merging my fuzzer against cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 sources and we could have caught cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 iOS 12.4 regression immediately. Chromium and OSS-Fuzz already have success with this model. A fuzzer I submitted to Chrome’s fuzzer program that only found 3 bugs initially, has now found 95 stability and security regressions since submission. By opening cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 sources more frequently to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 public, we have cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 opportunity to catch, by an order of magnitude, more critical bugs before cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y even make it to beta.

No comments:

Post a Comment