AspectJ是一个面向切面编程的框架,它扩展了Java语言。AspectJ定义了AOP语法所以它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。
AspectJ目前支持以下三种编织的方式
编译时编织:把aspect类(aop的切面)和目标类(被aop的类)放在一起用ajc编译。
后编译时编织:目标类可能已经被打成了一个jar包,这时候也可以用ajc命令将jar再编织一次
加载时编织Load-time weaving (LTW):在jvm加载类的时候,做字节码修改或替换
逆向工程中可以用到后两个:后编译时编织和加载时编织。
1. 安装AspectJ
下载地址:http://www.eclipse.org/aspectj/downloads.php
选择里面的1.9版本下载,下载完成之后是个jar包
安装命令
1
| java -jar aspectj-1.9.1.jar
|
根据提示设置好java主目录和aspectj的安装目录,这里采用默认的C:\aspectj1.9
安装完成后,设置如下环境变量
设置ASPECTJ_HOME
1
| ASPECTJ_HOME = C:\aspectj1.9
|
添加/修改CLASSPATH,把aspectjrt.jar添加进去
1
| CLASSPATH = .;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar;%JAVA_HOME%\lib\dt.jar;%ASPECTJ_HOME%\lib\aspectjrt.jar
|
PATH变量新增值
环境变量设置完成之后,验证命令
出现如下结果说明环境变量配置成功

2. 看两个简单的例子
1. 第一个项目helloworld
Java文件HelloWorld.java
1 2 3 4 5 6 7 8 9 10 11
| public class HelloWorld { public void sayHello() { System.out.println("Hello, world!"); }
public static void main(String[] argv) { HelloWorld hw = new HelloWorld(); hw.sayHello(); } }
|
Aspectj文件MyAspect.aj
1 2 3 4 5 6 7 8 9 10 11 12
| public aspect MyAspect { pointcut say():call(void HelloWorld.sayHello());
before():say() { System.out.println("before say hello...."); }
after():say() { System.out.println("after say hello...."); } }
|
Load-time weaving (LTW) 加载时编织命令
编译
1 2
| ajc -outjar myjar.jar HelloWorld.java ajc -outjar MyAspect.jar -outxml MyAspect.aj -classpath "myjar.jar;%CLASSPATH%"
|
运行
1
| aj5 -classpath "MyAspect.jar;myjar.jar;%CLASSPATH%" HelloWorld
|
运行结果

可以看到在helloworld字符串上有上下两行文字
2. 第二个项目,输出参数和返回值
java文件Main.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package com.vvvtimes;
public class Main {
public int add(int x, int y) { return x + y; }
public int add(int x, int y, int z) { return x + y + z; }
public static void main(String[] args) { Main m = new Main(); System.out.println(m.add(1, 2)); System.out.println(m.add(1, 2, 3)); } }
|
Aspectj文件Tracing.aj
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
| public aspect Tracing { private pointcut mainMethod(): execution(public static void main(String[]));
before(): mainMethod() { System.out.println("> " + thisJoinPoint); }
after(): mainMethod() { System.out.println("< " + thisJoinPoint); }
private pointcut addMethod(): execution(public int add(..));
before(): addMethod() { System.out.println("> " + thisJoinPoint); Object[] args = thisJoinPoint.getArgs(); for (int i = 0; i < args.length; i++) { System.out.println("args[" + i + "]: " + thisJoinPoint.getArgs()[i].toString()); }
}
after(): addMethod() { System.out.println("< " + thisJoinPoint); }
after() returning(Object o) :addMethod(){ System.out.println("Return value: " + o.toString()); } }
|
编译
1 2
| ajc -outjar myjar.jar com/vvvtimes/Main.java ajc -outjar Tracing.jar -outxml Tracing.aj -classpath "myjar.jar;%CLASSPATH%"
|
运行
1
| aj5 -classpath "Tracing.jar;myjar.jar;%CLASSPATH%" com.vvvtimes.Main
|
运行结果

可以看到,add方法的参数和返回值都打印出来了
顺便说一下三个编织期的区别,从命令行角度比较如下:
- Compile-time weaving 编译时编织
1
| ajc -outjar mytarget.jar HelloWorld.java MyAspect.aj
|
运行
1
| aj5 -classpath "mytarget.jar;%CLASSPATH%" HelloWorld
|
- Post-compile weaving 后编译时编织
1 2
| ajc -outjar myjar.jar HelloWorld.java ajc -inpath myjar.jar MyAspect.aj -outjar mytarget.jar
|
运行
1
| aj5 -classpath "mytarget.jar;%CLASSPATH%" HelloWorld
|
- Load-time weaving (LTW) 加载时编织
编译
1 2
| ajc -outjar myjar.jar HelloWorld.java ajc -outjar MyAspect.jar -outxml MyAspect.aj -classpath "myjar.jar;%CLASSPATH%"
|
运行
1
| aj5 -classpath "MyAspect.jar;myjar.jar;%CLASSPATH%" HelloWorld
|
3. ajc与aj5命令
acj命令是用于编译java文件和aj文件的编译器,相当于eclipse的编译器(ECJ)+aspectj运行时扩展
aj5命令在jdk1.5上使用-javaagent:pathto/aspectjweaver.jar加载aspectj程序,达到修改字节码的目的
这两个命令可以用java命令和javac来替代,我们以上文的命令为例
1. Load-time weaving (LTW) 加载时编织
编译
1 2
| ajc -outjar myjar.jar HelloWorld.java ajc -outjar MyAspect.jar -outxml MyAspect.aj -classpath "myjar.jar;%CLASSPATH%"
|
运行
1
| aj5 -classpath "MyAspect.jar;myjar.jar;%CLASSPATH%" HelloWorld
|
命令翻译
若已经在classpath环境变量里设置过aspectjrt.jar,可在 -classpath里省略%ASPECTJ_HOME%\lib\aspectjrt.jar;下同
编译
1 2 3
| javac HelloWorld.java jar cvf myjar.jar HelloWorld.class java -classpath "myjar.jar;%ASPECTJ_HOME%\lib\aspectjrt.jar;%ASPECTJ_HOME%\lib\aspectjtools.jar;%CLASSPATH%" org.aspectj.tools.ajc.Main -outxml MyAspect.aj -outjar MyAspect.jar
|
运行
1
| java -javaagent:%ASPECTJ_HOME%\lib\aspectjweaver.jar -classpath "MyAspect.jar;myjar.jar;%ASPECTJ_HOME%\lib\aspectjrt.jar;%CLASSPATH%" HelloWorld
|
2. Compile-time weaving 编译时编织
编译
1
| ajc -outjar mytarget.jar HelloWorld.java MyAspect.aj
|
运行
1
| aj5 -classpath "mytarget.jar;%CLASSPATH%" HelloWorld
|
命令翻译
编译
1
| java -classpath "myjar.jar;%ASPECTJ_HOME%\lib\aspectjrt.jar;%ASPECTJ_HOME%\lib\aspectjtools.jar;%CLASSPATH%" org.aspectj.tools.ajc.Main -outjar mytarget.jar HelloWorld.java MyAspect.aj
|
运行
1
| java -classpath "mytarget.jar;%ASPECTJ_HOME%\lib\aspectjrt.jar;%CLASSPATH%" HelloWorld
|
3. Post-compile weaving 后编译时编织
编译
1 2
| ajc -outjar myjar.jar HelloWorld.java ajc -inpath myjar.jar MyAspect.aj -outjar mytarget.jar
|
运行
1
| aj5 -classpath "mytarget.jar;%CLASSPATH%" HelloWorld
|
命令翻译
编译
1 2 3
| javac HelloWorld.java jar cvf myjar.jar HelloWorld.class java -classpath "myjar.jar;%ASPECTJ_HOME%\lib\aspectjrt.jar;%ASPECTJ_HOME%\lib\aspectjtools.jar;%CLASSPATH%" org.aspectj.tools.ajc.Main -inpath myjar.jar MyAspect.aj -outjar mytarget.jar
|
运行
1
| java -classpath "mytarget.jar;%ASPECTJ_HOME%\lib\aspectjrt.jar;%CLASSPATH%" HelloWorld
|
翻译成java命令的好处是,可以在Load-time weaving (LTW) 加载时编织直接指定多个-javaagent
如
1
| java -javaagent:aspectjweaver.jar -javaagent:ZKMAgent.jar -cp Tracing.jar;ZKM.jar com.zelix.ZKM
|
4. Eclipse插件AJDT
AJDT即Eclipse AspectJ Development Tools.是一个Eclipse插件,可以编写AspectJ项目
安装
help–>Install New Software
填写在线安装地址:http://download.eclipse.org/tools/ajdt/47_aj9/dev/update
安装完成之后,新建Aspectj项目

项目结构如下

把前面博文的aj文件和java文件内容复制过来
其中aj文件的新建方法
File–>New–>Project选择AspectJ Project

运行的时候,选中java文件,右键Run As–>AspectJ/Java Application

运行结果

下面看运行时加载项目的配置
新建项目结构如图

其中aj文件内容
1 2 3 4 5 6 7 8 9 10 11 12 13
| public aspect Tracing { private pointcut mainMethod(): execution(public static void main(String[]));
before(): mainMethod() { System.out.println("> " + thisJoinPoint); }
after(): mainMethod() { System.out.println("< " + thisJoinPoint); } }
|
Run–>Run configurations
运行项目类型选:Aspect Load-Time Weaving Application(默认的是下面的那个Aspect/Java Application 注意别选错了)
Project:AspectJDemo3
Main class:com.zelix.ZKM
LTW Aspect path:添加j外部jar选择ZKM.jar


最后点击配置里的Run命令,运行结果如下

这里面的导出jar没啥用,都是导出编译期的,不能导出加载期的,也许是不会用。。。
5. 获取成员变量的值
注意:由于JVM优化的原因,方法里面的局部变量是不能通过AspectJ拦截并获取其中的值的,但是成员变量可以
在逆向中,我们经常要跟踪某些类的成员变量的值,这里以获取ZKM9中的qs类的成员变量g为例进行说明
在StackOverFlow上有这么一篇提问:AspectJ: How to get accessed field’s value in a get() pointcut
将其中内容改写为qs类的代码如下:
1 2 3 4 5 6 7 8
| private pointcut qsfiledMethod() : get(* com.zelix.qs.*);
after() returning(Object field) :qsfiledMethod(){ System.out.println(thisJoinPoint.toLongString()); System.out.println(" " + thisJoinPoint.getSignature().getName()); System.out.println(" " + field); }
|
但是这个方法有缺陷,只能获取公共变量,运行之后获取到的都是qs的成员变量j和k
运行结果如下

所以此路不通,那么就需要再找一条路:反射
qs类中的某个方法调用了jj.a方法,所以用call找出调用者,然后通过反射方式获取filed,talk is cheap,show you code?
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
| private pointcut jjaMethod() : call(String com.zelix.jj.a(String, String, String, Object, int));
before() : jjaMethod() { System.out.println("> " + thisJoinPoint); if (thisJoinPoint.getThis() != null) { System.out.println("this "+thisJoinPoint.getThis().getClass().getName() + " " + thisJoinPoint.getSourceLocation()); Object obj = thisJoinPoint.getThis(); Class clazz = obj.getClass(); Field[] fileds = clazz.getDeclaredFields(); for (Field field : fileds) { System.out.println(field); } try { Field filed = clazz.getDeclaredField("g"); System.out.println(filed); filed.setAccessible(true); String[] g= (String[]) filed.get(obj); for (int i = 0; i < g.length; i++) { System.out.println("g["+i+"] ="+g[i]); } } catch (Exception e) { e.printStackTrace(); } }else if (thisJoinPoint.getTarget() != null) { System.out.println("target "+thisJoinPoint.getTarget().getClass().getName() + " " + thisJoinPoint.getSourceLocation()); } }
|
运行结果如下

before方法里的功能如下
打印出调用者的名称和位置
遍历打印qs类的所有成员名称
获取成员g的值,由于这个成员是数组类型,遍历这个数组打印值
6. Around方法修改方法体
在逆向中,我们往往通过修改某个方法达到目的,在javaassist中有insertBefore,insertAfter,setBody,在AspectJ中也可以通过Around实现类似的功能。
看一个简单的例子
java文件Main.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package com.vvvtimes;
public class Main {
public int add(int x, int y) { return x + y; }
public int add(int x, int y, int z) { return x + y + z; }
public static void main(String[] args) { Main m = new Main(); System.out.println(m.add(1, 2)); System.out.println(m.add(1, 2, 3)); } }
|
aj文件Tracing.aj
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
| public aspect Tracing { private pointcut mainMethod(): execution(public static void main(String[]));
before(): mainMethod() { System.out.println("> " + thisJoinPoint); }
after(): mainMethod() { System.out.println("< " + thisJoinPoint); } pointcut addMethodOne() : call (public int add(int,int));
int around() : addMethodOne() { System.out.println("> " + thisJoinPoint); Object[] args = thisJoinPoint.getArgs(); for (int i = 0; i < args.length; i++) { if( args[i]!=null) { System.out.println("args[" + i + "]: " + args[i].toString()); } } int result = proceed(); System.out.println("original return value: " + result); return 777; } pointcut addMethodTwo(int a, int b, int c) : call (public int add(int,int,int)) && args (a, b, c);
int around(int a, int b, int c) : addMethodTwo (a, b, c){ System.out.println("> " + thisJoinPoint); System.out.println("1st passed value: " + a); System.out.println("2nd passed value: " + b); System.out.println("3rd passed value: " + c); a = 6; b = 6; c = 6; int result = proceed(a, b, c); return result; }
}
|
运行后的结果如下

proceed用于执行原有方法体,return方法用于改变返回值