KASLR (Finding Kernel address)
Hello everyone, welcome to my blogpost. Today, I will be covering Linux kernel address space layout randomization. In this blogpost, I will explain what KASLR means, the attacks, and how we can find the kernel address. If you are ready, let's kick off!
What does KASLR mean?
KASLR is a security method that changes where in memory the kernel code is loaded when the computer first starts up. AsLR now has something extra that works in the kernel area: Address Area Layout Randomization (ASLR). KASLR’s main goal is to make it harder for hackers to figure out where core data and code are kept. Attacks that need to know these numbers can’t happen now.
A very important security feature of modern operating systems is Kernel Address Space Layout Randomization (KASLR). It stops hacks that use memory. Two important parts of KASLR are shown in the picture: randomization at start-up and randomization while a process is going. A random number generator is used by KASLR to set up random base addresses for loading kernel components when the system first starts up. This randomization at the start makes sure that every time the system starts up, the kernel’s memory structure is different. This makes it harder for attackers to guess where in memory certain kernel parts are kept. The “per execute” part of the diagram shows that KASLR adds another level of randomness each time a process starts. Bad guys can’t use what they know about the memory layout from one run to help them with later ones since the addresses are changed all the time. For each case, the names of the kernel parts (the pink and red blocks with the number “0x” on them) are picked at random. Remember that because the target is always moving, memory-based tactics like ROP or buffer overflows are much less likely to work. In order to protect the system, randomization changes the memory layout so that attackers have to guess where kernel components are stored instead of using fixed, known addresses.
You might be asking what the difference is between ASLR and KASLR. Well, let me explain a bit:
Address Space Layout Randomization (ASLR) and Kernel Address Space Layout Randomization (KASLR) are both security methods that use randomization of memory to keep threats at bay, but they are very different in how they work and how much they protect. The main job of ASLR is to randomly arrange the memory of user-space processes, which includes the stack, heap, libraries, and executable code. This is done every time a new process is started. KASLR, on the other hand, only affects the kernel. It changes the kernel’s memory layout once at boot time and keeps it that way until the next system restart. KASLR tries to protect the whole operating system kernel, while ASLR only protects specific apps. KASLR is usually harder to set up because of limitations in architecture and the need to keep kernel functionality. Because of limits in hardware and memory management, KASLR usually has less entropy than ASLR. This is because there are fewer random locations that can be chosen. KASLR is also more likely to have information leaks, as a single leak could make the whole kernel memory layout public until the next reboot. ASLR doesn’t have much of an effect on individual processes in terms of performance, while KASLR may add a little extra work when the system boots up but doesn’t have much of an effect when it’s running. Even though these methods are different, they are both very important for improving system security because they make it harder for attackers to guess memory addresses and run memory-based attacks.
Boot time and Runtime randomization
There are two different ways to apply address space layout randomization: at boot time and during runtime. Each has its own features and uses. Randomization at boot time, which is often linked to KASLR, happens only once when the system starts up and mostly affects the kernel code and data areas. It uses sources of entropy that are available early in the boot process and keeps the structure random until the next reboot. In comparison, runtime randomization, which is often used for user-space ASLR, happens every time a program is run or a new process is started. This method uses a wider range of entropy sources and gives each process its own unique random structure. Their timing, flexibility, performance effect, and scope of protection are the main things that make them different. Boot-time randomization may slow down the system a little, but it protects the kernel consistently. Runtime randomization, on the other hand, protects user-space apps more dynamically by spreading its effects across process creations. It’s possible that boot-time methods have less entropy than runtime methods, which could mean that the randomness isn’t as good. Some more advanced methods, like function-granular KASLR, try to improve randomization security at boot time. Other methods, like Randezvous, use both compile-time and boot-time techniques to provide better security on certain hardware. Ultimately, both ways are very important for making a system safer. Boot-time randomization protects the kernel, while runtime randomization protects individual processes.
# Check multiple boots
# Reboot your system and run the same command - you'll see different addresses
noob@noob:~/kernel-programming$ sudo cat /proc/kallsyms | grep "T _text"
ffffffff8a600000 T _text
The base address of the kernel text section in memory is shown by this command. This is an important part of how KASLR works in Linux. We can break down what we see: “ffffffff8a600000” is the hexadecimal address of the spot in memory where the kernel’s text section (executable code) is currently loaded. The “T” means that this is a text (code) part, and “_text” is the name of the symbol that starts the text section of the kernel. Under KASLR, this address will be different every time you restart the machine, which makes it even more interesting. Your computer would have a different base address the next time you ran the same command. It might be “ffffffff89400000” or some other random number. This randomization is what makes KASLR a good security tool; it stops attackers from knowing for sure where the kernel code is stored in memory. It always starts with “ffffffff” because the kernel works in the upper half of the virtual address space, which is set aside for kernel use in the x86_64 design, and user space programs work in the lower half.
# View kernel memory layout
noob@noob:~/kernel-programming$ sudo cat /proc/iomem | grep "Kernel"
23e800000-23fdfffff : Kernel code
23fe00000-240bfefff : Kernel rodata
240c00000-241056aff : Kernel data
241561000-2419fffff : Kernel bss
This output from /proc/iomem shows how the memory is currently laid out for different kernel parts. In hexadecimal code, each line shows a different part of the kernel and the memory range that goes with it. The kernel’s commands that can be run are in the “Kernel code” section (23e800000–23fdfffff). The “Kernel rodata” area (23fe00000–240bfefff) is right next to it. It holds read-only data like strings and constant values. The “Kernel data” part (240c00000–241056aff) comes next. It has initialized variables and other data structures that can be changed that the kernel uses. Lastly, the “Kernel BS” area (241561000–2419fffff) has data that hasn’t been set up yet. All of these addresses are in physical memory, and because of KASLR, they will be different every time the machine starts up again. This splitting into different areas (code, rodata, data, and BS) is important for both security and memory management because it lets the kernel protect and allow access to each section in different ways. For example, the code section could be executable but read-only, while the data section could be both read and written to.
# For more detailed memory map
noob@noob:~/kernel-programming$ sudo cat /proc/vmallocinfo
0xffffa79040000000-0xffffa79040005000 20480 irq_init_percpu_irqstack+0x114/0x1b0 vmap
0xffffa79040005000-0xffffa79040007000 8192 acpi_os_map_iomem+0x20a/0x240 phys=0x00000000bfefe000 ioremap
0xffffa79040007000-0xffffa79040009000 8192 acpi_os_map_iomem+0x20a/0x240 phys=0x00000000bfeff000 ioremap
0xffffa79040009000-0xffffa7904000b000 8192 acpi_os_map_iomem+0x20a/0x240 phys=0x00000000bfee5000 ioremap
0xffffa7904000b000-0xffffa7904000d000 8192 hpet_enable+0xb5/0x510 phys=0x00000000fed00000 ioremap
0xffffa7904000d000-0xffffa7904000f000 8192 gen_pool_add_owner+0x4b/0xf0 pages=1 vmalloc N0=1
0xffffa79040010000-0xffffa79040015000 20480 dup_task_struct+0x5b/0x1b0 pages=4 vmalloc N0=4
0xffffa79040015000-0xffffa79040017000 8192 bpf_prog_alloc_no_stats+0x42/0x290 pages=1 vmalloc N0=1
0xffffa79040018000-0xffffa7904001d000 20480 dup_task_struct+0x5b/0x1b0 pages=4 vmalloc N0=4
0xffffa7904001d000-0xffffa7904001f000 8192 gen_pool_add_owner+0x4b/0xf0 pages=1 vmalloc N0=1
0xffffa79040020000-0xffffa7904003a000 106496 acpi_os_map_iomem+0x20a/0x240 phys=0x00000000bfee6000 ioremap
0xffffa7904003a000-0xffffa7904003c000 8192 gen_pool_add_owner+0x4b/0xf0 pages=1 vmalloc N0=1
0xffffa7904003c000-0xffffa79040041000 20480 dup_task_struct+0x5b/0x1b0 pages=4 vmalloc N0=4
0xffffa79040041000-0xffffa79040043000 8192 gen_pool_add_owner+0x4b/0xf0 pages=1 vmalloc N0=1
0xffffa79040044000-0xffffa79040049000 20480 dup_task_struct+0x5b/0x1b0 pages=4 vmalloc N0=4
0xffffa79040049000-0xffffa7904004b000 8192 devm_ioremap+0x4a/0xb0 phys=0x00000000fd5ff000 ioremap
0xffffa7904004c000-0xffffa79040051000 20480 dup_task_struct+0x5b/0x1b0 pages=4 vmalloc N0=4
0xffffa79040051000-0xffffa79040053000 8192 acpi_os_map_iomem+0x20a/0x240 phys=0x00000000fe800000 ioremap
0xffffa79040054000-0xffffa79040059000 20480 dup_task_struct+0x5b/0x1b0 pages=4 vmalloc N0=4
0xffffa79040059000-0xffffa7904005b000 8192 acpi_os_map_iomem+0x20a/0x240 phys=0x00000000fe829000 ioremap
0xffffa7904005c000-0xffffa79040061000 20480 dup_task_struct+0x5b/0x1b0 pages=4 vmalloc N0=4
0xffffa79040061000-0xffffa79040063000 8192 pci_iomap+0xb7/0x1d0 phys=0x00000000fd5fe000 ioremap
0xffffa79040069000-0xffffa7904006b000 8192 bpf_prog_alloc_no_stats+0x42/0x290 pages=1 vmalloc N0=1
0xffffa79040071000-0xffffa79040073000 8192 bpf_prog_alloc_no_stats+0x42/0x290 pages=1 vmalloc N0=1
0xffffa79040073000-0xffffa79040078000 20480 ttm_bo_kmap+0x1c9/0x300 [ttm]
0xffffa79040079000-0xffffa7904007c000 12288 bpf_prog_alloc_no_stats+0x42/0x290 pages=2 vmalloc N0=2
0xffffa7904007c000-0xffffa79040081000 20480 dup_task_struct+0x5b/0x1b0 pages=4 vmalloc N0=4
0xffffa79040081000-0xffffa79040083000 8192 vmw_devcaps_create+0x35/0x130 [vmwgfx] pages=1 vmalloc N0=1
0xffffa79040084000-0xffffa79040089000 20480 dup_task_struct+0x5b/0x1b0 pages=4 vmalloc N0=4
0xffffa79040089000-0xffffa7904008c000 12288 drm_fbdev_generic_helper_fb_probe+0xa5/0x190 pages=2 vmalloc N0=2
0xffffa7904008c000-0xffffa79040091000 20480 dup_task_struct+0x5b/0x1b0 pages=4 vmalloc N0=4
0xffffa79040091000-0xffffa79040094000 12288 bpf_prog_alloc_no_stats+0x42/0x290 pages=2 vmalloc N0=2
0xffffa79040094000-0xffffa79040099000 20480 dup_task_struct+0x5b/0x1b0 pages=4 vmalloc N0=4
0xffffa79040099000-0xffffa7904009b000 8192 vcalloc+0x1e/0x40 pages=1 vmalloc N0=1
0xffffa7904009c000-0xffffa790400a1000 20480 dup_task_struct+0x5b/0x1b0 pages=4 vmalloc N0=4
0xffffa790400a1000-0xffffa790400a4000 12288 bpf_prog_alloc_no_stats+0x42/0x290 pages=2 vmalloc N0=2
0xffffa790400a4000-0xffffa790400a9000 20480 dup_task_struct+0x5b/0x1b0 pages=4 vmalloc N0=4
0xffffa790400a9000-0xffffa790400ab000 8192 bpf_prog_alloc_no_stats+0x42/0x290 pages=1 vmalloc N0=1
0xffffa790400ac000-0xffffa790400b1000 20480 dup_task_struct+0x5b/0x1b0 pages=4 vmalloc N0=4
0xffffa790400b1000-0xffffa790400b4000 12288 bpf_prog_alloc_no_stats+0x42/0x290 pages=2 vmalloc N0=2
0xffffa790400b4000-0xffffa790400b9000 20480 dup_task_struct+0x5b/0x1b0 pages=4 vmalloc N0=4
0xffffa790400b9000-0xffffa790400bb000 8192 __pci_enable_msix_range+0x303/0x5b0 phys=0x00000000febc0000 ioremap
0xffffa790400bc000-0xffffa790400c1000 20480 dup_task_struct+0x5b/0x1b0 pages=4 vmalloc N0=4
0xffffa790400c1000-0xffffa790400c3000 8192 qp_alloc_queue.isra.0+0x44/0x130 [vmw_vmci] pages=1 vmalloc N0=1
0xffffa790400c4000-0xffffa790400c9000 20480 dup_task_struct+0x5b/0x1b0 pages=4 vmalloc N0=4
0xffffa790400c9000-0xffffa790400cb000 8192 bpf_prog_alloc_no_stats+0x42/0x290 pages=1 vmalloc N0=1
0xffffa790400cc000-0xffffa790400d1000 20480 dup_task_struct+0x5b/0x1b0 pages=4 vmalloc N0=4
0xffffa790400d1000-0xffffa790400d3000 8192 bpf_prog_alloc_no_stats+0x42/0x290 pages=1 vmalloc N0=1
0xffffa790400d4000-0xffffa790400d9000 20480 dup_task_struct+0x5b/0x1b0 pages=4 vmalloc N0=4
0xffffa790400d9000-0xffffa790400db000 8192 bpf_prog_alloc_no_stats+0x42/0x290 pages=1 vmalloc N0=1
0xffffa790400dc000-0xffffa790400e1000 20480 dup_task_struct+0x5b/0x1b0 pages=4 vmalloc N0=4
0xffffa790400e1000-0xffffa790400e3000 8192 bpf_prog_alloc_no_stats+0x42/0x290 pages=1 vmalloc N0=1
0xffffa790400e4000-0xffffa790400e9000 20480 dup_task_struct+0x5b/0x1b0 pages=4 vmalloc N0=4
0xffffa790400e9000-0xffffa790400eb000 8192 bpf_prog_alloc_no_stats+0x42/0x290 pages=1 vmalloc N0=1
0xffffa790400ec000-0xffffa790400f1000 20480 dup_task_struct+0x5b/0x1b0 pages=4 vmalloc N0=4
0xffffa790400f1000-0xffffa790400f3000 8192 bpf_prog_alloc_no_stats+0x42/0x290 pages=1 vmalloc N0=1
0xffffa790400f4000-0xffffa790400f9000 20480 dup_task_struct+0x5b/0x1b0 pages=4 vmalloc N0=4
0xffffa790400f9000-0xffffa790400fb000 8192 bpf_prog_alloc_no_stats+0x42/0x290 pages=1 vmalloc N0=1
0xffffa790400fc000-0xffffa79040101000 20480 dup_task_struct+0x5b/0x1b0 pages=4 vmalloc N0=4
0xffffa79040101000-0xffffa79040103000 8192 bpf_prog_alloc_no_stats+0x42/0x290 pages=1 vmalloc N0=1
0xffffa79040104000-0xffffa79040109000 20480 dup_task_struct+0x5b/0x1b0 pages=4 vmalloc N0=4
0xffffa79040109000-0xffffa7904010b000 8192 bpf_prog_alloc_no_stats+0x42/0x290 pages=1 vmalloc N0=1
0xffffa7904010c000-0xffffa79040111000 20480 dup_task_struct+0x5b/0x1b0 pages=4 vmalloc N0=4
0xffffa79040111000-0xffffa79040113000 8192 bpf_prog_alloc_no_stats+0x42/0x290 pages=1 vmalloc N0=1
0xffffa79040114000-0xffffa79040119000 20480 dup_task_struct+0x5b/0x1b0 pages=4 vmalloc N0=4
0xffffa79040119000-0xffffa7904011b000 8192 bpf_prog_alloc_no_stats+0x42/0x290 pages=1 vmalloc N0=1
0xffffa7904011c000-0xffffa79040121000 20480 dup_task_struct+0x5b/0x1b0 pages=4 vmalloc N0=4
0xffffa79040121000-0xffffa79040123000 8192 bpf_prog_alloc_no_stats+0x42/0x290 pages=1 vmalloc N0=1
0xffffa79040124000-0xffffa79040129000 20480 dup_task_struct+0x5b/0x1b0 pages=4 vmalloc N0=4
0xffffa79040129000-0xffffa7904012b000 8192 bpf_prog_alloc_no_stats+0x42/0x290 pages=1 vmalloc N0=1
0xffffa7904012c000-0xffffa79040131000 20480 dup_task_struct+0x5b/0x1b0 pages=4 vmalloc N0=4
0xffffa79040131000-0xffffa79040133000 8192 bpf_prog_alloc_no_stats+0x42/0x290 pages=1 vmalloc N0=1
0xffffa79040134000-0xffffa79040139000 20480 dup_task_struct+0x5b/0x1b0 pages=4 vmalloc N0=4
0xffffa79040139000-0xffffa7904013b000 8192 bpf_prog_alloc_no_stats+0x42/0x290 pages=1 vmalloc N0=1
0xffffa7904013c000-0xffffa79040141000 20480 dup_task_struct+0x5b/0x1b0 pages=4 vmalloc N0=4
0xffffa79040141000-0xffffa79040143000 8192 bpf_prog_alloc_no_stats+0x42/0x290 pages=1 vmalloc N0=1
The output from /proc/vmallocinfo shows in detail how the kernel handles its virtual address space by showing how it assigns virtual memory. There are numbers in the kernel’s virtual address space (beginning with “ffffa7”) for each line in the output, which shows a different memory allocation. The format shows the beginning and ending addresses of each allocation, as well as the size of the allocation in bytes, the code that asked for it, and other information about the memory’s properties. When we look at the patterns, we can see that tasks like “dup_task_struct” (for creating processes) often use 20480 bytes of memory, “bpf_prog_alloc_no_stats” (for allocating BPF programs) usually use 8192 bytes, and different “ioremap” operations that map physical memory into kernel virtual space. Each allocation also has details about the type of allocation (vmalloc, ioremap, etc.) and the NUMA node allocation (N0=1 means Node 0). This information is very helpful for debugging kernels, finding memory leaks, and figuring out how the kernel is using its virtual address space under KASLR. This is because these numbers are randomly assigned and will be different each time the system boots.
noob@noob:~/kernel-programming$ cat /proc/cmdline
BOOT_IMAGE=/boot/vmlinuz-6.8.0-51-generic root=UUID=a88393e5-05d1-4382-9f83-b861dd10b0c4 ro quiet splash
This output from /proc/cmdline shows the kernel boot parameters that were passed to your Linux kernel during system startup. Breaking it down: “BOOT_IMAGE=/boot/vmlinuz-6.8.0–51-generic” indicates the path to your kernel image file, where 6.8.0–51-generic is the kernel version. The “root=UUID=a88393e5–05d1–4382–9f83-b861dd10b0c4” parameter tells the kernel which partition to use as the root filesystem, identified by its UUID (Universally Unique Identifier). The “ro” parameter specifies that the root filesystem should be initially mounted read-only during boot. The “quiet” parameter reduces the verbosity of kernel messages during boot, and “splash” enables the boot splash screen. Notably, there’s no “nokaslr” parameter present in the cmdline, which means KASLR is enabled by default on your system.
Common Methods for Finding Kernel Addresses
Finding kernel addresses is a very important part of both kernel exploitation and security study. Finding or guessing kernel codes can be done in a number of common ways, each with its own pros and cons. One way to get kernel module information directly is to use system calls like NtQuerySystemInformation on Windows or read from /proc/kallsyms on Linux. The “scan backwards” method is another way to do it. It needs a kernel address that has been stolen and will scan memory backwards until it finds the kernel’s PE header. Some attacks use known Relative Virtual Addresses (RVAs) that are hard-coded, but this needs accurate version detection. Kernel Address Space Layout Randomization (KASLR) makes these attempts harder, but there are ways to get around it, like using side-channel attacks or finding information leaks in kernel logs. You can look through kernel memory for certain numbers with memory searching tools, such as the “search” command in the crash utility. The /proc folder on Linux gives you useful details about how memory is mapped and where kernel addresses are. For more advanced methods, you might need to look at hardware-specific structures like the Interrupt Descriptor Table (IDT) or take advantage of flaws in kernel components to get addresses. For both offensive security study and defensive kernel hardening, it’s important to understand these methods.
We will learn through some practical examples below:
Kernel security research is based on finding kernel addresses. To understand and protect kernel memory, security researchers use a variety of methods. Accessing system information through the /proc filesystem is the easiest way to do this on Linux systems. This can give you clear information about the layout of kernel memory and where symbols are located. This filesystem holds important details about how the system’s memory is organized, which modules are loaded, and different kernel settings. These addresses are changed randomly by current security features like KASLR, which makes this harder to do. Leaks of information can happen through kernel logs (dmesg) or system interfaces that show memory addresses by mistake. Both security researchers working on kernel hardening and system managers checking the security of their systems need to know how these methods work. Each way shows different parts of the kernel’s memory layout. When used together, they give a full picture of how the kernel sets up its address space.
- Basic Kernel Information:
# View kernel symbols (requires root)
noob@noob:~/kernel-programming$ sudo cat /proc/kallsyms | grep ' T ' | head -n 5
ffffffff8a600000 T _stext
ffffffff8a600000 T _text
ffffffff8a600080 T startup_64
ffffffff8a6000f0 T secondary_startup_64
ffffffff8a6000f5 T secondary_startup_64_no_verify
# Check kernel module information
noob@noob:~/kernel-programming$ lsmod | head
sudo cat /proc/modules | head
Module Size Used by
isofs 61440 1
snd_seq_dummy 12288 0
snd_hrtimer 12288 1
intel_rapl_msr 20480 0
intel_rapl_common 40960 1 intel_rapl_msr
intel_uncore_frequency_common 16384 0
intel_pmc_core 118784 0
intel_vsec 20480 1 intel_pmc_core
pmt_telemetry 16384 1 intel_pmc_core
isofs 61440 1 - Live 0xffffffffc08e6000
snd_seq_dummy 12288 0 - Live 0xffffffffc08e0000
snd_hrtimer 12288 1 - Live 0xffffffffc083e000
intel_rapl_msr 20480 0 - Live 0xffffffffc08d3000
intel_rapl_common 40960 1 intel_rapl_msr, Live 0xffffffffc08c5000
intel_uncore_frequency_common 16384 0 - Live 0xffffffffc08be000
intel_pmc_core 118784 0 - Live 0xffffffffc089d000
intel_vsec 20480 1 intel_pmc_core, Live 0xffffffffc0894000
pmt_telemetry 16384 1 intel_pmc_core, Live 0xffffffffc083a000
snd_ens1371 36864 1 - Live 0xffffffffc0849000
This shows two important parts of the kernel memory structure and information about modules. Here is the first part of /proc/kallsyms that shows the kernel’s text segment symbols. It shows the starting address (ffffffff8a600000) of the kernel’s text section as well as some initial functions, such as startup_64 and secondary_startup_64. For safety, KASLR changes these locations every time the computer boots up. Both the lsmod and /proc/modules tools are used in the second part to show the loaded kernel modules. Some of the modules listed in the result are isofs (6,1440 bytes), snd_seq_dummy, snd_hrtimer, which are sound modules, and intel_rapl_msr and intel_pmc_core, which are Intel-specific modules. The entry for each module shows its size, the number of times it has been used, and most importantly, its loaded position in memory, which for isofs is 0xffffffffc08e6000. The status “live” means that these modules are running in the kernel right now. The “Used by” count and the full list in /proc/modules show how modules depend on each other. Access to these addresses is often limited on modern systems to protect against possible security holes. However, this knowledge is very helpful for understanding the kernel’s memory layout and module relationships.
2. Kernel logs for Information leaks
# Check kernel logs for address information
noob@noob:~/kernel-programming$ sudo dmesg | grep -i address
[ 0.519211] IOAPIC[0]: apic_id 1, version 32, address 0xfec00000, GSI 0-23
noob@noob:~/kernel-programming$ sudo dmesg | grep -i memory
[ 0.008074] ACPI: Reserving FACP table memory at [mem 0xbfefee73-0xbfefef66]
[ 0.008076] ACPI: Reserving DSDT table memory at [mem 0xbfee6d27-0xbfefee72]
[ 0.008076] ACPI: Reserving FACS table memory at [mem 0xbfefffc0-0xbfefffff]
[ 0.008077] ACPI: Reserving FACS table memory at [mem 0xbfefffc0-0xbfefffff]
[ 0.008077] ACPI: Reserving BOOT table memory at [mem 0xbfee6cff-0xbfee6d26]
[ 0.008078] ACPI: Reserving APIC table memory at [mem 0xbfee65bd-0xbfee6cfe]
[ 0.008078] ACPI: Reserving MCFG table memory at [mem 0xbfee6581-0xbfee65bc]
[ 0.008079] ACPI: Reserving SRAT table memory at [mem 0xbfee572f-0xbfee5ffe]
[ 0.008079] ACPI: Reserving HPET table memory at [mem 0xbfee56f7-0xbfee572e]
[ 0.008080] ACPI: Reserving WAET table memory at [mem 0xbfee56cf-0xbfee56f6]
[ 0.010626] Early memory node ranges
[ 0.519237] PM: hibernation: Registered nosave memory: [mem 0x00000000-0x00000fff]
[ 0.519239] PM: hibernation: Registered nosave memory: [mem 0x0009e000-0x0009efff]
[ 0.519240] PM: hibernation: Registered nosave memory: [mem 0x0009f000-0x0009ffff]
[ 0.519240] PM: hibernation: Registered nosave memory: [mem 0x000a0000-0x000dbfff]
[ 0.519241] PM: hibernation: Registered nosave memory: [mem 0x000dc000-0x000fffff]
[ 0.519242] PM: hibernation: Registered nosave memory: [mem 0xbfee0000-0xbfefefff]
[ 0.519242] PM: hibernation: Registered nosave memory: [mem 0xbfeff000-0xbfefffff]
[ 0.519243] PM: hibernation: Registered nosave memory: [mem 0xc0000000-0xefffffff]
[ 0.519244] PM: hibernation: Registered nosave memory: [mem 0xf0000000-0xf7ffffff]
[ 0.519244] PM: hibernation: Registered nosave memory: [mem 0xf8000000-0xfebfffff]
[ 0.519245] PM: hibernation: Registered nosave memory: [mem 0xfec00000-0xfec0ffff]
[ 0.519245] PM: hibernation: Registered nosave memory: [mem 0xfec10000-0xfedfffff]
[ 0.519246] PM: hibernation: Registered nosave memory: [mem 0xfee00000-0xfee00fff]
[ 0.519246] PM: hibernation: Registered nosave memory: [mem 0xfee01000-0xfffdffff]
[ 0.519247] PM: hibernation: Registered nosave memory: [mem 0xfffe0000-0xffffffff]
[ 0.986909] Memory: 9910676K/10329588K available (22528K kernel code, 4442K rwdata, 14332K rodata, 4976K init, 4732K bss, 418652K reserved, 0K cma-reserved)
[ 1.032941] Freeing SMP alternatives memory: 48K
[ 1.053823] x86/mm: Memory block size: 128MB
[ 1.817633] Freeing initrd memory: 64220K
[ 1.889624] Freeing unused decrypted memory: 2028K
[ 1.890136] Freeing unused kernel image (initmem) memory: 4976K
[ 1.890344] Freeing unused kernel image (rodata/data gap) memory: 4K
[ 4.321265] systemd[1]: Listening on systemd-oomd.socket - Userspace Out-Of-Memory (OOM) Killer Socket.
[ 4.433376] vmwgfx 0000:00:0f.0: [drm] Legacy memory limits: VRAM = 4096 kB, FIFO = 256 kB, surface = 0 kB
[ 4.433380] vmwgfx 0000:00:0f.0: [drm] Maximum display memory size is 262144 kiB
This message data tells us a lot about how system memory and addresses are being used during the boot process. The first grep for “address” shows the placement of the IOAPIC (I/O Advanced Programmable Interrupt Controller) setup, which is at physical address 0xfec00000. The more thorough memory-related output shows how the system initializes its memory, including ACPI (Advanced Configuration and Power Interface) table reservations at different memory locations that start with “bfef.” “PM: hibernation: Registered nosave memory” lines tell the system about protected memory areas that won’t be saved during hibernation. They record important memory ranges for hibernation. The result also shows how much memory the system is using, showing that out of a total of 10.3 GB, about 9.9 GB is available, with specific amounts set aside for kernel code (22528K), read-write data (4442K), read-only data (14332K), initialization (4976K), and BSS (4732K). There are also records that show memory being freed up after initialization. These include SMP alternatives, initial, and kernel image memory that isn’t being used. The last few articles show how VRAM and FIFO buffers are allocated for the VMware graphics driver (vmwgfx) and deal with graphics memory configuration. This detailed memory map is very important for learning how the system controls and assigns its physical memory resources.
3. System Security Settings:
# Check KASLR and memory randomization settings
noob@noob:~/kernel-programming$ cat /proc/cmdline | grep -i kaslr
sysctl kernel.randomize_va_space
sysctl kernel.kptr_restrict
kernel.randomize_va_space = 2
kernel.kptr_restrict = 1
This terminal output shows various kernel security mechanisms related to memory address randomization in Linux. The command queries both the kernel boot parameters (via /proc/cmdline) and current system settings (via sysctl) for KASLR (Kernel Address Space Layout Randomization) and related protections. The results indicate that address space randomization is fully enabled with a value of 2, meaning both stack and heap addresses are being randomized — this is a security feature that helps prevent memory-based attacks by making memory locations unpredictable. Additionally, kernel.kptr_restrict is set to 1, which means the system is hiding kernel pointer addresses in various kernel outputs and logs — another security measure that makes it harder for attackers to exploit kernel vulnerabilities by limiting information disclosure about kernel memory addresses. These settings suggest the system is configured with standard modern security hardening features enabled.
Let me summarize it: why is it important to check it out while using these commands? These are just examples:
Finding and analyzing kernel addresses is an important part of both studying system security and fixing kernel bugs. Researchers and system admins can learn more about how the kernel handles memory and how security features like KASLR work by looking at system interfaces such as /proc/kallsyms, /proc/modules, and kernel logs (dmesg). Looking at these addresses and memory layouts is important for many reasons: it helps security experts check how well address randomization works, it helps system administrators find memory leaks or other strange activities, and it gives developers the information they need to fix problems at the kernel level. Core tools, such as “sudo cat/proc/kallsyms,” show where kernel symbols are located. “lsmod” and “dmesg” results show loaded modules and memory-related events that happen while the system is running. This information is very helpful for protecting systems against memory-based threats and making sure that security features are used correctly. But it’s important to remember that modern systems often limit access to this kind of information as a safety measure. This is why you need the right permissions and know what the security risks might be when using these tools and tasks.
KASLR automation detectors in C
Before writing the code, let me explain some interesting points about the base address and others that require implementing it:
- Basic KASLR Offset Formula:
KASLR_OFFSET = RANDOM_NUMBER % MAX_SLIDE_SIZE
where MAX_SLIDE_SIZE = 1GB (0x40000000)
The Basic KASLR Offset Formula is what Kernel Address Space Layout Randomization is all about. At its core, the method finds a random offset that will be added to the boot sector’s base address. To find this position, you pick a random number and modulo it by the largest slide size, which is usually 1 GB or 0x40000000 in hexadecimal. When you use the formula KASLR_OFFSET = RANDOM_NUMBER % MAX_SLIDE_SIZE, it makes sure that the offset stays within the allowed range and meets the alignment needs. On x86_64 systems, this random offset is added to the 0xffffffff80000000 usual kernel base address to make the final random kernel location. This formula’s unpredictability is very important for system security because it makes it much harder for attackers to figure out where kernel code and data structures are located in memory. Even though the formula is simple, it is very important for current operating system security because it protects against many types of threats, especially those that depend on knowing how kernel memory is laid out. The modulo process makes sure that the offset stays within the acceptable range of kernel memory space even if an attacker changes the way the random number is generated.
KERNEL_BASE = DEFAULT_BASE + KASLR_OFFSET
where DEFAULT_BASE = 0xffffffff80000000 (x86_64)
The actual kernel base address calculation is the most important part of KASLR implementation because it tells you where the kernel will actually be in memory while the program is running. KERNEL_BASE = DEFAULT_BASE + KASLR_OFFSET is the solution. The final kernel location is made by adding the random offset that was already calculated to the system’s usual base address (0xffffffff80000000 on x86_64 systems). This process makes sure that the kernel stays in its assigned virtual address space, but that every time the computer starts up, its exact location is changed. The usual base address acts as a reference point and is the lowest location where the kernel can be loaded. The extra offset moves this target address up within the allowed range. This calculation is very important because it keeps the memory aligned correctly (usually 2 MB on current systems) to make sure the best speed and compatibility with hardware memory management features. The address that is given is used as a starting point for all kernel symbol places. Every function and data structure is placed based on this base. It is very important for security experts and system writers to understand this formula because it is the basis for analyzing kernel memory layout and finding security holes. Because this process adds randomness, it’s harder to guess exactly where kernel components are located. This makes memory-based attacks much harder, causing attackers to either guess addresses (which could cause system crashes) or find other ways to attack.
2. Actual Kernel Base Address:
VIRTUAL_ADDRESS = PHYSICAL_ADDRESS + PAGE_OFFSET
where PAGE_OFFSET = 0xffff888000000000 (typical x86_64)
The Virtual Address to Physical Address translation method is an important part of current operating systems, especially those that use x86_64 architectures, for managing memory. The equation VIRTUAL_ADDRESS = PHYSICAL_ADDRESS + PAGE_OFFSET creates a straight link between physical memory and a part of the kernel’s virtual address space. The base offset for this link is PAGE_OFFSET, which is usually 0xffff888000000000 on x86_64 systems. This translation is very important for the kernel to work because it makes it easy to access real memory through virtual addresses. The PAGE_OFFSET number is put in the upper half of the virtual address space on purpose. This makes sure that there is a clear boundary between the memory areas in user space and kernel space. By adding the PAGE_OFFSET to the physical address, this mapping makes it possible for the kernel to reach any physical memory location. This creates a linear mapping that makes managing memory easier. The way the method is used is especially important when direct memory access (DMA), memory-mapped I/O, and kernel memory allocators are present. This translation method is also needed for security features like Kernel Page Table Isolation (KPTI), which keeps user and kernel space page tables separate to protect against hardware flaws. Kernel workers who work on memory management subsystems, device drivers, or security mechanisms that need to quickly move between real and virtual address spaces need to know this formula.
3. Slide Range Calculation:
MODULE_BASE = KERNEL_BASE + MODULE_OFFSET
MODULE_OFFSET = RANDOM_NUMBER % MODULE_REGION_SIZE
A very important part of loading kernel modules and implementing KASLR for loadable kernel modules (LKMs) is figuring out the module base address. The formula MODULE_BASE = KERNEL_BASE + MODULE_OFFSET tells the computer where to load each kernel module into virtual memory. MODULE_OFFSET = RANDOM_NUMBER % tells the computer where to load each module. MODULE_REGION_SIZE makes sure that modules are placed randomly in the given module area. With this two-part method, modules are loaded at odd distances from the kernel’s base address, which makes the loading process safer. The MODULE_REGION_SIZE setting usually names a certain area of virtual memory that is set aside for loadable modules. The modulo action makes sure that the offset stays within this limited area while still meeting the alignment requirements. Randomization like this is very important for system security because it stops attackers from finding modules in regular places. The kernel uses this method to come up with a unique virtual address for each loaded module that doesn’t clash with any other modules that are also loaded. This keeps the benefits of KASLR. The way the method is used is especially important for systems that load and unload kernel modules all the time, because it needs to make sure that memory is used efficiently and security is maintained. Randomization must also be carefully handled to keep modules from overlapping and memory aligned correctly for the best performance. This math is very important for kernel developers working on module loaders, security experts looking at patterns in module placement, and system administrators managing kernel modules.
4. Physical to Virtual Address Translation:
ALIGNED_ADDRESS = (ADDRESS + ALIGNMENT - 1) & ~(ALIGNMENT - 1)
where ALIGNMENT = 2MB (0x200000)
The Memory Address Alignment Formula is one of the most important tools in kernel memory management for making sure that memory addresses are properly lined up. The equation ALIGNED_ADDRESS = (ADDRESS + ALIGNMENT—1) & ~(ALIGNMENT—1) uses a bitwise process to make sure that memory addresses are lined up with certain boundaries. For current x86_64 systems, ALIGNMENT is usually 2MB (0x200000). This alignment is very important for many reasons: it makes the best use of memory access patterns, makes sure that hardware memory management units (MMUs) can work with the system, and supports the big page features of current processors. To make sure we reach the next alignment boundary, the formula first adds (ALIGNMENT—1) to the address. It then does a bitwise AND with the reversed (ALIGNMENT — 1) value to clear the lower bits, which rounds down to the nearest aligned address. For instance, with 2MB alignment, this makes sure that addresses end up on 2MB borders, which is necessary for using large pages and getting the most out of the Translation Lookaside Buffer. The way the formula is used is especially important in KASLR because it keeps things aligned correctly while using randomness. This makes sure that speed gains aren’t given up for security. This alignment requirement is also important for hardware support, as many CPU features and memory management tasks need certain address layouts to work right. Kernel engineers who work on memory management tools need to know this formula. This is especially true for those who deal with page tables, memory allocation, and hardware interactions that need to be perfectly aligned.
and so on, I will not deep dive, especially for timing to detect “side- channel,” which will be another topic and also an interesting topic
Coding Time
Before coding, I usually always demonstrate the flowchart and then go on to explain what's going on. I believe showing the flowchart will make it easier to write the code. Lets go!
Worfklow-1
The first step of the KASLR (Kernel Address Space Layout Randomization) research tool is shown in this part of the flowchart. The program starts at “Start Program” and right away does a very important security check by using the `geteuid()` function to see if it has root access. This check makes two possible routes. The “Root” path goes straight to the “Continue” state if the program is running with root access. If it’s running without root rights, as shown by the “Non-root” path, it shows a warning message first to let the user know that some detection methods might not work because they don’t have enough access. This warning is important because a lot of actions that deal with the kernel need elevated rights to get to private system data. Once either path is taken, the program ends up in the “Continue” state and prints the KASLR Analysis Header. This starts the real analysis process. Users will be aware of any possible limits because of this starting structure, which also lets the program run with any amount of access.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
// Define the default base address (will be used later)
#define DEFAULT_BASE 0xffffffff80000000UL
int main() {
// START PROGRAM
// CHECK ROOT PRIVILEGES
if (geteuid() != 0) {
// NON-ROOT PATH: DISPLAY WARNING
printf("Warning: Running without root privileges (some methods may fail)\n\n");
}
// ROOT PATH leads directly to continue
// CONTINUE - happens for both paths
// PRINT KASLR ANALYSIS HEADER
printf("KASLR Analysis\n");
printf("---------------------------\n");
printf("Attempting multiple detection methods...\n\n");
// Code continues to method 1...
Workflow-2
This part of the flowchart shows the first way the program uses `/proc/kallsyms` to find the kernel base address. “Try Method 1: Kallsyms” starts the process. This is the same as the code’s `find_kernel_base_from_kallsyms()` method. The function tries to read the kernel symbol table from `/proc/kallsyms` and especially looks for symbols that have the words “_text” or “startup_64” in them. These words usually indicate the beginning of kernel text parts. The “found base address?” diamond decision node checks to see if a correct address was found. The program saves this base address in the `base_addr` variable if it works (the “Yes” option). The program goes to “Try Method 2: System.map” as a backup method in case the Kallsyms method doesn’t work (the “no” way). This shows that the program is strong because it tries several ways one after the other to find the kernel base address, even if entry to “/proc/kallsyms” is blocked by security settings or not enough privileges are granted.
// Function to read kernel symbols if available
unsigned long find_kernel_base_from_kallsyms() {
FILE *fp;
char line[256];
unsigned long addr = 0;
// Try Method 1: kallsyms - attempt to open the kallsyms file
fp = fopen("/proc/kallsyms", "r");
if (!fp) {
printf("Note: Cannot open /proc/kallsyms (normal if process has no permissions)\n");
return 0; // No base address found
}
// Found Base Address? - Look for known kernel symbols
while (fgets(line, sizeof(line), fp)) {
char symbol[64];
char type;
if (sscanf(line, "%lx %c %s", &addr, &type, symbol) == 3) {
// Look for common kernel text start symbols
if (strstr(symbol, "_text") || strstr(symbol, "startup_64")) {
fclose(fp);
return addr; // Yes - return found base address
}
}
}
fclose(fp);
return 0; // No - no base address found
}
// In main():
// Method 1: /proc/kallsyms
base_addr = find_kernel_base_from_kallsyms();
if (base_addr) { // Found Base Address? Yes
printf("Found kernel base from kallsyms: 0x%lx\n", base_addr);
}
// If no base address found or after storing, continue to Method 2
Workflow-3
This section of the flowchart shows Method 2 of the KASLR analysis, which attempts to find the kernel base address using the System.map file. The “find_kernel_base_from_system_map()” function looks in common locations like “/boot/System.map” and “/boot/System.map-$(uname -r)” for the kernel symbol mapping file. Similar to the previous method, it searches for “_text” or “startup_64” symbols to identify the kernel base address. The diamond decision node “Found Base Address?” checks if a valid address was found in System.map. If successful (the “Yes” path), the program only stores this address if no previous base address was found from the Kallsyms method (hence the “Store if no previous base address” action). This hierarchical storage approach ensures that earlier successful methods take precedence. Regardless of success or failure (the “No” path), the program continues to “Try Method 3: dmesg” as the final attempt to find the kernel base address. This demonstrates the program’s fallback strategy, where each subsequent method serves as a backup in case previous methods fail.
// Function to read System.map if available
unsigned long find_kernel_base_from_system_map() {
FILE *fp;
char line[256];
unsigned long addr = 0;
// Try Method 2: System.map - check common locations
const char *system_map_paths[] = {
"/boot/System.map",
"/boot/System.map-$(uname -r)",
NULL
};
// Try each possible System.map location
for (int i = 0; system_map_paths[i] != NULL; i++) {
fp = fopen(system_map_paths[i], "r");
if (fp) {
// Found Base Address? - Look for kernel symbols
while (fgets(line, sizeof(line), fp)) {
char symbol[64];
char type;
if (sscanf(line, "%lx %c %s", &addr, &type, symbol) == 3) {
if (strstr(symbol, "_text") || strstr(symbol, "startup_64")) {
fclose(fp);
return addr; // Yes - return found base address
}
}
}
fclose(fp);
}
}
return 0; // No - no base address found
}
// In main():
// Method 2: System.map
unsigned long system_map_addr = find_kernel_base_from_system_map();
if (system_map_addr) { // Found Base Address? Yes
printf("Found kernel base from System.map: 0x%lx\n", system_map_addr);
if (!base_addr) base_addr = system_map_addr; // Store if no previous base address
}
// Continue to Method 3: dmesg
Workflow-4
This part of the diagram shows Method 3, which is the last straight try to use the dmesg command to find the kernel base address. If you use the `dmesg` tool to get to the kernel message buffer, the `find_kernel_base_from_dmesg()` method looks through it for kernel address information, especially messages with “Kernel” and “text” that might show the base address. Like the other ways, this one has a decision point called “Found Base Address?” that checks to see if a legal address was found. If it works (the “yes” way), the program will only save this address if none of the other methods (kallsyms or System.map) worked, so the hierarchy of preferred sources stays the same. The program moves on to “Analyze Memory Maps” whether the dmesg method works or not (the “No” path). This is a different way to get information about kernel memory by looking at `/proc/self/maps`. This change shows how the program moves from using direct address recognition to a more analytical way of looking at the memory layout of the system.
// Function to try reading from dmesg
unsigned long find_kernel_base_from_dmesg() {
FILE *fp;
char line[512];
unsigned long addr = 0;
// Try Method 3: dmesg
fp = popen("dmesg", "r");
if (!fp) {
return 0; // No - couldn't open dmesg
}
// Found Base Address? - Look for kernel messages with addresses
while (fgets(line, sizeof(line), fp)) {
if (strstr(line, "Kernel") && strstr(line, "text")) {
unsigned long potential_addr;
if (sscanf(line, "%*[^f]fff%lx", &potential_addr) == 1) {
addr = 0xffffffff00000000UL | potential_addr;
break; // Yes - found base address
}
}
}
pclose(fp);
return addr;
}
// In main():
// Method 3: dmesg
unsigned long dmesg_addr = find_kernel_base_from_dmesg();
if (dmesg_addr) { // Found Base Address? Yes
printf("Found kernel base from dmesg: 0x%lx\n", dmesg_addr);
if (!base_addr) base_addr = dmesg_addr; // Store if no previous base address
}
// Proceed to analyze memory maps
analyze_memory_maps();
Workflow-5
After trying all the possible ways to find the kernel base address, this last part of the diagram shows how the program ends. After looking at memory maps in `/proc/self/maps`, the program comes to a crucial decision point called “Any Base Address Found?” This check sees if any of the earlier methods (kallsyms, System.map, dmesg) were able to find a kernel base address. After that, the flow breaks into two possible routes:
1. Success Path (Yes):
If a base address was found, the program goes down the left branch to “Show KASLR Offset,” which figures out and shows the difference between the found address and the default base address, which is defined in the code as “DEFAULT_BASE” and is 0xffffffff80000000UL.
2. If there is no failure path:
The program goes to the right branch to “Display Error Message” and then to “List Security Mechanisms” if there was no base address found. This way tells the user why the detection might not have worked by describing security features such as KPTI (Kernel Page Table Isolation), SMAP/SMEP protections, and limited access to kernel information that might have stopped the detection from working.
“End Program” is where both lines meet in the end, and the program ends after giving either the analysis results or the failure reason. This arrangement makes sure that the user always gets useful feedback, no matter if the study worked or not.
// Alternative method using memory mapping analysis
void analyze_memory_maps() {
FILE *fp;
char line[256];
fp = fopen("/proc/self/maps", "r");
if (!fp) {
printf("Cannot open memory maps\n");
return;
}
printf("\nMemory Map Analysis:\n");
printf("--------------------\n");
while (fgets(line, sizeof(line), fp)) {
unsigned long start, end;
char perms[5];
char path[256] = {0};
if (sscanf(line, "%lx-%lx %4s %*s %*s %*s %s", &start, &end, perms, path) >= 3) {
if (start >= 0xffffffff80000000UL && end <= 0xffffffffffffffffUL) {
printf("Kernel region: %016lx-%016lx %s %s\n",
start, end, perms, path[0] ? path : "anonymous");
}
}
}
fclose(fp);
}
// In main(), after all methods have been tried:
// Any Base Address Found?
if (base_addr) { // Yes path
// Display Analysis Results
printf("\nAnalysis Results:\n");
printf("----------------\n");
printf("Detected Kernel Base: 0x%lx\n", base_addr);
// Show KASLR Offset
printf("KASLR Offset: 0x%lx\n", base_addr - DEFAULT_BASE);
} else { // No path
// Display Error Message
printf("\nCould not reliably determine kernel base address.\n");
// List Security Mechanisms
printf("This is likely due to security mechanisms:\n");
printf("1. KPTI (Kernel Page Table Isolation)\n");
printf("2. SMAP/SMEP protections\n");
printf("3. Restricted access to kernel information\n");
}
// End Program
return 0;
Summary
I know it was a very long topic, but I wanted to explain it in a proper way. There will be more about related to these topics; I will make a series. I know that the code is not best optimized, but I am trying to make it better. Next blogs will be about kernel attacks, but first we should understand this topic; its crucial to learn and understand about it. I hope you enjoyed it. You can always ask me if you have not understood something. So I will see you then in my next blog!