contributed by < linD026
>
linux2021
In the kernel, a character-type device is represented by
struct cdev
, a structure used to register it in the system. Most driver operations use three important structures:struct file_operations
, struct file and struct inode.
struct file_operations
The registration/unregistration of a device is made by specifying the major and minor. The dev_t type is used to keep the identifiers of a device (both major and minor) and can be obtained using the
MKDEV
macro.
After assigning the identifiers, the character device will have to be initialized (
cdev_init
) and the kernel will have to be notified(cdev_add
). The cdev_add function must be called only after the device is ready to receive calls. Removing a device is done using the cdev_del function.
class_create
This is used to create a struct class pointer that can then be used in calls to
device_create()
.
Returns struct class pointer on success, orERR_PTR()
on error.
Note, the pointer created here is to be destroyed when finished by making a call toclass_destroy()
.
device_create
This function can be used by char device classes. A struct device will be created in sysfs, registered to the specified class.
A “dev” file will be created, showing thedev_t
for the device, if thedev_t
is not 0,0. If a pointer to a parent struct device is passed in, the newly created struct device will be a child of that device in sysfs. The pointer to the struct device will be returned from the call. Any further sysfs files that might be required can be created using this pointer.
Returns struct device pointer on success, orERR_PTR()
on error.
kstrtol(const char *s, unsigned int base, long *res)
const char *s
The start of the string. The string must be null-terminated, and may also include a single newline before its terminating null. The first character may also be a plus sign or a minus sign.
unsigned int base
The number base to use. The maximum supported base is 16. If base is given as 0, then the base of the string is automatically detected with the conventional semantics - If it begins with 0x the number will be parsed as a hexadecimal (case insensitive), if it otherwise begins with 0, it will be parsed as an octal number. Otherwise it will be parsed as a decimal.
long * res
Where to write the result of the conversion on success.
Using ftrace to hook to functions
GitHub - ilammy / ftrace hook
The ftrace infrastructure was originally created to attach callbacks to the beginning of functions in order to record and trace the flow of the kernel. But callbacks to the start of a function can have other use cases. Either for live kernel patching, or for security monitoring. This document describes how to use ftrace to implement your own function callbacks.
FTRACE_OPS_FL_SAVE_REGS
If the callback requires reading or modifying the pt_regs passed to the callback, then it must set this flag. Registering a
ftrace_ops
with this flag set on an architecture that does not support passing ofpt_regs
to the callback will fail.
FTRACE_OPS_FL_IPMODIFY
Requires FTRACE_OPS_FL_SAVE_REGS set. If the callback is to “hijack” the traced function (have another function called instead of the traced function), it requires setting this flag. This is what live kernel patches uses. Without this flag the
pt_regs->ip
can not be modified.
Note, only one ftrace_ops with FTRACE_OPS_FL_IPMODIFY set may be registered to any given function at a time.
So this will let hook_find_ge_pid
to replace the original one ( find_ge_pid
).
kernel/trace/ftrace.c
- register_ftrace_function / ftrace_set_filter_ip
ftrace_set_filter_ip(&hook->ops, hook->address, 0, 0);
設定 hook->address
至 filter hash 裡。大致流程會是:
原本的 function
( 在此為 find_ge_pid
) 的地址重新取代掉之前更改的 hook_find_ge_pid
。
find_ge_pid
與 struct pid
kernel/pid.c -
find_ge_pid
/ include/linux/pid.h -struct pid
pidof
pidof
will search all the pid inside of /proc
directory.
you can use ls /proc | grep pid
to find the target pid
pidof source code
pid manual page
在我的電腦 ubuntu ( 5.8.0-59-generic ) 直接編譯的話會出現上述錯誤資訊。
在 commit 0bd476e6c67190b5eb7b6e105c8db8ff61103281 修改了 kallsyms_lookup_name 和 kallsyms_on_each_symbol 函式的 export 形式,這是因為 MODULE_LICENSE("GPL")
等相關問題。
kallsyms: unexport kallsyms_lookup_name() and kallsyms_on_each_symbol()
kallsyms_lookup_name() and kallsyms_on_each_symbol() are exported to
modules despite having no in-tree users and being wide open to abuse by
out-of-tree modules that can use them as a method to invoke arbitrary
non-exported kernel functions.Unexport kallsyms_lookup_name() and kallsyms_on_each_symbol().
而在 lkml.org - Re: [PATCH 0/3] Unexport kallsyms_lookup_name() and kallsyms_on_each_symbol() 中有說明為何要 unexport 此函式:
Despite having just a single modular in-tree user that I could spot,
kallsyms_lookup_name() is exported to modules and provides a mechanism
for out-of-tree modules to access and invoke arbitrary, non-exported
kernel symbols when kallsyms is enabled.
而在 /proc/kallsyms
當中是可以搜尋的到 kallsyms_lookup_name
,並且也確認了 kernel 的 config 。
解決辦法除了替換 kernel 版本以及更改原始程式碼並重新編譯以外,也可以利用 VM 直接裝可呼叫之版本, ubuntu 版本約 20.04 以前。
It’s worth noting that, as of writing, the latest kernel available for Ubuntu 20.04 is 5.4.0-60-generic, so these changes won’t actually affect you yet if you’re on an LTS. But it’s nice to be ahead of the curve!
在此使用 Multipass - ubuntu VM ,因為測試過程中 kernel 有可能會被玩壞,利用 VM 可以比較沒顧慮。 VM 版本為:
搞定好版本,用最簡單的方式來測試上述問題。以下為 MIT license 編譯時使用 GPL 的函式,正常會出現:
nol.c
GPL 本身就是 GNU General Public License 的簡稱,你不需要說 GPL License,直接書寫 GPL
:notes: jserv
而這次先利用擁有 GPL 的 module 中的 kallsyms_lookup_name
輸出 kallsyms_lookup_name
的 va 並想辦法傳給另一個以 MIT license 宣告的 module 。在此用最蠢的方式,以 printk 輸出後手動修改 out.c
的程式碼。
in.c
之後在 out.c
中使用剛剛的 va 結合 function pointer 來呼叫原先不能呼叫的 kallsyms_lookup_name
函式。
out.c
因為 out.c
沒有宣告任何 GPL 的函式,因此編譯會過,sudo insmod out.ko
後會得到:
這邊也有另一個使用方法:How to use variables or functions that are not exported in the Linux kernel 。
因此,雖然函式地址會在每次重開機時而有所不同 ( ASLR ) ,但這嚴重的漏洞還是讓開發者們修改了此函式的 export 形式。
The address that the kernel is loaded from is called
startup_64
(you can find it in /proc/kallsyms), but kernel address space layout randomization means that this address will change at every boot.
而在 v5.7 之後的版本,則可以參考:Linux Rootkits: New Methods for Kernel 5.7+。
Reference
task_struct
中定義了自己的 pid 以及指向 parent 的指標。
因此只要利用給予的 pid 找到本身的 task_struct
再利用其中的 real_parent
來得到 parent 的 pid 即可。
在 main.c
當中新增 get_parent_pid
函式,並在 device_write
中增加對 PPID
的處理:
並撰寫 script 來驗證執行結果:
輸出測試:
Reference
cdev
unregistrationexample :