2015年8月21日 星期五
2015年7月6日 星期一
2015年7月2日 星期四
2015年4月28日 星期二
Plaid CTF 2015 Write-up
PlaidDB [550]
程式行為及概述
如名稱所述是個 database 的 service ,可加入資料進 db,而每筆資料都配合一組 key,並以 binary tree 的方式去儲存,其中每個 node 都有個 row struct 大致上如下
struct row {
char *key
int size
char *content
row *left
row *right
row *parent
bool is_leaf
}
而他有的功能如下
- GET
- 分配放 key 的空間,輸入 key , 可獲得 db 的內容,最後再把剛分配的空間 free 掉
- PUT
- 一開使會先分配 0x38 byte 放置 row ,再分配 8 byte 放 key,再依據所輸入的 size 大小,分配相對應的空間給你 ,而在輸入 key 時會檢查所輸入 key 的空間夠不夠用,一旦不夠用就會重新 realloc 兩倍的空間給你
- DUMP
- 會將所有 key 的資訊 dump 出來
- DEL
- 分配放 key 的空間,輸入 key 之後,會先比對是否 key 是否有在 tree 中,若有,則將相對應的 key、content、row 及用來比對 key 依序 free 掉,*若無則返回 menu 選單(並沒有將剛剛用來比對的 key free 掉),這部分在後續排 heap 的階段頗好用 *
- EXIT
- 離開程式
漏洞
- NULL byte overflow
- 再所有輸入 key 的功能中一旦把 key 輸入完就會在結尾補上零,然而在輸入的 key 剛好是最大分配的空間時,並不會重新 realloc 而卻也會在最後補上
\x00
此時,造成了 overflow ,並蓋到了 malloc_chunk 中的 size 欄位
- 再所有輸入 key 的功能中一旦把 key 輸入完就會在結尾補上零,然而在輸入的 key 剛好是最大分配的空間時,並不會重新 realloc 而卻也會在最後補上
保護機制
- CANARY : ENABLED
- FORTIFY : ENABLED
- NX : ENABLED
- PIE : ENABLED
- RELRO : FULL
漏洞利用及思路
此題一開始想觸發 unlink,但發現難以利用,很難找到一個 pointer 指回自己,也沒有看出可以 leak memory 的地方,後來看到了 google project zero 及 Glibc Adventures: The Forgotten Chunks 的用法,才理解到這題主要在考的是讓 chunk overlap 的情況,其中大致上的概念如下
- 一開始先 malloc 三塊相鄰的 chunk
- 接著 free(B)
- Null byte overflow ,這時候 libc 會認為這塊 chunk 剩下 0x100 的空間,但 c chunk 的
prev_size
卻是紀錄還有 0x120 byte
malloc(D)
會先從剛加入 unsortbin 的 chunk 中切出 chunk
malloc(E)
一樣從未分配完的 chunk 在切出空間來給 user
free(D)
此時 D 也會被加入 unsortbin
free(C)
此時 free 會根據C 這塊 chunk 的prev_size
去找尋上一塊chunk 因此會認為 D 開始到 C 前都是同一塊 chunk 並認為這塊 chunk 有 0x120 byte 卻不知道中間已經有一塊已經分配出去的 chunk ,並將它們合併成 0x220 byte的 chunk
malloc(0x200)
再次 malloc 夠大的空間時,會將之前已經分配出去的 chunk 一併也取進來,但使用者可以任意寫入該區內容,也就是 E 這塊 chunk 可以任意被改動,這題剛好就是讓 plaiddb 的row struct
落在這一塊而被任意改動
利用上述的手法改道舊有的
row struct
其實就已經差不多了,可微調 size 大小,讓舊有的 key 也落入 overlap 的 chunk 中,並很巧妙的 free 掉時,讓 free 將 bin 的位置剛好填入舊有的 key 之中,並用DUMP
就可以 leak 出 libc 的 base ,接著將舊有的 row 夠造成下列的形式
payload = pack64(binsh)
payload += pack64(7)
payload += pack64(0)
payload += pack64(free_hook)
payload += pack64(free_hook-0x30)
payload += pack64(free_hook)
payload += pack64(system)
- 接著在 DELE 時 , 就會將 system 寫入 free_hook 的欄位中,當下次在 free 時等同於也執行了 system,不過當
DELE
動作快結束時就可以拿到 shell ,這邊其實只要稍微 trace 或是用 binary tree 刪除節點的概念就會知道他如何去寫入了,細節我就不多寫了
心得
- 很可惜的沒在兩天內解完,最後都排得差不多了,但時間還是差一點點,寫 explit 速度還是太慢,這題的關鍵主要在排 heap 的部分,因為 fastbin 並不會觸發 unlink 不容易達成上述描述的情況,必須想辦法利用 key 來製造 smallbin 的大小,至於排的過程就不詳細描述了,如果有時間的話大家可以去解解看慢慢體會一下 :-)
Exploit
2015年4月2日 星期四
0ctf 2015 Write-up
程式概述
- freenote 為一個類似筆記功能的程式,這個程式分別有 List , New , Edit , Delete 四個主要的功能及 Exit 結束程式
- 會用個 note struct 去紀錄每個筆記
是否為有效筆記
、筆記大小
及指向筆記內容的 pointer
struct note{
int isValidNote; // 0 = not valid, 1 = valid
int length;
char content;
}
程式行為
經過 ltrace 分析之後,可發現到一開始程式會先 malloc(0x1810) 用來存放這些 note struct,並以陣列的形式去儲存,其中的 index 即為筆記的編號,在最前方也紀錄共有多少筆記
- List
- 會列出每個筆記的內容(也就是內容 pointer 所指向的地方),這裏使會列出筆記為
isValidNote == 1
的內容
- 會列出每個筆記的內容(也就是內容 pointer 所指向的地方),這裏使會列出筆記為
- New
- 再輸入你要的大小之後,如果小於 128 byte 就會分配 128 byte 給你,但如果大於 128 byte 例如 252 byte ,那麼就會給你 128 + 128 byte 的大小,依此類推
- Edit
- 在輸入完要編輯的筆記及大小之後,程式會先判斷這個大小是否與之前的一樣,如果一樣則不會重新分配空間直接編輯內容,如過不一樣則會 realloc 夠你筆記大小的空間給他,不過這部分會先看原先分配空間的後面是否有足夠用的空間給他,如果夠用的話就不會改變起始位置
- Delete
- 輸入完要刪除的筆記後,會將 note[i] 中的
isValidNote
改成 0,在free(note[i]->content)
,並將筆記總數 - 1
- 輸入完要刪除的筆記後,會將 note[i] 中的
- List
漏洞
Double free
- 在 Delete 時,並不會將筆記從
note[i]
中移除,只是將isValidNote = 0
,而 free 是根據note[i]
去決定要 free 哪邊,並沒有先去檢查note[i]->content
是否已經被 free 掉,一旦輸入同樣的i
就會造成 double free 的漏洞
- 在 Delete 時,並不會將筆記從
Memory leak
- 因在輸入筆記後,程式並沒有在使用者輸入的內容最後方補上
\0
,因此在free(note[i])
之後,該空間會被加入free chunk
並有fd
及bk
欄位,會指向 heap,當note[i-1]
使用edit
加大空間後,可巧妙的接續在fd
或bk
之前,而在使用 List 之後便可 leak 出 heap 中上次 free 掉空間的位置,這些位置的 offset 都是固定的,因此可以算出heap base
- 因在輸入筆記後,程式並沒有在使用者輸入的內容最後方補上
漏洞利用及思路
- 為了要利用 double free 這個漏洞去改其他位置的值,必須先觸發
unlink()
不過要觸發unlink()
必須滿足下列三個條件其中一種:- 如果下一塊是 top chunk,且上一塊是 free chunk
- 最後合併到 top chunk
- 如果下一塊不是 top chunk
- 上一塊是 free chunk
- 下一塊是 free chunk
- 如果下一塊是 top chunk,且上一塊是 free chunk
- 然而紀錄上一塊是不是 free chunk 的及大小資訊( free 是利用這些資訊去尋找上一塊 chunk 位置),會記錄在目前這塊 chunk 的 meta 中,也就是說要確定該快 chunk 是否為已經 free 的狀態是由下一塊的 chunk 所決定的,所以如果使用
下一塊是 free chunk
這個條件必須改到下下一塊 chunk 的 meta data 或是利用特殊的方法欺騙 free() 下下一塊的位置,也就是必須動到三塊的 chunk 的 meta data,所以這部分稍微會比較麻煩一點點,故決定採用上一塊是 free chunk
這個條件來達成。 - leak heap
- 建立四塊左右的 note,
delete 0,2 塊
,再利用前面所述的方法,算出 heap 位置
- 建立四塊左右的 note,
構建 fake chunk
- 先 new 三塊 note 之後,delete 第二塊的,再利用 edit 加大第一塊的空間,使得可以蓋過第二塊的
meta data
起初大概的改法如下
- 但使用後缺發現會一直出現
double linked corruption
- 仔細查看後才發現到原來有
FD->bk != P || BK->fd != P
這項保護的機制在,不能直接改,因此必須找到滿足P->fd->bk == P
及P->bk->fd == P
的 pointer,才有機會利用 過了很久才想到在 note[i] 中都有指向 content 的 pointer 只要稍作修改就可偽造不同 size 的 chunk 讓 free 以為
note[i]->content
所指的位置為 chunk 的 head,這一步應該就是最關鍵的地方,也是讓我卡比較多時間的地方,其最後改法如下圖所示(黃框為 fake chunk ):在
delete note[1]
也就是 free(note[1]->content) 之後便可成功改到note[0]
讓note[0]->content = &(note[0]->content)-0x10
亦及 FD->bk = BKnote[0]->content = &(note[0]->content)-0x18
亦及 BK->fd = FD
因此 note[0]->content 位置就變成了
&(note[0]->content)-0x18
,這樣就可以利用 edit 任意更改note[i]
的內容
- 先 new 三塊 note 之後,delete 第二塊的,再利用 edit 加大第一塊的空間,使得可以蓋過第二塊的
更改 note[i]
我這邊稍作了修改將 note 變成六塊
- 第 0-1 塊用來 leak heap 位置用
- 第 2-3 塊用來更改
note[i]
的內容 - 因此只要再次用 edit 更改同樣大小的內容,便可改掉整個 note,這部分定要跟之前說 new 的大小相同,否則會重新 realloc 會失敗,示意圖大概如下
- 第 4-5 塊最後會用來改 atoi 的 got
- 事實上可以不用這麼多塊,但只是怕亂掉所以每塊都分開
再來將
note[i]
部分內容改成free_got
及atoi_got
位置
- 使用 list 後,可利用 got 來算出 libc 的位置
改 got
- 再用 edit 更改
note[5]
後,便可將 atoi 的 got 內容改為system
- 再用 edit 更改
跳轉到 system
- 直接輸入
/bin/sh
就會去執行system('/bin/sh')
,這樣就拿到 shell 了
- 直接輸入
exploit exploit
心得
- 這次 0ctf 題目算是不會很難,只是不知道為什麼第二天就體力不支了,整整兩天只解了 freenote 這題,不過這次題目出的我個人覺得還算不錯,也挺好玩的,只是實力與經驗還需再加強,也要再多多練一下其他領域的題目,不然每次解 pwn 之外的題目都幾乎不會解,就連最簡單的 SQL injection 都會有點問題,不過我覺得 freenote 這題是很棒的一題,可以拿來練習 heap exploition 的部分,未來有時間再來整理有關 heap exploition 的資料。
2015年1月28日 星期三
GHOST : The Vulnerability of Glibc
前言
偶然間在網路上看到了一個名為 Ghost 的漏洞,所以稍微整理一下,以方便自己理解,背景知識為敘述漏洞時可能需要知道的地方,由於網路上文件頗多,這裡僅挑一些 link 作為參考,便不在此多談。
背景知識
- vulnerability
- protection
- glibc
受影響版本
- glibc 2.2 - 2.17
漏洞概述
簡單來說在 glibc 中的 __nss_hostname_digits_dots()
這個 function 中存在著 buffer overflow 的問題,而會 call 這個 function 的則是 gethostbyname*()
系列的 function , 這系列 funtion 主要是用來做 IP 和 hostname 的轉換也就是所謂的 DNS Query ,但是當給的 hostname 已經本身就是 IP 的型態呢?為了這種情況 glibc 便在 __nss_hostname_digits_dots()
加了判斷只要是 IP 的型態,便會跳過 DNS 查詢,然而這個地方卻發生了 buffer overflow 的問題,但 payload 必須符合下列條件
- 第一個字元必須是數字
- 最後一個不能為 dot (.)
- 全部都必須是數字跟點
- 需要夠大,足以 overflow
然而既然不能塞任意的 shellcode ,卻為什麼還是非常危險呢?這裡可能必須先知道 free chunk 是什麼?簡單來說是 glibc 中 malloc 管理分配記憶體的一個機制,會將目前為 free 的空間分成許多 chunk 並用 linked-list 串起來,只要 malloc 一個空間,就會從這個 linked-list 拿出來,主要是為了不讓分配的空間支離破碎,而這紀錄這些資訊的地方就是在每個 chunk 中最前面的地方,存有這個 chunk 是不是 inused 或是這個 chunk 大小多大等等資訊,更詳細的內容可參考這篇,或者是自行 google。
回到主題,為什麼這樣的問題會如此危險?原因是 overflow 之後攻擊者可以利用它去更改 heap 中,鄰近的 free chunk 的 header
,以增加 free chunk 大小,要是後面又接著程式已使用的空間,那麼當系統在 allocate 給 user 這段空間時,便能任意更改程式所存在記憶體中的資訊,也有機會造成 memory leak 的問題,而且可以繞過 (ASLR,NX) 等保護。
漏洞細節
漏洞發生的 function 為 __nss_hostname_digits_dots()
主要在 hostname 為 IP 時就會使用到這個 function,而這個 function 的 code 中
85 size_needed = (sizeof (*host_addr)
86 + sizeof (*h_addr_ptrs) + strlen (name) + 1);
size_needed
會先去計算儲存 host_addr
, h_addr_ptrs
, 和 name (the hostname)
這三個空間的大小,接下來的 code 主要是確認預先分配的 buffer 是否夠大,要是不夠大則會再分配更大的空間給他,不過分成 reentrant 跟 non-reentrant 兩種 case ,這裡小提一下什麼 reentrant 跟 non-reentrant 的差別,差別在於主要是可是重複執行及不可重複執行的程式碼,像是遞迴的 code 都是使用 local variable 的,所以基本上重複執行沒問題,但如果 code 使用到的是 static variable 並且是在 multi thread 的情況下,很有可能就會有 Race condition 的情況發生,詳細的就不在這裡多說了。
reentrant
88 if (buffer_size == NULL)
89 {
90 if (buflen < size_needed)
91 {
92 if (h_errnop != NULL)
93 *h_errnop = TRY_AGAIN;
94 __set_errno (ERANGE);
95 goto done;
96 }
97 }
non-reentrant
98 else if (buffer_size != NULL && *buffer_size < size_needed)
99 {
100 char *new_buf;
101 *buffer_size = size_needed;
102 new_buf = (char *) realloc (*buffer, *buffer_size);
103
104 if (new_buf == NULL)
105 {
106 save = errno;
107 free (*buffer);
108 *buffer = NULL;
109 *buffer_size = 0;
110 __set_errno (save);
111 if (h_errnop != NULL)
112 *h_errnop = TRY_AGAIN;
113 *result = NULL;
114 goto done;
115 }
116 *buffer = new_buf;
117 }
接下來就是最關鍵的地方
121 host_addr = (host_addr_t *) *buffer;
122 h_addr_ptrs = (host_addr_list_t *)
123 ((char *) host_addr + sizeof (*host_addr));
124 h_alias_ptr = (char **) ((char *) h_addr_ptrs + sizeof (*h_addr_ptrs));
125 hostname = (char *) h_alias_ptr + sizeof (*h_alias_ptr);
從上面可以看到 buffer
的部分一共有四個不同的 pointer 指向他的的記憶體區塊,分別是 host_addr
, h_addr_ptrs
, h_alias_ptr
, 和 hostname
,但回頭看看剛剛 size_needed
那部份的 code 卻沒有計算到 sizeof (*h_alias_ptr)
的部分,因此我們可以 overflow 一個 char pointer 的大小,說到這裡可能又頭昏了,只好用圖來說明:
下圖是說明 buffer 這個空間是透過
gethostbyname*()
去分配到的 heap 中的記憶體區塊,並且有四個 pointer 分別指到其中的一部分然而
size_needed
卻少計了sizeof (*h_alias_ptr)
,讓系統以為所分配的大小夠塞全部的東西
但接下來這段 code 就是造成 overflow 的地方,主要是將 name 的內容塞到 hostname 所指的記憶體區塊
157 resbuf->h_name = strcpy (hostname, name);
問題來了,假設 size_needed
剛好為預先分配的最大值 1024 ,那麼這時候 hostname 所指的位置應該只剩 sizeof(name)-sizeof(*h_alias_ptrs)
的空間可以放而已,因此當 name 複製到 hostname 時就會造成 overflow,就如下圖所示
這就是這次漏洞的主要問題點,不過要走到這項流程必須符合之前所說的條件才會達成,更詳細的內容請參考 Qualys 所釋出的漏洞分析,上面有更詳細的說明及測試。
POC
可測試系統中是否為安全的,使用方式為,請將下列程式碼寫入 ghost.c
檔案中,然後執行
$ gcc ghost.c -o ghost
$ ./ghost
# 如果出現 vulnerable 那麼就代表系統中存在這個風險
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define CANARY "in_the_coal_mine"
struct {
char buffer[1024];
char canary[sizeof(CANARY)];
} temp = { "buffer", CANARY };
int main(void) {
struct hostent resbuf;
struct hostent *result;
int herrno;
int retval;
/*** strlen (name) = size_needed - sizeof (*host_addr) - sizeof (*h_addr_ptrs) - 1; ***/
size_t len = sizeof(temp.buffer) - 16*sizeof(unsigned char) - 2*sizeof(char *) - 1;
char name[sizeof(temp.buffer)];
memset(name, '0', len);
name[len] = '\0';
retval = gethostbyname_r(name, &resbuf, temp.buffer, sizeof(temp.buffer), &result, &herrno);
if (strcmp(temp.canary, CANARY) != 0) {
puts("vulnerable");
exit(EXIT_SUCCESS);
}
if (retval == ERANGE) {
puts("not vulnerable");
exit(EXIT_SUCCESS);
}
puts("should not happen");
exit(EXIT_FAILURE);
}
受引響的服務
Exim、clockdiff、pppd 等調用 gethostbyname*()
的服務
新增 wordpress 等 php 相關的 CMS 也有機會受影響
排除名單
apache, cups, dovecot, gnupg, isc-dhcp, lighttpd, mariadb/mysql, nfs-utils, nginx, nodejs, openldap, openssh, postfix, proftpd, pure-ftpd, rsyslog, samba, sendmail, sysklogd, syslog-ng, tcp_wrappers, vsftpd, xinetd. 雖然上述是目前應該不會有問題的,但還是建議更新一下 glibc 以防萬一
同場加映
這幾天下來又有研究人員發現以 php 所寫的相關應用也都有機會受到影響,像是 wordpress 等等 CMS ,攻擊者有機會藉由惡意的 domain 來觸發 ghost 這個漏洞,危害極大,所以老話一句,無論如何都請更新 Glibc ,以確保伺服器的安全。
修補方式
Debian 系列
# apt-get clean
# apt-get update
# apt-get upgrade
# reboot
Red Hat 系列
# yum clean
# yum update
# yum install glibc*
# reboot
其他各版本的修補方式可參考這篇
Reference
Qualys Security Advisory CVE-2015-0235 glibc Cross Reference Freebuf threatpost
附註
如上述內容有任何錯誤的地方歡迎留言告知
2015年1月20日 星期二
CTCTF
CTCTF
這場 CTF 是交大程式安全及台大電腦安全課程的期末考,不過玩起來就跟真正的比賽一樣完全不像期末考,這次是以旁聽生的身份去參加,也是第一次參加現場攻防類型的 CTF ,不過因為經驗不足的關係,完全只能墊底,不過也當作一次經驗,這次實在過於注重在解題部分,並沒有考慮到要先把洞補起來,以及封包的重送等等,導致失去得分的機會,這類比賽跟以往的 Jeopardy 更有挑戰性,也相當有趣,只可惜這類比賽不多也不常見,希望哪天有機會可以多出去玩玩看,也希望能在這段時間能多多訓練自己,這條路實在是太廣了。
在這做個小筆記好了,未來有機會在玩這類型的比賽時,可以更快地上手
- 賽前準備
- 寫好可以直接送封包 data 的 script
- 寫好可以直接送 key 的 script
- 寫好 exploit 大致上的雛形
- 寫好 wrapper .......
- 安裝好各項工具
- gdb , wireshark , tcpdump , IDA ......
- 賽中
- 先把已知道洞補起來!!!
- 盡量在其他組別解出來前先解出來,雖然很不容易 XD
- 要有人做封包分析及備份
- 一旦有人攻擊成功,立即找出攻擊的封包,並利用它來送給其他組,以防與他組分數落差太大
- 分析完立刻補起漏洞
- 結束後
- 分析嚴重失分原因
- 分析分工是否妥當
- 下次該如何應對