本文参考:http://www.vuln.cn/7118

本文参考:《Reverse Engineering for Beginners》Dennis Yurichev著

本文需要用到IDA


简单的补丁

看一个例子

public class nag {
	public static void nag_screen() {
		System.out.println("This program is not registered");
	};

	public static void main(String[] args) {
		System.out.println("Greetings from the mega-software");
		nag_screen();
	}
}

我们怎样去掉打印"This program is not registered"这个字符串?

编译后用IDA载入class文件

作者的IDA view视图同步显示了hex的值,可能装了插件或者改了设置,这里的IDA view视图与hex需要切换视图

ALT+T,搜索文本This program is not registered,定位到相关指令附近

Java逆向基础之简单的补丁_crackme

我们首先尝试将getstatic指令改成返回指令

点击getstatic,然后点击hex视图

Java逆向基础之简单的补丁_crackme_02

可以看到B2 00 02对应这条指令

右键修改B2 为B1,然后右键应用更改

Java逆向基础之简单的补丁_补丁_03

再切回IDA view视图

Java逆向基础之简单的补丁_crackme_04

最后菜单点击Edit-->Patch Program-->Apply patches to input file,将更改应用到文件

Java逆向基础之简单的补丁_java_05

在弹出的对话框点确定即可修改class文件

但是这么修改运行的时候会报错,可能是有一些栈和帧的校验

C:\Users\admin\Desktop>java nag
Error: A JNI error has occurred, please check your installation and try again
Exception in thread "main" java.lang.VerifyError: Expecting a stack map frame
Exception Details:
  Location:
    nag.nag_screen()V @1: nop
  Reason:
    Error exists in the bytecode
  Bytecode:
    0x0000000: b100 0212 03b6 0004 b1

        at java.lang.Class.getDeclaredMethods0(Native Method)
        at java.lang.Class.privateGetDeclaredMethods(Unknown Source)
        at java.lang.Class.privateGetMethodRecursive(Unknown Source)
        at java.lang.Class.getMethod0(Unknown Source)
        at java.lang.Class.getMethod(Unknown Source)
        at sun.launcher.LauncherHelper.validateMainClass(Unknown Source)
        at sun.launcher.LauncherHelper.checkAndLoadMain(Unknown Source)

原作者在JDK1.7下不工作,我在1.8下也是,报的是栈帧映射异常

既然此路不通,那么就选另一条路,去掉nag()方法的调用

找到main方法

Java逆向基础之简单的补丁_crackme_06

中间的

.line 8

invokestatic nag.nag_screen()V

这一句就是调用nag()方法,查看对应的hex视图

Java逆向基础之简单的补丁_补丁_07

其中的B8 00 06就是调用这个方法

改成00 00 00(填充3个NOP指令)

Java逆向基础之简单的补丁_补丁_08

对应的IDA view视图

Java逆向基础之简单的补丁_补丁_09

变成了3个nop指令

最后菜单点击Edit-->Patch Program-->Apply patches to input file,将更改应用到文件

修改完看运行效果

Java逆向基础之简单的补丁_java_10

修改成功


再看第二个例子

这是一个简单的crackme的例子

public class password {
	public static void main(String[] args) {
		System.out.println("Please enter the password");
		String input = System.console().readLine();
		if (input.equals("secret"))
			System.out.println("password is correct");
		else
			System.out.println("password is not correct");
	}
}

反编译后的class用IDA打开

搜索字符串"secret"定位到判断代码附近

Java逆向基础之简单的补丁_补丁_11

其中ifeq指令当栈顶int型数值等于0时跳转 ,栈顶存的是String.equals()方法的返回值

首先我们考虑改跳转的位置,改到line 6后面的getstatic指令那里,那么这个指令对应偏移块是多少呢

点击getstatic,状态栏会显示

Java逆向基础之简单的补丁_crackme_12

这个偏移块是24

目标就是将ifeq met002_35修改成ifeq met002_24

ifeq指令所在偏移块是21,35-21=14对应16进制是E,找出hex视图对应的E

Java逆向基础之简单的补丁_crackme_13

可以看到99 00 0E,所以我们要修改其中的E

那么改成多少呢,24-21=3对应16进制的3,所以将E改成3

Java逆向基础之简单的补丁_java_14

再看看IDA view视图符不符合预期

Java逆向基础之简单的补丁_补丁_15

最后菜单点击Edit-->Patch Program-->Apply patches to input file,将更改应用到文件

原作者的修改在JDK1.7下不工作,我这里的JDK1.8也报错了

C:\Users\admin\Desktop>java password
Error: A JNI error has occurred, please check your installation and try again
Exception in thread "main" java.lang.VerifyError: Expecting a stackmap frame at branch target 24
Exception Details:
  Location:
    password.main([Ljava/lang/String;)V @21: ifeq
  Reason:
    Expected stackmap frame at this location.
  Bytecode:
    0x0000000: b200 0212 03b6 0004 b800 05b6 0006 4c2b
    0x0000010: 1207 b600 0899 0003 b200 0212 09b6 0004
    0x0000020: a700 0bb2 0002 120a b600 04b1
  Stackmap Table:
    append_frame(@35,Object[#20])
    same_frame(@43)

        at java.lang.Class.getDeclaredMethods0(Native Method)
        at java.lang.Class.privateGetDeclaredMethods(Unknown Source)
        at java.lang.Class.privateGetMethodRecursive(Unknown Source)
        at java.lang.Class.getMethod0(Unknown Source)
        at java.lang.Class.getMethod(Unknown Source)
        at sun.launcher.LauncherHelper.validateMainClass(Unknown Source)
        at sun.launcher.LauncherHelper.checkAndLoadMain(Unknown Source)

这里必须要提的是,这种改法再JDK1.6上是可以运行的

我们也尝试将if eq 这个占用3个字节的指令用0(NOP指令)填充,结果也是校验失败,不能工作

貌似再JDK1.7上比1.6多了更多的帧栈校验


接下来我们尝试替换整个equals方法,并用iconst_1将常数1压入栈顶,这样后面ifeq判断的时候值始终为flase,就不往偏移块35跳转了

iconst_1的指令是0x04

Java逆向基础之简单的补丁_java_16

即将图中的2B 12 07 B6 00 08改成04 00 00 00 00 00

改完后看下IDA view视图

Java逆向基础之简单的补丁_crackme_17

改完之后工作正常