首页
论坛
专栏
课程

[原创]smartgit18.2 软件验证过程分析

2019-1-3 14:43 7155

[原创]smartgit18.2 软件验证过程分析

2019-1-3 14:43
7155

0x00准备条件

jd-gui.exe :反编译工具    
eclipse + "Enhanced Class Decompiler" :IDE + 反编译插件。    
JByteMod :字节码编辑器

0x01 先找切入点:

在界面上,通过“Register”操作,随便提供了一个文件作为输入,界面提示:"The specified file is invalid.",
到软件的安装目录的lib中找到smartgit.jar文件,基本可以认定它是核心功能包,所以对它进行逆向分析。

./smartgit/aqU.java:/* 120 */     return "Please enter or choose a valid license file.";
./smartgit/aqU.java:/* 124 */     return "The specified file is invalid.";
  public static String u()
  {
    return "Please enter or choose a valid license file.";
  }

  public static String v()
  {
    return "The specified file is invalid.";
  }

从上面的提示找到的信息,基本上都是一个通用aqU字符返回类,里面有比较多的信息,但是提供不了处理逻辑,但是
返回字符中有关于版本的信息,通过对反编译的工程进行call调用查找,定位到aqT类(说明:windows不区分文件大小
写很坑,混淆过的文件中,很多不区分大小写就是重名的,通过luyten.exe导出可以解决)

    C2582aqT(C2340alT c2340alT, boolean z) {
        this(c2340alT.a(C2583aqU.f()),     // Type
             c2340alT.a(C2583aqU.g()),       // Subscription
             c2340alT.b(C2583aqU.i()),       // Name
             c2340alT.a(C2583aqU.j()),       // Address
             c2340alT.a(C2583aqU.l()),       // Email
             c2340alT.a(C2583aqU.o()),       // ProgramVersion
             c2340alT.b(C2583aqU.m(), true), // FreeUpdatesUntil
             c2340alT.b(C2583aqU.n(), true), // ValidUntil
             c2340alT.b(C2583aqU.k(), true), // DemoUntil
             c2340alT.b(C2583aqU.s(), true), // SupportUntil
             c2340alT.b(C2583aqU.h(), true), // SubscriptionTo
             c2340alT.a(C2583aqU.p(),        // LicenseCount
                 c2340alT.a(C2583aqU.r(), null)), // MaxUsers
             c2340alT.a(C2583aqU.q(), false), z, c2340alT);            // Enterprise
    }

虽然没有定位到关键处理点,但是上面的信息基本提供了证书文件的字段内容,仔细分析这个类后,可以基本确定证书文件格式:

Type=professional
Name=asdf
Address=asdf.cn BLE Str.
Email=asdf@qq.com
ProgramVersion=18.2
FreeUpdatesUntil=2999-12-30
ValidUntil=2999-12-30
DemoUntil=2999-12-30
SupportUntil=2999-12-30
LifeLongUpdates=true
MaxUsers=1000

当然上面的还不是证书文件的全部信息,需要继续分析,再次查找aqT类的创建点:

public abstract class C2576aqV {
    @NotNull
    protected final QX a(ake ake, String str, String str2, boolean z, boolean z2) {
        QX a = new C2342alV(C2229ajA.a(str), C2229ajA.a(str2), z).a(ake); // 分析证书信息。
        QX qx = new QX(new C2582aqT((C2340alT) a.a(), z2), a.b());      // ===> License info record .
        if (qx != null) {
            return qx;
        }
        throw new IllegalStateException(String.format("@NotNull method must not return null", new Object[0]));
    }
}
这儿基本是证书文件的关键处理入口,不过现在因为是混淆的,啥也看不出来。

0x02构建可调试工程

因为代码进行了混淆,看起来还是非常的低效,所 以想着能构建一个可调试工程,但是这个程序是从smartgit.exe进行运行的,它
是用的自己的加载器进行jar的加载,所以转到分析smartgit.exe,程序并没有加壳,直接找smartgit.jar字符串相关的信息,结果
什么也没有找到。但是在查看数据目录(C:\Users\<username>\AppData\Roaming\syntevo\SmartGit\18.2)时,有个logger.properties
配置文件,看着像可以配置日志级别,以为能收集数据,就改了下配置,结果启动程序的时候,直接出错了,而且提示有完整DUMP文件,
从文件中看到比较有意思的信息:

com.exe4j.runtime.WinLauncher.main()   smartgit.exe
   +-> com.syntevo.QBootLoader.main()  (SourceFile:102)
            +-> com.syntevo.smartgit.SmartGit.main()  (SourceFile:11)
                    +-> smartgit.apB.a()  (SourceFile:245)

基本理清楚加载的过程:
smartgit.exe ---> (bootloader.jar + bootloader-config.jar) ---> smartgit.jar

 

好吧,现在可以不管smartgit.exe了,直接复制lib目录,在eclipse中创建新的工程,加载这些jar文件(创建过程不表了)
以bootloader.jar作为启动,确认可以加载运行起来,通过反编译插件就可以进行断点调试了,一大进步。

0x03处理强制升级

可以调试后,调试了几次感觉信息还是比较多的,可以有一次直接就走到了软件升级框,而且是强制的,升级完成了,再后来进行调试时,
smartgit的版本都变化了,这让我感觉比较奇怪,因为我构建的工程目录不在安装目录,它是怎么升级的?或者加载器加载了其它文件 ?
转到分析bootloader.jar文件,发现还真有不少信息:

0x03-1检测版本信息

首先bootloader.jar文件自身会被写入一些key=value信息:
buildNubmer=13205
buildDate=2018-12-06

 

smartboot.majorVersion=18.2
smartboot.productName=SmartGit
smartboot.productKey=SmartGit
smartboot.productCompany=syntevo
smartboot.additionalJars=org.eclipse.swt.$G.$P.jar
在bootloader.jar加载的时候,它会读取自己身的这些信息,同时和临时目录C:\Users\<username>\AppData\Roaming\syntevo\SmartGit\18.2\updates\18.2
中的current文件内容比较,判断版本是不是最新的,如果不是,则会根据contorl-<version number>文件的内容,进行加载,contorl-<version number>
记录了需要加载的文件和对应升级的文件路径信息。

0x03-2自动升级功能

搞清楚了这部分基本就可以调试升级部分功能了,修改current信息,然后进次启动,弹出了升级框,进行主线程暂停,得到栈信息:

Po.a(Pk, afj, Pf, PE, anc, aaP) line: 43    
q.a(aoY, mY, anc, ay, asN, W) line: 1704    
q.a(aoY, mY, aC, Display, Boolean, anc, amf, ajZ) line: 511    
q.b(aoY, mY, aC, Display, Boolean, anc, amf, ajZ) line: 367

查看代码中的http处理的API,重新设置断点,得到了请求远端服务器信息:

a    URL  (id=268)    
    authority    "www.syntevo.com" (id=276)    
    file    "/smartgit/autoupdate?type=foundation&version=18.2.1&build=13205&os=Windows+7&osv=6.1&java=1.8.0_172&locale=31&bugtraces=off&id=33lsndalsb&email=&lifelong=off&git=off&svn=off&hg=off&dreview=off&dg=off" (id=277)    
        hash    0    
        value    (id=284)    
    handler    Handler  (id=278)    
    hashCode    -1    
    host    "www.syntevo.com" (id=276)    
    hostAddress    null    
    path    "/smartgit/autoupdate" (id=281)    
    port    -1    
    protocol    "http" (id=282)    
    query    "type=foundation&version=18.2.1&build=13205&os=Windows+7&osv=6.1&java=1.8.0_172&locale=31&bugtraces=off&id=33lsndalsb&email=lifelong=off&git=off&svn=off&hg=off&dreview=off&dg=off" (id=283)    
    ref    null    
    tempState    null    
    userInfo    null

通过向升级服务器获取最新版本信息,再与本地版本进行比较确认是否需要升级。
这样比较简单的办法就是hosts文件,直接把www.syntevo.com 重定向到127.0.0.1.

0x04查找关键验证流程

解决升级问题,再次回到之前版本进行调试,从掌握的信息,先直接对aqT类的函数都设置了断点,看看有哪些有意思的栈信息,再逐个分析:

C2576aqV.b(ake, "S1", "S2", true)
        +->C2576aqV.a(ake, S1, S2, true, false)
               +->C2576aqV.a()
                     +-> new C2582aqT((C2340alT) a.a(), z2), a.b())

和上面的分析基本对应上了,在aqT的调用点之前,ake是证书文件信息,S1,S2是公钥信息。
通过对上面的调用逐个调试,定位到alV类函数:

    /*     */            public QX a(final ake ake) {  // 输入为选择的文件信息,可以确认证书文件应该是ZIP文件。
    /* 233 */                try (final ZipInputStream zipInputStream = new ZipInputStream(ake.createStream())) {
    /*     */        
    /* 235 */                    for (ZipEntry zipEntry = zipInputStream.getNextEntry();       // 遍历压缩文件中的文件名。
                                     zipEntry != null; zipEntry = zipInputStream.getNextEntry()) { 
    /*     */        
    /*     */        
    /*     */        
    /* 240 */                        if (zipEntry.getName().lastIndexOf('/') <= 1) {           // 如果文件中没有'/'分隔符
    /*     */        
    /*     */                            try {
    /*     */        
    /*     */                                // read content 
    /* 245 */                                return this.a((InputStream)new akH((InputStream)zipInputStream)); // goto 300 line function 
    /*     */        
    /*     */                            }
    /*     */        
    /*     */                            catch (alR alR) {}
    /*     */        
    /*     */                        }
    /*     */        
    /*     */                    }
    /*     */        
    /*     */                }
    /*     */                catch (IOException ex) {}
    /* 257 */                try (final InputStream stream = ake.createStream()) {
    /* 258 */                    return this.a(stream);
    /*     */                }
    /*     */            }

进行文件解析处理

public alT a(InputStream paramInputStream)  {  }
/* 263 */     MessageDigest localMessageDigest1 = a(); //line 62 ==> alV :: a() 
/* 264 */     MessageDigest localMessageDigest2 = a();
/* 265 */     alT localalT = a(paramInputStream, jdField_a_of_type_Boolean, localMessageDigest1, localMessageDigest2); // line 135: 
/* 266 */     ajA localajA1 = localalT.b();
/* 267 */     ajA localajA2 = localalT.a();    ajA localajA3;
/* 268 */     if ((jdField_a_of_type_Boolean) && (localajA1 != null))    {
/* 269 */       localajA3 = a(localajA1, jdField_a_of_type_SmartgitAjA, b);
/* 270 */       a(localajA3, localMessageDigest2);
/*     */     }
/* 272 */     else if ((!jdField_a_of_type_Boolean) && (localajA2 != null))    {
/* 273 */       localajA3 = a(localajA2, jdField_a_of_type_SmartgitAjA, b);
/* 274 */       a(localajA3, localMessageDigest1);
/*     */     }    else
/*     */     {
/* 277 */       throw new alS();
/*     */ 
/*     */     }
/* 280 */     return localalT;
/*     */   }
/*     */   }
/*     */            public static void a(final InputStream inputStream, final OutputStream outputStream) {
/*     */                try {
/* 111 */                    final byte[] array = new byte[65536];
/* 112 */                    for (int i = inputStream.read(array); i > 0; i = inputStream.read(array)) {
/* 113 */                        outputStream.write(array, 0, i);
/*     */                    }
/*     */                }
/*     */                finally {
/* 117 */                    outputStream.flush();
/*     */        
/*     */                }
/*     */        
/*     */            }

0x5 构建最小调试工程

从上面的信息基本可以把调用过程都走一遍,不过依赖的信息太多了,每次调试都比较麻烦,所以通过staruml对相关的类进行了梳理,结果如
下图所示:

把这些相关的类反编译代码提取出来,再构建新的工程,就不再依赖smartgit.jar进行调试了,代码比较多,就不逐个函数说了,整理出来的
验证信息如下:
1, 证书文件是ZIP包,内部有一个证书文件,文件可以是UTF8格式;
2, 证书文件中包含了相关的证书字段,以Key = Value 方式写入(明文格式),其中以#表示该行注释。
3,对于证书的验证,软件提供了两个方式,要么是有一个Signature=XXX的内容,要么是在文件的最后有
有一个文件不包含“=”的签名,其中这个签名至少两行,第一行字符长度 != 80,内容不重要,后面的行长度 == 80 ,如果不足,可以补0.

 

整理出来的校验逻辑如下:
1, 提供的KEY 值格式(string1 , string2, bool1, bool2)
其中string1, string2均为JAVA BigInteger的对应结果。
bool1从分析,可以知道在验证中,如果为true, 那么校验时使用了 最后<key, value>中的value, 反之,使用key .
2, 具体算法:
string1 ---> SIGA (BigInteger)
string2 ---> SIGB (BigInteger)
fileSig ---> SIGC (BigInteger) # 说明,中间经过了一些从字符串到BigInteger转换。
encryptSourceStr =bool1 ? value : key # 说明:使用文件最后一组值对的内容。

 ## 校验过程
 Byte[] array1 = SHA1.digest(encryptSourceStr) #  对加密源进行SHA1,同时取数组低16位。
 SIGD = SIGC.modPow(SIGA, SIGB)
 Byte[] array2 = SIGD.getBytes()
 如果array1 == array2,就通过,否则就抛出异常。 

关键函数:

/*     */   public static void a_49(ajA paramajA, MessageDigest paramMessageDigest)
/*     */   {
/*  49 */     byte[] arrayOfByte1 = paramajA.a();
/*  50 */     byte[] arrayOfByte2 = a(paramMessageDigest, 16);
/*     */     
/*  52 */     int i = Math.min(arrayOfByte1.length, arrayOfByte2.length);
/*  53 */     for (int j = 0; j < i; j++) {
/*  54 */       if (arrayOfByte1[j] != arrayOfByte2[j]) {
/*  55 */         throw new alS();
/*     */       }
/*     */     }
/*     */   }

可以看到只要验证结果不正确,就会抛出异常alS。
从上面的分析,由于对算法不熟悉,数据太大不能穷举,放弃,选择对alV类进行暴破,因为有混淆,不能构建
出可编译环境,直接对上面的函数进行字节码修改,验证删除抛出异常部分,进行return.将修改后的alV.class更新到smartgit.jar
中,再构建完整证书文件:

Format=2
Type=professional
Name=asdf
Address=asdf.cn BLE Str.
Email=asdf@qq.com
ProgramVersion=18.2
FreeUpdatesUntil=2999-12-30
ValidUntil=2999-12-30
DemoUntil=2999-12-30
SupportUntil=2999-12-30
LifeLongUpdates=true
MaxUsers=1000
1243d43f8054acd784937ef98fae48b4de70248c
12312341234123123421111114444444444444444444Ee8000000000000000000000000000000000

启动smartgit,提交压缩的证书文件,直接验证OK,分析成功,在IDA和OD之外,解析型语言的分析尝试了一波!!!

 

JavaAgent方案:

最后更新一波,Have Fun !!!
1, crackSmartGit.jar (说明只支持到18.2.5,后续版本不支持)SHA256:9dbb09bcf08d68af7ad0a6aa752b709d00e28f507e5dc5373d0c0aa39c5a4998
2, 添加配置信息到smartgit.vmoptions文件 :

-javaagent:<file path>/crackSmartGit.jar


[推荐]看雪企服平台,提供安全分析、定制项目开发、APP等级保护、渗透测试等安全服务!

最后于 4天前 被nevinhappy编辑 ,原因:
上传的附件:
上一主题 下一主题
最新回复 (10)
nevinhappy 1 2019-1-3 22:07
2
0
自古一楼是楼主,好像对这个企业级的git软件不感冒!
realzyc 2019-1-30 04:54
3
0
感谢大神的分享。这篇对我太有用了。

最近升级SmartGit到18.2.4,发现如果Windows系统是中文版的话,每次启动SmartGit都会强制弹出许可证窗口(非商业版或30试用都有),必须要等待10秒才能正常使用(如果Windows是英文系统用的话没有影响,看来是专门针对非英语用户的...)。我这种强迫症完全受不了。

现在网上关于SmartGit全是删除setting.xml的教程,根本不治本。还好发现了这篇文章!感谢感谢感谢!我以前也玩过一段JAVA的反编译,所以按照这个教程来做,真的成功了!成功晋级专业版用户,开心

下面是我在操作时的一些心得,完全是从小白的角度,大神勿喷:
- 尝试使用“jd-gui-0.3.6”进行反编译的时候,发现smartgit.jar并没有完全加载出来(后来尝试用“jad158g.win”也看不到全部),所以没能通过关键字找到aqU.class或alV.class。
- 又尝试通过直接ZIP解压的方式,发现由于Windows系统对文件名大小写不区分,导致很多class被覆盖,解压出的文件少了很多,还是没办法找到。
- 最后我直接打开ZIP文件,手动提取了aqU.class和alV.class,终于找到了真身。可想到大神调试的时候有多困难。
- 用eclipse + "Enhanced Class Decompiler"查看真的比jd-gui舒服很多,估计是用eclipse习惯了。
- 此外,用JByteMod爆破a_49的时候,我采用的方式是找到for语句的终止的判断,直接换成goto,这样可以比较小的方式跳过for语句,直接return。
- 最后,我在替换jar中class时也遇到了一点小困难,最后发现jar命令还是好用。jar -uvf smartgit.jar smartgit/alV.class

现在终于没有非商业版提示了,再次感谢分享
nevinhappy 1 2019-1-30 18:26
4
0
realzyc 感谢大神的分享。这篇对我太有用了。 最近升级SmartGit到18.2.4,发现如果Windows系统是中文版的话,每次启动SmartGit都会强制弹出许可证窗口(非商业版或30试用都有),必须 ...
加精了,开心!我也是新手,相互学习。
关于Windows文件名大小写不敏感的问题,我上面的提到了,有个工具可以编码导出,不过不支持调试。
另外,在调试的时候不需要考虑文件大小写的问题,直接导入jar包调试,不过在构建最小证书验证运行环境的时候,会有这样的问题,不过识别出来的类基本没有重名,所以也还好,只是要查看反编译代码比较麻烦;
另外,有一个小心得是,因为混淆很多函数名都是a,,在构建运行工程时,通过调试,对函数都进行编号,如函数名加第一行代码的行号方式如a_49(),比较有效。
替换jar包可以直接使用winrar打开,但不解压smartgit.jar文件,手动找到原alV.class进行删除,再把修改后的alV.class添加进去,可能会有名称的变化,再调整正确就OK了,作为一个压缩包操作。
最后于 2019-1-30 18:27 被nevinhappy编辑 ,原因:
codemaker 2019-2-1 23:01
5
0
补充一下根据楼主帖子制作的license.zip
上传的附件:
killpy 2 2019-2-11 08:56
6
0
你用的啥工具分析的 那张图用啥做的
nevinhappy 1 2019-2-11 09:50
7
0
killpy 你用的啥工具分析的 那张图用啥做的
一分钟3贴,兄弟你有毒
codemaker 2019-2-26 17:14
8
0
更新:18.2.6已经失效
nevinhappy 1 2019-2-28 19:42
9
0
codemaker 更新:18.2.6已经失效
用破解不能高调,出一个通用方法,肯定会被和谐的,欢迎提供新patch. 
darly牛 4天前
10
0
建议楼主出一个对应破解的版本下载。否则我们下载新的了,就没有办法破解了。
nevinhappy 1 4天前
11
0
darly牛 建议楼主出一个对应破解的版本下载。否则我们下载新的了,就没有办法破解了。
发这个本意是技术学习,不持续更新对应版本破解。上面的附件已经是“最后更新一波”
游客
登录 | 注册 方可回帖
返回