浅谈RASP技术攻防之基础篇

作者:sky@iiusky

目录

引言

本文就笔者研究RASP的过程进行了一些概述,技术干货略少,偏向于普及RASP技术。中间对java如何实现rasp技术进行了简单的举例,想对大家起到抛砖引玉的作用,可以让大家更好的了解一些关于web应用程序安全防护的技术。文笔不好,大家轻拍。

一 、什么是RASP?

在2012年的时候,Gartner引入了“Runtime application self-protection”一词,简称为RASP。它是一种新型应用安全保护技术,它将保护程序像疫苗一样注入到应用程序中,应用程序融为一体,能实时检测和阻断安全攻击,使应用程序具备自我保护能力,当应用程序遭受到实际攻击伤害,就可以自动对其进行防御,而不需要进行人工干预。

RASP技术可以快速的将安全防御功能整合到正在运行的应用程序中,它拦截从应用程序到系统的所有调用,确保它们是安全的,并直接在应用程序内验证数据请求。Web和非Web应用程序都可以通过RASP进行保护。该技术不会影响应用程序的设计,因为RASP的检测和保护功能是在应用程序运行的系统上运行的。

二、RASP vs WAF

很多时候大家在攻击中遇到的都是基于流量规则的waf防御,waf往往误报率高,绕过率高,市面上也有很多针对不同waf的绕过方式,而RASP技术防御是根据请求上下文进行拦截的,和WAF对比非常明显,比如说:

攻击者对urlhttp://http.com/index.do?id=1进行测试,一般情况下,扫描器或者人工测试sql注入都会进行一些sql语句的拼接,来验证是否有注入,会对该url进行大量的发包,发的包可能如下

http://xxx.com/index.do?id=1' and 1=2--

但是应用程序本身已经在程序内做了完整的注入参数过滤以及编码或者其他去危险操作,实际上访问该链接以后在数据库中执行的sql语句为

select id,name,age from home where id='1 \' and 1=2--'

可以看到这个sql语句中已经将单引号进行了转义,导致无法进行,但是WAF大部分是基于规则去拦截的(也有小部分WAF是带参数净化功能的),也就是说,如果你的请求参数在他的规则中存在,那么waf都会对其进行拦截(上面只是一个例子,当然waf规则肯定不会这么简单,大家不要钻牛角尖。),这样会导致误报率大幅提升,但是RASP技术可以做到程序底层拼接的sql语句到数据库之前进行拦截。也就是说,在应用程序将sql语句预编译的时候,RASP可以在其发送之前将其拦截下来进行检测,如果sql语句没有危险操作,则正常放行,不会影响程序本身的功能。如果存在恶意攻击,则直接将恶意攻击的请求进行拦截或净化参数。

三、国外的RASP

我搜集到国外RASP的产品有的下面几个,可能列表不足,欢迎补充:P

公司名字 产品官网 产品照片
Micro Focus https://www.microfocus.com/en-us/products/application-defender/features
Prevoty https://www.prevoty.com/
waratek https://www.waratek.com/application-security-platform/
OWASP AppSensor http://appsensor.org/
Shadowd https://shadowd.zecure.org/overview/introduction/
immun https://www.immun.io/features
Contrast Security https://www.contrastsecurity.com/runtime-application-self-protection-rasp
Signal Sciences https://www.signalsciences.com/rasp-runtime-application-self-protection/
BrixBits http://www.brixbits.com/security-analyzer.html

笔者只列举了部分国外产品,关于更多这些产品的介绍请大家自行咨询。

根据上述收集到的国外RASP技术产品,发现大家都各有千秋,甚至还有根据RASP技术衍生出来的一些其他技术名词以及解决方案。

并且大家对数据可视化越来越注重,让使用者可以一目了然的看清楚入侵点以及攻击链,以此来对特定的点进行代码整改或者RASP技术层面的攻击拦截。

四、国内RASP技术实现进度以及状态

国内目前做RASP技术的厂家不多,我收集的只有以下几家。如有不足,欢迎补充。

产品名 or 公司名 产品官网 产品照片 概述
灵蜥 http://www.anbai.com/lxPlatform/ 灵蜥是北京安百科技研发的一款RASP防御产品,而RASP安全研究团队的核心是之前乌云核心成员,据我了解,乌云在2012年后半年左右的时候就对RASP技术开始进行研究了。目前灵蜥RASP安全防御技术以及升级到了2.0版本,对灵蜥1.0版本的架构以及防御点进行了大版本的升级,应该是目前防御比较完善的一家了。
OneRasp https://www.oneapm.com/ 然后蓝海讯通研究的OneRasp也出现在市场,后面不知什么原因,蓝海讯通好像放弃了对oneRasp的研究以及售卖,现在oneRasp官网以及下线。
OpenRasp https://rasp.baidu.com/ 在随后可能就是大家都听过或者用过百度开源的OpenRasp,百度开源的rasp产品让更多的企业以及安全研究者接触和认识到了rasp技术。该项目目前社区活跃度挺高,插件开发也很简单,大大降低了使用门槛。
云锁 https://www.yunsuo.com.cn 云锁我是在18年的360安全大会上了解到的,官网将RASP技术列入了一个小功能,主打是的微隔离技术,关于微隔离技术,在这边我们不做探讨。有兴趣的可以搜索一下相关的技术文章。
安数云 http://www.datacloudsec.com/#/product-4 安数云WEB应用实时防护系统RASP是在我写这篇文章的时候,发现他们也开发了一款RASP产品才知道的。不过没有找到其技术白皮书等可参考的文章。

五、各种语言RASP技术实现方式

1、JAVA

Java是通过Java Agent方式进行实现(Agent本质是java中的一个动态库,利用JVMTI暴露的一些接口实现的),具体是使用ASM(`或者其他字节码修改框架`)技术实现RASP技术。Java Agent有三种机制,分别是Agent_OnLoad、Agent_OnAttach、Agent_OnUnload,大部分时候使用的都是Agent_OnLoad技术和Agent_OnAttach技术,Agent_OnUnload技术很少使用。具体关于Java Agent的机制大家可以看一下下面这些文章

JVMTM Tool Interface
javaagent加载机制分析
JVM 源码分析之 javaagent 原理完全解读

2、PHP

PHP是通过开发第php扩展库来进行实现。

3、.NET

.NET是通过 IHostingStartup(承载启动)实现,具体的大家可以下面这篇文章

在 ASP.NET Core 中使用承载启动程序集

4、other

RASP技术其实主要就是对编程语言的危险底层函数进行hook,毕竟在怎么编码转换以及调用,最后肯定会去执行最底层的某个方法然后对系统进行调用。由此可以反推出其hook点,然后使用不同的编程语言中不同的技术对其进行实现。

六、举个例子

1. 如何在Java中实现RASP技术

目前笔者我参与研究&研发了JAVA 版本的RASP技术,下面给大家简单的进行讲解。

在jdk1.5之后,java提供一个名为Instrumentation的API接口,Instrumentation 的最大作用,就是类定义动态改变和操作。开发者可以在一个普通 Java 程序(带有 main 函数的 Java 类)运行时,通过 – javaagent参数指定一个特定的 jar 文件(包含 Instrumentation 代理)来启动 Instrumentation 的代理程序。

关于premain的介绍,大家可以去我博客看一下
java Agent 简单学习

关于ASM的介绍以及使用,大家可以看一下下面的介绍:
ASM官方操作手册
AOP 的利器:ASM 3.0 介绍
关于ASM的时序图,我这边引用一下IBM上的一张图:
ASM时序图

正文开始

首先编写一个premain函数,然后在premain中添加一个我们自定义的Transformer。

我们的Transformer需要实现ClassFileTransformer接口中的transform方法,我这边就简单的先进行一个打印包名的操作。

在编译以后,运行结果如下:

由此可见我们的transform已经生效了。
我们来简单的类比一下加入agent以及未加入agent的过程。

由此可见,在使用了transform以后,可以对jvm中的未加载的类进行重写。已经加载过的类可以使用retransform去进行重写。
在java中有个比较方便的字节码操作库,ASM(ASM是一个比较方便的字节码操作框架,可以借助ASM对字节码进行修改,这样就可以实现动态修改字节码的操作了。ASM介绍

ClassVisitor接口,定义了一系列的visit方法,而这些visit方法,。

下面这段代码是对方法进行重写的一个过程。

首先定义一个ClassVisitor,重写visitMethod方法。

可以看到该方法返回的对象为MethodVisitor(后面简称mv),我们如果要修改其中的逻辑过程,需要对mv进行操作,下面展示一个简单的修改代码的过程。

上面visitCode中代码的大概意思为,插入一个调用TestInsert类中的hello方法,并且将一个数字8压入进去。
TestInsert类的代码如下

如果不出意外,运行jar包以后将输出类似下面这样的信息:

可以看一下加入agent运行以后的类有什么变化。

源代码

加入Agent运行以后的源代码

可以看到代码

System.out.println(TestInsert.hello(8));

成功在

System.out.println("Hello,This is TestAgent Program!");

之前运行,而调用TestInsert.hello这个方法在源代码中原来是没有的,我们是通过java的agent配合asm对运行的字节码进行了修改,这样就达到了埋点hook的目的。

在了解了上面的技术以后,就可以其他对关键的点进行hook了,hook的方法大同小异,这里仅仅是抛砖引玉,大家自行发挥。可以给大家推荐一个园长师傅写的一个简单例子:
javaweb-codereview

笔者展示的该例子项目地址为:javaagent

2. 如何在PHP中实现RASP技术

关于php中的rasp技术实现,笔者我还没有进行深入研究,大家可以参考以下两个开源的扩展进行研究,笔者接下来也准备研究php方面的rasp技术实现,欢迎大家一起讨论。

1、xmark
2、taint

七、RASP技术的其他方面应用场景

在RASP技术实现的过程中,我横向了遐想一些其他的用法以及参考了一下其他rasp产品的功能点,比如:

代码审计

对于rasp中运用到的技术,换一种思维方式,可以不进行拦截,而进行记录,对所有记录的日志结合上下文进行代码审计。

0day防御

对已经hook的关键点进行告警通知并且要拦截攻击行为,然后在公网部署多种不同cms的web蜜罐。如若已经触发到了告警通知,那么已经证明攻击已经成功。且拦截到的漏洞可能为0day。

攻击溯源

对所有攻击ip以及攻击的文件进行聚合,用时间轴进行展示。这样就可以定位到黑客是从上面时候开始进行攻击的,攻击中访问了哪些文件,触发了哪些攻击拦截。然后对所有大致相同的ip进行归类,可以引出来一个专业用于攻击溯源的产品。

DevOps

对所有事件详细信息提供完整的执行路径,包括代码行,应用程序中使用的完整上下文查询以及丰富的属性详细信息。

其他方面

当然Rasp技术的应用场景肯定不止这么多,还有其他很多方面的一些应用场景,大家可以发散思维去想。本文章仅用于抛砖引玉。

八、RASP技术有什么缺陷

  • 不同的编程语言可能编译语言和应用程序的版本不一致都导致RASP产品无法通用,甚至导致网站挂掉
  • 如果RASP技术中对底层拦截点不熟悉,可能导致漏掉重要hook点,导致绕过。
  • 对于csrf、ssrf、sql语句解析等问题目前还是基于部分正则进行防护(对于sql语句的解析问题可以使用AST语法树进行解析)。
  • ……

九、总结

目前 RASP 还处于一个发展的阶段,尚未像防火墙等常见的安全产品一样有非常明确的功能边界(scope),此文仅抛砖引玉,更多由RASP防御技术的用法可以发挥想象,比如日志监控、管理会话、安全过滤、请求管理等。
在研究RASP技术的时候,我发现RASP与APM有着部分相同的技术,APM本意是用于监控应用程序的,反转思路,在APM中监控应用程序的时候加入漏洞防护功能,即可形成了一个简单的RASP demo。在APM这方面,大家参考的对象可就很多了,比如:skywalkingnewrelic等,skywalking是开源产品,在github上就可以直接查看其源代码,而且是Apache的项目,而newrelic的所有agent都没有进行任何代码混淆,官方安装文档也很丰富,这些都是可以进行参考的。
由此可见,有时候技术换个平台、换个想法,就是一个完全不一样的东西了。所以对于任何技术都可以发散性的去想象一下其他的应用场景,万一恰到好处呢?
不论研究的技术是什么样的,可能大家实现的方式不一样,使用的技术不一样,使用的名称不一样,不过最终的目的就是为了保护应用程序安全,防止攻击者入侵成功。

十、参考

  1. https://searchsecurity.techtarget.com/opinion/McGraw-on-why-DAST-and-RASP-arent-enterprise-scale
  2. http://www.cnblogs.com/kingreatwill/p/9756222.html
  3. https://techbeacon.com/security/it-time-take-rasp-seriously
  4. https://www.gartner.com/doc/2856020/maverick-research-stop-protecting-apps
  5. https://github.com/anbai-inc/javaweb-codereview/tree/master/javaweb-codereview-agent
  6. https://www.03sec.com/3232.shtml

ps:本文首发于先知社区.

JVM 指令集

指令码 助记符 说明
0×00 nop 什么都不做
0×01 aconst_null 将null推送至栈顶
0×02 iconst_m1 将int型-1推送至栈顶
0×03 iconst_0 将int型0推送至栈顶
0×04 iconst_1 将int型1推送至栈顶
0×05 iconst_2 将int型2推送至栈顶
0×06 iconst_3 将int型3推送至栈顶
0×07 iconst_4 将int型4推送至栈顶
0×08 iconst_5 将int型5推送至栈顶
0×09 lconst_0 将long型0推送至栈顶
0x0a lconst_1 将long型1推送至栈顶
0x0b fconst_0 将float型0推送至栈顶
0x0c fconst_1 将float型1推送至栈顶
0x0d fconst_2 将float型2推送至栈顶
0x0e dconst_0 将double型0推送至栈顶
0x0f dconst_1 将double型1推送至栈顶
0×10 bipush 将单字节的常量值(-128~127)推送至栈顶
0×11 sipush 将一个短整型常量值(-32768~32767)推送至栈顶
0×12 ldc 将int, float或String型常量值从常量池中推送至栈顶
0×13 ldc_w 将int, float或String型常量值从常量池中推送至栈顶(宽索引)
0×14 ldc2_w 将long或double型常量值从常量池中推送至栈顶(宽索引)
0×15 iload 将指定的int型本地变量推送至栈顶
0×16 lload 将指定的long型本地变量推送至栈顶
0×17 fload 将指定的float型本地变量推送至栈顶
0×18 dload 将指定的double型本地变量推送至栈顶
0×19 aload 将指定的引用类型本地变量推送至栈顶
0x1a iload_0 将第0个int型本地变量推送至栈顶
0x1b iload_1 将第1个int型本地变量推送至栈顶
0x1c iload_2 将第2个int型本地变量推送至栈顶
0x1d iload_3 将第3个int型本地变量推送至栈顶
0x1e lload_0 将第0个long型本地变量推送至栈顶
0x1f lload_1 将第1个long型本地变量推送至栈顶
0×20 lload_2 将第2个long型本地变量推送至栈顶
0×21 lload_3 将第3个long型本地变量推送至栈顶
0×22 fload_0 将第0个float型本地变量推送至栈顶
0×23 fload_1 将第1个float型本地变量推送至栈顶
0×24 fload_2 将第2个float型本地变量推送至栈顶
0×25 fload_3 将第3个float型本地变量推送至栈顶
0×26 dload_0 将第0个double型本地变量推送至栈顶
0×27 dload_1 将第1个double型本地变量推送至栈顶
0×28 dload_2 将第2个double型本地变量推送至栈顶
0×29 dload_3 将第3个double型本地变量推送至栈顶
0x2a aload_0 将第0个引用类型本地变量推送至栈顶
0x2b aload_1 将第1个引用类型本地变量推送至栈顶
0x2c aload_2 将第2个引用类型本地变量推送至栈顶
0x2d aload_3 将第3个引用类型本地变量推送至栈顶
0x2e iaload 将int型数组指定索引的值推送至栈顶
0x2f laload 将long型数组指定索引的值推送至栈顶
0×30 faload 将float型数组指定索引的值推送至栈顶
0×31 daload 将double型数组指定索引的值推送至栈顶
0×32 aaload 将引用型数组指定索引的值推送至栈顶
0×33 baload 将boolean或byte型数组指定索引的值推送至栈顶
0×34 caload 将char型数组指定索引的值推送至栈顶
0×35 saload 将short型数组指定索引的值推送至栈顶
0×36 istore 将栈顶int型数值存入指定本地变量
0×37 lstore 将栈顶long型数值存入指定本地变量
0×38 fstore 将栈顶float型数值存入指定本地变量
0×39 dstore 将栈顶double型数值存入指定本地变量
0x3a astore 将栈顶引用型数值存入指定本地变量
0x3b istore_0 将栈顶int型数值存入第0个本地变量
0x3c istore_1 将栈顶int型数值存入第1个本地变量
0x3d istore_2 将栈顶int型数值存入第2个本地变量
0x3e istore_3 将栈顶int型数值存入第3个本地变量
0x3f lstore_0 将栈顶long型数值存入第0个本地变量
0×40 lstore_1 将栈顶long型数值存入第1个本地变量
0×41 lstore_2 将栈顶long型数值存入第2个本地变量
0×42 lstore_3 将栈顶long型数值存入第3个本地变量
0×43 fstore_0 将栈顶float型数值存入第0个本地变量
0×44 fstore_1 将栈顶float型数值存入第1个本地变量
0×45 fstore_2 将栈顶float型数值存入第2个本地变量
0×46 fstore_3 将栈顶float型数值存入第3个本地变量
0×47 dstore_0 将栈顶double型数值存入第0个本地变量
0×48 dstore_1 将栈顶double型数值存入第1个本地变量
0×49 dstore_2 将栈顶double型数值存入第2个本地变量
0x4a dstore_3 将栈顶double型数值存入第3个本地变量
0x4b astore_0 将栈顶引用型数值存入第0个本地变量
0x4c astore_1 将栈顶引用型数值存入第1个本地变量
0x4d astore_2 将栈顶引用型数值存入第2个本地变量
0x4e astore_3 将栈顶引用型数值存入第3个本地变量
0x4f iastore 将栈顶int型数值存入指定数组的指定索引位置
0×50 lastore 将栈顶long型数值存入指定数组的指定索引位置
0×51 fastore 将栈顶float型数值存入指定数组的指定索引位置
0×52 dastore 将栈顶double型数值存入指定数组的指定索引位置
0×53 aastore 将栈顶引用型数值存入指定数组的指定索引位置
0×54 bastore 将栈顶boolean或byte型数值存入指定数组的指定索引位置
0×55 castore 将栈顶char型数值存入指定数组的指定索引位置
0×56 sastore 将栈顶short型数值存入指定数组的指定索引位置
0×57 pop 将栈顶数值弹出 (数值不能是long或double类型的)
0×58 pop2 将栈顶的一个(long或double类型的)或两个数值弹出(其它)
0×59 dup 复制栈顶数值并将复制值压入栈顶
0x5a dup_x1 复制栈顶数值并将两个复制值压入栈顶
0x5b dup_x2 复制栈顶数值并将三个(或两个)复制值压入栈顶
0x5c dup2 复制栈顶一个(long或double类型的)或两个(其它)数值并将复制值压入栈顶
0x5d dup2_x1 <待补充>
0x5e dup2_x2 <待补充>
0x5f swap 将栈最顶端的两个数值互换(数值不能是long或double类型的)
0×60 iadd 将栈顶两int型数值相加并将结果压入栈顶
0×61 ladd 将栈顶两long型数值相加并将结果压入栈顶
0×62 fadd 将栈顶两float型数值相加并将结果压入栈顶
0×63 dadd 将栈顶两double型数值相加并将结果压入栈顶
0×64 isub 将栈顶两int型数值相减并将结果压入栈顶
0×65 lsub 将栈顶两long型数值相减并将结果压入栈顶
0×66 fsub 将栈顶两float型数值相减并将结果压入栈顶
0×67 dsub 将栈顶两double型数值相减并将结果压入栈顶
0×68 imul 将栈顶两int型数值相乘并将结果压入栈顶
0×69 lmul 将栈顶两long型数值相乘并将结果压入栈顶
0x6a fmul 将栈顶两float型数值相乘并将结果压入栈顶
0x6b dmul 将栈顶两double型数值相乘并将结果压入栈顶
0x6c idiv 将栈顶两int型数值相除并将结果压入栈顶
0x6d ldiv 将栈顶两long型数值相除并将结果压入栈顶
0x6e fdiv 将栈顶两float型数值相除并将结果压入栈顶
0x6f ddiv 将栈顶两double型数值相除并将结果压入栈顶
0×70 irem 将栈顶两int型数值作取模运算并将结果压入栈顶
0×71 lrem 将栈顶两long型数值作取模运算并将结果压入栈顶
0×72 frem 将栈顶两float型数值作取模运算并将结果压入栈顶
0×73 drem 将栈顶两double型数值作取模运算并将结果压入栈顶
0×74 ineg 将栈顶int型数值取负并将结果压入栈顶
0×75 lneg 将栈顶long型数值取负并将结果压入栈顶
0×76 fneg 将栈顶float型数值取负并将结果压入栈顶
0×77 dneg 将栈顶double型数值取负并将结果压入栈顶
0×78 ishl 将int型数值左移位指定位数并将结果压入栈顶
0×79 lshl 将long型数值左移位指定位数并将结果压入栈顶
0x7a ishr 将int型数值右(符号)移位指定位数并将结果压入栈顶
0x7b lshr 将long型数值右(符号)移位指定位数并将结果压入栈顶
0x7c iushr 将int型数值右(无符号)移位指定位数并将结果压入栈顶
0x7d lushr 将long型数值右(无符号)移位指定位数并将结果压入栈顶
0x7e iand 将栈顶两int型数值作“按位与”并将结果压入栈顶
0x7f land 将栈顶两long型数值作“按位与”并将结果压入栈顶
0×80 ior 将栈顶两int型数值作“按位或”并将结果压入栈顶
0×81 lor 将栈顶两long型数值作“按位或”并将结果压入栈顶
0×82 ixor 将栈顶两int型数值作“按位异或”并将结果压入栈顶
0×83 lxor 将栈顶两long型数值作“按位异或”并将结果压入栈顶
0×84 iinc 将指定int型变量增加指定值,可以有两个变量,分别表示index, const,index指第index个int型本地变量,const增加的值
0×85 i2l 将栈顶int型数值强制转换成long型数值并将结果压入栈顶
0×86 i2f 将栈顶int型数值强制转换成float型数值并将结果压入栈顶
0×87 i2d 将栈顶int型数值强制转换成double型数值并将结果压入栈顶
0×88 l2i 将栈顶long型数值强制转换成int型数值并将结果压入栈顶
0×89 l2f 将栈顶long型数值强制转换成float型数值并将结果压入栈顶
0x8a l2d 将栈顶long型数值强制转换成double型数值并将结果压入栈顶
0x8b f2i 将栈顶float型数值强制转换成int型数值并将结果压入栈顶
0x8c f2l 将栈顶float型数值强制转换成long型数值并将结果压入栈顶
0x8d f2d 将栈顶float型数值强制转换成double型数值并将结果压入栈顶
0x8e d2i 将栈顶double型数值强制转换成int型数值并将结果压入栈顶
0x8f d2l 将栈顶double型数值强制转换成long型数值并将结果压入栈顶
0×90 d2f 将栈顶double型数值强制转换成float型数值并将结果压入栈顶
0×91 i2b 将栈顶int型数值强制转换成byte型数值并将结果压入栈顶
0×92 i2c 将栈顶int型数值强制转换成char型数值并将结果压入栈顶
0×93 i2s 将栈顶int型数值强制转换成short型数值并将结果压入栈顶
0×94 lcmp 比较栈顶两long型数值大小,并将结果(1,0,-1)压入栈顶
0×95 fcmpl 比较栈顶两float型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将-1压入栈顶
0×96 fcmpg 比较栈顶两float型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将1压入栈顶
0×97 dcmpl 比较栈顶两double型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将-1压入栈顶
0×98 dcmpg 比较栈顶两double型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将1压入栈顶
0×99 ifeq 当栈顶int型数值等于0时跳转
0x9a ifne 当栈顶int型数值不等于0时跳转
0x9b iflt 当栈顶int型数值小于0时跳转
0x9c ifge 当栈顶int型数值大于等于0时跳转
0x9d ifgt 当栈顶int型数值大于0时跳转
0x9e ifle 当栈顶int型数值小于等于0时跳转
0x9f if_icmpeq 比较栈顶两int型数值大小,当结果等于0时跳转
0xa0 if_icmpne 比较栈顶两int型数值大小,当结果不等于0时跳转
0xa1 if_icmplt 比较栈顶两int型数值大小,当结果小于0时跳转
0xa2 if_icmpge 比较栈顶两int型数值大小,当结果大于等于0时跳转
0xa3 if_icmpgt 比较栈顶两int型数值大小,当结果大于0时跳转
0xa4 if_icmple 比较栈顶两int型数值大小,当结果小于等于0时跳转
0xa5 if_acmpeq 比较栈顶两引用型数值,当结果相等时跳转
0xa6 if_acmpne 比较栈顶两引用型数值,当结果不相等时跳转
0xa7 goto 无条件跳转
0xa8 jsr 跳转至指定16位offset位置,并将jsr下一条指令地址压入栈顶
0xa9 ret 返回至本地变量指定的index的指令位置(一般与jsr, jsr_w联合使用)
0xaa tableswitch 用于switch条件跳转,case值连续(可变长度指令)
0xab lookupswitch 用于switch条件跳转,case值不连续(可变长度指令)
0xac ireturn 从当前方法返回int
0xad lreturn 从当前方法返回long
0xae freturn 从当前方法返回float
0xaf dreturn 从当前方法返回double
0xb0 areturn 从当前方法返回对象引用
0xb1 return 从当前方法返回void
0xb2 getstatic 获取指定类的静态域,并将其值压入栈顶
0xb3 putstatic 为指定的类的静态域赋值
0xb4 getfield 获取指定类的实例域,并将其值压入栈顶
0xb5 putfield 为指定的类的实例域赋值
0xb6 invokevirtual 调用实例方法
0xb7 invokespecial 调用超类构造方法,实例初始化方法,私有方法
0xb8 invokestatic 调用静态方法
0xb9 invokeinterface 调用接口方法
0xba
0xbb new 创建一个对象,并将其引用值压入栈顶
0xbc newarray 创建一个指定原始类型(如int, float, char…)的数组,并将其引用值压入栈顶
0xbd anewarray 创建一个引用型(如类,接口,数组)的数组,并将其引用值压入栈顶
0xbe arraylength 获得数组的长度值并压入栈顶
0xbf athrow 将栈顶的异常抛出
0xc0 checkcast 检验类型转换,检验未通过将抛出ClassCastException
0xc1 instanceof 检验对象是否是指定的类的实例,如果是将1压入栈顶,否则将0压入栈顶
0xc2 monitorenter 获得对象的锁,用于同步方法或同步块
0xc3 monitorexit 释放对象的锁,用于同步方法或同步块
0xc4 wide 当本地变量的索引超过255时使用该指令扩展索引宽度。
0xc5 multianewarray create a new array of dimensions dimensions with elements of type identified by class reference in constant pool index (indexbyte1 << 8 + indexbyte2); the sizes of each dimension is identified by count1, [count2, etc.]
0xc6 ifnull if value is null, branch to instruction at branchoffset (signed short constructed from unsigned bytes branchbyte1 << 8 + branchbyte2)
0xc7 ifnonnull if value is not null, branch to instruction at branchoffset (signed short constructed from unsigned bytes branchbyte1 << 8 + branchbyte2)
0xc8 goto_w goes to another instruction at branchoffset (signed int constructed from unsigned bytes branchbyte1 << 24 + branchbyte2 << 16 + branchbyte3 << 8 + branchbyte4)
0xc9 jsr_w jump to subroutine at branchoffset (signed int constructed from unsigned bytes branchbyte1 << 24 + branchbyte2 << 16 + branchbyte3 << 8 + branchbyte4) and place the return address on the stack
0xca breakpoint reserved for breakpoints in Java debuggers; should not appear in any class file
0xcb-0xfd 未命名 these values are currently unassigned for opcodes and are reserved for future use
0xfe impdep1 reserved for implementation-dependent operations within debuggers; should not appear in any class file
0xff impdep2 reserved for implementation-dependent operations within debuggers; should not appear in any class file

英文参照表:

https://en.wikipedia.org/wiki/Java_bytecode_instruction_listings

搭建Selenium 集群

引言

工作中用到了Selenium作为爬虫去解析网页,发现单机跑慢的要死,所以找了点资料,将官方的Selenium 集群搭建了起来,下面是搭建的过程。

环境介绍

系统 jdk环境 selenium版本 安装的浏览器 用途 别名
Centos7 1.8 3.141.59 chrome server Hub
Centos7 1.8 3.141.59 chrome node Node1
Centos7 1.8 3.141.59 chrome node Node2
Centos7 1.8 3.141.59 chrome node Node3

安装Hub

首先从官网上下载好最新版本的selenium-server-standalone.jar

在Hub机器上执行下面的命令,运行hub管理端

screen -dmS selenium  java -jar selenium-server-standalone.jar -role hub -maxSession 100  -log /var/log/selenium.log

执行以后会出现下面这样的提示

记住下面的两个URL地址:

第一个地址是注册node的时候回使用到,第二个地址是在代码中会用到

安装Node

将上面下载的jar文件复制到各个节点机器上,然后执行下面命令,注册node节点

screen -dmS selenium java -jar selenium-server-standalone.jar -role node -hub http://10.10.88.51:4444/grid/register/ -capabilities browserName=chrome,platform=linux,maxInstances=30 -log /var/log/selenium.log

记住将-hub后面的url地址更改为你的hub地址,执行以后会出现下面的界面

然后我们返回Hub机器上看Hub机器上回出现一条这样的提示

可以看到已经成功注册了节点机器。其他节点按照同样的方式进行注册即可。
关于其中的参数可以参考下面的文章:

查看web界面

在注册完node节点以后,我们可以在web界面中看到我们注册的机器的列表以及配置
我们访问Hub机器的url地址为:

http://10.10.88.51:4444/grid/console

将其中的10.10.88.51换为你hub机器的ip地址即可访问

至此我们的Selenium 集群以及初步搭建完成,接下来就是使用了

使用Selenium集群

这边我用python代码进行测试Selenium机器是否能正常使用。
代码如下

# -*- coding:utf-8 -*-
# Author: sky
# Email:  sky@03sec.com
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--disable-gpu')
chrome_options.add_argument('--hide-scrollbars')
driver = webdriver.Remote(command_executor="http://10.10.88.51:4444/wd/hub",options=chrome_options)
driver.get("https://www.03sec.com")
driver.implicitly_wait(3)
driver.find_elements_by_tag_name("div")
print(driver.page_source)
driver.quit()

其中chrome_options请根据自己的情况进行自定义,
command_executor为上面我们记录的第二条url地址,请自行更换

其实和平时写没有什么区别,唯一的区别就是

driver = webdriver.Remote(command_executor="http://10.10.88.51:4444/wd/hub",options=chrome_options)

这条配置

设置Centos7 开机启动

  • selenium grid 设置开机启动的命令
mkdir /opt/script
touch /opt/script/autostart.sh
echo "
#!/bin/bash
#description:启动selenium Grid
screen -dmS selenium java -jar selenium-server-standalone.jar -role hub -maxSession 100 -log /var/log/selenium.log
" >> /opt/script/autostart.sh
chmod +x /opt/script/autostart.sh
echo "su - root -c '/opt/script/autostart.sh'" >> /etc/rc.d/rc.local
chmod +x /etc/rc.d/rc.local
cat /etc/rc.d/rc.local
ls -la /etc/rc.d/rc.local
cat /opt/script/autostart.sh
ls -la /opt/script/autostart.sh
echo "ok"


  • selenium node 设置开机启动的命令
mkdir /opt/script
touch /opt/script/autostart.sh
echo "
#!/bin/bash
#description:启动selenium节点
screen -dmS selenium java -jar selenium-server-standalone.jar -role node -hub http://selenium-grid 服务器IP:4444/grid/register/ -capabilities browserName=chrome,platform=linux,maxInstances=30 -maxSession 60  -log /var/log/selenium.log
" >> /opt/script/autostart.sh
chmod +x /opt/script/autostart.sh
echo "su - root -c '/opt/script/autostart.sh'" >> /etc/rc.d/rc.local
chmod +x /etc/rc.d/rc.local
cat /etc/rc.d/rc.local
ls -la /etc/rc.d/rc.local
cat /opt/script/autostart.sh
ls -la /opt/script/autostart.sh
echo "ok"

安装中遇到的问题

可能会有中文乱码的问题,在centos7下安装下中文字体库即可,

yum groupinstall fonts

总结

集群安装以后,使用和平时没什么两样,速度也差不多。如果你是单机单进程跑,没必要使用集群。如果涉及到多进程或者多线程这样的话,Selenium集群才能真正发挥作用。

hub端传入的timeout会同时设置到节点上(应该)

参考

java Agent 简单学习

java_logo

前言

java.lang.instrument包是Java中来增强JVM上的应用的一种方式,机制是在JVM启动前或启动后attach上去进行修改方法字节码的方式。
instrument包的用途很多,主要体现在对代码侵入低的优点上,例如一些监控不方便修改业务代码,但是可以使用这种方式在方法中植入特定逻辑,这种方式能够直接修改JVM中加载的字节码的内容,而不需要在像Spring AOP实现中创建新的代理类,所以在底层侵入更高,但是对开发者更透明。用于自动添加getter/setter方法的工具lombok就使用了这一技术。另外btrace和housemd等动态诊断工具也是用了instrument技术。

创建一个简单的Agent

关于premain的介绍:

通常agent的包里面MATE-INF目录下的MANIFEST.MF中会有这样一段声明

Premain-Class: cn.org.javaweb.test.Agent

在你通过启动命令添加-javaagent:xxx.jar 的时候,JVM会去xxx.jar中找其中Premain-Class是你在MANIFEST.MF中声明的cn.org.javaweb.test.Agent这个类中的public static void premain(String agentOps, Instrumentation instrumentation)或者public static void premain(String agentOps)方法.

注意这里,public static void premain(String agentOps, Instrumentation instrumentation)public static void premain(String agentOps)这两个是有优先级的,其中当premain有两个参数,也就是public static void premain(String agentOps, Instrumentation instrumentation)的时候优先级最高,如果两个都存在,public static void premain(String agentOps)则会被忽略。

其中agentOps将获得程序的参数,会随着-javaagent一起传入,如下:

java -javaagent:agent-0.0.1.jar="Hello World.?"  -jar agent-1.0-SNAPSHOT.jar

将会将Hello World.?传入进去,和main方法不一样的是,该处传入的是一串完整的字符串,并不会传入解析以后的字符串,所以此处如果有需要的话,那么此处传入字符串将由agent完成解析

其中instrumentationjava.lang.instrument.Instrumentation 的实例,由 JVM 自动传入。java.lang.instrument.Instrumentationinstrument 包中定义的一个接口,也是这个包的核心部分,集中了其中几乎所有的功能方法,例如类定义的转换和操作等等。


了解了以上知识以后,我们来创建一个agent。

创建agent

首先创建一个名为cn.org.javaweb.test的包,然后在该包下创建一个为Agent的类,
如下:

package cn.org.javaweb.test;
/*
 * Copyright sky 2018-11-20 Email:sky@03sec.com.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @author sky
 */
public class Agent {
}

然后写一个premain方法

public static void premain(String agentOps, Instrumentation inst) {
        System.out.println("=======this is agent premain function=======");
    }

将其使用maven打包为jar文件,pom.xml文件中build的内容为:

<plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.0.2</version>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                        </manifest>
                        <manifestEntries>
                            <Premain-Class>
                                cn.org.javaweb.test.Agent
                            </Premain-Class>
                            <can-redefine-classes>
                                true
                            </can-redefine-classes>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>
</plugins>

然后使用maven中的clean install命令安装即可(或者使用mvn clean package),将会生成一个agent.jar的文件。

创建测试程序

此时我们随便新建一个带有main方法的程序,

package cn.org.javaweb.test;
/*
 * Copyright sky 2018-11-20 Email:sky@03sec.com.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @author sky
 */
public class TestAgent {

    public static void main(String[] args) {
        System.out.print("Hello,This is TestAgent Program!");
    }
}

pom.xml文件中build的内容是


        <plugins>
            <plugin>
                <artifactId>maven-shade-plugin</artifactId>
                <groupId>org.apache.maven.plugins</groupId>
                <version>1.2.1</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <transformers>
                                <transformer
                                        implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>cn.org.javaweb.test.TestAgent</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
</plugins>

然后使用maven生成jar文件。

测试agent

这时候我们会得到两个jar文件,

agent.jar --> agent生成的jar文件
test.jar  --> 测试程序

我们先来运行下测试程序,看是否正常

java -jar test.jar 

输出

Hello,This is TestAgent Program!

然后我们加入agent运行试试看

java -javaagent:agent.jar -jar test.jar

输出

=======this is agent premain function=======
Hello,This is TestAgent Program!

可以看到premain会在程序的main方法之前运行,此时我们可以简单的看下jvm中加入agent以及不加入agent的运行过程。

不加入agent的运行过程

加入agent的运行过程

可以简单明了的看到agent会在运行程序之前运行,但是其后面涉及到其他复杂的逻辑,这里不做解释。

使用instrumentation参数

在上面我们只是进入了premain方法,其中premain方法的instrumentation参数我们并没有用到,这里我们将简单的介绍使用instrumentation参数。

关于Transformer解释

此方法的实现可以转换提供的类文件,并返回一个新的替换类文件。
有两种装换器,由 Instrumentation.addTransformer(ClassFileTransformer,boolean)canRetransform 参数确定:

  • 可重转换 转换器,将 canRetransform 设为 true 可添加这种转换器
  • 不可重转换 转换器,将 canRetransform 设为 false 或者使用 Instrumentation.addTransformer(ClassFileTransformer)可添加这种转换器

在转换器使用 addTransformer 注册之后,每次定义新类和重定义类时都将调用该转换器。
每次重转换类时还将调用可重转换转换器。
对新类定义的请求通过 ClassLoader.defineClass或其本机等价方法进行。
对类重定义的请求通过 Instrumentation.redefineClasses 或其本机等价方法进行。
对类重转换的请求将通过Instrumentation.retransformClasses 或其本机等价方法进行。
转换器是在验证或应用类文件字节之前的请求处理过程中调用的。
当存在多个转换器时,转换将由 transform 调用链组成。 也就是说,一个 transform 调用返回的 byte 数组将成为下一个调用的输入(通过 classfileBuffer 参数)。

转换将按以下顺序应用:

  • 不可重转换转换器
  • 不可重转换本机转换器
  • 可重转换转换器
  • 可重转换本机转换器

对于重转换,不会调用不可重转换转换器,而是重用前一个转换的结果。对于所有其他情况,调用此方法。在每个这种调用组中,转换器将按照注册的顺序调用。本机转换器由 Java 虚拟机 Tool 接口中的 ClassFileLoadHook 事件提供。

ClassLoader loader                  ----> 这个没什么解释的,就是classloader
String className                    ----> 包名(jvm中包名是/而不是.)
Class<?> classBeingRedefined        ----> 如果是被重定义或重转换触发,则为重定义或重转换的类;如果是类加载,则为 null
ProtectionDomain protectionDomain   ----> 保护域
byte[] classfileBuffer              ----> 二进制字节码

添加一个新的Transformer类

我们新建一个名为TestTransformer的类,内容入下:

/*
 * Copyright sky 2018-11-20 Email:sky@03sec.com.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.org.javaweb.test;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

/**
 * @author sky
 */
public class TestTransformer implements ClassFileTransformer {

    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                            ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {

        System.out.println(className.replace("/", "."));
        return classfileBuffer;
    }
}

在Agent.java中修改premain方法为

System.out.println("=======this is agent premain function=======");
inst.addTransformer(new TestTransformer());

将我们新建的TestTransformer注册进去
这时候我们在重新生成agent后运行如下命令

java -javaagent:agent.jar -jar test.jar

将会输出

=======this is agent premain function=======
sun.launcher.LauncherHelper
cn.org.javaweb.test.TestAgent
java.lang.Void
Hello,This is TestAgent Program!
java.lang.Shutdown
java.lang.Shutdown$Lock

可以看到会输出很多包名,这是因为我们在TestTransformer中将包名打印了出来。

结束语

关于agent的简单使用就介绍到了此处,此文仅抛砖引玉,更多用法可以发挥想象,比如性能监控,日志监控、管理会话、安全过滤、请求管理等。

文章中涉及到的代码全部都在下面地址:

https://gitlab.com/iiusky/javaagent

参考:

PHPdocX7 Source code

====PHPdocX 7 by 2mdc.com==== https://www.phpdocx.com/

PHPDOCX is a PHP library designed to dynamically generate reports in
Word format (WordprocessingML).

The reports may be built from any available data source like a MySQL
database or a spreadsheet. The resulting documents remain fully
editable in Microsoft Word (or any other compatible software like
OpenOffice) and therefore the final users are able to modify them as
necessary.

The formatting capabilities of the library allow the programmers to
generate dynamically and programmatically all the standard rich
formatting of a typical word processor.

This library also provides an easy method to generate documents in
other standard formats such as PDF or HTML.


phpdocx 可以使用word模板将数据填充到word里面并且生成新的word文档。具体使用方式看官方文档

https://www.phpdocx.com/documentation

下载地址:phpdocx7.zip

git地址:phpdocx7-git