某样本算法分析 密级: 【C-1】 | 时间:2024-02-22 | 目录:测试笔记 | 编辑本文 上一版本 | 版本差异 | 下一版本 文章距今已发表三个月,请自行判断文中技术方法、代码的有效性:) ## 背景 样本sign值的产生算法。 ## 静态分析 找到静态绑定的函数 `Java_com_yuanrenxue_match2022_fragment_challenge_ChallengeTwoFragment_sign` ![](https://img.meituan.net/imgupload/e643706141f0a53952316c6c0cf561cd146939.png) 入参进入sub_AE0(v4, &v7); ``` padLength = strlen(a1); // 获取入参长度 v5 = (int8x16_t *)malloc((int)padLength + 16);// 开辟长度+16的空间 v6 = v5; if ( padLength ) // 存在v4 memcpy(v5, a1, padLength); // 把入参拷贝至v5 if ( (padLength & 0xF) != 16 ) // 对16取余,是否有值,有值就开始填充 { memset((char *)v6 + padLength, 16 - (padLength & 0xF), 16 - (padLength & 0xF));// PKCS7 填充 padLength = padLength - (padLength & 0xF) + 16;// 填充后长度 } ``` 函数以16为单位进行分组,然后根据余数进行填充,填充方式 PKCS#7。 填充完毕进入一个循环逻辑,用到了aes的sbox。 ``` if ( padLength ) { count1 = 0LL; v8 = 0; message = (char *)v6; do { for ( i = 0LL; i != 16; ++i ) { v8 = *((_BYTE *)&v27 + i) ^ byte_E73[(unsigned __int8)(message[i] ^ v8)]; *((_BYTE *)&v27 + i) = v8; } v11 = count1++ == (unsigned __int64)(padLength - 1) >> 4; message += 16; } while ( !v11 ); // 分组大循环 v12 = v27; v13 = v28; v14 = BYTE1(v28); v15 = BYTE2(v28); v16 = BYTE3(v28); } ``` 总结方法: 1、v8进行循环变化,不断影响v27中的值 2、v27的值最终只和最后一个分组的值+当时的v8有关 根据伪代码写出python代码: ```python from Crypto.Util.Padding import pad from Crypto.Cipher import AES import binascii def apply_pkcs7_padding(input_string, block_size=16): # Ensure the input is in bytes if isinstance(input_string, str): input_string = input_string.encode('utf-8') # Apply PKCS#7 padding padded_data = pad(input_string, block_size, style='pkcs7') return padded_data # Example usage input_string = "1:17070096491:17070096491:1707009649" block_size = 16 # Block size for AES (128 bits) padded_string = apply_pkcs7_padding(input_string, block_size) # Convert to a hex representation for display purposes padded_hex = binascii.hexlify(padded_string) byte_E73 = [ 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16 ] paddingLength = len(padded_string); count1 = 0 v8 = 0 v27 = [0] * 16 message_index = 0 while True: for i in range(16): v8 = v27[i] ^ byte_E73[padded_string[message_index+i] ^ v8] v27[i] = v8 message_index += 16 v11 = count1 == (paddingLength - 1) >> 4 print(v11) count1 += 1 if v11: break hex_string = ''.join(format(byte, '02X') for byte in v27) ``` 入参:1:17070096491:17070096491:1707009649 结果:C6E5B898303AD28762F97DAB99E2389C ## 模拟执行 用unidbg进行断点调试,模拟执行。 ![](https://img.meituan.net/imgupload/c23dab6e1f8fdc75a671549895f37c66111228.png) v27在这个位置赋值给v12,看一下指令: ![](https://img.meituan.net/imgupload/14c3b417e7a1717e26e631a248d8c36f77331.png) 0xbbc这个位置做了ldr操作,赋值给d0寄存器。 unidbg添加代码: ``` emulator.attach().addBreakPoint(module.base+0xbbc); emulator.traceCode(module.base,module.base+module.size); ``` 在这个偏移位置进行断点,并打印tracecode。 ![](https://img.meituan.net/imgupload/59cb092ac89f8a6b38dfac7b1ede081e50015.png) 在这个位置取值ldr,m0xbffff668查看他的值。 ![](https://img.meituan.net/imgupload/1dd80ddabd16e01c827e0db755c2350633640.png) 发现hex一致。说明上面的算法没问题。 接着分析算法 ![](https://img.meituan.net/imgupload/fd3654436949e8204df4a91946760a4532070.png) 看着很奇怪,查看汇编 ![](https://img.meituan.net/imgupload/626e894c50fa780b6835db606c1b77fa55221.png) ![](https://img.meituan.net/imgupload/af71b034e1aed9e169aea229b30b883b5893.png) ``` .text:0000000000000BBC LDR D0, [SP,#40] .text:0000000000000BC0 LDRB W11, [SP,#48] .text:0000000000000BC4 LDRB W10, [SP,#49] .text:0000000000000BC8 LDRB W9, [SP,#50] .text:0000000000000BCC LDRB W8, [SP,#51] ``` 将SP,#40开始取8个字节存入d0寄存器,ldrb是取一个字节,分别取9、10、11、12字节。分别存入w8-w11寄存器。 ![](https://img.meituan.net/imgupload/2e14cc211987705cbbcb208e129e7a6450599.png) 后续让v17 为v6数据的地址,分别把8、9、10、11字节赋值为前面保存的值。也就是取加密后结果的9-12个字节。 后面byte456以及hibyte继续拿13-16字节,就不赘述了。 ![](https://img.meituan.net/imgupload/9267fbac51772d21f87ae2c21260c251208900.png) 把d0 strb进去(前8字节),后续依次strb前面13-16字节的值。此时x12寄存器会保存加密结果的地址。 ![](https://img.meituan.net/imgupload/954e51cbb077b798cb1fc6cfa46245b9217799.png) 查看内存布局: ![](https://img.meituan.net/imgupload/50cb4e37b0901d9b37649c1bf0ed432127630.png) 反复改变输入发现布局均为如下规律: 明文输入+pkcs#7填充+校验hash(16字节) 继续进入循环,首先做了分组长度计算: ![](https://img.meituan.net/imgupload/f61f79680d1af21154650faa3106bc4e18106.png) padlength是之前的填充后的字节长度,想以16字节为一组向上取整,所以对padlength+了15并做了右移四位的操作。由于前面计算存在hash校验位,所以再对结果+了1。 ![](https://img.meituan.net/imgupload/4abbb95deb17a32f1abcd8ce4138b48f9318.png) 从 X20 寄存器指向的内存地址加载128位(16字节)的数据到 Q0 寄存器。Q0 是一个向量寄存器,常用于浮点数和 SIMD(单指令多数据)操作。 加载操作完成后,自动将 X20 寄存器的值增加16(即0x10)。 后续将q0寄存器的值装载到sp指针上,并且将x1、x0、x2寄存器分别保存sp、sp+24、sp+24的地址,作为函数入参进入sub_A40。 ![](https://img.meituan.net/imgupload/d03f00c46eb9f70de3dc054b09766c0611072.png) 这里开辟了48字节大小的空间,分成三部分: 第一部分是参数1的值 第二部分是参数2的值 第三部分是参数1和参数2异或之后的值。 之后进入do while+for的循环嵌套: ![](https://img.meituan.net/imgupload/496e9d15ae4cb4ee8c3edbf3525d69ec12361.png) 写出对应的python脚本: ``` byte_E73 = [0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16] v6 = [0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x31,0x3a,0x31,0x37,0x30,0x37,0x30,0x30,0x39,0x36,0x34,0x39,0x31,0x31,0x3a,0x31,0x31,0x3a,0x31,0x37,0x30,0x37,0x30,0x30,0x39,0x36,0x34,0x39,0x31,0x31,0x3a,0x31] v7 = 0 v8 = 0 # 初始化v8,具体值取决于上下文 # 执行循环操作 while v7 != 20: for i in range(0,48): v8 = v8 & 0xff v8 = v6[i] ^ byte_E73[v8] v6[i] = v8 v8 += v7 v7 += 1 print(bytearray(v6).hex()[:32]) ``` 结果如下: ``` d37cc271b9de69c5ea59734d6632f350 ``` 使用unidbg进行验证,因为第一次进入循环 v6为16字节0,第一组明文,0与第一组明文异或结果还是第一组明文。最后得到的值取16字节长度。 ![](https://img.meituan.net/imgupload/55de215bafe57cfb09a108287d679b4215715.png) ![](https://img.meituan.net/imgupload/a01a4a3434a4d39971562e8bed1b4b8074633.png) 小端序,结果一致。 此时获取的内容,会作为前置函数新的入参。我们可以看看ae0函数每次的入参来看: ![](https://img.meituan.net/imgupload/bf0480a7abf492d5644510135de0cf1522563.png) 结果正好是之前的入参,抽象概括就是栈上某个位置存在16字节内容,根据填充内容、以及循环变换不断更新。 1、明文输入+pkcs#7填充+校验hash(16字节) 2、分组进入循环,更改栈上值 写出对应代码并比对结果: ![](https://img.meituan.net/imgupload/0cdfb3656beb309a746b2b3c1564637132067.png) ![](https://img.meituan.net/imgupload/0f0cbc4c01f63ef0c2298b9aa88af63a25268.png) 随机抽两个分组都对上了,改变入参也一致,算法至此没有问题。 最后一个结果不会进入循环函数,但是会作为新的函数入参 ![](https://img.meituan.net/imgupload/41c4bde2f0e09461369db743e9d618b25520.png) 将结果赋值给入参2,入参2作为c8c函数入参。 ![](https://img.meituan.net/imgupload/a0f3cf20d39be5288bd075b53728f95b10269.png) 用unidbg对照发现一致。 ![](https://img.meituan.net/imgupload/51f4b7cdad93e5325e8999dc38ffc0cf31039.png) 评论列表 写评论 您的IP:3.15.208.109,临时用户名:7999e941评论已接入DepyWAF审计与流量系统,请勿频繁操作导致IP拉黑 提交评论 © 版权声明:非标注『转载』情况下本文为原创文章,版权归 Depy's docs 所有,转载请联系博主获得授权。