From 795187b934d42e6ae80f2d53ced6a816d921c55e Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Wed, 10 Jun 2020 18:20:57 +0000 Subject: [PATCH 1/7] dwarf: support long long type --- volatility/dwarf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/volatility/dwarf.py b/volatility/dwarf.py index 01164f861..9173ca529 100644 --- a/volatility/dwarf.py +++ b/volatility/dwarf.py @@ -50,6 +50,7 @@ class DWARFParser(object): 'unsigned int': 'unsigned int', 'sizetype' : 'unsigned long', 'ssizetype' : 'long', + '__int128 unsigned': 'unsigned long long', } From 5df5d0999692b2d5981e3de2e2920fb0f5cedc2f Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Wed, 10 Jun 2020 18:21:35 +0000 Subject: [PATCH 2/7] linux: recognize arm64 memory model Notice that the condition to use ia64 if 64bit is not needed because it already exists in parse_system_map --- volatility/plugins/overlays/linux/linux.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/volatility/plugins/overlays/linux/linux.py b/volatility/plugins/overlays/linux/linux.py index d0df4ecc3..6c0596d6a 100644 --- a/volatility/plugins/overlays/linux/linux.py +++ b/volatility/plugins/overlays/linux/linux.py @@ -156,6 +156,9 @@ def parse_system_map(data, module): if symbol == "arm_syscall": arch = "ARM" + if symbol == "arm64_dma_phys_limit": + arch = "arm64" + if not symbol in sys_map[module]: sys_map[module][symbol] = [] @@ -192,9 +195,6 @@ def LinuxProfileFactory(profpkg): sysmapdata = profpkg.read(f.filename) arch, memmodel, sysmap = parse_system_map(profpkg.read(f.filename), "kernel") - if memmodel == "64bit": - arch = "x64" - if not sysmapdata or not dwarfdata: # Might be worth throwing an exception here? return None From 0254d83d451f97eea79db753c6852390475d596c Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Wed, 10 Jun 2020 18:21:53 +0000 Subject: [PATCH 3/7] address space: add arm64 option --- volatility/plugins/addrspaces/arm64.py | 92 ++++++++++++++++++++++ volatility/plugins/overlays/linux/linux.py | 20 +++++ 2 files changed, 112 insertions(+) create mode 100644 volatility/plugins/addrspaces/arm64.py diff --git a/volatility/plugins/addrspaces/arm64.py b/volatility/plugins/addrspaces/arm64.py new file mode 100644 index 000000000..3883b0c30 --- /dev/null +++ b/volatility/plugins/addrspaces/arm64.py @@ -0,0 +1,92 @@ +# Volatility +# +# This file is part of Volatility. +# +# Volatility is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Volatility is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Volatility. If not, see . +# + +import struct +import volatility.obj as obj +import volatility.debug as debug #pylint: disable-msg=W0611 +import volatility.plugins.addrspaces.paged as paged + + +class Arm64AddressSpace(paged.AbstractWritablePagedMemory): + """Address space for ARM64 processors""" + + order = 800 + pae = False + paging_address_space = True + checkname = 'Arm64ValidAS' + minimum_size = 0x1000 + alignment_gcd = 0x1000 + _longlong_struct = struct.Struct('> self.ptbl_lowbits(level)) & 0x1ff + + def ptbl_walk(self, vaddr, base): + if (vaddr == 0): + return None + for level in xrange(4): + index = self.ptbl_index(vaddr, level) + entry = self.read_longlong_phys(base + (index << 3)) + if not entry: + return None + # clear high bits + entry_addressbits = entry & ((1 << 47) - 1) + # clear low bits + entry_addressbits = entry_addressbits & (~0xfff) + # entry not valid + if not (entry & 0x1): + return None + if (level == 3 and (entry & 0x3 != 0x3)): + return None + # entry points to final address + if (level == 3 or (entry & 0x3 == 0x1)): + lowbitmask = (1 << self.ptbl_lowbits(level)) - 1 + return entry_addressbits & (~lowbitmask) | (vaddr & lowbitmask) + # entry points to next table + base = entry_addressbits + return None + + def vtop(self, vaddr): + debug.debug("\n--vtop start: {0:x}".format(vaddr), 4) + + return self.ptbl_walk(vaddr, self.dtb) + + # FIXME + # this is supposed to return all valid physical addresses based on the current dtb + # this (may?) be painful to write due to ARM's different page table types and having small & large pages inside of those + def get_available_pages(self): + + for i in xrange(0, (2 ** 32) - 1, 4096): + yield (i, 0x1000) diff --git a/volatility/plugins/overlays/linux/linux.py b/volatility/plugins/overlays/linux/linux.py index 6c0596d6a..23b0f0de3 100644 --- a/volatility/plugins/overlays/linux/linux.py +++ b/volatility/plugins/overlays/linux/linux.py @@ -120,6 +120,7 @@ def is_writable(self): 'ArmValidAS' : [ 0x0, ['VolatilityLinuxARMValidAS']], 'IA32ValidAS' : [ 0x0, ['VolatilityLinuxIntelValidAS']], 'AMD64ValidAS' : [ 0x0, ['VolatilityLinuxIntelValidAS']], + 'Arm64ValidAS' : [ 0x0, ['VolatilityLinuxARM64ValidAS']], }], 'vm_area_struct' : [ None, { 'vm_flags' : [ None, ['LinuxPermissionFlags', {'bitmap': {'r': 0, 'w': 1, 'x': 2}}]], @@ -2493,6 +2494,24 @@ def generate_suggestions(self): yield fork_off - task_off == sym_addr_diff +class VolatilityLinuxARM64ValidAS(obj.VolatilityMagic): + """An object to check that an address space is a valid Arm64 Paged space""" + def generate_suggestions(self): + + init_task_addr = self.obj_vm.profile.get_symbol("init_task") + do_fork_addr = self.obj_vm.profile.get_symbol("_do_fork") + + if not do_fork_addr or not init_task_addr: + return + + task_paddr = self.obj_vm.vtop(init_task_addr) + do_fork_paddr = self.obj_vm.vtop(do_fork_addr) + + if not do_fork_paddr or not task_paddr: + return + + yield do_fork_paddr - task_paddr == do_fork_addr - init_task_addr + class LinuxObjectClasses(obj.ProfileModification): conditions = {'os': lambda x: x == 'linux'} before = ['BasicObjectClasses'] @@ -2518,6 +2537,7 @@ def modification(self, profile): 'Ipv6Address': basic.Ipv6Address, 'VolatilityLinuxIntelValidAS' : VolatilityLinuxIntelValidAS, 'VolatilityLinuxARMValidAS' : VolatilityLinuxARMValidAS, + 'VolatilityLinuxARM64ValidAS' : VolatilityLinuxARM64ValidAS, 'kernel_param' : kernel_param, 'kparam_array' : kparam_array, 'desc_struct' : desc_struct, From 79741914a25af1f027823c2106868491b9ca8592 Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Wed, 10 Jun 2020 18:22:12 +0000 Subject: [PATCH 4/7] VolatilityDTB: add support for arm64 use the correct size, symbol, shofts for arm64. Don't assume first dt entry cannot be zero. --- volatility/plugins/overlays/linux/linux.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/volatility/plugins/overlays/linux/linux.py b/volatility/plugins/overlays/linux/linux.py index 23b0f0de3..516262de1 100644 --- a/volatility/plugins/overlays/linux/linux.py +++ b/volatility/plugins/overlays/linux/linux.py @@ -2331,7 +2331,12 @@ def generate_suggestions(self): config = self.obj_vm.get_config() tbl = self.obj_vm.profile.sys_map["kernel"] - if profile.metadata.get('memory_model', '32bit') == "32bit": + if profile.metadata.get('arch') == 'arm64': + sym = "swapper_pg_dir" + shifts = [0xffff000000000000, 0xffff800000000000] + read_sz = 8 + fmt = " Date: Wed, 10 Jun 2020 18:22:29 +0000 Subject: [PATCH 5/7] arm64: support dual-pagetable The main pagetable volatility finds is used for TTBR1, it is used to translate all kernel-space addesses. Userspace addresses should be translated with the per-process dtb, which is used for TTBR0. An address is known to be kernel or userspace by looking at it's high bit. --- volatility/plugins/addrspaces/arm64.py | 13 ++++++++++++- volatility/plugins/overlays/linux/linux.py | 7 ++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/volatility/plugins/addrspaces/arm64.py b/volatility/plugins/addrspaces/arm64.py index 3883b0c30..cca1c2efe 100644 --- a/volatility/plugins/addrspaces/arm64.py +++ b/volatility/plugins/addrspaces/arm64.py @@ -32,6 +32,7 @@ class Arm64AddressSpace(paged.AbstractWritablePagedMemory): minimum_size = 0x1000 alignment_gcd = 0x1000 _longlong_struct = struct.Struct('> 63): + return self.ptbl_walk(vaddr, type(self)._valid_dtb_base) + else: + return self.ptbl_walk(vaddr, self.dtb) + + def set_curr_base_valid(self): + cls = type(self) + if not cls._valid_dtb_base: + cls._valid_dtb_base = self.dtb # FIXME # this is supposed to return all valid physical addresses based on the current dtb diff --git a/volatility/plugins/overlays/linux/linux.py b/volatility/plugins/overlays/linux/linux.py index 516262de1..7f348a3bf 100644 --- a/volatility/plugins/overlays/linux/linux.py +++ b/volatility/plugins/overlays/linux/linux.py @@ -2516,7 +2516,12 @@ def generate_suggestions(self): if not do_fork_paddr or not task_paddr: return - yield do_fork_paddr - task_paddr == do_fork_addr - init_task_addr + res = (do_fork_paddr - task_paddr == do_fork_addr - init_task_addr) + + if res: + self.obj_vm.set_curr_base_valid() + + yield res class LinuxObjectClasses(obj.ProfileModification): conditions = {'os': lambda x: x == 'linux'} From f3d7c545c66713be5cf6b18937a5d2a1c0aaa24e Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Tue, 16 Jun 2020 14:34:16 +0000 Subject: [PATCH 6/7] arm64: support get_available_pages This implementation parses only the user-pagetable, to avoid returning the entire kernel mapping for each user process. --- volatility/plugins/addrspaces/arm64.py | 40 ++++++++++++++++++++------ 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/volatility/plugins/addrspaces/arm64.py b/volatility/plugins/addrspaces/arm64.py index cca1c2efe..4742cc632 100644 --- a/volatility/plugins/addrspaces/arm64.py +++ b/volatility/plugins/addrspaces/arm64.py @@ -33,6 +33,7 @@ class Arm64AddressSpace(paged.AbstractWritablePagedMemory): alignment_gcd = 0x1000 _longlong_struct = struct.Struct(' Date: Wed, 19 Aug 2020 10:44:05 +0000 Subject: [PATCH 7/7] arm64: support 64KB pagetables --- volatility/plugins/addrspaces/arm64.py | 36 +++++++++++++++++++------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/volatility/plugins/addrspaces/arm64.py b/volatility/plugins/addrspaces/arm64.py index 4742cc632..8956308e7 100644 --- a/volatility/plugins/addrspaces/arm64.py +++ b/volatility/plugins/addrspaces/arm64.py @@ -22,7 +22,7 @@ import volatility.plugins.addrspaces.paged as paged -class Arm64AddressSpace(paged.AbstractWritablePagedMemory): +class AbstractArm64AddressSpace(paged.AbstractWritablePagedMemory): """Address space for ARM64 processors""" order = 800 @@ -33,7 +33,6 @@ class Arm64AddressSpace(paged.AbstractWritablePagedMemory): alignment_gcd = 0x1000 _longlong_struct = struct.Struct('> self.ptbl_lowbits(level)) & 0x1ff + return (vaddr >> self.ptbl_lowbits(level)) & ((1 << self.pgtable_level_bits) - 1) def ptbl_walk(self, vaddr, base): if (vaddr == 0): return None - for level in xrange(4): + + # 4KB pages will further mask only 48 bits using ptbl_index + vaddr = vaddr & ((1 << 52) - 1) + + for level in xrange(self.pgtable_level_start, 4): index = self.ptbl_index(vaddr, level) entry = self.read_longlong_phys(base + (index << 3)) + debug.debug("level: {0:d} index: {1:3x} base: {2:16x} entry {3:16x}".format(level, index, base, entry), 4) if not entry: return None entry_addressbits = entry & self._entry_addressmask @@ -72,7 +76,9 @@ def ptbl_walk(self, vaddr, base): # entry points to final address if (level == 3 or (entry & 0x3 == 0x1)): lowbitmask = (1 << self.ptbl_lowbits(level)) - 1 - return entry_addressbits & (~lowbitmask) | (vaddr & lowbitmask) + result = entry_addressbits & (~lowbitmask) | (vaddr & lowbitmask) + debug.debug("-result {0:16x}".format(result), 4) + return result # entry points to next table base = entry_addressbits return None @@ -93,12 +99,12 @@ def set_curr_base_valid(self): cls._valid_dtb_base = self.dtb def get_available_pages(self): - level = 0 + level = self.pgtable_level_start ptbl_descs = [self.dtb, 0, 0, 0] ptbl_indexes = [0, 0, 0, 0] while True: - if ptbl_indexes[level] == 512: - if level == 0: + if ptbl_indexes[level] == 1 << self.pgtable_level_bits: + if level == self.pgtable_level_start: break level -= 1 ptbl_indexes[level] += 1 @@ -123,3 +129,15 @@ def get_available_pages(self): ptbl_indexes[level] += 1 + +# 4-level , 4KB pagetable +class Arm64AddressSpace_4KB(AbstractArm64AddressSpace): + pgtable_level_bits = 9 + _entry_addressmask = ((1 << 48) - 1) & (~((1 << 12) - 1)) + pgtable_level_start = 0 + +# 3-level 64KB pagetable, for kernel compiled with CONFIG_ARM64_PA_BITS_52 +class Arm64AddressSpace_64KB(AbstractArm64AddressSpace): + pgtable_level_bits = 13 + _entry_addressmask = ((1 << 48) - 1) & (~((1 << 16) - 1)) + pgtable_level_start = 1