给安卓12内核增加个syscall

VSole2023-01-10 10:37:53

给android 12增加个syscall,没想到这里面涉及东西还挺多的,网上的东西都太过陈旧(尤其是bionic这里),中间踩了不少坑。单独写一篇文章记录下怎么给android 12添加个syscall。(现在依旧还是有问题,printk的日志dmesg看不到)

环境:lineageos 19.1

编译环境:Ubuntu 20.04

实际上我操作的顺序是本篇倒着来的,正序写比较方便观看。

1. 给libc添加个导出符号(可不做)

直接在linux内核里添加了调用,直接syscall(_NR_ID)内核,是可以实现自定义syscall的,主要是这里也了解到了就顺手记下。

bionic调用gensyscall.py,从SYSCALL.TXT中读文件,判断哪些函数要生成syscall的汇编指令。

在unistd.h中添加syscall id,跟着别的写就行了,sys_antidebug_update是linux内核自定义函数,下面会提到。

比如我在这里加了__antidebug_update函数,将会调用linux内核里的antidebug_update函数,在arm64架构下的。

这句话的含义:

return_type func_name[|alias_list]:syscall_name[:socketcall_id] arch_list

# int __antidebug_update:antidebug_update(pid_t) arm64int getrusage(int, struct rusage*)  allint __getpriority:getpriority(int, id_t)  all

生成的结果在:

生成了这个函数的汇编,就可以在代码里以extern c的方式直接调用__antidebug_update函数(其实就是syscall),我这里不想改makefile直接用了一个现成的内核cpp。

然后添加libc的导出符号,在这个路径下,编译即可直接调用libc的函数实现封装好的syscall。

bionic工具生成的map文件在这里能看到:

踩坑1

syscall的编号_NR_xxx在bionic里面是从external\kernel-headers\original\uapi\asm-generic\unistd.h拷贝过来的,所以要改这个文件,直接改include下的是没用的。这里我加了限定arm64生成syscall的宏,在下面linux内核里要做同样的限定。

踩坑2

如果不想给arm的libc导出自己的函数,只想在arm64导出,函数声明这里一定要加上预编译宏。我在unistd.h里已经加了限定arm64架构才会生成antidebug_update的宏,这里如果不加,bionic那么就会尝试arm架构的antidebug_update syscall汇编,因为找不到而报错。

后面再user层就可以先用extern c 声明下 aadebug,然后直接调用了。

2. 在linux内核里添加syscall

先看异常分发,kernel\xiaomi\sm8250\arch\arm64\kernel\entry.S

调用syscall会产生中断,在arm64的汇编是svc,中断产生异常后,进入svc handler。

在kernel\xiaomi\sm8250\arch\arm64\kernel\syscall.c下,我复制一段关键代码。

asmlinkage void el0_svc_handler(struct pt_regs *regs){    sve_user_discard();    el0_svc_common(regs, regs->regs[8], __NR_syscalls, sys_call_table);} static void el0_svc_common(struct pt_regs *regs, int scno, int sc_nr,               const syscall_fn_t syscall_table[]){       unsigned long flags = current_thread_info()->flags;     regs->orig_x0 = regs->regs[0];    regs->syscallno = scno;         ...    invoke_syscall(regs, scno, sc_nr, syscall_table);        ...}static long __invoke_syscall(struct pt_regs *regs, syscall_fn_t syscall_fn){    return syscall_fn(regs);} static void invoke_syscall(struct pt_regs *regs, unsigned int scno,               unsigned int sc_nr,               const syscall_fn_t syscall_table[]){    long ret;    if (scno < sc_nr) {        syscall_fn_t syscall_fn;        syscall_fn = syscall_table[array_index_nospec(scno, sc_nr)];        ret = __invoke_syscall(regs, syscall_fn);    } else {        ret = do_ni_syscall(regs, scno);    }     regs->regs[0] = ret;}

总结起来就一句话:

syscall_table[scno](regs);

编译时生成syscall_table,根据id索引到handler的地址(所以hook syscall函数可以直接替换这个syscall_table[id]寻址得到的函数地址,主要是要找到syscall_table的地址,和call回原函数处理)。

所以要添加个syscall,就需要做两件事,定义个handler(SYSCALL_DEFINEx 宏包起来的声明,x是参数数量,具体看其他函数实现就知道了),增加个NR_ID(如果上面第1点也做了需要bionic的NR_ID和linux的NR_ID一致)并声明syscall。

(1)定义handler

图方便还是用了先有文件kernel\xiaomi\sm8250\kernel\sys.c

增加NR_ID,看好路径,因为我再bionic也新增了syscall,这里要对应上sys_antidebug_update,这个前缀sys是SYSCALL_DEFINEx 宏添加的,所以这里面要加上sys,看其他syscall声明或者展开下宏就知道了。

添加完后build编译不报错,刷机,这里可以直接写个demo验证下。

用 aarch64-linux-gnu-gcc 编译(ubuntu 可以直接apt装arm的交叉编译工具),不要忘记-static选项,刷机到手机上运行下,可以看到函数逻辑是走了的。

传入参数123,return 123+100 ,所以syscall没有问题。

但我就是看不到printk打的日志去哪了,打算封装个函数把日志写到文件去了。unistd.h到处都是,看得人晕。

修改源码重新编译,时间成本比较大,安装驱动(.ko)是比较常用的了,有需要就syscall hook下,毕竟扩展性比较好,我也试过,内核编译选项把强制校验签名关了,可以随便安装未签名的驱动了,但同样是printk打不出来日志。不过自己的环境定制内核还是最方便的,本着目的就是“一劳永逸”。

kernel
本作品采用《CC 协议》,转载必须注明作者和本文链接
kernel-pwn之ret2dir利用技巧
Kernel-Pwn-FGKASLR
2023-07-10 10:24:04
是KASLR的加强版,增加了更细粒度的地址随机化
Kernel PWN从入门到提升
2023-03-23 10:17:57
所以我决定用此文章结合一道不错的例题尽可能详细的来讲一下kernel pwn从入门过渡到较高难度的部分,供想要学习kernel pwn的小伙伴们参考。文件系统kernel题一般都会给出一个打包好的文件系统,因此需要掌握常用到的打包/解包命令。
可是当我们开启了smap保护之后,内核态就没有办法访问用户态的数据,此时当我们再hijack tty_operation到我们的用户态时,我们的kernel就会panic,更别说劫持执行流到用户态上执行rop了。当我们调用msgsnd时,在linux内核中会调用do_msgsnd。
然而在内核态中,堆内存的分配策略发生了变化。并把这个slab划分为一个个object,并将这些object组成一个单向链表进行管理,这里需要注意slub系统把内存块当成object看待,而不是伙伴系统中的页。本次选择演示的例题是2019-SUCTF的sudrv例题,查看start.sh中的信息可以发现开启了kaslr保护与smep保护。
条件竞争类型的漏洞
笔者分享的两种利用方式都不算困难,但是需要注意
前言Kernel ROP本质上还是构造ropchain来控制程序流程完成提权,不过相较于用户态来说还是有了一些变化,这里选取的例题是2018年强网杯的赛题core,本来觉得学起来会很快的但是没想到还是踩了不少坑。iretq指令则用来恢复用户态的cs、ss、rsp、rip、rflags的信息。
Kernel从0开始
2021-12-10 13:42:20
网上一大堆教编译内核的,但很多教程看得特别迷糊。第一次编译内核时,没设置好参数,直接把虚拟机编译炸开了。所以就想着能不能先做个一键获取内核源码和相关vmlinux以及bzImage的脚本,先试试题,后期再深入探究编译内核,加入debug符号,所以就有了这个一键脚本。
Kernel pwn CTF 入门 – 1
2021-10-21 16:39:08
01简介内核 CTF 入门,主要参考 CTF-Wiki。02环境配置调试内核需要一个优秀的 gdb 插件,这里选用 gef。
VSole
网络安全专家