5.7 visitXxxInsn
根据opcode和操作数进行push和pop操作,模拟了JVM Frame中的OperandStack操作
5.7.1 visitInsn
访问0操作数指令
public void visitInsn(int opcode) { // 这些是临时变量,用于复制等指令 Set<T> saved0, saved1, saved2, saved3; // 对模拟stack的size进行验证 sanityCheck(); switch(opcode) { // NOP跳过 case Opcodes.NOP: break; // push null case Opcodes.ACONST_NULL: // push int case Opcodes.ICONST_M1: case Opcodes.ICONST_0: case Opcodes.ICONST_1: case Opcodes.ICONST_2: case Opcodes.ICONST_3: case Opcodes.ICONST_4: case Opcodes.ICONST_5: case Opcodes.FCONST_0: case Opcodes.FCONST_1: case Opcodes.FCONST_2: // 模拟进行一次push push(); break; // long和double的size为2 case Opcodes.LCONST_0: case Opcodes.LCONST_1: case Opcodes.DCONST_0: case Opcodes.DCONST_1: // size为2需要两次push push(); push(); break; // 这里是push各种类型的数组的索引对应的值 // array,index -> value case Opcodes.IALOAD: case Opcodes.FALOAD: case Opcodes.AALOAD: case Opcodes.BALOAD: case Opcodes.CALOAD: case Opcodes.SALOAD: // 弹出数组引用和弹出index pop(); pop(); // push进去value push(); break; // long和double的size为2 case Opcodes.LALOAD: case Opcodes.DALOAD: // 弹出数组引用和弹出index pop(); pop(); // 两次push因为size为2 push(); push(); break; // 弹出各种数组以及index和value case Opcodes.IASTORE: case Opcodes.FASTORE: case Opcodes.AASTORE: case Opcodes.BASTORE: case Opcodes.CASTORE: case Opcodes.SASTORE: // value pop(); // index pop(); // array pop(); break; // 多pop一次因为size为2 case Opcodes.LASTORE: case Opcodes.DASTORE: pop(); pop(); pop(); pop(); break; // 显而易见 case Opcodes.POP: pop(); break; // 显而易见 case Opcodes.POP2: pop(); pop(); break; // 复制栈顶元素 case Opcodes.DUP: push(get(0)); break; // 复制栈顶插入到第2位 case Opcodes.DUP_X1: saved0 = pop(); saved1 = pop(); push(saved0); push(saved1); push(saved0); break; // 复制栈顶插入到第3位 case Opcodes.DUP_X2: saved0 = pop(); // a saved1 = pop(); // b saved2 = pop(); // c push(saved0); // a push(saved2); // c push(saved1); // b push(saved0); // a break; // 复制栈顶两个 case Opcodes.DUP2: push(get(1)); push(get(1)); break; // 复制两个并向下插入 // ..., value3 , value2 , value1 → // ..., value2 , value1 , value3 , value2 , value1 case Opcodes.DUP2_X1: saved0 = pop();// value1 saved1 = pop();// value2 saved2 = pop();// value3 push(saved1);// value2 push(saved0);// value1 push(saved2);// value3 push(saved1);// value2 push(saved0);// value1 break; // 复制两个再向下两个插入 // ..., value4 , value3 , value2 , value1 → // ..., value2 , value1 , value4 , value3 , value2 , value1 case Opcodes.DUP2_X2: saved0 = pop(); saved1 = pop(); saved2 = pop(); saved3 = pop(); push(saved1); push(saved0); push(saved3); push(saved2); push(saved1); push(saved0); break; // 交换栈顶和第二个元素 case Opcodes.SWAP: saved0 = pop(); saved1 = pop(); push(saved0); push(saved1); break; // 取栈顶两元素做完数学操作后push结果 case Opcodes.IADD: case Opcodes.FADD: case Opcodes.ISUB: case Opcodes.FSUB: case Opcodes.IMUL: case Opcodes.FMUL: case Opcodes.IDIV: case Opcodes.FDIV: case Opcodes.IREM: case Opcodes.FREM: pop(); pop(); push(); break; // long和size都乘2 case Opcodes.LADD: case Opcodes.DADD: case Opcodes.LSUB: case Opcodes.DSUB: case Opcodes.LMUL: case Opcodes.DMUL: case Opcodes.LDIV: case Opcodes.DDIV: case Opcodes.LREM: case Opcodes.DREM: pop(); pop(); pop(); pop(); push(); push(); break; // 取出栈顶元素判断是否为int // 结果push回去 case Opcodes.INEG: case Opcodes.FNEG: pop(); push(); break; // long和double都乘2 case Opcodes.LNEG: case Opcodes.DNEG: pop(); pop(); push(); push(); break; // 取栈顶两个进行位运算 // 结果push回去 case Opcodes.ISHL: case Opcodes.ISHR: case Opcodes.IUSHR: pop(); pop(); push(); break; // long和double的size为2 // 操作数是int为1所以是3次pop case Opcodes.LSHL: case Opcodes.LSHR: case Opcodes.LUSHR: pop(); pop(); pop(); push(); push(); break; // 取栈顶两个进行位运算 // 结果push回去 case Opcodes.IAND: case Opcodes.IOR: case Opcodes.IXOR: pop(); pop(); push(); break; // long和double的size为2 // long和long自己操作,所以是4次pop case Opcodes.LAND: case Opcodes.LOR: case Opcodes.LXOR: pop(); pop(); pop(); pop(); push(); push(); break; // 类型转换结果push回去 case Opcodes.I2B: case Opcodes.I2C: case Opcodes.I2S: case Opcodes.I2F: pop(); push(); break; // 转long要2次push case Opcodes.I2L: case Opcodes.I2D: pop(); push(); push(); break; // long转要2次pop case Opcodes.L2I: case Opcodes.L2F: pop(); pop(); push(); break; // long转double各2次 case Opcodes.D2L: case Opcodes.L2D: pop(); pop(); push(); push(); break; // float转int case Opcodes.F2I: pop(); push(); break; // long是2次 case Opcodes.F2L: case Opcodes.F2D: pop(); push(); push(); break; // double是2次 case Opcodes.D2I: case Opcodes.D2F: pop(); pop(); push(); break; // 比较栈顶两个long,结果push case Opcodes.LCMP: pop(); pop(); pop(); pop(); push(); break; // 比较栈顶两个float,结果push case Opcodes.FCMPL: case Opcodes.FCMPG: pop(); pop(); push(); break; // 比较栈顶两个double,结果push case Opcodes.DCMPL: case Opcodes.DCMPG: pop(); pop(); pop(); pop(); push(); break; // return弹出一个 case Opcodes.IRETURN: case Opcodes.FRETURN: case Opcodes.ARETURN: pop(); break; // size为2 case Opcodes.LRETURN: case Opcodes.DRETURN: pop(); pop(); break; // void没操作 case Opcodes.RETURN: break; // 算数组长度 case Opcodes.ARRAYLENGTH: pop(); push(); break; // 抛出异常类似return case Opcodes.ATHROW: pop(); break; // 监视对象,弹出1个即可 case Opcodes.MONITORENTER: case Opcodes.MONITOREXIT: pop(); break; default: throw new IllegalStateException("Unsupported opcode: " + opcode); } // 传递 super.visitInsn(opcode); // stack校验 sanityCheck(); }
5.7.2 visitIntInsn
单int操作数指令
// 类似上文 public void visitIntInsn(int opcode, int operand) { switch(opcode) { // push一个值 case Opcodes.BIPUSH: case Opcodes.SIPUSH: push(); break; // 弹出一个size创建数组push回去引用 case Opcodes.NEWARRAY: pop(); push(); break; default: throw new IllegalStateException("Unsupported opcode: " + opcode); } // 传递 super.visitIntInsn(opcode, operand); // stack校验 sanityCheck(); }
5.7.3 visitVarInsn
加载或存储局部变量值的指令
能够进行数据流动正式因为这一步从局部变量表获得或设置数据,而局部变量表数据是从5.4中获得,形成一个完整的流程
模拟JVM进行PUSH/POP操作
@Override public void visitVarInsn(int opcode, int var) { // 同步到本地模拟local variables for (int i = savedVariableState.localVars.size(); i <= var; i++) { savedVariableState.localVars.add(new HashSet<T>()); } // 临时变量 Set<T> saved0; switch(opcode) { // push int/float case Opcodes.ILOAD: case Opcodes.FLOAD: push(); break; // push long/double case Opcodes.LLOAD: case Opcodes.DLOAD: push(); push(); break; // push object case Opcodes.ALOAD: // 从局部变量表里push一个object push(savedVariableState.localVars.get(var)); break; // pop int/float -> 加入局部变量表 case Opcodes.ISTORE: case Opcodes.FSTORE: pop(); savedVariableState.localVars.set(var, new HashSet<T>()); break; // size为2 case Opcodes.DSTORE: case Opcodes.LSTORE: pop(); pop(); savedVariableState.localVars.set(var, new HashSet<T>()); break; // pop object -> 加入局部变量表 case Opcodes.ASTORE: saved0 = pop(); savedVariableState.localVars.set(var, saved0); break; // JSR相关,对stack和局部变量表没用影响 case Opcodes.RET: break; default: throw new IllegalStateException("Unsupported opcode: " + opcode); } // 传递 super.visitVarInsn(opcode, var); // stack校验 sanityCheck(); }
5.7.4 visitTypeInsn
以类的内部名称作为参数的指令
@Override public void visitTypeInsn(int opcode, String type) { switch(opcode) { // push object case Opcodes.NEW: push(); break; // 弹出个size后push数组 case Opcodes.ANEWARRAY: pop(); push(); break; // 检查类型,stack不变化 case Opcodes.CHECKCAST: break; // 判断类是否一致 // pop类引用,push结果 case Opcodes.INSTANCEOF: pop(); push(); break; default: throw new IllegalStateException("Unsupported opcode: " + opcode); } // 传递 super.visitTypeInsn(opcode, type); // stack校验 sanityCheck(); }
5.7.5 visitFieldInsn
加载或存储对象字段值的指令
程序在这一步并没有过多的操作,只是简单的POP和PUSH
@Override public void visitFieldInsn(int opcode, String owner, String name, String desc) { // size int typeSize = Type.getType(desc).getSize(); switch (opcode) { // 获得static属性push进去 case Opcodes.GETSTATIC: for (int i = 0; i < typeSize; i++) { push(); } break; // 设置static属性pop出 case Opcodes.PUTSTATIC: for (int i = 0; i < typeSize; i++) { pop(); } break; // pop出对象 // 从对象里获取的值push进去 case Opcodes.GETFIELD: // ref pop(); for (int i = 0; i < typeSize; i++) { // value push(); } break; // pop出对象和值 // 设置到对象里面(不影响stack) case Opcodes.PUTFIELD: for (int i = 0; i < typeSize; i++) { // value pop(); } // ref pop(); break; default: throw new IllegalStateException("Unsupported opcode: " + opcode); } // 传递 super.visitFieldInsn(opcode, owner, name, desc); // stack校验 sanityCheck(); }
5.7.6 visitMethodInsn
方法调用,比较核心的方法
根据方法调用需要的参数,在Stack中POP,这是对真实情况的模拟
如果是构造方法,那么argTaint第0位的this添加到污染
如果是void ObjectInputStream.defaultReadObject()不传参,这时候对象本身this就是污染,给当前局部变量表第0位设置污染(这种情况下这一步拿不到污染,在后续的数据流中得到污染)
如果目前的方法恰好匹配到白名单(很可能存在漏洞)那么白名单函数的参数位置设置到污染(其实白名单就是简化了分析,固定出了哪些类的哪些函数是存在漏洞的,它的第几个参数是可被污染的,如果匹配到白名单,直接设置该参数即可)
根据已有的passthroughDataflow得到与返回值有关的参数索引Set,加入污染(这一步是外部生成的,也就是Visit其他类的生成的,根据已有信息进行污点设置。参考8.7中的分析,调用链最末端的最优先被分析,因此调用到的方法必然已被visit分析过。由于Set的特性,并不会冲突,只是一次补充的效果)
如果当前类是集合类子类,认为集合中所有元素都是污染,这里不得到结果,只是设置污染,然后继续分析;如果返回对象或数组,认为返回是污染,这个结果是要并入PUSH的污染中的
最后把污染结果入栈,这模拟的就是执行完方法的PUSH返回值(代码第二次的PUSH是为了补位)
进一步的分析参考7.5
给出一个非STATIC方法调用的图
@Override public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { // 方法信息,这里Handle是重新 final MethodReference.Handle methodHandle = new MethodReference.Handle( new ClassReference.Handle(owner), name, desc); // 所有参数类型 Type[] argTypes = Type.getArgumentTypes(desc); // 非静态调用 if (opcode != Opcodes.INVOKESTATIC) { // 如果执行的非静态方法,则本地变量[0]=this // 这里获得的参数类型argTypes中不存在this,需要手动加 Type[] extendedArgTypes = new Type[argTypes.length+1]; System.arraycopy(argTypes, 0, extendedArgTypes, 1, argTypes.length); // 把this的type加到argTypes[0] extendedArgTypes[0] = Type.getObjectType(owner); argTypes = extendedArgTypes; } // 返回类型和size final Type returnType = Type.getReturnType(desc); final int retSize = returnType.getSize(); switch (opcode) { // 四种方法调用 case Opcodes.INVOKESTATIC: case Opcodes.INVOKEVIRTUAL: case Opcodes.INVOKESPECIAL: case Opcodes.INVOKEINTERFACE: // 构造污染参数集合,方法调用前先把操作数入栈 final List<Set<T>> argTaint = new ArrayList<Set<T>>(argTypes.length); for (int i = 0; i < argTypes.length; i++) { // 占位 argTaint.add(null); } for (int i = 0; i < argTypes.length; i++) { Type argType = argTypes[i]; // 方法调用需要消耗掉这些参数,全部pop了 if (argType.getSize() > 0) { for (int j = 0; j < argType.getSize() - 1; j++) { pop(); } // 根据参数类型大小,从栈底获取入参,参数入栈是从右到左的 // 整体过程参考图片 argTaint.set(argTypes.length - 1 - i, pop()); } } Set<T> resultTaint; // 构造 if (name.equals("<init>")) { // 如果被调用的方法是构造方法,则直接通过this污染 // 之前已经设置第0位是this resultTaint = argTaint.get(0); } else { resultTaint = new HashSet<>(); } // void ObjectInputStream.defaultReadObject() if (owner.equals("java/io/ObjectInputStream") && name.equals("defaultReadObject") && desc.equals("()V")) { // this加入到局部变量表,污染与参数无关 savedVariableState.localVars.get(0).addAll(argTaint.get(0)); } // 这是一个很大的白名单,在下文给出 for (Object[] passthrough : PASSTHROUGH_DATAFLOW) { // 恰好匹配到item if (passthrough[0].equals(owner) && passthrough[1].equals(name) && passthrough[2].equals(desc)) { for (int i = 3; i < passthrough.length; i++) { // 保存信息 resultTaint.addAll(argTaint.get((Integer)passthrough[i])); } } } // 方法返回值与哪个参数有关系(见7,8) if (passthroughDataflow != null) { // 与哪个参数有关 Set<Integer> passthroughArgs = passthroughDataflow.get(methodHandle); if (passthroughArgs != null) { for (int arg : passthroughArgs) { // 污点是第几个参数 resultTaint.addAll(argTaint.get(arg)); } } } // 如果对象实现java.util.Collection或java.util.Map // 则假定任何接受对象的方法都污染了collection // 假设任何返回对象的方法都返回集合的污点 if (opcode != Opcodes.INVOKESTATIC && argTypes[0].getSort() == Type.OBJECT) { // 获取被调用函数的所有基类 Set<ClassReference.Handle> parents = inheritanceMap.getSuperClasses(new ClassReference.Handle(argTypes[0].getClassName().replace('.', '/'))); // 如果基类中存在collection或map if (parents != null && (parents.contains(new ClassReference.Handle("java/util/Collection")) || parents.contains(new ClassReference.Handle("java/util/Map")))) { // 如果该类为集合类,则存储的所有元素都是污染 for (int i = 1; i < argTaint.size(); i++) { argTaint.get(0).addAll(argTaint.get(i)); } // 如果返回是对象或数组,认为this是污染 if (returnType.getSort() == Type.OBJECT || returnType.getSort() == Type.ARRAY) { resultTaint.addAll(argTaint.get(0)); } } } // 如果有返回 if (retSize > 0) { // 污染结果入栈 push(resultTaint); // return的push从1开始,少1位 for (int i = 1; i < retSize; i++) { push(); } } break; default: throw new IllegalStateException("Unsupported opcode: " + opcode); } // 传递 super.visitMethodInsn(opcode, owner, name, desc, itf); // stack校验 sanityCheck(); }
白名单
private static final Object[][] PASSTHROUGH_DATAFLOW = new Object[][] { { "java/lang/Object", "toString", "()Ljava/lang/String;", 0 }, { "java/io/ObjectInputStream", "readObject", "()Ljava/lang/Object;", 0}, { "java/io/ObjectInputStream", "readFields", "()Ljava/io/ObjectInputStream$GetField;", 0}, ...... }
5.7.7 visitInvokeDynamicInsn
动态调用方法
@Override public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { // 参数总size int argsSize = 0; for (Type type : Type.getArgumentTypes(desc)) { argsSize += type.getSize(); } // 返回size int retSize = Type.getReturnType(desc).getSize(); // 方法调用需要pop参数 for (int i = 0; i < argsSize; i++) { pop(); } // 返回值需要push进去 for (int i = 0; i < retSize; i++) { push(); } // 传递 super.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); // stack校验 sanityCheck(); }
5.7.8 visitJumpInsn
跳到其他操作的操作
jump后应该处理Stack和局部变量表的问题
@Override public void visitJumpInsn(int opcode, Label label) { switch (opcode) { // 取出栈顶元素判断 case Opcodes.IFEQ: case Opcodes.IFNE: case Opcodes.IFLT: case Opcodes.IFGE: case Opcodes.IFGT: case Opcodes.IFLE: case Opcodes.IFNULL: case Opcodes.IFNONNULL: pop(); break; // 取出栈顶两个元素判断 case Opcodes.IF_ICMPEQ: case Opcodes.IF_ICMPNE: case Opcodes.IF_ICMPLT: case Opcodes.IF_ICMPGE: case Opcodes.IF_ICMPGT: case Opcodes.IF_ICMPLE: case Opcodes.IF_ACMPEQ: case Opcodes.IF_ACMPNE: pop(); pop(); break; // goto case Opcodes.GOTO: break; // 跳转子程序 // push地址 case Opcodes.JSR: push(); super.visitJumpInsn(opcode, label); return; default: throw new IllegalStateException("Unsupported opcode: " + opcode); } // 合并goto后的stack和local variables mergeGotoState(label, savedVariableState); // 传递 super.visitJumpInsn(opcode, label); // stack校验 sanityCheck(); } private void mergeGotoState(Label label, SavedVariableState savedVariableState) { if (gotoStates.containsKey(label)) { // goto需要合并stack和local variables SavedVariableState combinedState = new SavedVariableState(gotoStates.get(label)); combinedState.combine(savedVariableState); // 出现过的label直接合并 gotoStates.put(label, combinedState); } else { gotoStates.put(label, new SavedVariableState(savedVariableState)); } }
5.7.9 visitLabel
标签指定了紧接着它将被访问的指令
@Override public void visitLabel(Label label) { // 如果跳转label已被初始化过 if (gotoStates.containsKey(label)) { // 从已被初始化过的地方拿到stack和局部变量表信息 savedVariableState = new SavedVariableState(gotoStates.get(label)); } // 如果是异常label,类似return需要把异常push进去 if (exceptionHandlerLabels.contains(label)) { // 只是占位 push(new HashSet<T>()); } // 传递 super.visitLabel(label); // stack校验 sanityCheck(); }
5.7.10 visitLdcInsn
常量池操作
@Override public void visitLdcInsn(Object cst) { // push 常量池 // size为2 if (cst instanceof Long || cst instanceof Double) { push(); push(); } else { // size为1 push(); } // 传递 super.visitLdcInsn(cst); // stack校验 sanityCheck(); }
5.7.11 visitTableSwitchInsn
通过索引访问跳转表并跳转
@Override public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) { // pop index pop(); // 根据跳转label合并状态 mergeGotoState(dflt, savedVariableState); for (Label label : labels) { mergeGotoState(label, savedVariableState); } // 传递 super.visitTableSwitchInsn(min, max, dflt, labels); // stack校验 sanityCheck(); }
5.7.12 visitLookupSwitchInsn
通过键匹配和跳转访问跳转表
任何跳转都需要处理Stack和局部变量表
@Override public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { // pop key pop(); // 根据跳转label合并状态 mergeGotoState(dflt, savedVariableState); for (Label label : labels) { mergeGotoState(label, savedVariableState); } // 传递 super.visitLookupSwitchInsn(dflt, keys, labels); // stack校验 sanityCheck(); }
5.7.13 visitMultiANewArrayInsn
创建新的多维数组
@Override public void visitMultiANewArrayInsn(String desc, int dims) { // 每个维度有个size for (int i = 0; i < dims; i++) { pop(); } // 创建完把引用push回去 push(); // 传递 super.visitMultiANewArrayInsn(desc, dims); // stack校验 sanityCheck(); }
5.7.14 visitOthers
这部分基本没有业务逻辑,也没有POP/PUSH操作
@Override public AnnotationVisitor visitInsnAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { return super.visitInsnAnnotation(typeRef, typePath, desc, visible); } @Override public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { // 异常label保存 exceptionHandlerLabels.add(handler); super.visitTryCatchBlock(start, end, handler, type); } @Override public AnnotationVisitor visitTryCatchAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { return super.visitTryCatchAnnotation(typeRef, typePath, desc, visible); } @Override public void visitMaxs(int maxStack, int maxLocals) { super.visitMaxs(maxStack, maxLocals); } @Override public void visitEnd() { // visit完毕 super.visitEnd(); }