跳转至内容
  • 版块
  • 最新
  • 标签
  • 热门
  • 用户
  • 群组
  • 友情链接
皮肤
  • Light
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • 默认(不使用皮肤)
  • 不使用皮肤
折叠

酸枝论坛

  1. 主页
  2. 万象杂谈
  3. 领创动态密码算法解析

领创动态密码算法解析

已定时 已固定 已锁定 已移动 万象杂谈
3 帖子 2 发布者 308 浏览
  • 从旧到新
  • 从新到旧
  • 最多赞同
登录后回复
此主题已被删除。只有拥有主题管理权限的用户可以查看。
  • 010 离线
    010 离线
    玉玉公主 内测用户 CS-RushA-版主
    写于 最后由 01 编辑
    #1

    领创动态密码算法解析

    最近发现有人似乎存在忘记密码等存在相关问题,我干脆吧22年底研究的东西放出来吧,反正这东西我早就不搞了

    0. 声明

    本报告仅用于技术交流,严禁用于非法用途。

    0.0 目录

    • 1. 算法介绍
    • 2. 算法原理
    • 3. 算法解析
      • 3.1 反编译
      • 3.2 代码分析
        目录以后再写☺️

    1. 算法介绍

    该算法用于生成领创动态密码。动态密码可用于跳过手势密码、退出登录、锁机解锁。

    2. 算法原理

    加盐md5

    3. 算法解析

    预备材料:

    • apk安装包
      • 这里选用5.03的安装包,实际上5和6的算法是相同的,但jadx在反编译6时可能会出现失败只给伪代码的情况,虽然不影响分析,但不美观。
    • jadx
    • 使用搜索引擎的能力

    3.1 反编译

    丢到jadx里面,开启反混淆

    3.2 分析

    3.2.1 代码定位

    按照逆向常规尤其是破jie,反编译后第一步就是定位代码位置。定位方法很多,一般来说,字符串定位是最常用且最简单的,简单步骤此处不赘述。

    3.2.2 代码分析

    3.2.2.1 顺藤摸瓜
        public boolean m4493a(String str) {
            int m3546b = f2681c.m3546b("dynamic_pass", 1);
            String m4491c = m4491c();
            String m4495a = m4495a();
            if (m3546b == 0) {
                if (str.equals(m4491c) || MD5Util.m3060a(str).equals(m4495a)) {
                    return true;
                }
            } else if (m3546b == 1 && str.equals(m4491c)) {
                return true;
            }
            return false;
        }
    
        public String m4495a() {
            this.f2683e = f2681c.m3544b("password2", "");
            return !TextUtils.isEmpty(this.f2683e) ? this.f2683e : "unknown";
        }
    
    

    m4493a()判断输入的密码是否正确的函数。
    这里我们不难发现,返回true有两个条件:

    • 当策略未启用动态密码时,使用固定的管理员密码(我们的是huananfz1!)
    • 启用动态密码,则先通过函数m4491c计算动态密码,然后与输入的密码进行比较
    3.2.2.2 外围代码分析

    算法其实很简单

        private String m4491c() {
            String m4492b = m4492b();
            if (m4492b.length() >= 6) {
                return m4492b.substring(0, 6);
            }
            return m4492b;
        }
    
    

    我们可以看到,m4491c只是截取了算完后的六个字符作为最终结果。这里的判断理论上始终为true,因为m4492b是从md5得来的,具我们经过下面的分析后就会明白。

        public String m4492b() {
            String m2965a = LTKDeviceUtil.m2965a(f2680b);
            if (!TextUtils.isEmpty(m2965a) && !"unknown".equals(m2965a)) {
                this.f2682d = SettingUtil.m3539a(f2680b).m3538a(m2965a);
            }
            return !TextUtils.isEmpty(this.f2682d) ? this.f2682d : "unknown";
        }
    
        public static String m2965a(Context context) {
            if ((LTKDeviceTypeUtil.m3011i() || LTKDeviceTypeUtil.m3021d()) && Build.VERSION.SDK_INT >= 29) {
                return m2947g(context).toLowerCase();
            }
            String m2951e = m2951e(context);
            if (!m2951e.contains("unknown")) {
                new LTKSpfUtil(context, "linslib").m2858a("wifi_mac", m2951e);
                return m2951e;
            }
            String m2857b = new LTKSpfUtil(context, "linslib").m2857b("wifi_mac", "unknown");
            if (m2857b.equals("unknown")) {
                String m2960b = m2960b(context);
                return m2960b.equals("unknown") ? m2954d(context) : m2960b;
            }
            return m2857b;
        }
    
    

    我们先来看m2965a的来龙去脉。如果是安卓10+,,且设备类型为华为等,那么这里会选用sn(设备序列号),否则用wifi mac地址。

    我们不管那么多,反正都一样。现在假定返回的是sn,进入核心代码。

    3.2.2.3 核心代码分析
        public String m3538a(String str) {
            Log.i("CalculatePwd", "pwd is start");
            String str2 = new SimpleDateFormat("yyyyMMdd").format(new Date()) + str + "40E06F51-30D0-D6AD-7F7D-008AD0ADC570";
            String m5067X = UserInfoUtil.m5026f().m5067X();
            if (!TextUtils.isEmpty(m5067X)) {
                str2 = str2 + m5067X;
            }
            if (!TextUtils.isEmpty(str2)) {
                String m3537b = m3537b(str2);
                if (m3537b.length() > 8) {
                    String m3536c = m3536c(m3537b.substring(m3537b.length() - 8, m3537b.length()));
                    Log.i("CalculatePwd", "pwd is start");
                    return m3536c;
                }
            }
            return "";
        }
        
        /*UserInfoUtil*/
    
        public String m5067X() {
            if (TextUtils.isEmpty(this.f2139G)) {
                this.f2139G = f2132H.m3544b("student_id", "");
            }
            return this.f2139G;
        }
    
        public String m3537b(String str) {
            Log.i("CalculatePwd", "toMd5 is start");
            /* 算md5的,省略 */
        }
    
    
    

    我们发现,这里首先将时间字符串、给定的str(即sn)和一串特殊字符连接起来,然后再从UserInfoUtil中获取student_id,加入字符串并计算md5。

    student id在登录时调api获取并保存到本地,这里暂不做讲解,等下周再开一个帖子来说

    显然,这里的m3537b肯定大于8个字符,所以这里会截取后8个字符转换为long

        public static String m3536c(String str) {
            Log.i("CalculatePwd", "hexToLong is start");
            String valueOf = String.valueOf(Long.parseLong(str, 16));
            if (!TextUtils.isEmpty(valueOf) && valueOf.length() > 8) {
                return valueOf.substring(valueOf.length() - 8, valueOf.length());
            }
            return valueOf;
        }
    

    一样的,转换为long之后如果太长,也截取后8个字符。

    这样我们就得到了8位数字,再回到前面,这8位数的前6位就是最终的密码。

    3.2.3 梳理

    • 集齐设备信息、日期、student_id
    • 加盐计算md5
    • 截取,转数字,再截取

    是不是很简单?接下来我们试着自己写出完整的计算算法。

    3.3 算法复现

    知道了原理,写起来是很轻松的

    代码已经开源在Github,点此链接查看,
    这里也放上完整代码

    /*
    Author: SuchAnIdi0t
    
    *** !!NOTICE!!
    * These codes are opensourced under MIT License.
    * Modifications and redistribution should be in compliance with the License.
    * PoC only, use at your own risk.
    
    For more information, please refer to https://github.com/SuchAnIdi0t/linspirer-dynamic-password
    */
    
    package main
    
    import (
    	"crypto/md5"
    	"fmt"
    	"log"
    	"strconv"
    	"strings"
    	"time"
    )
    
    func ca(str1 string) string {
    	str2, _ := strconv.ParseInt(str1, 16, 64)
    	v := strconv.Itoa(int(str2))
    	if len(v) <= 8 {
    		return v
    	} else {
    		return v[len(v)-8:]
    	}
    }
    
    func calc(swdid string, stu_id string) string {
    	str3 := time.Now().Format("20060102") + swdid + "40E06F51-30D0-D6AD-7F7D-008AD0ADC570" + stu_id
    	log.Println(str3)
    	h := md5.Sum([]byte(str3))
    	b := fmt.Sprintf("%x", h)
    	log.Println(b)
    	if len(b) > 8 {
    		return ca(b[len(b)-8:])
    	} else {
    		return "err"
    	}
    }
    
    func main() {
    	var swdid, stu_id string
    	fmt.Print("swdid:")
    	fmt.Scanln(&swdid)
    	swdid = strings.ToLower(swdid)
    	fmt.Print("stu_id:")
    	fmt.Scanln(&stu_id)
    	log.Printf("Result: %s", calc(swdid, stu_id)[:6])
    }
    
    

    4. 总结

    动态密码的实现原理并不复杂,逆向也毫无阻碍,但其中理解涉及到的算法和逻辑还是需要一定耐心和基本能力。

    有任何问题欢迎在此处探讨,点赞过5,下周讲如何获取student_id 🤩

    111

    点击进入>>F-Res,这里有全套的辅立码课资源

    1 条回复 最后回复
    1
    • 会 离线
      会 离线
      会说话的三角龙
      写于 最后由 编辑
      #2

      stu_id 是登陆辅立码课用的那串用户名数字吗?
      为什么我运行之后返还的6为密码是进去显示密码错误呢?

      010 1 条回复 最后回复
      0
      • 010 离线
        010 离线
        玉玉公主 内测用户 CS-RushA-版主
        在 回复了 会说话的三角龙 最后由 编辑
        #3

        会说话的三角龙 不是。stu id要通过api获取

        111

        点击进入>>F-Res,这里有全套的辅立码课资源

        1 条回复 最后回复
        0
        • 010 01 在 中 引用了 这个主题

        © 2024 酸枝论坛
        • 登录

        • 没有帐号? 注册

        • 登录或注册以进行搜索。
        • 第一个帖子
          最后一个帖子
        0
        • 版块
        • 最新
        • 标签
        • 热门
        • 用户
        • 群组
        • 友情链接