Headline
CVE-2023-23306: garmin-ciq-app-research/CVE-2023-23306.md at main · anvilsecure/garmin-ciq-app-research
The Toybox.Ant.BurstPayload.add
API method in CIQ API version 2.2.0 through 4.1.7 suffers from a type confusion vulnreability, which can result in an out-of-bounds write operation. A malicious application could create a specially crafted Toybox.Ant.BurstPayload
object, call its add
method, override arbitrary memory and hijack the execution of the device’s firmware.
Relative Out-of-bound Write and Type Confusion in Toybox.Ant.BurstPayload
The BurstPayload object supports the add method to add a message to its internal queue. Before adding a message, it checks that its size attribute is within a limit. If it is above the limit, it raises an Out of Memory exception.
However, the add method fails to check if the size is negative. It is possible to create a class inheriting from BurtPayload with a negative size attribute. When adding messages, it will write outside of the intended location the 8 bytes of the message to add.
Even if the method checks if the size is negative, it can still be abused to write outside of the intended buffer. Indeed, the size and burstDataBlob attributes are not correlated. By inheriting from BurstPayload, it is possible to override burstDataBlob with an object whose size is smaller than the size attribute, leading to out-of-bound write.
Furthermore, overriding the burstDataBlob attribute with another object results in a type confusion. The add method will attempt to write at offset 0x0c and 0x10 of the object, regardless of its type. By specifying an array, it is possible to override its first two objects in memory.
This can be leveraged to corrupt the memory of the device, and most likely be abused to hijack the execution of the firmware.
e_tvm_error native:Toybox.Ant.BurstPayload.add(s_tvm_ctx *ctx,uint nb_args) { // […] // Anvil: Get the `size` field as a 4-byte integer eVar1 = tvm_get_field_size_as_int(ctx,object,&field_size); uVar2 = (uint)eVar1; if (uVar2 == 0) { // Anvil: Check that we’re not exceeding 0x2000 messages but does not check that it is positive if (0x1fff < (int)field_size) { return OUT_OF_MEMORY_ERROR; } // Anvil: Get the 8 bytes of data to add to the message eVar1 = tvm_message_copy_payload_data(ctx,ctx->frame_ptr + 10,payload_data); // […] // Anvil: Retrieve `burstDataBlob` attribute eVar1 = tvm_object_get_field_value-?(ctx,object,field_burstDataBlob,&burst_data_blob,1); if ((uVar2 == 0) && (uVar2 = _tvm_object_get_object_data(ctx,burst_data_blob.value,(undefined *)&blob_data), uVar2 == 0)) { // Anvil: Write the 8 bytes of data to the blob data, at `size` offset. *(undefined4 *)(blob_data + field_size + 0xc) = payload_data._0_4_; *(undefined4 *)(blob_data + field_size + 0x10) = payload_data._4_4_; // […]
The following proof-of-concept will attempt to write 0x44 8 times at the relative location + 0xdeadbeef:
class MyBurstPayload extends Ant.BurstPayload { function initialize() { Ant.BurstPayload.initialize(); self.size = 0xdeadbeef; } } // […]
var burst = new MyBurstPayload();
var data = new[8]; for (var j = 0; j < 8; j++) { data[j] = 0x44; }
burst.add(data);
The proof-of-concept applications can be found here:
- Relative out-of-bound write https://github.com/anvilsecure/garmin-ciq-app-research/blob/main/poc/GRMN-09.prg
- Type confusion https://github.com/anvilsecure/garmin-ciq-app-research/blob/main/poc/GRMN-11.prg