IOBluetoothHCIUserClient uses an IOCommandGate to dispatch external methods; it passes a pointer to the structInput
of the external method as arg0 and ::SimpleDispatchWL as the Action. It neither passes nor checks the size of that structInput,
and SimpleDispatchWL goes on to read the field at +0x70 of the structInput:
__text:00000000000118EB mov esi, [rbx+70h] <-- rbx is structInput, size never checked so +0x70 can be OOB
__text:00000000000118EE test esi, esi
__text:00000000000118F0 mov r13d, 0E00002C7h
__text:00000000000118F6 js loc_11C5B <-- fail if negative
__text:00000000000118FC lea rdx, _sRoutineCount
__text:0000000000011903 cmp esi, [rdx]
__text:0000000000011905 jge loc_11C5B <-- fail if >= number of routines
This alone would be uninteresting, except that there is another fetch from rbx+0x70 which assumes the value hasn't changed:
__text:0000000000011995 movsxd rax, dword ptr [rbx+70h] <-- fetch OOB again
__text:0000000000011999 mov rcx, rax
__text:000000000001199C shl rcx, 4
__text:00000000000119A0 lea rdx, _sRoutines
__text:00000000000119A7 mov r14d, [rdx+rcx+8]
__text:00000000000119AC cmp r14d, 7
__text:00000000000119B0 mov r13d, 0E00002C2h
__text:00000000000119B6 ja loc_11C5B <-- test that sRoutines[OOB].nParams is <= 7
__text:00000000000119BC mov rcx, [rdx+rcx]
__text:00000000000119C0 mov [rbp+var_40], rcx <-- save sRoutines[OOB].fptr into var_40
the code then sets the required registers/stack entries for the number of parameters and calls var_40:
__text:0000000000011B77 mov rdi, r15
__text:0000000000011B7A call [rbp+var_40]
Therefore, by being able to change what follows the mach message corrisponding to this external method call in memory between the checks at +0x118eb
and the second fetch at +0x11995 we can defeat the bounds check and get a function pointer read out of bounds and called.
Tested on OS X ElCapitan 10.11 (15A284) on MacBookAir 5,2
Strongly recommended to use the gazalloc boot args as shown above to repro this!