給安卓12內核增加個syscall

給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打不出來日志。不過自己的環境定制內核還是最方便的,本著目的就是“一勞永逸”。