Ax王者 发表于 2020-1-13 20:40

FinalShell 3.0.10 逆向破解

本帖最后由 Ax王者 于 2021-10-3 18:57 编辑

高级版功能(不断增加中)
1. 网络监控可选择接口,同时监控多个网络接口速度.
2. 打包传输,自动压缩解压,适合传输大量文件,文件夹和文本文件.
3. 高级网络监控,监控每个进程监听的端口,以及网络
4. 高级进程管理,详细显示进程信息.连接状态.
5. 无限制的终端命令历史,路径历史,可快速输入命令,切换路径.

这是 FinalShell 高级版的功能。

我打算直接静态破解,毕竟这款软件的静态混淆没有那么的强。

首先进行命名反混淆。
(反混淆后启动软件发现是秒启动,原生版本都需要很长时间去加载,难不成是直接跳过了证书验证?)

然后我们用 JbyteMod-Reborn 加载这个反混淆后的文件。



直接搜索“高级版”看看会有些什么?
结果显而易见,升级高级版对应的是免费版本,“高级版”字符串则对应的是付费版本了。

我们直接跳转到 “MainPanel” 对主页面进行分析。



这里会有两个对字符串字段的定义,我们直接搜索“高级版”所对应的字段的调用记录。



发现这里会有三个对这个字段的调用,我们随便进去一个,来查询一下它的具体条件是怎么样?

private void OO00O0OOOO0O00O0OO0O0OOO0OOO0OOOO0OOO0OO0OO0O00O0O000O0O0OO0OO0OO0O0O0O0OOO0OO0O00000OOOOO0OO0OO0OOO() {
    boolean pro = myssh.App.OO0OO0000OOO0OO0O0O0000O000OO00O0O0OO00OO00O0O000OOO00O0O00O00OOO0O0O000O0O0O0OOO000OO000OOOOO0O0O().O0000OOO0000O0OO0OO0O0O0OO00O000O0OO0O0OO0OOO0OOOOOO0000OOO00O00O0O0OOOOOO0OO0000OO0OOO00OOOOOOO0().O00OOOO00O00OO0OOOO00O00000OOOO0OOOO0000000O0OOOO00OO0000O00O00OO0OOOOOO0O0OO0O00OOOOOOO0O0O0O0000OO();
    if (pro) {
      this.O000O0O0OOO0O0O000000OO0OOO00OO000OO00O0OOO0OOOOOOOO0OOOOOOO000O0OOO0OO00O0O000OO00OOOO00O000O.setText(this.O0OOOOOO000OO0O0O0OOOO000O0O000OO0OOOOOO0OO00OOO00OOO0O0000OO000OOO0O00OO00O0O0OO00O00O00000OO0OOOO);
    } else {
      this.O000O0O0OOO0O0O000000OO0OOO00OO000OO00O0OOO0OOOOOOOO0OOOOOOO000O0OOO0OO00O0O000OO00OOOO00O000O.setText(this.OOOO00O0OOO00OOOO0OO0OOO0OOO0O000OOOO0000O0000OO00OOO00O0OO0OO0OO0O0OO0OO0O0OOOOO0O00OOOOOOOO000O0);
    }
    this.O0OOO0000OO00O0OOOOO0OO0OO00O0OO0O00O00OOOO000OOOO0OO0O00O00OOOO0000O0O000OOOO0O00000O00O0OOO0O.O00OOOO00O00OO0OOOO00O00000OOOO0OOOO0000000O0OOOO00OO0000O00O00OO0OOOOOO0O0OO0O00OOOOOOO0O0O0O0000OO(pro);
    OO0O00OO000O0O0OO00OO00OOOOO0OOOOO0OO000O0OOOO0OOO0000O000O0OOO0OOOO0O00000OOOOO0OO0000O0O0OO000OO.OO0O00OO00OOOOOO0000O0O000O0O0000OOOO00OO000OO0000OO000000O00O0O000OO00O0O000O00O0O00OOOOOO0OOO0().O00OOOO00O00OO0OOOO00O00000OOOO0OOOO0000000O0OOOO00OO0000O00O00OO0OOOOOO0O0OO0O00OOOOOOO0O0O0O0000OO(pro);
    OO0O00OO00OOOOOO0000O0O000O0O0000OOOO00OO000OO0000OO000000O00O0O000OO00O0O000O00O0O00OOOOOO0OOO0.OOOO000O00OO0O0000O0000OO0O0O00OOOO00OO00OOO000000O0OOO0OOOOOOOOOOOOO000OO0OOOO000O00OO000OOOOOOO000().O00OOOO00O00OO0OOOO00O00000OOOO0OOOO0000000O0OOOO00OO0000O00O00OO0OOOOOO0O0OO0O00OOOOOOO0O0O0O0000OO(pro);
}

通过CFR反编译器反编译出来的代码,我们可以轻松的知道他是如何判断用户是否为“Pro”用户了。

我们直接跳转到它赋值 布尔值 “pro” 所调用的方法。

我们直接进入了类“ControlClient”,看来这就是主要的验证类了。
我们要开始动手了?

首先我注意到的是这两个方法:
    private boolean method1() {
      boolean success = false;
      this.code= 0;
      this.O000OOOO00OO0O0O0O0OO0OO0OO000O0O00000OOO0O0OO0O00O00OOOOO00O00OOOO0O000OO00OOO00OO0OOOOO0O0O0000O = false;
      this.O000000000O0000OOOO0O0O000O0000OOOO0OO0O0O00OOO00OO00O000O0OO0OOO0000000OO000O0OOO00OOOO00000OO0OOOO = false;
      JSONObject requestMeaaage = new JSONObject();
      requestMeaaage.put("username", (Object)App.OO0OO0000OOO0OO0O0O0000O000OO00O0O0OO00OO00O0O000OOO00O0O00O00OOO0O0O000O0O0O0OOO000OO000OOOOO0O0O().O000OOOO00OO0O0O0O0OO0OO0OO000O0O00000OOO0O0OO0O00O00OOOOO00O00OOOO0O000OO00OOO00OO0OOOOO0O0O0000O().O00OOO00O00OOOOO0OO0OOOO00O00OO0OO00OOOOO0O000OO00O0OO000O000O0OOO0OOOO000OOO0000OO000OOOOOOOOOOO00());
      requestMeaaage.put("password", (Object)App.OO0OO0000OOO0OO0O0O0000O000OO00O0O0OO00OO00O0O000OOO00O0O00O00OOO0O0O000O0O0O0OOO000OO000OOOOO0O0O().O000OOOO00OO0O0O0O0OO0OO0OO000O0O00000OOO0O0OO0O00O00OOOOO00O00OOOO0O000OO00OOO00OO0OOOOO0O0O0000O().OOO00OO00OOOO00OO0O00O0O00OOO0OOO000O00OOOO00OOOOOO000O0OO0O0OO0OO000O00O0OOOOOO00OO00OO00OOOO0OO000());
      requestMeaaage.put("fix", (Object)Tools.OO0O00OO00OOOOOO0000O0O000O0O0000OOOO00OO000OO0000OO000000O00O0O000OO00O0O000O00O0O00OOOOOO0OOO0((int)this.O000O0OOOOO00O0O00O0OO00O000O00O00000O0OO00000OOOO0O0OOOOO0O0O0OOOOO00OOO000OOO0000O0O0OOO0O0OO00OO0.nextInt(1024)));
      try {
            String msg;
            JSONObject responJson = this.O00OOOO00O00OO0OOOO00O00000OOOO0OOOO0000000O0OOOO00OO0000O00O00OO0OOOOOO0O0OO0O00OOOOOOO0O0O0O0000OO(requestMeaaage, "http://www.hostbuf.com/fs.c", this.OOO00O00OO00O0O000000O0OOOOOO0OOOO0O000OO000OO0OO0O0O00O0O0O00OO000O00OO000OOO00OO0O0O00O0O00OO0000);
            this.code = responJson.getIntValue("code");
            LoginDialog.OO0O000000O0OO000000O0O0000O0OO0O00OOO000OOOOOOO00OO0O00O0OO000O00OO00OOO000O00OOOOO0O0OOO0O0O0O0 = msg = responJson.getString("msg");
            this.O000OOOO00OO0O0O0O0OO0OO0OO000O0O00000OOO0O0OO0O00O00OOOOO00O00OOOO0O000OO00OOO00OO0OOOOO0O0O0000O = true;
            while (this.O000OOOO00OO0O0O0O0OO0OO0OO000O0O00000OOO0O0OO0O00O00OOOOO00O00OOOO0O000OO00OOO00OO0OOOOO0O0O0000O) {
                Thread.sleep((long)100L);
            }
      }
      catch (Exception e) {
            e.printStackTrace();
      }
      return success;
    }

    public boolean OOO0OOOOO00O0OO00O000OOOO0OO00OOO00000OO0000000OOOOOO00O0OOO00O0O00O0OOOOOO0OOO0O000OOOO0000OO0O000O() {
      return this.O000OOOO00OO0O0O0O0OO0OO0OO000O0O00000OOO0O0OO0O00O00OOOOO00O00OOOO0O000OO00OOO00OO0OOOOO0O0O0000O;
    }

    void method2() {
      if (this.code== 1) {
            if (this.O0O0O0000OO00O0O0000OO0OOO00O0OO00OO0OO0OOO0000OO00O00O0OOOO00OOOO00OO00O00O000OO00OOOO0000O000O != null) {
                this.O0O0O0000OO00O0O0000OO0OOO00O0OO00OO0OO0OOO0000OO00O00O0OOOO00OOOO00OO00O00O000OO00OOOO0000O000O.O00OOOO00O00OO0OOOO00O00000OOOO0OOOO0000000O0OOOO00OO0000O00O00OO0OOOOOO0O0OO0O00OOOOOOO0O0O0O0000OO(true);
            }
      } else {
            this.O0O0O0000OO00O0O0000OO0OOO00O0OO00OO0OO0OOO0000OO00O00O0OOOO00OOOO00OO00O00O000OO00OOOO0000O000O.O00OOOO00O00OO0OOOO00O00000OOOO0OOOO0000000O0OOOO00OO0000O00O00OO0OOOOOO0O0OO0O00OOOOOOO0O0O0O0000OO(false);
      }
      this.O000000000O0000OOOO0O0O000O0000OOOO0OO0O0O00OOO00OO00O000O0OO0OOO0000000OO000O0OOO00OOOO00000OO0OOOO = true;
      this.O000OOOO00OO0O0O0O0OO0OO0OO000O0O00000OOO0O0OO0O00O00OOOOO00O00OOOO0O000OO00OOO00OO0OOOOO0O0O0000O = false;
    }

第二个方法会首先判断第一个方法所取得的 code,如果为1,那么就继续。
然后会判断一个interface是否为null。
随后就会把

第一个方法会获取responJson作为信息返回,是在登陆窗口弹出的以及显示出的信息。


具体思路已经理顺,我们直接对其进行修改!

首先patch掉它这个类中用来获取请求的方法。
public com.alibaba.fastjson.JSONObject O00OOOO00O00OO0OOOO00O00000OOOO0OOOO0000000O0OOOO00OO0000O00O00OO0OOOOOO0O0OO0O00OOOOOOO0O0O0O0000OO(com.alibaba.fastjson.JSONObject requestMeaaage, java.lang.String url, org.apache.http.client.HttpClient httpclient) throws java.lang.Exception {
    com.alibaba.fastjson.JSONObject responeMessage = null;
    boolean success = false;
    for (int n = 0;
   n < this.O0O000OOOOO0000O0000O000O00O000O0O0OO0O00OO0000OO00OO0OOO0O0O0OO00OO00O0OOO0OOOOOO0OOO0O00000OO0OO; ++n) {
      org.apache.http.client.methods.HttpPost post = new org.apache.http.client.methods.HttpPost(url);
      java.io.DataInputStream dis = null;
      try {
            byte[] requestData = requestMeaaage.toString().getBytes("utf-8");
            requestData = myssh.OOO00O00OO00O0O000000O0OOOOOO0OOOO0O000OO000OO0OO0O0O00O0O0O00OO000O00OO000OOO00OO0O0O00O0O00OO0000.DesUtilPro.O00OOOO00O00OO0OOOO00O00000OOOO0OOOO0000000O0OOOO00OO0000O00O00OO0OOOOOO0O0OO0O00OOOOOOO0O0O0O0000OO((byte[])requestData);
            org.apache.http.entity.ByteArrayEntity entity = new org.apache.http.entity.ByteArrayEntity(requestData);
            post.setEntity((org.apache.http.HttpEntity)entity);
            org.apache.http.HttpResponse respone = httpclient.execute((org.apache.http.client.methods.HttpUriRequest)post);
            org.apache.http.HttpEntity responeEntity = respone.getEntity();
            int responeLength = (int)responeEntity.getContentLength();
            java.io.InputStream is = responeEntity.getContent();
            dis = new java.io.DataInputStream(is);
            byte[] responeData = new byte;
            dis.readFully(responeData);
            responeData = myssh.OOO00O00OO00O0O000000O0OOOOOO0OOOO0O000OO000OO0OO0O0O00O0O0O00OO000O00OO000OOO00OO0O0O00O0O00OO0000.DesUtilPro.OO0O00OO00OOOOOO0000O0O000O0O0000OOOO00OO000OO0000OO000000O00O0O000OO00O0O000O00O0O00OOOOOO0OOO0((byte[])responeData);
            java.lang.String string = new java.lang.String(responeData, "utf-8");
            responeMessage = com.alibaba.fastjson.JSONObject.parseObject((java.lang.String)string);
            success = true;
            break;
      }
      catch (java.lang.Exception e) {
            e.printStackTrace();
            try {
                java.lang.Thread.sleep((long)100L);
            }
            catch (java.lang.InterruptedException e1) {
                e1.printStackTrace();
            }
            continue;
      }
      finally {
            if (dis != null) {
                try {
                  dis.close();
                }
                catch (java.io.IOException e) {
                  e.printStackTrace();
                }
            }
      }
    }
    if (!success) {
      throw new java.lang.Exception("request failed!");
    }
    return responeMessage;
}

直接return null;

然后开始patch上面所说的“method1”

1.Patch 方法返回的布尔值,直接return true;
2.Patch Code检测和返回的Message

3.Patch “method2” 内的一切条件判断


好了,我们现在启动一下试试看。


出现了JVM虚拟机字节码校验错误,我们上面修改的代码是不存在的问题的
所以不知道哪里出现了问题,直接注入代码反射修改虚拟机,强制跳过验证。

启动。


看来是启动成功了,接下来测试一下破解是否成功。




看来是成功了?
投入实际应用测试一下。



成功了!


总结一下,
这个软件的验证没有我认为的那么困难,可能主要是因为这个软件本来我感觉就挺良心的,没有要商业化的意思。

破解副本仅供学习参考,请勿用于商业用途。
若您喜欢这个软件,请支持正版,也就35块钱,资助一下人家。

另外,给点分?

新版
FinalShell 3.9.2.2 离线激活 key-gen - 『原创发布区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn
https://www.aliyundrive.com/s/cFew9qVL8pQ


文中所用的静态修改工具可以在Github内搜索到,是一个Fork版本,由于自己没能力更新多少,就不在这里放出连接了。

zhh 发表于 2020-1-13 22:40

支持技术上的学习,有条件支持正版软件也是极好的!

羽未 发表于 2020-7-3 15:07

ecareyu 发表于 2020-5-29 10:59
无脑问下,在逆向过程中,是否发现有挖矿木马的存在?我觉得如果没有的话,最好替作者澄清一下。

我用了一年多了,确实遇到过一次CPU爆满的情况,java占用99%。当时作者很久没更新,现在作者重新拾起,不知道修复没有,具体的忘了。结论就是多开不要长期挂起,每天重启一次软件,到现在没遇到第二次。

huming899 发表于 2020-1-13 21:32

有用免费版,唉~!不经常使用,想着用到就支持一下开发者!开发不易!!!

fnycwfj 发表于 2020-1-13 22:23

感谢,非常好!

chenjingyes 发表于 2020-1-13 22:32

感谢楼主分享技术:lol

zy1234 发表于 2020-1-13 23:58

挺好的一个软件,免费也很好用的

XCIX 发表于 2020-1-14 02:15

谢谢楼主,学习中

2Burhero 发表于 2020-1-14 02:33

评分路过

egaokiss 发表于 2020-1-14 08:21

感谢您的分享,非常有用,谢谢。

gblw 发表于 2020-1-14 08:38

值得学习,谢谢分享~
页: [1] 2 3 4 5 6 7 8 9 10
查看完整版本: FinalShell 3.0.10 逆向破解