君は春の中にいる、かけがえのない春の中にいる.

你驻足于春色中,于那独一无二的春色之中.

基于Python实现一个RASP-demo

一直想用Python的上层覆盖机制HOOK关键函数来实现一个RASP,最近有时间收集了这方面的一些资料。

0x00 引子

RASP (Runtime application self-protection),运行时应用自我保护。很早之前提出的一个防御概念,将防御注入到应用程序中,不需要修改原有应用的业务代码即可实现对原有应用的防护。

详细概念参考绿盟的这篇讲解:

http://blog.nsfocus.net/rasp-tech/#RASP-2

Python HOOK 函数劫持打算通过 Python 动态分析中用到的命名空间覆盖的机制,对危险函数及类操作进行中转重写。其实相当于

相应的技术参考下面篇技术:

逢魔安全实验室的研究

聂大佬的研究

pyekaboo-自动化的hook生成工具

0x01 从函数hook到类hook

函数 hook

模块函数 hook 可以直接通过覆盖劫持的方式进行,这种情况适用于面向过程以函数为主的模块。

示例如下,这里为了环境方便,直接在 umarfarook882 的一个 web demo 上进行修改测试(其实他在视频里已经展示了自己针对不同语言实现的RASP,但是 github 上只放出了测试环境,并未提供RASP代码)。

首先是一个命令执行的测试页面,现在我们希望不修改代码,直接在应用中内嵌防御模块。

命令执行的实现代码如下:

1
2
3
4
5
6
7
8
9
10
def post(self):
print("POST call")
print("POST ", self.request.uri,"\nBODY", self.request.body)
ping = self.get_argument('ping')
result = os.popen(ping)
res = result.read()
a = ''
for line in res.splitlines():
a += str(line)
self.render("ping.html",msg=a)

通过 hook 关键 os 函数 popen,可以实现一个针对命令执行的防御,详细代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

首先,在应用的同目录下,新建 os.py 文件进行覆盖
然后,中转到正常的 os 模块并在 popen 函数调用时增加

import sys
import io

class _InstallFcnHook(object):
def __init__(self, fcn, debug=False):
self.debug = debug
self._fcn = fcn

def _pre_hook(self, *args, **kwargs):
return (args, kwargs)

def _post_hook(self, retval, *args, **kwargs):
#print(retval.read())
if 'whoami' in args:
a = io.open('log','w+')
a.write(u"'whoami' is dangerous")
#file = open('log',O_RDONLY)
a.close()
a = io.open('log','r')
retval = a
return retval

def __call__(self, *args, **kwargs):
_hook_args = args
_hook_kwargs = kwargs
(_hook_args, _hook_kwargs) = self._pre_hook(*args, **kwargs)

retval = self._fcn(*_hook_args, **_hook_kwargs)

retval = self._post_hook(retval, *_hook_args, **_hook_kwargs)

return retval

def _load_and_regisqter_as(input_modname, output_modnames=[], look_path=[]):
import imp
mod = None
fd = None
try:
fd, pathname, description = imp.find_module(input_modname, look_path)
for output_name in output_modnames:
mod = imp.load_module(output_name, fd, pathname, description)
finally:
if fd is not None:
fd.close()
return mod

if __name__ != "__main__":
_load_and_regisqter_as(__name__, [__name__, "orig_" + __name__], sys.path[::-1])

popen = _InstallFcnHook(popen)

其中 _InstallFcnHook 类实现了对应模块函数的劫持,_load_and_regisqter_as 函数则相当于是一个重定向的加载,加载完成后,对相应的函数进行覆盖。

而RASP就可以在覆盖的过程中,对整个应用调用前和调用后的参数进行获取,进而进行判断和阻断。这里需要注意的是,由于原来的代码使用 popen 来做命令执行的复现,因此这里劫持时需要返回一个 file 对象,又由于 os 模块自带 open 的机制和 io.open 不同,所以需要我们使用 io.open 重新生成一个 file 对象,在命中过滤规则时重写原有的 file 对象,也就是下面这段代码。

1
2
3
4
5
6
7
8
9
10
def _post_hook(self, retval, *args, **kwargs):
#print(retval.read())
if 'whoami' in args:
a = io.open('log','w+')
a.write(u"'whoami' is dangerous")
#file = open('log',O_RDONLY)
a.close()
a = io.open('log','r')
retval = a
return retval

我们先判断传递进来的命令是否命中规则(作为demo,这里仅以 whoami 为例),如果命中规则,就更改返回对象。这里选择了在命令执行后阻断结果回显,还有许多命令显然需要在执行前进行阻断,因此这一类操作就需要在 _pre_hook 中编写相应的防御代码。

类 hook

(由于最新的一篇论文阅读笔记发布,该部分实验未完待续~~)