代码还原小试牛刀(一):魔改的MD5
一、目标
2023年了,MD5已经是最基础的签名算法了,但如果你还只是对输入做了简单的MD5,肯定会被同行们嘲笑。加点盐(salt)是一种基本的提升,但在这个就业形势严峻的时代,仅仅加盐肯定不够了。
今天我们就来讲一讲魔改的MD5,让这个算法高大上起来。
1、菜卷
最简单的魔改方法就是改变MD5的初始参数,
context->state[0] = 0x67452301;context->state[1] = 0xEFCDAB89;context->state[2] = 0x98BADCFE;context->state[3] = 0x10325476;
把这四个参数修改一下就行了。通过修改这些参数,我们可以改变MD5的运算结果。但这种方法实在太简单了,卷不起来。
接下来,我们要介绍更高级的卷法。
2、肉卷
md5会进行64轮运算,每轮运算都会用到一个常量,组成一个常量表K。
K原始值的计算方式是 2^32 * |sin i |,而后取其整数部分。
那么有理想的同学就可以更改这个K值,比如把 sin改成 cos或者tan之类的,这样就可以卷起来了。
3、卷中卷
//F,G,H,I四个非线性变换函数
#define F(x,y,z) ((x & y) | (~x & z))
#define G(x,y,z) ((x & z) | (y & ~z))
#define H(x,y,z) (x^y^z)
#define I(x,y,z) (y ^ (x | ~z))
//x循环左移n位的操作
#define ROTATE_LEFT(x,n) ((x << n) | (x >> (32-n)))
要真正卷起来,我们需要改变MD5中的四个非线性变换函数F、G、H、I。我们可以加上 异或 或者 减少 与 操作,整个算法就换了个面貌。这种高级卷法可以忽悠住老板,让算法高大上起来。
我们今天的目标是尝试还原一个魔改之后的MD5算法,通过这次实践来了解算法还原的基本方法。
这个样本我们的入参是字符串: “1677038066553”
返回值是32个字符: “DD89CA684D91818B970710F75A75743D”
二、步骤
第一步
我们需要用Unidbg跑通算法,比起上古时期用ida调试的前辈,Unidbg的出现直接把算法还原的难度降了一个数量级。
第二步
我们需要把结果Z通过反向推导一步一步回到原始输入A。这种方法叫做倒果为因,是逆向分析的一种基本套路。
我们假设这个样本是MD5或者是魔改的MD5,我们可以用以下几种方法来还原算法:
1、调试断点
2、条件断点
3、数据打印
4、Trace内存读写
5、Trace代码
1、调试断点
逆向分析是经验科学,虽然有一些基本套路,但是还是以试为主,先用IDA打开 libnative-lib.so,从 Exports 导出表里面找到导出函数 Java_com_littleq_cryptography_md5_MainActivity_sign
这个函数的开始地址在0x1234, 结束地址在0x12B4,但是主要的代码逻辑在函数sub_A3C里面, 我们先在sub_A3C函数的末端下个断点试试,
text:00000000000011D4 E0 07 40 F9 LDR X0, [SP,#0x110+var_108]
.text:00000000000011D8 03 00 00 90+ ADRL X3, aSSSS ; "%s%s%s%s"
.text:00000000000011D8 63 EC 0A 91
.text:00000000000011E0 E4 83 01 91 ADD X4, SP, #0x110+var_B0
.text:00000000000011E4 E5 43 01 91 ADD X5, SP, #0x110+var_C0
.text:00000000000011E8 E6 03 01 91 ADD X6, SP, #0x110+var_D0
.text:00000000000011EC E7 C3 00 91 ADD X7, SP, #0x110+var_E0
.text:00000000000011F0 01 00 80 92 MOV X1, #0xFFFFFFFFFFFFFFFF
.text:00000000000011F4 02 08 80 52 MOV W2, #0x40 ; '@'
这个 0x11D8 很像是格式化字符串。
我们在Unidbg里面给 0x11D8 下个断点
Debugger debugger = emulator.attach();debugger.addBreakPoint(module.base + 0x11D8);
运行一下,顺利的断下来了
debugger break at: 0x400011d8 @ Function64 address=0x40001234, arguments=[unidbg@0xfffe1640[libandroid.so]0x640, 1853170425, 2008362258]
>>> x0=0xbffff690(-1073744240) x1=0x0 x2=0x4 x3=0xbfffed20 x4=0x40230200 x5=0x402302c0 x6=0x1 x7=0xbffff708 x8=0x0 x9=0x0 x10=0x1 x11=0x0 x12=0x8 x13=0x8 x14=0x8
>>> x15=0x8 x16=0x40228d70 x17=0x40177ddc x18=0x8 x19=0x4cf3a208 x20=0x400012b8 x21=0x0 x22=0x68ca89dd x23=0x3d74755a x24=0x72e737bb x25=0xddf5ac1 x26=0xd0d5adc6 x27=0x8b81914d x28=0xf7100797 fp=0xbffff680
LR=RX@0x400011d4[libnative-lib.so]0x11d4
SP=0xbffff570
PC=RX@0x400011d8[libnative-lib.so]0x11d8
nzcv: N=0, Z=1, C=1, V=0, EL0, use SP_EL0
start + 0xae8
=> *[libnative-lib.so*0x011d8]*[03000090]*0x400011d8:*"adrp x3, #0x40001000"[libnative-lib.so 0x011dc] [63ec0a91] 0x400011dc: "add x3, x3, #0x2bb"[libnative-lib.so 0x011e0] [e4830191] 0x400011e0: "add x4, sp, #0x60"[libnative-lib.so 0x011e4] [e5430191] 0x400011e4: "add x5, sp, #0x50"[libnative-lib.so 0x011e8] [e6030191] 0x400011e8: "add x6, sp, #0x40"[libnative-lib.so 0x011ec] [e7c30091] 0x400011ec: "add x7, sp, #0x30"[libnative-lib.so 0x011f0] [01008092] 0x400011f0: "mov x1, #-1"[libnative-lib.so 0x011f4] [02088052] 0x400011f4: "mov w2, #0x40"[libnative-lib.so 0x011f8] [5bfdff97] 0x400011f8: "bl #0x40000764"
在Arm汇编里面,调用一个函数之前,会把入参存入到 x0,x1,x2 ……
从这段代码可以看出 地址 0x400011f8 会调用 0x40000764 函数,并且传入了 7个参数, 从x0,一直赋值到x7。
Unidbg的调试虽然有些简陋,但是已经够用了,有如此神器在手,你还要啥自行车?
调试命令先掌握以下几个:
s 单步步入,就是遇到函数调用会进入。
n 单步步过,遇到函数调用不会进入函数。
c 继续执行
b 下断点
r 取消当前断点
m 查看内存
我们先 s s s 几下,单步执行到 0x400011f8
debugger break at: 0x400011f8 @ Function64 address=0x40001234, arguments=[unidbg@0xfffe1640[libandroid.so]0x640, 1853170425, 2008362258]
>>> x0=0xbffff690(-1073744240) x1=0xffffffffffffffff x2=0x40 x3=0x400012bb x4=0xbffff5d0 x5=0xbffff5c0 x6=0xbffff5b0 x7=0xbffff5a0 x8=0x0 x9=0x0 x10=0x1 x11=0x0 x12=0x8 x13=0x8 x14=0x8
LR=RX@0x400011d4[libnative-lib.so]0x11d4
SP=0xbffff570
PC=RX@0x400011f8[libnative-lib.so]0x11f8
nzcv: N=0, Z=1, C=1, V=0, EL0, use SP_EL0
start + 0xb08
=> *[libnative-lib.so*0x011f8]*[5bfdff97]*0x400011f8:*"bl #0x40000764"
这个时间点,入参都已经准备好了,我们来一个一个看看这些入参。
mx7>-----------------------------------------------------------------------------<
[10:40:26 646]x7=unidbg@0xbffff5a0, md5=d6c164ca9ef531557fc14e1bf7173663,
size: 112
0000: 35 41 37 35 37 34 33 44 00 B3 22 40 00 00 00 00 5A75743D.."@....
0010: 39 37 30 37 31 30 46 37 00 8D 09 40 00 00 00 00 970710F7...@....
0020: 34 44 39 31 38 31 38 42 00 77 12 40 02 00 00 00 4D91818B.w.@....
0030: 44 44 38 39 43 41 36 38 00 1B 17 40 02 00 00 00 DD89CA68...@....
0040: 31 36 37 37 30 33 38 30 36 36 35 35 33 80 00 00 1677038066553...
0050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
^-----------------------------------------------------------------------------^
可以看到这次调用 函数 0x40000764, 基本就是在组装最后的结果了。
我们要做的就是找到这些结果生成的位置,来分析最终结果是如何计算出来的,也就是 Y → Z 的过程。
4、Trace内存读写
现在我们已经知道了结果Z的位置,下一步就是需要知道谁计算出了Z。
这就需要用到Unidbg的一个强大功能:内存读写监控
这一次我们先把调试断点下早一点,在 sub_A3C 函数开头就断下来。
debugger break at: 0x40000a3c @ Function64 address=0x40001234, arguments=[unidbg@0xfffe1640[libandroid.so]0x640, 1853170425, 2008362258]
>>> x0=0x40004000 x1=0xbffff690 x2=0x0 x3=0x1 x4=0x0 x5=0x1 x6=0x0 x7=0x0 x8=0xfffe0a70 x9=0x3002 x10=0x0 x11=0x1 x12=0x3 x13=0x40003018 x14=0x40003028
>>> x15=0x1 x16=0x40228910 x17=0x0 x18=0x17 x19=0xfffe1640 x20=0xbffff708 x21=0x0 x22=0x0 x23=0x0 x24=0x0 x25=0x0 x26=0x0 x27=0x0 x28=0x0 fp=0xbffff6f0
LR=RX@0x40001280[libnative-lib.so]0x1280
SP=0xbffff690
PC=RX@0x40000a3c[libnative-lib.so]0xa3c
nzcv: N=0, Z=0, C=1, V=0, EL0, use SP_EL0
start + 0x34c
=> *[libnative-lib.so*0x00a3c]*[ff8304d1]*0x40000a3c:*"sub sp, sp, #0x120"traceWrite 0xbffff5d0 0xbffff5d8
Set trace 0xbffff5d0->0xbffff5d8 memory write success.
c
[11:41:41 656] Memory WRITE at 0xbffff5d8, data size = 1, data value = 0x0, PC=RX@0x40001168[libnative-lib.so]0x1168, LR=null
[11:41:41 657] Memory WRITE at 0xbffff5d0, data size = 8, data value = 0x0, PC=RX@0x4000116c[libnative-lib.so]0x116c, LR=null
[11:41:41 661] Memory WRITE at 0xbffff5d8, data size = 1, data value = 0x0, PC=RX@0x401b48cc[libc.so]0x648cc, LR=RX@0x401b48c8[libc.so]0x648c8
traceWrite 就是监控写内存命令。
看上去0xbffff5d0这段内存,写入 DD89CA68 数据的位置是: 0x116c
text:000000000000114C 14 00 00 90+ ADRL X20, unk_12B8
.text:000000000000114C 94 E2 0A 91
.text:0000000000001154 C4 0A C0 5A REV W4, W22
.text:0000000000001158 E0 83 01 91 ADD X0, SP, #0x110+var_B0
.text:000000000000115C 21 01 80 52 MOV W1, #9
.text:0000000000001160 22 01 80 52 MOV W2, #9
.text:0000000000001164 E3 03 14 AA MOV X3, X20
.text:0000000000001168 FF A3 01 39 STRB WZR, [SP,#0x110+var_A8]
.text:000000000000116C FF 33 00 F9 STR XZR, [SP,#0x110+var_B0]
.text:0000000000001170 7D FD FF 97 BL sub_764
0x116c 的指令 STR XZR 是写入 没错,但是看上去不像是写入数据,而是把 SP,#0x110+var_B0 这个地址的数据清零。
那我们重来一次,(Unidbg的优点就是可以无限重放,比真机调试App方便了不知道多少倍。)
这次往前一点点,在 0x114C 下断点。
断下来之后,每s单步一次之后,就去查看 m0xbffff5d0。
最后发现跑完 0x1170 , 0xbffff5d0内存的值就改变成了, DD89CA68 。 这说明 0xbffff5d0 是 sub_764 函数去写的。
debugger break at: 0x40001170 @ Function64 address=0x40001234, arguments=[unidbg@0xfffe1640[libandroid.so]0x640, 1853170425, 2008362258]
>>> x0=0xbffff5d0(-1073744432) x1=0x9 x2=0x9 x3=0x400012b8 x4=0xdd89ca68 x5=0xe6cd8e62 x6=0x24523012 x7=0x29b9c389 x8=0x40 x9=0x40318041 x10=0xbffff5e0 x11=0x40 x12=0x3d5ebb2b x13=0x6450c165 x14=0xfc63b7e7
>>> x15=0x49ac16b x16=0xac6af723 x17=0xf3d1564b x18=0x18 x19=0x4cf3a208 x20=0x400012b8 x21=0x0 x22=0x68ca89dd x23=0x3d74755a x24=0x72e737bb x25=0xddf5ac1 x26=0xd0d5adc6 x27=0x8b81914d x28=0xf7100797 fp=0xbffff680
LR=null
SP=0xbffff570
PC=RX@0x40001170[libnative-lib.so]0x1170
nzcv: N=0, Z=1, C=1, V=0, EL0, use SP_EL0
start + 0xa80
=> *[libnative-lib.so*0x01170]*[7dfdff97]*0x40001170:*"bl #0x40000764"
不过回到 0x1170,我们发现了一串熟悉的数字 x4=0xdd89ca68 , 好吧,我们的问题又变成了 x4的值是怎么算出来的?
三、总结
首先要习惯看Arm汇编,一步一步单步调试,然后熟悉寄存器的变化。特别对一些关键数字要敏感。
要掌握Unidbg的基础调试命令。
常见的加密算法要熟悉一下,在开发环境里多调试几遍,熟悉它的算法流程。
1:ffshow
多方分别,是非之窦易开;一味圆融,人我之见不立。
相关文章:

代码还原小试牛刀(一):魔改的MD5
一、目标 2023年了,MD5已经是最基础的签名算法了,但如果你还只是对输入做了简单的MD5,肯定会被同行们嘲笑。加点盐(salt)是一种基本的提升,但在这个就业形势严峻的时代,仅仅加盐肯定不够了。 …...
6. 找大佬
1 题目描述 找大佬成绩20开启时间2021年09月24日 星期五 18:00折扣0.8折扣时间2021年11月15日 星期一 00:00允许迟交否关闭时间2021年11月23日 星期二 10:00 众所周知,每个专业里都会有一些大佬隐藏在人群里。软件工程专业也是如此。今天的你就像从人群中找到真正的…...

【CSS】标签显示模式 ① ( 标签显示模式 | 块级元素 )
文章目录一、标签显示模式 ( 块级元素 | 行内元素 )二、块级元素1、块级元素简介2、块级元素特点3、文字块级元素4、代码示例一、标签显示模式 ( 块级元素 | 行内元素 ) 标签显示模式 : 指的是 标签显示的方式 , 标签类型有很多 , 不同的情景使用不同类型的标签 ; 块级元素 : …...

hive真实表空间大小统计
1. 问题 如果是采用hdfs上传加载的表、或者是flume直接写hdfs的表空间通常看hive的属性是不准确的。 2. 思路 为了使结果更精确,我们直接使用linux下命令统计hive仓库目录下的每个表对应的文件夹目录占用空间的大小。 3. 解决方法 这里建立三层表结构 ods: 原始…...
微信小程序引入Vant UI步骤
官方文档教程 1、通过 npm 安装 # 通过 npm 安装 npm i vant/weapp -S --production# 通过 yarn 安装 yarn add vant/weapp --production# 安装 0.x 版本 npm i vant-weapp -S --production2、修改 app.json 将 app.json 中的 “style”: “v2” 去除,小程序的新…...
【震撼发布】《致敬未来的攻城狮计划》| 文末赠书3本
《致敬未来的攻城狮计划》—— 文末有福利 摘要: 一个崭新的计划,寻找那群有志于向嵌入式发展的未来工程师! 文章目录1 活动计划初衷2 活动计划形式3 活动计划收获4 活动计划要求5 活动计划时间6 活动计划致谢7 活动计划特别说明8 温馨提示9 …...

8.装饰者模式
目录 简介 角色组成 实现步骤 1. 新建 Log.class,添加如下代码 2. 新建 Log4j.class,继承 Log.class,并实现 record() 方法 3. 新建 Decorator.class,继承 Log.class 4. 新建 Log4jDecorator.class,继承 Decorat…...

GIT基础常用命令-1 GIT基础篇
git基础常用命令-1 GIT基础篇1.git简介及配置1.1 git简介1.2 git配置config1.2.1 查看配置git config1.2.2 配置设置1.2.3 获取帮助git help2 GIT基础常用命令2.1 获取镜像仓库2.1.1 git init2.1.2 git clone2.2 本地仓库常用命令2.2.1 git status2.2.2 git add2.2.3 git diff2…...

华为OD机试题,用 Java 解【数列描述】问题
华为Od必看系列 华为OD机试 全流程解析+经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试人员的实战技巧华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典使用说明 参加华为od机试,一定要注意不…...

2022掉队的“蔚小理”,按下了兔年加速键
配图来自Canva可画 进入2023年,各大车企又展开了新一轮的“竞速”。尽管1月份汽车整体销量出现了“阴跌”,但从各路车企发布的销量目标来看,车企对于2023依旧保持着较高的信心和预期。在一众车企中,以“蔚小理”为代表的新势力们…...

【NLP相关】attention的代码实现
❤️觉得内容不错的话,欢迎点赞收藏加关注😊😊😊,后续会继续输入更多优质内容❤️👉有问题欢迎大家加关注私戳或者评论(包括但不限于NLP算法相关,linux学习相关,读研读博…...

凌恩生物资讯
凌恩生物转录组项目包含范围广,项目经验丰富,人均10年以上项目经验,其中全长转录组测序研究基因结构已经成为发文章的趋势,研究物种包括高粱、玉米、拟南芥、鸡、人和小鼠、毛竹、棉花等。凌恩生物提供专业的全长转录组测序及分析…...
Leetcode 148. 排序链表(二路归并)
题目: 给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。 解法一: 递归解法,自顶向下 链表版二路归并排序(升序,递归版),稳定排序 时间复杂度…...

记录Paint部分常用的方法
Paint部分常用的方法1、实例化之后Paint的基本配置2、shader 和 ShadowLayer3、pathEffect4、maskFilter5、colorFilter6、xfermode1、实例化之后Paint的基本配置 Paint.Align Align指定drawText如何将其文本相对于[x,y]坐标进行对齐。默认为LEFTPaint.Cap Cap指定了笔画线和路…...

ArrayList集合底层原理
ArrayList集合底层原理ArrayList集合底层原理1.介绍2.底层实现3.构造方法3.1集合的属性4.扩容机制5.其他方法6.总结ArrayList集合底层原理 1.介绍 ArrayList是List接口的可变数组的实现。实现了所有可选列表操作,并允许包括 null 在 内的所有元素。 每个 Array…...

内网部署swagger快解析映射方案发布让外网访问
计算机业内人士对于swagger并不陌生, 不少人选择用swagger做为API接口文档管理。Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。总体目标是使客户端和文件系统作为服务器以同样的速度来更新文件的方法&#x…...

全网最全整理,自动化测试10种场景处理(超详细)解决方案都在这......
目录:导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜)前言 自动化工作流程 自动…...
【c++】指针的学习
指针是C中非常重要的概念,理解指针的使用可以使程序更高效,并且可以处理更加复杂的数据结构。 指针是一个变量,它存储了另一个变量的地址。通过指针访问这个变量可以提高程序的效率,尤其是在处理大型数据结构时。 在C中࿰…...

华为OD机试题,用 Java 解【水仙花数】问题
华为Od必看系列 华为OD机试 全流程解析+经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试人员的实战技巧华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典使用说明 参加华为od机试,一定要注意不…...

【Linux】-- 基本指令
目录 用户管理 adduser passwd userdel pwd ls指令 -l -a -d -F -r -t -R -1 which alias ll ls -n cd cd - cd ~ touch -d stat mkdir -p rmdir rm -r -f man cp 编辑 -r -f mv cat -n tac more less -N head tail | 管道 dat…...

UE5 学习系列(二)用户操作界面及介绍
这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…...

华为云AI开发平台ModelArts
华为云ModelArts:重塑AI开发流程的“智能引擎”与“创新加速器”! 在人工智能浪潮席卷全球的2025年,企业拥抱AI的意愿空前高涨,但技术门槛高、流程复杂、资源投入巨大的现实,却让许多创新构想止步于实验室。数据科学家…...

7.4.分块查找
一.分块查找的算法思想: 1.实例: 以上述图片的顺序表为例, 该顺序表的数据元素从整体来看是乱序的,但如果把这些数据元素分成一块一块的小区间, 第一个区间[0,1]索引上的数据元素都是小于等于10的, 第二…...
QMC5883L的驱动
简介 本篇文章的代码已经上传到了github上面,开源代码 作为一个电子罗盘模块,我们可以通过I2C从中获取偏航角yaw,相对于六轴陀螺仪的yaw,qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...

智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用
1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...

项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)
Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败,具体原因是客户端发送了密码认证请求,但Redis服务器未设置密码 1.为Redis设置密码(匹配客户端配置) 步骤: 1).修…...
Qt 事件处理中 return 的深入解析
Qt 事件处理中 return 的深入解析 在 Qt 事件处理中,return 语句的使用是另一个关键概念,它与 event->accept()/event->ignore() 密切相关但作用不同。让我们详细分析一下它们之间的关系和工作原理。 核心区别:不同层级的事件处理 方…...

ubuntu中安装conda的后遗症
缘由: 在编译rk3588的sdk时,遇到编译buildroot失败,提示如下: 提示缺失expect,但是实测相关工具是在的,如下显示: 然后查找借助各个ai工具,重新安装相关的工具,依然无解。 解决&am…...
基于 HTTP 的单向流式通信协议SSE详解
SSE(Server-Sent Events)详解 🧠 什么是 SSE? SSE(Server-Sent Events) 是 HTML5 标准中定义的一种通信机制,它允许服务器主动将事件推送给客户端(浏览器)。与传统的 H…...