Chinaunix首页 | 论坛 | 博客
  • 博客访问: 478730
  • 博文数量: 60
  • 博客积分: 7346
  • 博客等级: 少将
  • 技术积分: 1980
  • 用 户 组: 普通用户
  • 注册时间: 2006-06-08 15:56
文章分类

全部博文(60)

文章存档

2022年(1)

2014年(5)

2012年(12)

2011年(1)

2010年(2)

2009年(34)

2008年(5)

我的朋友

分类: Java

2009-11-06 09:34:55

      最近换了一个新手机samsung F488E,终于可以在自己的手机上耍耍游戏了(以前开发J2ME的游戏,都是用的公司测试机)。于是疯狂的下载了一些适合触摸屏240*320的 java游戏,其中有一款RPG游戏玩得很开心,可惜某些地方要发短信收费,一气之下动了破解的念头。以前做过J2ME的开发,熟知J2ME程序都会在最 终发布的时候进行混淆编译,一是避免程序被反编译,二是可以大大的减小程序包体积。

破解的思路很简单:找到实际调用发送接口的方法,让它始终返回“发送成功”,于是多种工具齐上阵就开始干了。

      首先需要把class文件都反编译出来,java反编译工具很多,网上评论也不尽相同,但我试过很多,面对混淆后的字节码,表现得都一样。我使用的是XJad。经过一番查找,找到这样一个方法:

  1. private static boolean a(String s1, String s2)  
  2. {  
  3.     MessageConnection messageconnection;  
  4.     TextMessage textmessage = (TextMessage)(messageconnection = (MessageConnection)Connector.open(s1)).newMessage("text");  
  5.     if (ad.a(messageconnection) || ad.a(textmessage))  
  6.         return false;  
  7.     textmessage.setAddress(s1);  
  8.     textmessage.setPayloadText(s2);  
  9.     messageconnection.send(textmessage);  
  10.     messageconnection.close();  
  11.     return true;  
  12.     JVM INSTR pop ;  
  13.     return false;  
  14.       
  15. }  

      毫无疑问,这就是实际发送短信的地方了。我曾试着把所有反编译过来的java文件中的错误全部改掉,里面有很多乱七八糟的代码,最终自然是编译通过,但不能运行。

      接下来,就要针对这个方法做处理了,有两个思路:一是直接修改字节码,把上述的"return false"的地方统统改成"return true";二是在这个方法的开始加入这段代码"if(true)return true;"。这两个方法在理论上都是可行的。

      一、直接修改字节码

      要查看“return false”对应字节码需要使用JClassLib工具,JClassLib有一个可视化的界面,方便我们查看类的变量、方法、静态数据等,如下图:

这个a方法(也许实际名称就是sendsms)的字节码如下:

 0 aload_0
 1 invokestatic #42
 4 checkcast #11
 7 dup
 8 astore_2
 9 ldc #61
11 invokeinterface #48 count 2
16 checkcast #12
19 astore_3
20 aload_2
21 invokestatic #29
24 ifne 34 (+10)
27 aload_3
28 invokestatic #29
31 ifeq 36 (+5)
34 iconst_0
35 ireturn
36 aload_3
37 aload_0
38 invokeinterface #50 count 2
43 aload_3
44 aload_1
45 invokeinterface #51 count 2
50 aload_2
51 aload_3
52 invokeinterface #49 count 2
57 aload_2
58 invokeinterface #47 count 1
63 iconst_1
64 ireturn
65 pop
66 iconst_0
67 ireturn
通过查找JVM指令集,0x03 iconst_0   将int型0推送至栈顶 ,0x04 iconst_1   将int型1推送至栈顶 ,0xac ireturn    从当前方法返回int,很明显,63-67行就是对应的

return true;
JVM INSTR pop ;
return false;

这5行对应的16进制值是:0x04,0xAC,0x57,0x03,0xAC,于是,用16进制编辑器打开,查找04AC5703AC的位置,果 然不负众望,接下来你知道怎么做了吧,把0x03这个位置的值换成0x04,那么这个return false不就变成return true了,呵呵。

      二、在该方法体前加直接返回true的代码

      这个方法比上一方法更加灵活强大,使用Javassite和JClassLib相结合,甚至可以将class改得“体无完肤”,哈哈哈哈。还是以这个方法 为例,通过反编译后,知道该方法名和参数,则可以通过Javassite获取到该方法对象,进而进行更多的处理。在eclipse建一个Java项目,把 Javassite和JClassLib的jar包加进去,演示代码如下:

  1. public static void main(String[] args) throws Exception{  
  2.   
  3.       
  4.     ClassPool pool = ClassPool.getDefault();  
  5.     CtClass cc = pool.get("s");  
  6.     System.out.println(cc.getSuperclass().getName());  
  7.       
  8.     CtMethod cm = cc.getDeclaredMethod("a"new CtClass[]{pool.get("java.lang.String"),pool.get("java.lang.String")});  
  9.       
  10.     cm.insertBefore("{if(true)return true;}");  
  11.     cc.writeFile();  
  12.       
  13. }  

      其中的pool.get("s"),s就是方法所在的类名,这个地方整得很简洁,连包名都没得,呵呵,一般可能是com.pa.s。CtMethod有很 多有用的方法,参考来自http://dev.csdn.net/article/53/53243.shtm 的文章可以发现,这里只使用到insertBefore,还有setBody也是很实用的。

      以下是使用setBody方法实现的方法替换:

  1. public static void main(String[] args) throws Exception{      
  2.       
  3.     ClassPool pool = ClassPool.getDefault();  
  4.     CtClass cc = pool.get("s");  
  5.     System.out.println(cc.getSuperclass().getName());  
  6.       
  7.     CtMethod cm = cc.getDeclaredMethod("a"new CtClass[]{pool.get("java.lang.String"),pool.get("java.lang.String")});  
  8.     /* 
  9.     cm.insertBefore("{if(true)return true;}"); 
  10.     */  
  11.     CtMethod m2 = CtNewMethod.copy(cm, cc, null);     
  12.     cm.setName(cm.getName() + "_orig");  
  13.     //setBody第一个参数是要设置的方法体,第二个参数就是CtClass中对应的类名  
  14.     //m2.setBody("{ System.out.println(\"call\"); return $proceed($$);}", "s", cm.getName());  
  15.     m2.setBody("{ return true;}""s", cm.getName());  
  16.     cc.addMethod(m2);  
  17.     cc.writeFile();  
  18.       
  19. }  

      经过如此替换之后,该方法变成如下:

  1. private static boolean a_orig(String s1, String s2)  
  2. {  
  3.     MessageConnection messageconnection;  
  4.     TextMessage textmessage = (TextMessage)(messageconnection = (MessageConnection)Connector.open(s1)).newMessage("text");  
  5.     if (ad.a(messageconnection) || ad.a(textmessage))  
  6.         return false;  
  7.     textmessage.setAddress(s1);  
  8.     textmessage.setPayloadText(s2);  
  9.     messageconnection.send(textmessage);  
  10.     messageconnection.close();  
  11.     return true;  
  12.     JVM INSTR pop ;  
  13.     return false;  
  14. }  
  15. private static boolean a(String s1, String s2)  
  16. {  
  17.     return true;  
  18. }  

     可见,原方法已经被新方法替换了。

     CtClass的wrtieFile方法,将更新后的字节码写入class文件。     

      但在本例中,采用insertBefore这样生成的class,安装到手机上后,调用该方法时出现了程序直接跳出来的现象,可能是某些改得引起了手机的 不兼容,而setBody方法没有这个问题。当然,对于其他的java应用,这样的改动应该是不会出现这样的问题的。

      最后总结一下,破解J2ME游戏的简要步骤就是:

     1、反编译class文件,可能部分代码不可读,但瞄准破解的关键地方,针对某个类某个方法来处理;

     2、使用JClassLib分析类、方法的字节码,与反编译的代码进行比对,查找对应的16进制值位置;或者使用Javassite修改对应的方法字节码,重新生成class文件。

     3、对于简单的值修改,使用16进制编辑器进行修改即可。

     以上是今天破解一个J2ME游戏和字节码操作的心得,供大家参考,欢迎交流!

阅读(1710) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~