某加密样本 Trace分析 密级: 【C-1】 | 时间:2024-02-01 | 目录:测试笔记 | 编辑本文 上一版本 | 版本差异 | 文章距今已发表三个月,请自行判断文中技术方法、代码的有效性:) ## Unidbg模拟执行 ![](https://img.meituan.net/imgupload/9f61c54e84a609da48b570b00b6e80b251967.png) ## so文件反编译 跳转该偏移位置 ![](https://img.meituan.net/imgupload/9d0b3c9771c946a205648ec321105d0c120261.png) 发现文件加固了,使用unidbg打印trace ![](https://img.meituan.net/imgupload/53fceb39aa95735260d41e9f3c2dec1f656023.png) ## 基础分析 ![](https://img.meituan.net/imgupload/1227dc3257c08c4a9d9155046698ed5b63244.png) 最终的结果通过 JNIEnv->NewStringUTF("8ea60836") 方法调用,使用了jni的方法,其中入参两个,第一个是env,第二个是8ea60836。调用地址为:0x40002c37 在trace中搜索0x40002c37 ![](https://img.meituan.net/imgupload/dfba3502a93addc14b307a1b66f66ab5681039.png) 只找到一处,这里是通过blx指令跳转,它将会将程序控制转移到0xfffe0c30地址处执行,并将返回地址保存在lr寄存器中,该返回地址为0x40002c37。 前面提到,这个入参为8ea60836,保存在r1寄存器中。向上找能看到mov指令操作: ![](https://img.meituan.net/imgupload/2031da7f9912cda632c85b10be6dae2417613.png) r1地址是入参的值,他一定存在Memory write的操作,指令继续搜索: ![](https://img.meituan.net/imgupload/29ecf0168243c52177d929483a51bd0a66334.png) 在地址0x401d7010处写入了一个字节大小的数据,值为0x38。当前指令的地址为0x40002492。 一眼看0x38 == 8,使用cyberchef把8ea60836转换成hex: ![](https://img.meituan.net/imgupload/7e9cd35de946e160c29e3b8fd8fe301552246.png) 和这里对上了 ``` [08:27:44 696][libencrypt.so 0x02491] [e17d ] 0x40002490: "ldrb r1, [r4, #0x17]" r4=0x40007050 => r1=0x38 [08:27:44 696][libencrypt.so 0x02493] [0170 ] 0x40002492: "strb r1, [r0]" r1=0x38 r0=0x401d7010[08:27:44 697] Memory WRITE at 0x401d7010, data size = 1, data value = 0x38, PC=RX@0x40002492[libencrypt.so]0x2492, LR=unidbg@0x18 [08:27:44 697][libencrypt.so 0x02495] [e17f ] 0x40002494: "ldrb r1, [r4, #0x1f]" r4=0x40007050 => r1=0x65 [08:27:44 697][libencrypt.so 0x02497] [4170 ] 0x40002496: "strb r1, [r0, #1]" r1=0x65 r0=0x401d7010[08:27:44 697] Memory WRITE at 0x401d7011, data size = 1, data value = 0x65, PC=RX@0x40002496[libencrypt.so]0x2496, LR=unidbg@0x18 [08:27:44 697][libencrypt.so 0x02499] [a17f ] 0x40002498: "ldrb r1, [r4, #0x1e]" r4=0x40007050 => r1=0x61 [08:27:44 697][libencrypt.so 0x0249b] [8170 ] 0x4000249a: "strb r1, [r0, #2]" r1=0x61 r0=0x401d7010[08:27:44 697] Memory WRITE at 0x401d7012, data size = 1, data value = 0x61, PC=RX@0x4000249a[libencrypt.so]0x249a, LR=unidbg@0x18 [08:27:44 697][libencrypt.so 0x0249d] [a17e ] 0x4000249c: "ldrb r1, [r4, #0x1a]" r4=0x40007050 => r1=0x36 [08:27:44 697][libencrypt.so 0x0249f] [c170 ] 0x4000249e: "strb r1, [r0, #3]" r1=0x36 r0=0x401d7010[08:27:44 697] Memory WRITE at 0x401d7013, data size = 1, data value = 0x36, PC=RX@0x4000249e[libencrypt.so]0x249e, LR=unidbg@0x18 [08:27:44 697][libencrypt.so 0x024a1] [a17a ] 0x400024a0: "ldrb r1, [r4, #0xa]" r4=0x40007050 => r1=0x30 [08:27:44 698][libencrypt.so 0x024a3] [0171 ] 0x400024a2: "strb r1, [r0, #4]" r1=0x30 r0=0x401d7010[08:27:44 698] Memory WRITE at 0x401d7014, data size = 1, data value = 0x30, PC=RX@0x400024a2[libencrypt.so]0x24a2, LR=unidbg@0x18 [08:27:44 698][libencrypt.so 0x024a5] [a17d ] 0x400024a4: "ldrb r1, [r4, #0x16]" r4=0x40007050 => r1=0x38 [08:27:44 698][libencrypt.so 0x024a7] [4171 ] 0x400024a6: "strb r1, [r0, #5]" r1=0x38 r0=0x401d7010[08:27:44 698] Memory WRITE at 0x401d7015, data size = 1, data value = 0x38, PC=RX@0x400024a6[libencrypt.so]0x24a6, LR=unidbg@0x18 [08:27:44 698][libencrypt.so 0x024a9] [e179 ] 0x400024a8: "ldrb r1, [r4, #7]" r4=0x40007050 => r1=0x33 [08:27:44 698][libencrypt.so 0x024ab] [8171 ] 0x400024aa: "strb r1, [r0, #6]" r1=0x33 r0=0x401d7010[08:27:44 698] Memory WRITE at 0x401d7016, data size = 1, data value = 0x33, PC=RX@0x400024aa[libencrypt.so]0x24aa, LR=unidbg@0x18 [08:27:44 698][libencrypt.so 0x024ad] [617b ] 0x400024ac: "ldrb r1, [r4, #0xd]" r4=0x40007050 => r1=0x36 [08:27:44 698][libencrypt.so 0x024af] [c171 ] 0x400024ae: "strb r1, [r0, #7]" r1=0x36 r0=0x401d7010[08:27:44 698] Memory WRITE at 0x401d7017, data size = 1, data value = 0x36, PC=RX@0x400024ae[libencrypt.so]0x24ae, LR=unidbg@0x18 ``` 可以看到都在往0x40007050 地址上取数据,所以得看什么时候往这个地址上写数据。数据是这么取的: ``` str = tmp[0x17]+tmp[0x1f]+tmp[0x1e]+tmp[0x1a]+tmp[0xa]+tmp[0x16]+tmp[0x7]+tmp[0xd] # 8ea60836 ``` 写入一定比这行trace早,进行分割,然后搜索0x40007050: ![](https://img.meituan.net/imgupload/21f64a434d04e14c24c326c1562db183259007.png) 发现有一段memory write的操作,把这段trace 复制下来: ![](https://img.meituan.net/imgupload/d83d41c7b20a83df8b1b44bfc9b756b8493638.png) r0从0开始,增加立即数1,无条件跳转到比较函数,比较r0是否为0x20(32),如果相等就跳转 到0x400020b4。否则就ldrb 加载某个内存地址(0xbffff650)+索引值(r0)到寄存器r2,然后再strb 写入这个r2的值,到0x40007050地址+索引值(r0)的位置。 所以很简单吧,内存拷贝。拷贝的值是: ``` 66 66 39 63 38 65 39 33 30 37 30 62 38 36 62 32 39 36 31 35 39 33 38 38 61 34 36 38 37 63 61 65 ff9c8e93070b86b296159388a4687cae ``` 那么接着就是看0xbffff650 这个地址: ![](https://img.meituan.net/imgupload/a6c27ad7f132730364710de441970e4590084.png) 发生了memory copy,网上找最近的汇编指令(libc中的指令没有被记录) ![](https://img.meituan.net/imgupload/8a93c13028513dd8ecc524634707934b80110.png) 根据定义,参数2代表要复制的内存区域,也就是寄存器r1 ![](https://img.meituan.net/imgupload/ab79692c71ac2eba1e9a28fd86e44d4643407.png) 往上就很难跟了,但是我们通过前面拿到了内存里的值ff9c8e93070b86b296159388a4687cae,假定他是4字节一组进行加密,小端序,第一组应该是938e9cff。 ![](https://img.meituan.net/imgupload/675a05ee0735a1d36c36c6cb0d6a8a45203872.png) 找到了最早生成的地方 ``` [08:27:44 420][libencrypt.so 0x01e53] [0360 ] 0x40001e52: "str r3, [r0]" r3=0x938e9cff r0=0xbffff680[08:27:44 420] Memory WRITE at 0xbffff680, data size = 4, data value = 0x938e9cff, PC=RX@0x40001e52[libencrypt.so]0x1e52, LR=null [08:27:44 420][libencrypt.so 0x01e55] [62ea0503] 0x40001e54: "orn r3, r2, r5" r2=0x2c4979fe r5=0xfac46ee8 => r3=0x2d7bf9ff [08:27:44 420][libencrypt.so 0x01e59] [4b40 ] 0x40001e58: "eors r3, r1" r3=0x2d7bf9ff r1=0xb3fbb84f => r3=0x9e8041b0 [08:27:44 421][libencrypt.so 0x01e5b] [5d44 ] 0x40001e5a: "add r5, fp" r5=0xfac46ee8 fp=0x31313339 => r5=0x2bf5a221 [08:27:44 421][libencrypt.so 0x01e5d] [2344 ] 0x40001e5c: "add r3, r4" r3=0x9e8041b0 r4=0x30614c41 => r3=0xcee18df1 [08:27:44 421][libencrypt.so 0x01e5f] [3f4c ] 0x40001e5e: "ldr r4, [pc, #0xfc]" => r4=0xbd3af235 [08:27:44 421][libencrypt.so 0x01e61] [2344 ] 0x40001e60: "add r3, r4" r3=0xcee18df1 r4=0xbd3af235 => r3=0x8c1c8026 [08:27:44 421][libencrypt.so 0x01e63] [059c ] 0x40001e62: "ldr r4, [sp, #0x14]" sp=0xbffff540 => r4=0x10325476 [08:27:44 422][libencrypt.so 0x01e65] [02ebb353] 0x40001e64: "add.w r3, r2, r3, ror #22" r2=0x2c4979fe r3=0x8c1c8026 => r3=0x9e4a142e [08:27:44 422][libencrypt.so 0x01e69] [1c44 ] 0x40001e68: "add r4, r3" r4=0x10325476 r3=0x9e4a142e => r4=0xae7c68a4 [08:27:44 422][libencrypt.so 0x01e6b] [c460 ] 0x40001e6a: "str r4, [r0, #0xc]" r4=0xae7c68a4 r0=0xbffff680[08:27:44 422] Memory WRITE at 0xbffff68c, data size = 4, data value = 0xae7c68a4, PC=RX@0x40001e6a[libencrypt.so]0x1e6a, LR=null [08:27:44 422][libencrypt.so 0x01e6d] [63ea0104] 0x40001e6c: "orn r4, r3, r1" r3=0x9e4a142e r1=0xb3fbb84f => r4=0xde4e57be [08:27:44 422][libencrypt.so 0x01e71] [7144 ] 0x40001e70: "add r1, lr" r1=0xb3fbb84f lr=0x0 => r1=0xb3fbb84f [08:27:44 423][libencrypt.so 0x01e73] [5440 ] 0x40001e72: "eors r4, r2" r4=0xde4e57be r2=0x2c4979fe => r4=0xf2072e40 [08:27:44 423][libencrypt.so 0x01e75] [2c44 ] 0x40001e74: "add r4, r5" r4=0xf2072e40 r5=0x2bf5a221 => r4=0x1dfcd061 [08:27:44 423][libencrypt.so 0x01e77] [3a4d ] 0x40001e76: "ldr r5, [pc, #0xe8]" => r5=0x2ad7d2bb [08:27:44 423][libencrypt.so 0x01e79] [2c44 ] 0x40001e78: "add r4, r5" r4=0x1dfcd061 r5=0x2ad7d2bb => r4=0x48d4a31c [08:27:44 423][libencrypt.so 0x01e7b] [089d ] 0x40001e7a: "ldr r5, [sp, #0x20]" sp=0xbffff540 => r5=0x98badcfe [08:27:44 424][libencrypt.so 0x01e7d] [03eb7444] 0x40001e7c: "add.w r4, r3, r4, ror #17" r3=0x9e4a142e r4=0x48d4a31c => r4=0xefd83898 [08:27:44 424][libencrypt.so 0x01e81] [64ea0202] 0x40001e80: "orn r2, r4, r2" r4=0xefd83898 r2=0x2c4979fe => r2=0xfffebe99 [08:27:44 424][libencrypt.so 0x01e85] [2544 ] 0x40001e84: "add r5, r4" r5=0x98badcfe r4=0xefd83898 => r5=0x88931596 [08:27:44 424][libencrypt.so 0x01e87] [5a40 ] 0x40001e86: "eors r2, r3" r2=0xfffebe99 r3=0x9e4a142e => r2=0x61b4aab7 [08:27:44 424][libencrypt.so 0x01e89] [8560 ] 0x40001e88: "str r5, [r0, #8]" r5=0x88931596 r0=0xbffff680[08:27:44 424] Memory WRITE at 0xbffff688, data size = 4, data value = 0x88931596, PC=RX@0x40001e88[libencrypt.so]0x1e88, LR=null [08:27:44 424][libencrypt.so 0x01e8b] [1144 ] 0x40001e8a: "add r1, r2" r1=0xb3fbb84f r2=0x61b4aab7 => r1=0x15b06306 [08:27:44 425][libencrypt.so 0x01e8d] [354a ] 0x40001e8c: "ldr r2, [pc, #0xd4]" => r2=0xeb86d391 [08:27:44 425][libencrypt.so 0x01e8f] [099d ] 0x40001e8e: "ldr r5, [sp, #0x24]" sp=0xbffff540 => r5=0xefcdab89 [08:27:44 425][libencrypt.so 0x01e91] [1144 ] 0x40001e90: "add r1, r2" r1=0x15b06306 r2=0xeb86d391 => r1=0x1373697 [08:27:44 425][libencrypt.so 0x01e93] [2544 ] 0x40001e92: "add r5, r4" r5=0xefcdab89 r4=0xefd83898 => r5=0xdfa5e421 [08:27:44 425][libencrypt.so 0x01e95] [05ebf121] 0x40001e94: "add.w r1, r5, r1, ror #11" r5=0xdfa5e421 r1=0x1373697 => r1=0xb2860b07 [08:27:44 426][libencrypt.so 0x01e99] [4160 ] 0x40001e98: "str r1, [r0, #4]" r1=0xb2860b07 r0=0xbffff680[08:27:44 426] Memory WRITE at 0xbffff684, data size = 4, data value = 0xb2860b07, ``` 通过write at的顺序 1、0x938e9cff 2、0xb2860b07 3、0x88931596 4、0xae7c68a4 注意小端序,拼起来就是上面的值。16个字节,四个字节一组进行拼接,是不是很像md5,如何分析? ## 分析是否为魔改md5 ### 标准伪代码 标准md5的伪代码如下: ``` // : All variables are unsigned 32 bit and wrap modulo 2^32 when calculating var int s[64], K[64] var int i // s specifies the per-round shift amounts s[ 0..15] := { 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22 } s[16..31] := { 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20 } s[32..47] := { 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23 } s[48..63] := { 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 } // Use binary integer part of the sines of integers (Radians) as constants: for i from 0 to 63 do K[i] := floor(232 × abs(sin(i + 1))) end for // (Or just use the following precomputed table): K[ 0.. 3] := { 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee } K[ 4.. 7] := { 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501 } K[ 8..11] := { 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be } K[12..15] := { 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821 } K[16..19] := { 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa } K[20..23] := { 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8 } K[24..27] := { 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed } K[28..31] := { 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a } K[32..35] := { 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c } K[36..39] := { 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70 } K[40..43] := { 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05 } K[44..47] := { 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665 } K[48..51] := { 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039 } K[52..55] := { 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1 } K[56..59] := { 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1 } K[60..63] := { 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 } // Initialize variables: var int a0 := 0x67452301 // A var int b0 := 0xefcdab89 // B var int c0 := 0x98badcfe // C var int d0 := 0x10325476 // D // Pre-processing: adding a single 1 bit append "1" bit to message< // Notice: the input bytes are considered as bit strings, // where the first bit is the most significant bit of the byte.[53] // Pre-processing: padding with zeros append "0" bit until message length in bits ≡ 448 (mod 512) // Notice: the two padding steps above are implemented in a simpler way // in implementations that only work with complete bytes: append 0x80 // and pad with 0x00 bytes so that the message length in bytes ≡ 56 (mod 64). append original length in bits mod 264 to message // Process the message in successive 512-bit chunks: for each 512-bit chunk of padded message do break chunk into sixteen 32-bit words M[j], 0 ≤ j ≤ 15 // Initialize hash value for this chunk: var int A := a0 var int B := b0 var int C := c0 var int D := d0 // Main loop: for i from 0 to 63 do var int F, g if 0 ≤ i ≤ 15 then F := (B and C) or ((not B) and D) g := i else if 16 ≤ i ≤ 31 then F := (D and B) or ((not D) and C) g := (5×i + 1) mod 16 else if 32 ≤ i ≤ 47 then F := B xor C xor D g := (3×i + 5) mod 16 else if 48 ≤ i ≤ 63 then F := C xor (B or (not D)) g := (7×i) mod 16 // Be wary of the below definitions of a,b,c,d F := F + A + K[i] + M[g] // M[g] must be a 32-bit block A := D D := C C := B B := B + leftrotate(F, s[i]) end for // Add this chunk's hash to result so far: a0 := a0 + A b0 := b0 + B c0 := c0 + C d0 := d0 + D end for var char digest[16] := a0 append b0 append c0 append d0 // (Output is in little-endian) ``` ### 标准md5的处理流程 1、填充 当 K%512 等于 448 如果原始消息的长度 K 模 512 正好等于 448,这意味着在当前的消息块中,已经恰好有 448 位,只剩下 64 位空间用于存放消息的长度。按照附加填充位的规则,至少需要填充 1 位。由于没有足够的空间在当前块中完成填充和存放长度信息,因此需要添加一个新的 512 位块。这个新块的前 1 位是 1,后面的 511 位是 0。 当 K%512 在 [448, 512) 之间 如果原始消息的长度 K 模 512 落在 448 到 512 之间(不包括 512,因为模 512 等于 0 意味着正好是块的边界),这意味着当前块不仅已经超过了可以填充位的空间,而且还不足以存放 64 位的长度信息。在这种情况下,也需要添加一个新的 512 位块,其中填充位的长度是 512 - (K%512 - 448),即填充位开始于新块的第一位,并且第一位是 1,其余直到新块的 448 位都是 0。 当 K%512 小于 448 如果原始消息的长度 K 模 512 小于 448,这意味着在当前块中有足够的空间来填充位,并且还能存放 64 位的长度信息。在这种情况下,填充位的长度是 448 - K%512。填充开始于原始消息之后的第一位,且这一位是 1,后面跟随足够的 0 位,直到整个消息的长度达到 448 位的倍数。 无论原始消息的长度如何,填充位的附加总是确保消息的总长度是 512 位的倍数,且最后一个 512 位块有 64 位用于存放原始消息的长度(通常是以二进制表示的位数)。这样的设计允许消息摘要算法(如 MD5,SHA-1,SHA-256 等)能够正确处理并生成固定长度的散列值。 ![](https://img.meituan.net/imgupload/568d76e9f4f8de7b90e1ed70eb2741fc196886.png) 2、初始化MD缓冲区 3、Process Message in 16-Word Blocks 处理消息组 总的来说就是,先根据512bit长度分割填充后的数据,再把每个block进行32bit进行分割,每32bits明文数据为一个单位,要进行4轮的向量运算;而每一个block要对16个(32bits)单位明文数据进行运算,共计64次。 MD Buffer就是A、B、C、D,MD5的核心就是不断对MD Buffer做线性、非线性向量变换,最终得到消息摘要。 ### 纯净md5算法 ![](https://img.meituan.net/imgupload/88b1bb2e7d25deec049ea9cffe7c96e8215983.png) 在trace中能看见大量md5中的k表的值,但是不一定没有经过魔改,有可能对k表做了顺序调整啥的。 ![](https://img.meituan.net/imgupload/6a6daf01041a4cd831a929875ba38086113606.png) 在trace某处,连续写入四个值,刚好是md5的Buffer。 可以搜索到K表中的值以及魔数,所以可以断定是一个MD5或者MD5的魔改版本。 ### 找到算法的输出 ![](https://img.meituan.net/imgupload/db340c96ff12f2858f3a2bad1572200b28174.png) 标准算法中,最后输出结果是魔数+产生的值再拼接。那么我们可以通过魔数来找到最后与谁做了相加?算出的结果就是加密结果的一部分,首先看0x67452301: ![](https://img.meituan.net/imgupload/ba98518a1d1183b10a8313b54d9e6ed050130.png) 这还没完,你以为是结果吗?由于md5可能会做多轮分组运算,所以得搜一下这个结果是否参与后续运算?(结果就是新的魔数,参与到下一个循环)如果找到了,说明明文长度超过了一个分组长,继续做了运算,但是一样的,也是最后进行了相加。 ![](https://img.meituan.net/imgupload/b320eb07e9e710fbc0f4348f043723e8186481.png) 发现没有了,只做了一个分组的加密。小端序,结果就是: ff9c8e93 同理,找到第二个魔数和谁相加? ![](https://img.meituan.net/imgupload/816387d3394c90db4c2a089dd53a160470434.png) ff9c8e93 070b86b2 ``` [08:27:44 424][libencrypt.so 0x01e85] [2544 ] 0x40001e84: "add r5, r4" r5=0x98badcfe r4=0xefd83898 => r5=0x88931596 [08:27:44 422][libencrypt.so 0x01e69] [1c44 ] 0x40001e68: "add r4, r3" r4=0x10325476 r3=0x9e4a142e => r4=0xae7c68a4 ``` ff9c8e93 070b86b2 96159388 a4687cae 这就是结果的密文。和前文通过trace分析拿到的完全一致。 ### 找到算法的输入 前面搜索魔数的时候,在trace可以看到分布: ![](https://img.meituan.net/imgupload/d7d002f05549b775e3a0d0dd8674c23a38687.png) 说明trace用到了三次的md5,而我们出结果的是在靠后面的位置: ![](https://img.meituan.net/imgupload/6866387692b8d0ddcbb9ee7d10fd99ca45304.png) 为了排除干扰,可以把前面的trace去掉,防止由于多次md5造成影响。 ![](https://img.meituan.net/imgupload/b8060456f57469b5091043f44da60b92136559.png) M也就是将填充后的明文进行4字节一组进行分割,然后进入FF函数进行非线性变化。 ![](https://img.meituan.net/imgupload/48d069c18720d5e1b98a199142fbb9ea22454.png) FF函数中,M是以加法的方式进行操作的。还会和t进行相加,也就是k表中值。根据这个特点,去trace中寻找,k表元素被相加的位置(前16个)就能把入参获取。首先输入k表的第一个: ![](https://img.meituan.net/imgupload/d343f29bf23840e755c0a35fcbc1f1d5142327.png) 所以只需要观察3w后之后的就行了,此时的r1寄存器的值就是 a + F(b, c, d) + M的结果,所以往上看其他的add操作: ![](https://img.meituan.net/imgupload/fdfe4767ec81dea160122c184146a61227202.png) 说明m[0]就是 0x32353533,也就是输入的前四个字节。这里不难发现,a + F(b, c, d) 第一次计算的结果是非常特殊的0xffffffff,知道这个技巧后,后续可以用这个当作锚点来定位第一轮的运算。 ![](https://img.meituan.net/imgupload/747fbf6b8dcf7b67b552a99b16f31924221138.png) 接着换k值搜就可以了,比如第二个: ![](https://img.meituan.net/imgupload/839a57f6d26460c2ac898b9e78e17565239974.png) 这里可以看到,在trace里,第一个k表值运算,先计算F,再算a+F,再算a+F+[m0],最后加上k[0]。 但是第二个值是先算a+[m1]再加上F。为什么呢? 查看openssl里的源代码定义:https://github.com/Chronic-Dev/openssl/blob/master/crypto/md5/md5_locl.h ![](https://img.meituan.net/imgupload/029b6f21f7878128752d3123f7a39c7f22611.png) 发现这个函数是宏定义,在编译器内会进行优化。所以不会像函数表达式一样,从左到右的增加顺序。查找时,可能会造成一点困扰。这个时候就有个问题了,你如何区分a和m的值,谁是谁? 在第一步中,我们知道所有的abcd,运算结束后我们也知道所有的abcd。 ![](https://img.meituan.net/imgupload/239ff5b2e96342e918ca29e9fa586fa2367829.png) 一步步往下分析,我们都应该知道所有的abcd。 ![](https://img.meituan.net/imgupload/839a57f6d26460c2ac898b9e78e17565239974.png) 按照这样的方法,可以拿到明文入参: 0x32353533 0x32323937 0x31313339 0x31313131 0x30333031 0x7c643830 0x2c392a3d .... 0x80 (这个时候需要注意 可能是填充开始) ..... 0x0 ... 0x100 (突然不是0x0了,可能是长度写入) 小端序,翻转一下,输入是这样的hex: 33353532373932323933313131313131313033303038647c3d2a392c65732e758000000000000000000000000000000000000000000000000001000000000000。 根据末尾的长度位(8字节),就是0x100(小端序,256),处以8就是32字节。就是取前32字节当作明文,也就是: 33353532373932323933313131313131313033303038647c3d2a392c65732e75,刚好在80前面。 ![](https://img.meituan.net/imgupload/624e350663f36ca572b6c9f6edf974b429127.png) ![](https://img.meituan.net/imgupload/ef2bd9809749221152defb63a1f1dd7084761.png) 这样,只通过一份trace,就能拿到输入和输出,并且知道他是一个没有被魔改的md5。 ## 算法实现 通过上面,可以写出一份python代码 ![](https://img.meituan.net/imgupload/5b9a72ed334c2201f009fc1d8262bae369310.png) 修改arg3,发现数据变了,按照上面的办法分析一遍,最终得出代码: ![](https://img.meituan.net/imgupload/7a2bb789acca0166123fd4d22563de92105532.png) 评论列表 bbba5df5 2024/01/29 大佬你好 样本文件有吗 想演示一下 写评论 您的IP:18.223.205.217,临时用户名:35fe828a评论已接入DepyWAF审计与流量系统,请勿频繁操作导致IP拉黑 提交评论 © 版权声明:非标注『转载』情况下本文为原创文章,版权归 Depy's docs 所有,转载请联系博主获得授权。
大佬你好 样本文件有吗 想演示一下