volatility3 icon indicating copy to clipboard operation
volatility3 copied to clipboard

Linux: add sanity check in `find_aslr`

Open vobst opened this issue 2 years ago • 1 comments

Describe the bug We encountered a situation where Volatility would wrongly assume that a memory image had a virtual ASLR of zero because the first matching init_task was in a fragment of the .data section of the kernel ELF file in the page cache, instead of the one in the running kernel's .data section. The kernel ELF file was opened prior to acquisition and thus fragments of it were scattered in physical memory.

The kernel ELF file's .data section contains the init_task's task_struct with values for the files, thread_pid, fs, ... members initialized to the non-randomized addresses that are also found in the system map.

$ objcopy --dump-section .data=/tmp/linux.data bpfvol3-roboxes-centos9s-vmlinux_extracted-8b0ac.elf
$ readelf --symbols bpfvol3-roboxes-centos9s-vmlinux_extracted-8b0ac.elf | rg init_files
189146: ffffffff83084800   704 OBJECT  GLOBAL DEFAULT   28 init_files
$ rg 'init_files' bpfvol3-roboxes-centos9s-vmlinux_extracted-8b0ac.map
171871:ffffffff83084800 D init_files
$ xxd /tmp/linux.data | rg swapper -C 30 | rg 0048
0001b650: 20d2 0883 ffff ffff 0048 0883 ffff ffff   ........H......

Volatility currently picks the first valid-looking init_task it finds in the image and thus if such a fragment of the data section precedes the real init_task a virtual ASLR of zero is determined by the following code:

            aslr_shift = (
                init_task.files.cast("long unsigned int")
                - module.get_symbol("init_files").address
            )

To identify such "phony" matches I'd propose two heuristics:

  1. "match is phony" <=> .active_mm == init_mm where the address of init_mm is taken from the system map/json symbols. This works because kernel threads, i.e. tasks with .mm=NULL, "steal" the active_mm of the previous task when they are scheduled to run on a CPU. Thus, as long as some CPU on the system has been idle at least once the active_mm of the real init_task will be unequal to init_mm, even on systems that really had no ASLR. (Theoretically it would be possible to construct an edge case where this is wrong, but it is incredibly unlikely ^^)
  2. There are multiple doubly-linked lists in the init_task's task_struct that are statically initialized to be empty. Thus, a possible heuristic based on this would be: "match is phony" <=> .tasks.next == .tasks.prev, which should never be the case for the real init_task.

I personally prefer the latter option due to its simplicity (just tell me if you think so too and I'll happily remove the other one from the PR).

Context Volatility Version: a4b927f

To Reproduce In case you are interested I uploaded some dump+symbol that exhibits this behavior. Download

Process listing plugins will show no output (empty task list), other plugins like kmsg will list dereference invalid pointers and crash.

vobst avatar Dec 20 '23 17:12 vobst

@atcuno could you give this a look over please, it's very short but I just can't tell if the technique it's using is valid or if there'll be corner cases that might go wrong...

ikelos avatar Feb 01 '24 11:02 ikelos