在江北研究jsunpackn(源码分析)
2012-10-18 10:37
429 查看
Jsunpack-n 的核心是SpiderMonkey + Python 源代码javascript和python两部分
pre.js javaScript hooks and environment,这个文件中,有hook eval函数的部分,但是被注是掉了,关于eval的hook通过修改
SpiderMonkey来完成,这段代码将会加载到 main JS(也就是要解密的js) 的之前运行,来hook关键函数,已经加载plugin,设置环境变量等。
这两个JS文件会分别在main JS 的之前和之后运行。
Hook函数的清单
这里说一下,jsunpackn检测恶意代码的两种方式
静态检测使用过Yara模块实现
detection.py use rules files to find content
pdf.py swf.py Extraction of javascript
gzip.py unctions that read and write gzipped files
html.py A simple HTML language parser. Uses the 'htmlparse.conf' file to define rules.
lzw.py lzw压缩,所使用的模块
urlattr.py his is a helper class within jsunpack-n 存放一些属性信息之类的,还有处理url的一些工具函数
debuger Used to track performance statistics Within the application being debugged
rules 这是Yara模块所使用的规则文件,支持自定义规则
rules.ascii 只支持ascii码的规则,不知道有什么作用
htmlparse.config 提供parsing HTML的逻辑支持,用来寻找藏匿JS的HTML field,支持自定义规则
options.config 存放一些路径数据,和配置数据
eval的hook的关键代码,在jsobj.c函数的obj_eval函数中
detection shell的核心代码是这样,很迷惑,后来经伟哥指点,这个shellcode是连续的16进制存储,这只是一个粗略的查找方法。
修改在jsstr.c,核心代码如下
main函数的开始,就是对一些全局变量的定义与模块的检测,以及Optionparser的初始化,类似这样。
1273行进行参数的读取
1279使用configparser对配置文件options.configfile进行读取,configparser也是常用的读取配置文件的
python模块,在配置文件中中括号包含的值 section,其下包含值的为 key,对照options.config,发现
读入了path下的value,pre.js post.js tmpdir thmlparse.config
1312行 读取了rules rules.ascii option.htmlparse,这三个规则配置文件,其中内容
存储在数组options中
1329行开始抓取页面内容,这里有三个判断,file interface与urlfatch, 一般情况下我们直接输入网址,
进入urlfatch这个分支,interface应该是处理包的还原,file是处理本地文件的分支。之后调用
jsunpack函数进行抓取,在这里已经完成了对url中的JS文件的查找,以及decode,结果被存储到了
JS之中,调用前使用正则替换re.sub替换掉了url地址的https;//头
1351行之后就是输出结果clean history
从144行开始decode initialize,首先判断是interface , urlfetch 还是 local file,这和main函数调用此函数时的判断一致
interface分支使用了nids模块作包的还原,local file中也有pcap文件的判断。
这里self.fetch一句会把页面抓取到本地,并在控制台输出信息。
934之前是对url的规范化,和一些参数的定义,不去深究,之后开始构造request头,检查proxy,并准备发送请求
之后通过,下面这一句,取得了主机ip,做了一些设定,
到970行转入decode函数,正式开始解码JS,这里的remote变量存出了,页面完整源代码
此函数只作一系列的判断和一部分文件的JS搜索工作,比如FWS文件的JS搜索,页面中的搜索
工作应该是交给find_urls来作,解码工作,应该是交给decode_JS来作
原理很简单,就是查找关键字,就不多说了,查找规则应该在htmlparser.config里面
随后进入decodeVersion函数
decode_Helper函数
定义于313行,其中关键代码如下,明显是直接调用了SpiderMonkey进行解析,通过观察可以发现,执行原理是把页面源码抓去的到本地文件之后放在pre.js之后执行,并不
急于执行post.js,此时的抓取的页面代码是html混杂js的代码,通过stdout和stderr这两个文件来收集信息。
在这里把信息存入内存
直接看下面,函数会模拟各种浏览器解析页面代码,其中都调用到了decodeJShelper这个函数,并把浏览器的各种信息当作参数传递进去
模拟的浏览器有这几个
JS部分
post.js (post-processing) 做收尾工作的脚本,在main js 被interpreted之后执行,输出结果。pre.js javaScript hooks and environment,这个文件中,有hook eval函数的部分,但是被注是掉了,关于eval的hook通过修改
SpiderMonkey来完成,这段代码将会加载到 main JS(也就是要解密的js) 的之前运行,来hook关键函数,已经加载plugin,设置环境变量等。
这两个JS文件会分别在main JS 的之前和之后运行。
Hook函数的清单
eval window.eval window.execScript String.eval app.eval addEventListener attachEvent app.setTimeOut window.onload app.setInterval
这里说一下,jsunpackn检测恶意代码的两种方式
静态检测使用过Yara模块实现
• Static – “rules” file ( decodedPDF and decodedOnly classes ) rule mediaNewplayer: decodedPDF { meta: ref = "CVE-2009-4324" hide = true strings: $cve20094324 = "media.newPlayer" nocase fullword condition: 1 of them }动态监测,以及很多加密的代码是通过JS HOOK 来实现
• Dynamic – “pre.js” hooking file var media = { newPlayer : function(a){ if (a == null){ print("//alert CVE-2009-4324 media.newPlayer with NULL parameter"); } else { print("//warning CVE-2009-4324 media.newPlayer access"); } },
python部分
junpackn.py 主文件detection.py use rules files to find content
pdf.py swf.py Extraction of javascript
gzip.py unctions that read and write gzipped files
html.py A simple HTML language parser. Uses the 'htmlparse.conf' file to define rules.
lzw.py lzw压缩,所使用的模块
urlattr.py his is a helper class within jsunpack-n 存放一些属性信息之类的,还有处理url的一些工具函数
debuger Used to track performance statistics Within the application being debugged
配置文件
这些文件都会在,jsunpackn.py运行的开始,被读入内存rules 这是Yara模块所使用的规则文件,支持自定义规则
rules.ascii 只支持ascii码的规则,不知道有什么作用
htmlparse.config 提供parsing HTML的逻辑支持,用来寻找藏匿JS的HTML field,支持自定义规则
options.config 存放一些路径数据,和配置数据
SpirderMonkey源码修改
SpriderMonkey的源代码修改分为两部分,第一部分是eval的Hook,第二部分是detection shellcode。eval的hook的关键代码,在jsobj.c函数的obj_eval函数中
//added if (JSSTRING_IS_DEPENDENT(str)) { n = (size_t)JSSTRDEP_LENGTH(str); s = JSSTRDEP_CHARS(str); } else { n = (size_t)str->length; s = str->u.chars; } printf("\n//eval\n"); for (i = 0; i < n; i++){ if (s[i] == '\0'){ break; } printf("%c",s[i]); } printf("\n"); //end added
detection shell的核心代码是这样,很迷惑,后来经伟哥指点,这个shellcode是连续的16进制存储,这只是一个粗略的查找方法。
修改在jsstr.c,核心代码如下
while (i<n*2 && found_shellcode_char == JS_FALSE){ if ((a > 0 && a < ' ' && a != '\r' && a != '\n' && a != '\t') || (a >= '\x7f')){ count_threshold ++; if (count_threshold > 25){ printf("\n//warning CVE-NO-MATCH Shellcode Engine Binary Threshold\n",a ); found_shellcode_char = JS_TRUE; return JS_TRUE; } } i++; }
python代码调试方法
python -m pdb 1.py n 下一行 list 最近代码段 breadk 1.py:6 设断点 pp sum 打印变量 break 或 b 设置断点 continue 或 c 继续执行程序 list 或 l 查看当前行的代码段 step 或 s 进入函数 return 或 r 执行代码直到从当前函数返回 exit 或 q 中止并退出 next 或 n 执行下一行 pp 打印变量的值 help
Jsunpackn.py 源码
main函数
main函数在1192行开始,jsunpackn使用了一种,叫做Optionparser的模块来实现,模拟Linux规范的命令行参数格式main函数的开始,就是对一些全局变量的定义与模块的检测,以及Optionparser的初始化,类似这样。
parser = OptionParser(message) parser.add_option('-t', '--timeout', dest='timeout', help='limit on number of seconds to evaluate JavaScript', #default=30, action='store')
1273行进行参数的读取
(options, args) = parser.parse_args()
1279使用configparser对配置文件options.configfile进行读取,configparser也是常用的读取配置文件的
python模块,在配置文件中中括号包含的值 section,其下包含值的为 key,对照options.config,发现
读入了path下的value,pre.js post.js tmpdir thmlparse.config
config = ConfigParser.RawConfigParser() if config.read(options.configfile): for path, value in config.items('paths'): if value == 'NULL': value = '' fileopt[path] = value for path, value in config.items('decoding'): if value == 'True': value = True elif value == 'False': value = False fileopt[path] = value
1312行 读取了rules rules.ascii option.htmlparse,这三个规则配置文件,其中内容
存储在数组options中
fin = open('rules', 'r') if fin: options.rules = fin.read() fin.close() fin = open('rules.ascii', 'r') if fin: options.rulesAscii = fin.read() fin.close() if options.htmlparse: fin = open(options.htmlparse, 'r') if fin: options.htmlparseconfig = fin.read() fin.close()
1329行开始抓取页面内容,这里有三个判断,file interface与urlfatch, 一般情况下我们直接输入网址,
进入urlfatch这个分支,interface应该是处理包的还原,file是处理本地文件的分支。之后调用
jsunpack函数进行抓取,在这里已经完成了对url中的JS文件的查找,以及decode,结果被存储到了
JS之中,调用前使用正则替换re.sub替换掉了url地址的https;//头
urlattr.verbose = True #shows [nothing found] entries options.urlfetch = re.sub('^[https]+://', '', options.urlfetch) js = jsunpack(options.urlfetch, ['', '', ''], options) prevRooturl = js.rooturl
1351行之后就是输出结果clean history
jsunpack类
主函数
65行处定义,被main函数所调用,负责主要的JS查找与解密工作,函数介绍如下INPUT: These are the main input modes: 1) options.urlfetch: URL to fetch and decode (if options.active, then follow up) OR 2) todecode: local contents or static string as: todecode[0]=url_or_name(optional) todecode[1]=data(mandatory) todecode[2]=filename OUTPUT: check the <jsunpack Object>.rooturl structure. To decode multiple files and not create separate trees, passing rooturl between different decodings is necessary (as prevRooturl). parameters: @_start = url of root node @options = configuration and user settings; includes rules as strings (not filenames) @prevRooturl = continuity of tree between decodings, after decoding pass in <jsunpack Object>.rooturl之后到144行都是一些,初始化,路径的设置,temp文件的创建等,我们不仔细看他们
从144行开始decode initialize,首先判断是interface , urlfetch 还是 local file,这和main函数调用此函数时的判断一致
interface分支使用了nids模块作包的还原,local file中也有pcap文件的判断。
这里self.fetch一句会把页面抓取到本地,并在控制台输出信息。
if self.OPTIONS.interface: nids.param('device', self.OPTIONS.interface) self.run_nids() elif self.OPTIONS.urlfetch: if not self.OPTIONS.quiet: print 'URL fetch %s' % (self.OPTIONS.urlfetch) status, fname = self.fetch(options.urlfetch) if not self.OPTIONS.quiet: print status else: #local file decode if not self.url:
fetch函数
def fetch(self, url): 在910处定义,由jsunpack类main函数调用,934之前是对url的规范化,和一些参数的定义,不去深究,之后开始构造request头,检查proxy,并准备发送请求
hostname, dstport = self.hostname_from_url(url) if self.OPTIONS.proxy and (not self.OPTIONS.currentproxy): proxies = self.OPTIONS.proxy.split(',') self.OPTIONS.currentproxy = proxies[random.randint(0, len(proxies) - 1)] if not self.OPTIONS.quiet: print '[fetch config] random proxy %s' % (self.OPTIONS.currentproxy) request = urllib2.Request('http://' + url) request.add_header('Referer', 'http://' + refer) request.add_header('User-Agent', 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0)')在955行左右使用了urllib2模块的opener函数进行据抓取,在url不可用时速度较慢。
opener = urllib2.build_opener() try: remote = opener.open(request).read() except urllib2.HTTPError, error: remote = error.read()之后调用了create_sha1file这个函数,定义在192,因为是创建文件的函数,就没有具体分析。
之后通过,下面这一句,取得了主机ip,做了一些设定,
resolved = socket.gethostbyname(hostname)
到970行转入decode函数,正式开始解码JS,这里的remote变量存出了,页面完整源代码
self.main_decoder(remote, url)
main_decode函数
定义于980,由fetch函数调用,函数开始是一些对文件类型的判断,比如PDF的判断方式是这样if 0 <= data[0:1024].find('%PDF-') <= 1024: isPDF = TrueSWF文件是这样
if data.startswith('CWS') or data.startswith('FWS'): isSWF = True
此函数只作一系列的判断和一部分文件的JS搜索工作,比如FWS文件的JS搜索,页面中的搜索
工作应该是交给find_urls来作,解码工作,应该是交给decode_JS来作
if data.startswith('CWS') or data.startswith('FWS'): isSWF = True
self.rooturl[self.url].filetype = 'SWF'
msgs, urls = swf.swfstream(data)
for url in urls:
swfjs_obj = re.search('javascript:(.*)', url, re.I)
if swfjs_obj:
swfjs += swfjs_obj.group(1) + '\n'
else:
#url only
multi = re.findall('https?:\/\/([^\s<>\'"]+)', url)
if multi:
for m in multi:
self.rooturl[self.url].setChild(m, 'swfurl')
else:
#no http
if url.startswith('/'):
#relative root path
firstdir = re.sub('([^/])/.*$', '\\1', self.url)
m = firstdir + url
else:
#relative preserve directory path
lastdir = re.sub('/[^\/]*$', '/', self.url)
m = lastdir + url
self.rooturl[self.url].setChild(m, 'swfurl')
else:
isSWF = False
find_urls函数
定义于471,作用就是这个'''returns JavaScript (if it exists)'''
原理很简单,就是查找关键字,就不多说了,查找规则应该在htmlparser.config里面
decode_JS函数
定义在265行,函数开始会先对页面源码进行解析,这里调用了html.py的htmlparser函数,间接使用了Beatuiful Soup模块,函数很短随后进入decodeVersion函数
to_write_headers, to_write = self.hparser.htmlparse(content)
decode_Helper函数
定义于313行,其中关键代码如下,明显是直接调用了SpiderMonkey进行解析,通过观察可以发现,执行原理是把页面源码抓去的到本地文件之后放在pre.js之后执行,并不急于执行post.js,此时的抓取的页面代码是html混杂js的代码,通过stdout和stderr这两个文件来收集信息。
po = subprocess.Popen(['js', '-f', self.OPTIONS.pre, '-f', current_filename + '.js', '-f', self.OPTIONS.post], shell=False, stdout=js_stdout, stderr=js_stderr)
在这里把信息存入内存
js_stdout.close() js_stdout = open(current_filename + '.stdout', 'rb') decoded = js_stdout.read() js_stdout.close() js_stderr.close() js_stderr = open(current_filename + '.stderr', 'rb') errors = js_stderr.read() js_stderr.close()
decodeVersion函数
定义于240行,在函数的开始部分,可以使解析器不处理源码中的中文字符,为了效率,但需要设置 fasteval,不过也无关紧要。if not self.OPTIONS.fasteval: #don't evaluate HTML en/zh-cn in favor of performance
直接看下面,函数会模拟各种浏览器解析页面代码,其中都调用到了decodeJShelper这个函数,并把浏览器的各种信息当作参数传递进去
模拟的浏览器有这几个
'Vista', 'Mozilla/4.0 'Opera', 'Opera/9.64 'Firefox', 'Mozilla/5.0代码如下
if not self.OPTIONS.fasteval: browsers.append(['IE8/Vista', 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)']) browsers.append(['Opera', 'Opera/9.64 (Windows NT 6.1; U; de) Presto/2.1.1']) browsers.append(['Firefox', 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2 (.NET CLR 3.5.30729)']) for name, browser in browsers: if duration < self.OPTIONS.timeout and runningTime <= self.OPTIONS.redoevaltime: midpoint = browser.find('/') appCodeName = browser[:midpoint] appVersion = browser[midpoint + 1:] decoded, currentRunningTime = self.decodeJShelper('navigator.appCodeName = String("%s"); navigator.appVersion = String("%s"); navigator.userAgent = String("%s"); document.lastModified = String("%s");\n%s' % (appCodeName, appVersion, browser, self.lastModified, need_to_write)) runningTime = currentRunningTime duration += currentRunningTime decodings.append(['browser=' + name, decoded])
相关文章推荐
- zepto源码研究 - ajax.js($.ajaxJSONP 的分析)
- zepto源码研究 - ajax.js($.ajax具体流程分析)
- zepto源码研究 - ajax.js(请求过程中的各个事件分析)
- Vue学习之源码分析--从Vue.js源码角度再看数据绑定(三)
- zepto源码研究 - zepto.js - 6(模板方法)
- draggable.js源码分析
- jQuery源码研究分析学习笔记-jQuery.deferred()(12)
- Irrlicht 0.1引擎源码分析与研究(二)
- SpringMVC关于json、xml自动转换的原理研究[附带源码分析]
- qq农场js外挂详细制作(提供源码、有注释、有抓包数据分析、不再更新、不回答提问)
- 转载_LKM backdoor研究linux系列--insmod源码分析篇
- Redis源码分析系列十六:processCommand研究
- Underscore.js 1.3.3 源码分析收藏
- Nutch1.7源码再研究之---12 Fetch中的FetcherThread线程源码分析
- thrift源码研究-TJSONProtocol代码分析
- SpringMVC关于json、xml自动转换的原理研究[附带源码分析]
- PHP内核研究: PHP源码目录分析
- SpringMVC关于json、xml自动转换的原理研究[附带源码分析]
- JS中判断相等的方法(underscore中eq方法源码分析)