续:
http://bbs.pediy.com/showthread.php?s=&threadid=32484
舵手兄终于处理了 Zelix KlassMaster,为了回应舵手的功劳。
我再次奉献 DashO Pro Evaluation 3.2 版本的逆向分析
DashO Pro 3.2 试用版 注册机
对于Java软件来说,安全性一直是非常脆弱的。虽然出现了很多保护手段,但是都并不完善。对于Java软件来说使用最多的保护手段就是使用混乱器了。这里使用DashO Pro作为混乱器的效果是很好的。但是怎样才能无限制的使用这个版本呢。制作出注册机就可以了吧?!
Author:vhly[FR]
Tools:DJ Java Decompiler 3.8.8
对于如今的混乱器而言,字符串加密简直是必备的功能,如果哪一个混乱器没有此项功能,那么并不算一个好的混乱器。同时混乱器还应该包含控制流程混乱、堆栈混乱、字节代码混乱等等。只有符合上述功能才算一个好的软件吧?!
1. 思路
对于经过自身混乱的DashO Pro 3.2 Evaluation来说,他的保护非常好,首先,如果按照程序启动顺序的话,就跟本不可能破解。因为这个软件GUI部分使用DashoProGuiEval类作为主类,同时主类会调用b类的方法 a(String [] args),但是最为关键的是,b类当使用DJ Java Decompiler 也就是 jad 时出现非法操作。针对这种情况,应该可以推断,要么使用了自定义类装载器,要么使用了字节代码陷阱或者畸形的类文件。鉴于以前破解Smokescreen时的经验,针对这种大型的,包含许多类文件的软件,首先要找到关键信息。可以直接通过类文件中的常数池读出String类型的常量就可以了。同时DashO使用了字符串加密,解密方法如下:
public static String A_B_C_D(String s) // DashO 所使用的字符串加密/解密方法
{
char ac[] = new char[s.length()];
s.getChars(0, s.length(), ac, 0);
char c = '\0';
for(int i = 0; i < ac.length; i++)
ac[i] = (char)(ac[i] - 1 ^ c++);
return new String(ac);
}
就可以制作字符串提取器了 如下 com.vhly.anti.dasho.DecodeString 类
package com.vhly.anti.dasho;
import java.io.*;
import java.util.*;
import java.util.zip.*;
import java.util.jar.*;
import com.vhly.classfile.*; // 添加了我的类文件分析库
public class DecodeString
{
private String targetName;
private String outName;
public DecodeString()
{
Debug.setEnable(false);
}
public static String decodeString(String oldString)
{
char ac[] = new char[oldString.length()];
oldString.getChars(0, oldString.length(), ac, 0);
char c = '\0';
for(int i = 0; i < ac.length; i++)
ac[i] = (char)(ac[i] - 1 ^ c++);
return new String(ac);
}
public void setTargetName(String fn)
{
targetName = fn;
}
public void setOutName(String on)
{
outName = on;
}
public void doWork() throws IOException
{
JarFile target = new JarFile(targetName);
FileWriter out = new FileWriter(outName);
BufferedWriter bout = new BufferedWriter(out);
PrintWriter pw = new PrintWriter(bout);
Enumeration en = target.entries();
JavaClass jc = null; // 通过输入流获取类
InputStream in = null;
while(en.hasMoreElements())
{
JarEntry je = (JarEntry)(en.nextElement());
String name = je.getName();
if( name.endsWith(".class") )
{
in = target.getInputStream(je);
jc = new JavaClass();
jc.read(in);
pw.println("Find The Class:"+name);
ConstantPool cp = jc.getCP();
int size = cp.size();
for(int i=1; i< size; i++)
{
CPEntry cpe = cp.getCPEntryAtIndex(i);
if( cpe.getType() == CPEntry.TYPE_STRING )
{ CPString cps = (CPString)cpe; int sindex = cps.getStringIndex();
CPUtf8 utf8 = (CPUtf8)(cp.getCPEntryAtIndex(sindex));
String data = utf8.getString();
pw.println(" String data["+data+"]:");
pw.println(" \""+decodeString(data)+"\";");
}
}
}
}
jc = null;
in = null;
pw.close();
bout.close();
out.close();
target = null;
System.gc();
}
public static void main(String args[]) throws IOException
{
DecodeString ds = new DecodeString();
ds.setTargetName(args[0]);
ds.setOutName(args[1]);
ds.doWork();
ds = null;
}
}
由于DashO对类文件进行了处理,上面代码会在某些类上出错。
提取出的字符串内容 (节选完整内容另存文件)
Find The Class:c6.class 包含了程序第一次运行和注册失败后的用户信息注册对话框类
String data[Sefkxrug}aff ]:
"Registration?";
String data[Fmdki&Ucpaz?j? ]:
"Email Register"; 注册的两种方式 必须使用此种方式
String data[Xea$Wabo|~pz ]:
"Web Register"; web注册
String data[Damabj ]:
"Cancel";
String data[Cabi ]:
"Back";
String data[Tuaonr ]:
"Submit";
Find The Class:c7.class 构造第一次注册用户信息的对话框
String data[Giqqq&Igfm ]:
"First Name?";
String data[Marx%Lhkn ]:
"Last Name?";
String data[Fmdki&hdm|py? ]:
"Email addres联?";
String data[Qjnnb&Isflpz ]:
"Phone Number?";
String data[Ga{$Kqlfn| ]:
"Fax Number?";
String data[Doptfl?)Jpmcaep|z} ]:
"Compan?/Affiliation?";
String data[Uiwpb ]:
"Title?";
String data[Bfgrbwv(: ]:
"Address 1?";
String data[Bfgrbwv(; ]:
"Address 2?";
String data[Diw{ ]:
"City?";
String data[Tvdxb ]:
"State?";
String data[[is-Ukvtjf+Idjl ]:
"Zip/Postal Code?";
String data[Doxnqx?)[mncdd ]:
"Countr?/Region?";
String data[Teqkfj'J~eio ]:
"Serial Number?";
String data[Tvdxrw'Kn{zkli ]:
"Status Message?";
String data[uzwFnxvtGiho ]:
"txtFirstName?";
String data[Qnhcxa'cg~pz-ub{c2}szq7w:otz>YwSSW kELC PDOAJ ]:
"Please enter your name in the First Name field.?";
部分出错信息以及控件位置信息未列出
String data[Qjnnb&isflpz-a|}e2qw5uc8u}|ii>00 FLENRV. ]:
"Phone number must be at least 10 digits
?";
String data[Fpwgw&h(igci.~kcyt?5|d{{}i ]:
"Enter a valid serial number┳";
String data[Fpwgw&ei}b+nf?~|1q}x5zxem:u{ryn ]:
"Enter both fi曼st and last names
2. 寻找软件关键点
当找到程序第一次注册的类之后,第二次运行时,会出现 “Step 2” 注册对话框。对话框中出现:“Serial Number”、“Confirmation Code”等字段,那么在我们提取的字符串文件中寻找 “Confirmation Code”字符串 发现,在d0 类中有着部分信息,同时会安装事件处理类 d1 反汇编这个类,发现如下代码片断:
class d1 implements ActionListener
{
private final d0 a; /* synthetic field */
public final void actionPerformed(ActionEvent actionevent)
{
boolean flag = true;
if(d0.a(a)) // return d0.i;
{
String s = d0.b(a).getText().trim();
if(s.length() > 0)
d0.c(a).t(s); // cw d0.c(d0 a) cw.t(s); 就是设置cw.m = s
else
flag = false;
}
try
{
d0.c(a).b(Integer.parseInt(d0.d(a).getText().trim()));
// 设置cw.al = int;
}
catch(NumberFormatException numberformatexception)
{
flag = false;
}
if(flag && d0.c(a).ag()) // cw.ag() -> 真正的序列号校验
{
d0.c(a).e(true);
d0.a(a, 0);
d0.e(a);
} else
{
d0.c(a).e(false); JOptionPane.showMessageDialog(((Component)actionevent.getSource()).getParent().getParent(), "Invalid Confirmation Code. Please try again.", "Error", 0);
}
}
public d1()
{}
}
3. 注册算法
关键点 cw类的 ag()方法
public final boolean ag()
{
boolean flag = false; // al 为软件 Confirmation Code 可以自定义
int i1 = (al & 0xff) << 8;
i1 |= (al & 0xff00) >> 8;
if(m != null)
try
{
if((i1 ^ 0x30cd) == Integer.parseInt(m)) // m为序列号
flag = true;
}
catch(NumberFormatException numberformatexception) { }
return flag;
}
4. 制作注册机
package com.vhly.keygen.dasho;
public class ConfirmCodeSum
{
public static String gen(String conf)
{
int tmp = Integer.parseInt(conf);
int i1 = (tmp & 0xff) << 8;
i1 |= (tmp & 0xff00) >> 8;
i1 ^= 0x30cd;
String ret = Integer.toString(i1);
return ret;
}
public static void main(String args[])
{
String conf = "99334827"; // 此处可以随意修改,但必须为十进制数!
System.out.println("Confirm Code:"+conf+" Gen Serial Code:"+gen(conf));
}
}
我的注册界面是:
其中的 vhly FR是在第一次注册时填上的 注意第一次输入序列号时候,注册码可以随意,但是必须选择使用 Email Register方式
5. 软件的逆向工程
第一步、去除(EVALUATION ONLY)标记,去除生成类文件中的 DashoEval_前缀,可修改为 DashO_,也就是去除所有的试用信息
首先:由于软件中使用了字符串加密的方式,因此如果我们要加入任何信息,都必须使用加密后的内容,根据解密程序,分析出DashO的字符串加密方法。
代码见下一页:
public class GenString
{
public static String gen(String s)
{
char ac[] = new char[s.length()];
s.getChars(0, s.length(), ac, 0);
char c = '\0';
for(int i = 0; i < ac.length; i++)
ac[i] = (char)((ac[i] ^ c++)+1);
return new String(ac);
}
public static void main(String args[])
{
String my_title = "=(Modified by vhly[FR])=";
System.out.println("Gen String:"+gen(my_title));
System.out.println(" Method "+ gen("Method"));
System.out.println("DashO_Pro_cracked_:"+ gen("DashO_Pro_cracked_"));
}
}
找到关于对话框中的 EVALUATION COPY 的相应加密内容
String data[!*HVFJTG]AFF-OB`J9 ]:
" (EVALUATION COPY)";
使用直接修改类文件的方式,找到 上述内容
进行体换,替换为 [>*Pmamaonn+jv.yh}iJVGI@+] => “=(Modified by vhly[FR])=”
结果如下:
此画面为修给后的对话框,之前的 EVALUATION COPY 消失了,取代的是我的信息。
关于对于类生成后重命名的修改,由于生成的类,使用试用版会使用 DashEval_
作为前缀。我希望修改为 自定义的类名称
重点在于处理 hp.class,采用十六进制编辑器直接修改 CPUtf8
找到 DashEval_的字符串
Gen String:>*Pmamaonn+jv.yh}iJVGI@+
Method Newllb
DashO_Pro_cracked_:EarlL[WvhWjznofkuO
vwwwwvvwv_ gen:wwvuttqqW // 使用这个
Gen String:>*Pmamaonn+jv.yh}iJVGI@+
在hp.class中找到两处包含 Earl的地方 注意时 CPUtf8格式
即: 01 short_len Ear....
01 short_len Ear...
修改为 wwvuttqqW =〉 vwwwwvvwv_
wwvuttqq =〉 vwwwwvvwv
之后的生成结果
类名为: vwwwwvvwv_a vwwwwvvwv_b vwwwwvvwv_c 等等
成员变量: 同理
最后对于舵手兄说的 JProbe我现在上班只有周日有时间,不过会继续努力的
Author: vhly[FR]
Date: 2006/10/03
Time: 03:33:33 AM
Have A Good Time And I will Sleep
All The Time Gen This 5 Hours
[课程]Android-CTF解题方法汇总!