Linux-Rootkit-Symbols-2

Perfect! Let's dive deep into symbol resolution - this is absolutely crucial for rootkit development.


Part 1: What Are Symbols?

1.1 Understanding Symbols

Symbols are names given to memory addresses in programs. They make code readable for humans.

Example in C:

// You write this:
int my_variable = 42;
void my_function() {
    printk("Hello\n");
}

// Compiler converts to this (simplified):
// Address 0xffffffffa1234000: value = 42
// Address 0xffffffffa1234100: <function code>

Symbols map names to addresses:

  • my_variable0xffffffffa1234000

  • my_function0xffffffffa1234100

1.2 Why Symbols Matter in Kernel

In userspace programs, you rarely worry about symbols. In kernel development (especially rootkits), symbols are critical:

For Normal Development:

  • Debugging with GDB

  • Linking kernel modules

  • Understanding crash dumps

For Rootkit Development:

  • Finding functions to hook (like sys_call_table)

  • Locating kernel data structures

  • Hiding your rootkit from detection

  • Bypassing protections

1.3 Types of Symbols

Global Symbols (Exported):

Local Symbols (Static):

External Symbols:


Part 2: The Kernel Symbol Table

2.1 What is the Symbol Table?

The kernel maintains a symbol table that lists:

  • Every function name

  • Every global variable name

  • Their memory addresses

  • Their types

Think of it as a phone book for the kernel: "To call function X, dial address Y"

2.2 Why the Kernel Needs a Symbol Table

1. Module Loading: When you load a module with insmod, the kernel needs to resolve symbols:

2. Dynamic Linking: Modules don't contain all code - they reference kernel functions. The symbol table connects these references.

3. Debugging: When kernel crashes, the symbol table translates addresses back to function names in the crash dump.


Part 3: /proc/kallsyms - The Live Symbol Table

3.1 What is /proc/kallsyms?

/proc/kallsyms is a pseudo-file that shows all kernel symbols at runtime.

ADDRESS: Memory location (64-bit hex) TYPE: Symbol type (letter code) NAME: Symbol name

3.3 Symbol Types (The Letters)

Type
Meaning
Example

T

Text (code) in .text section

ffffffffc0000000 T sys_open

t

Local text (static function)

ffffffffc0001000 t helper_function

D

Initialized data in .data

ffffffffc0002000 D sys_call_table

d

Local initialized data

ffffffffc0003000 d local_var

R

Read-only data (.rodata)

ffffffffc0004000 R version_string

r

Local read-only data

ffffffffc0005000 r const_value

B

Uninitialized data (.bss)

ffffffffc0006000 B global_buffer

b

Local uninitialized data

ffffffffc0007000 b static_buffer

U

Undefined (external symbol)

U printk

W

Weak symbol

W some_optional_func

A

Absolute (address won't change)

0000000000000000 A _text

Uppercase = Global (exported) Lowercase = Local (static, not exported)

3.4 Practical Examples

Finding a function:

Why the '$' in grep?

  • $ means "end of line"

  • Prevents matching commit_creds_2 or my_commit_creds

  • Only matches exact name

Finding all syscalls:

Finding module symbols:

3.5 Security Restrictions on /proc/kallsyms

The kptr_restrict Setting:

Values:

  • 0: Everyone can see addresses (insecure, useful for debugging)

  • 1: Root can see addresses, users see zeros (default)

  • 2: Even root sees zeros (maximum security)

Example with kptr_restrict=1:

Changing kptr_restrict:


Part 4: System.map - The Static Symbol Table

4.1 What is System.map?

System.map is a file created when the kernel is compiled. It contains the symbol table for that specific kernel build.

Location:

Multiple files: One for each installed kernel version.

4.2 System.map vs /proc/kallsyms

Feature
System.map
/proc/kallsyms

When created

Kernel compile time

Runtime (dynamic)

Updates

Never changes

Updates when modules load

Shows modules

No

Yes

Location

/boot/ (file)

/proc/ (pseudo-file)

Root needed

No (it's a file)

Depends on kptr_restrict

Use case

Debugging crashes

Finding runtime addresses

4.3 Viewing System.map

4.4 When to Use System.map

Use System.map for:

  • Analyzing kernel crash dumps (vmcore)

  • Understanding kernel layout at compile time

  • When /proc/kallsyms is restricted

  • Reverse engineering specific kernel builds

Use /proc/kallsyms for:

  • Runtime symbol lookup (preferred for rootkits!)

  • Finding module symbols

  • Getting current addresses (KASLR might change them)


Part 5: Finding Symbols in Kernel Modules

5.1 Exported vs Non-Exported Symbols

Exported symbols are intentionally made available to modules:

Non-exported symbols are hidden from modules (but still in kallsyms):

5.2 Checking if a Symbol is Exported

Method 1: Check /proc/kallsyms

Method 2: Check Module.symvers

Method 3: Try to use it

5.3 Using Exported Symbols in Your Module

The kernel automatically resolves this symbol when loading your module.


Part 6: Finding Unexported Symbols (Rootkit Technique!)

6.1 Why Find Unexported Symbols?

Many interesting kernel internals are not exported:

  • sys_call_table (the syscall table - CRITICAL for rootkits!)

  • Internal kernel functions

  • Security-sensitive structures

Rootkits need to find these anyway!

6.2 Method 1: Read /proc/kallsyms at Runtime

The most common rootkit technique:

This is THE standard way rootkits find unexported symbols.

6.3 Method 2: Pattern Scanning (Advanced)

If kallsyms_lookup_name is disabled, scan kernel memory:

This is how advanced rootkits work when kallsyms is restricted!

6.4 Method 3: Read System.map from Disk

From kernel module code, read /boot/System.map:

Less common because it requires filesystem access from kernel module.


Part 7: Symbol Resolution in Action

7.1 How Kernel Modules Use Symbols

When you load a module:

This is called "symbol resolution" or "linking"

7.2 Viewing Module Dependencies

7.3 Symbol Conflicts

What if two modules export the same symbol?

Result: Module B fails to load with "symbol conflict" error!

Solution: Use unique prefixes


Part 8: Rootkit Symbol Hiding Techniques

8.1 Why Hide Symbols?

When your rootkit loads, its symbols appear in /proc/kallsyms:

This reveals your rootkit to detection tools!

8.2 Technique 1: Remove Module from List

Problem: Symbols still visible in /proc/kallsyms!

8.3 Technique 2: Hide Symbols from kallsyms

More advanced - need to manipulate kallsyms data structures:

8.4 Technique 3: Use Static Functions

Static functions show as lowercase letters in kallsyms (less obvious).

8.5 Detection: How to Find Hidden Rootkits

For defenders:


Part 9: Practical Examples

Example 1: Finding sys_call_table

This is essential for syscall hooking (next topic!):

Test it:

Example 2: Listing All Symbols Starting with "sys_"

Example 3: Resolving Symbol at Runtime


Part 10: Symbol Resolution Tools

Tool 1: nm (Name list)

Tool 2: objdump

Tool 3: readelf

Tool 4: modinfo


Part 11: Common Issues and Troubleshooting

Issue 1: "Unknown symbol" Error

Cause: Module uses a symbol that kernel doesn't export

Debug:

Solution:

  • Check if symbol exists: grep some_function /proc/kallsyms

  • If exists but not exported, use kallsyms_lookup_name()

  • If doesn't exist, you're calling wrong function

Issue 2: kallsyms_lookup_name Returns NULL

Causes:

  1. Symbol name is wrong (typo)

  2. Symbol doesn't exist in this kernel

  3. kallsyms_lookup_name itself is not exported (newer kernels!)

Solution for #3 (modern kernels disable kallsyms_lookup_name):

Issue 3: Addresses are All Zeros

Cause: kptr_restrict is set to 1 or 2

Solution:

Last updated