rt,转自qq空间
玉玉公主
-
高三4班迎来化竞真神 -
领创动态密码算法解析领创动态密码算法解析
最近发现有人似乎存在忘记密码等存在相关问题,我干脆吧22年底研究的东西放出来吧,反正这东西我早就不搞了
0. 声明
本报告仅用于技术交流,严禁用于非法用途。
0.0 目录
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
- apk安装包