> KernelSU对内核导出符号限制的绕过 - Britney >
mobile wallpaper 1mobile wallpaper 2
859 字
3 分钟
KernelSU对内核导出符号限制的绕过
2026-03-02

/proc/kallsyms#

/proc/kallsyms是一个符号表,它包含了内核中所有的符号信息,包括函数、变量、常量等等。这些符号信息可以被内核模块和其他程序使用,这些符号类型可以帮助开发人员更好地理解内核中的符号信息,从而更好地进行内核开发和调试。

System.map​ 相比,/proc/kallsyms 是动态的,其包含了内核模块的符号地址

image

格式为 addr | type | symbol | [module name]

type的定义

T:表示该符号是一个函数,可以被其他代码调用。

t:表示该符号是一个局部函数,只能在当前文件中使用。

D:表示该符号是一个全局变量,可以被其他代码访问和修改。

d:表示该符号是一个局部变量,只能在当前文件中使用。

R:表示该符号是一个只读变量,不能被修改。

r:表示该符号是一个只读局部变量,只能在当前文件中使用。

A:表示该符号是一个可读写的变量,可以被其他代码访问和修改。

a:表示该符号是一个可读写的局部变量,只能在当前文件中使用。

B:表示该符号是一个未初始化的全局变量,它的值在程序启动时被初始化为0。

b:表示该符号是一个未初始化的局部变量,它的值在程序启动时被初始化为0。

G:表示该符号是一个全局变量,但是它的值在程序运行时可能会被修改。

C:表示该符号是一个常量,它的值在程序运行时不能被修改。

W:表示该符号是一个弱符号,

?: 表示该符号的类型未知。

/proc/sys/kernel/kptr_restrict#

linux-kernel/Documentation/sysctl/kernel.txt at master · tinganho/linux-kernel

kptr_restrict:

This toggle indicates whether restrictions are placed on
exposing kernel addresses via /proc and other interfaces. When
kptr_restrict is set to (0), there are no restrictions. When
kptr_restrict is set to (1), the default, kernel pointers
printed using the %pK format specifier will be replaced with 0’s
unless the user has CAP_SYSLOG. When kptr_restrict is set to
(2), kernel pointers printed using %pK will be replaced with 0’s
regardless of privileges.

kptr_restrict输出(在能访问/proc/kallsyms的前提下)
0任何用户读取kallsyms都输出addr
1拥有CAP_SYSLOG权能的用户可以输出addr
2任何用户读取kallsyms输出的addr均为0

上文中的截图 addr​ 输出为0就是因为 kptr_restrict 的值被设置为2

设置为1或0即可读取addr

image

ksuinit#

KernelSU/userspace/ksuinit/src/loader.rs at main · tiann/KernelSU

use anyhow::{Context, Result};
use goblin::elf::{Elf, section_header, sym::Sym};
use rustix::{cstr, system::init_module};
use scroll::{Pwrite, ctx::SizeWith};
use std::collections::HashMap;
use std::fs;
struct Kptr {
value: String,
}
impl Kptr {
pub fn new() -> Result<Self> {
let value = fs::read_to_string("/proc/sys/kernel/kptr_restrict")?;
fs::write("/proc/sys/kernel/kptr_restrict", "1")?;
Ok(Kptr { value })
}
}
impl Drop for Kptr {
fn drop(&mut self) {
let _ = fs::write("/proc/sys/kernel/kptr_restrict", self.value.as_bytes());
}
}
fn parse_kallsyms() -> Result<HashMap<String, u64>> {
let _dontdrop = Kptr::new()?;
let allsyms = fs::read_to_string("/proc/kallsyms")?
.lines()
.map(|line| line.split_whitespace())
.filter_map(|mut splits| {
splits
.next()
.and_then(|addr| u64::from_str_radix(addr, 16).ok())
.and_then(|addr| splits.nth(1).map(|symbol| (symbol, addr)))
})
.map(|(symbol, addr)| {
(
symbol
.find("$")
.or_else(|| symbol.find(".llvm."))
.map_or(symbol, |pos| &symbol[0..pos])
.to_owned(),
addr,
)
})
.collect::<HashMap<_, _>>();
Ok(allsyms)
}
pub fn load_module(path: &str) -> Result<()> {
// check if self is init process(pid == 1)
if !rustix::process::getpid().is_init() {
anyhow::bail!("{}", "Invalid process");
}
let mut buffer = fs::read(path).with_context(|| format!("Cannot read file {}", path))?;
let elf = Elf::parse(&buffer)?;
let kernel_symbols = parse_kallsyms().context("Cannot parse kallsyms")?;
let mut modifications = Vec::new();
for (index, mut sym) in elf.syms.iter().enumerate() {
if index == 0 {
continue;
}
if sym.st_shndx != section_header::SHN_UNDEF as usize {
continue;
}
let Some(name) = elf.strtab.get_at(sym.st_name) else {
continue;
};
let offset = elf.syms.offset() + index * Sym::size_with(elf.syms.ctx());
let Some(real_addr) = kernel_symbols.get(name) else {
log::warn!("Cannot find symbol: {}", &name);
continue;
};
sym.st_shndx = section_header::SHN_ABS as usize;
sym.st_value = *real_addr;
modifications.push((sym, offset));
}
let ctx = *elf.syms.ctx();
for ele in modifications {
buffer.pwrite_with(ele.0, ele.1, ctx)?;
}
init_module(&buffer, cstr!("")).context("init_module failed.")?;
Ok(())
}

为了绕过内核中导出函数的限制

ksuinit​在启动时将kptr_restrict设置为1,并且读取各个函数的地址

并且遍历 kernelsu.ko 的符号表,修复SHN_UNDEF的符号为SHN_ABS,并且写入函数的绝对地址

这样内核模块在加载时就不会爆找不到符号的错误

另一种绕过方法#

rwProcMem33/hwBreakpointProcModule/hwBreakpointProc_module/kallsyms_lookup_api.h at master · abcz316/rwProcMem33

利用 kallsyms_lookup_name这个函数获取内核函数的绝对地址,然后通过这个地址来调用对应的函数

这里要注意,调用时的参数必须严格与这个函数的参数签名相同,否则可能无法通过cfi check

KernelSU对内核导出符号限制的绕过
https://www.britn3y.top/posts/android-kernel/kernelsu对内核导出符号限制的绕过/
作者
Britney
发布于
2026-03-02
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

封面
Sample Song
Sample Artist
封面
Sample Song
Sample Artist
0:00 / 0:00