-
-
[原创] 看雪 2025 KCTF 第三题 邪影显现
-
发表于: 2025-8-20 01:11 6059
-
主要逻辑在 sub_402380 里,此函数非常长,不过 AI 的初步分析还是给出了很多有价值的信息:
接下来提示AI,a1是已知的name,a2是待求的serial,AI给出了进一步的分析:
从最终情况看,其中关于椭圆曲线/有限域的结论是错误的,但其他分析结论完全正确(而且此时提供给AI的上下文只包含 sub_402380 的反编译伪代码,还不包含任何它调用的子函数,因此这只是对sub_402380整体逻辑的初步印象),特别的是给出了a1(name)参数经过运算的比较串"KCTF2025" || MD5(a1)[0..11]、大数运算、++v262[0]的扰动这三项提示,对后续的人工分析启发很大。
优先查看 AI 指出的几个子函数,注意到引用了常量区的一些字符串:
容易查到这些字符串出自 freelip 大数计算库(386K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6D9M7X3!0T1L8%4c8Q4x3V1k6X3M7X3g2W2L8r3W2H3i4K6u0r3j5X3I4G2j5W2)9J5c8X3#2S2M7%4c8W2M7W2)9J5c8X3I4A6M7q4)9J5k6h3x3`.)
而在对a2(serial)参数做hexdecode的代码后面,第一段代码恰好调用到了这些函数:
其中sub_454440和sub_4541F0可以直接从函数里面引用的常量字符串确认是zmod和zinvmod,头尾的sub_455880和sub_4556D0按照常理应该是大数的导入和导出(与源代码对照后可以确认是zultoz和ztoul),那么中间的sub_4547E0必然是某种大数运算函数。
freelip这个库的大数内部表示有些奇怪,它内部的int数组好像每一项只使用了30bit左右,导致经过zultoz后内部的数组与原始字节并不一致,这点与其他常见库完全不同,导致不太方便通过调试判断sub_4547E0的变换,但是可以猜测大概率不是加法就是乘法
不过既然前面的zultoz以及后面的zmod和zinvmod已知,那么可以先从最上面0x402ADC处的zultoz导出v275的内存值确认v191的模数的值(记为pp),再分别测试一下x*(-3)%pp和x**(-3)%pp,然后调试查看ztoul导出的v274,即可确定sub_4547E0是zmul,执行的是乘法运算。
顺便导出了模数为 0x56f67550f16a00390dcf0b2715708e61c5b3f23101862fc1,是0x402780附近的赋值的常量。
通过yafu或factordb可以得到模数的质因数分解,它恰好可以分解为两个质数的乘积:
根据RSA的计算原理,对于 c = pow(m, e, n) ,在已知n的分解为p*q时,可以由c反向计算出m:phi = (p-1)*(q-1) , d = pow(e, -1, phi) , m = pow(c, d, n)
因此,这里对a2(serial)的第一次运算tmp1 = pow(a2, -3, pp)完全可逆。
再注意到 0x402C47 出的运算 ++*(_DWORD *)v275; (回顾开始,AI也注意到了这处扰动) ,在 tmp1 的大端内存表示的前四个字节做了一次自增。虽然运算很奇怪,但可逆,于是暂记结果为 tmp2。
再看接下来的一段代码:
sub_406450函数被多次调用,它的传入参数有点混乱(除了栈,还包含ecx和eax)。在最后还调用了sub_4067B0和sub_4074B0,整体的调用模式与第一段看起来非常相似。
这里仍然先猜测是大数运算,这里应该是换了一个大数库,
但经过调试验证,容易验证sub_406450、sub_4067B0、sub_4074B0分别是mul、mod、invmod,模数与上面相同,计算结果为tmp3 = pow(tmp2, -7, pp)
加上最后0x402E19处的++*(_DWORD *)v275;,仍然是大端序高位DWORD自增扰动得到的tmp4
接下来的一组sub_41A180、sub_41AB20、sub_41AF50分别也是mul、mod、invmod,以及0x402FC7处相同的扰动,得到tmp5 = pow(tmp4, -11, pp)和tmp6
以 ++*(_DWORD *)v275; 为支点,发现 sub_402380 大函数里相同的计算模式反复出现了9次,每次都换了一个大数库,但都是先对上一轮的结果做若干次乘法累加(后面几轮的中间穿插了取模)、一次取模、一次模逆、一次扰动。
因此,后面的逻辑无需再仔细逆向,直接找到每组重复次数最多的函数记为mul,然后静态计算出每轮mul的次数(这里也很有规律,每一轮最终的mul指数构成了递增的质数序列)并通过调试验证即可。
以下整理出每轮的三个大数运算函数以及最终的幂次:
(注意不同库的参数不同(例如,有的是三参数且出参可以分别位于三个位置或返回值,有的是两参数且出参复用某个入参的位置,还有多参数包含一些额外的长度信息等),中间还会穿插大数构造/析构/复制等辅助函数,排除干扰即可)
sub_402380的校验逻辑整理为python后非常短小:
根据name计算serial的反向代码为:
最终答案:
zultoz((int)v275, 6, &v191); v26 = Buffer; if ( strlen(Buffer) == 48 ) { for ( j = 0; j < 24; ++j ) { v28 = *v26; if ( (*v26 < 48 || v28 > 57) && (unsigned __int8)(v28 - 65) > 5u ) goto LABEL_209; v29 = v26[1]; if ( (v29 < 48 || v29 > 57) && (unsigned __int8)(v29 - 65) > 5u ) goto LABEL_209; if ( sscanf(v26, "%02X", &v275[j]) != 1 ) goto LABEL_209; v26 += 2; } v30 = 0; v31 = &v275[23]; do { v32 = *v31--; v274[v30++] = v32; } while ( v30 < 24 ); v227 = 0; zultoz((int)v274, 6, &v227); // sub_455880, v274 (first arg) is reversed hexdecoded a2 , which is serial zmul(v227, v227, &v245); // sub_4547E0, v245 = v227 * v227 = x * x = x**2 zmul(v227, v245, &v263); // sub_4547E0, v263 = v227 * v245 = x* x**2 = x**3 zmod(v263, v191, &v245); // sub_454440 zinvmod(v245, v191, &v245); // sub_4541F0, tmp2 = pow(tmp1, -3, pp) v266 = 6; ztoul(v245, (int *)v274, &v266); // sub_4556D0, now v274 (second arg) is serial ** (-3) % pp v33 = 0; v34 = 4 * v266; v266 = v34; if ( v34 > 0 ) { do { v275[v33] = v273[v34 - v33 + 255]; ++v33; } while ( v33 < v34 ); } ++*(_DWORD *)v275; // ?? tmp1 -> tmp2... zultoz((int)v275, 6, &v191); v26 = Buffer; if ( strlen(Buffer) == 48 ) { for ( j = 0; j < 24; ++j ) { v28 = *v26; if ( (*v26 < 48 || v28 > 57) && (unsigned __int8)(v28 - 65) > 5u ) goto LABEL_209; v29 = v26[1]; if ( (v29 < 48 || v29 > 57) && (unsigned __int8)(v29 - 65) > 5u ) goto LABEL_209; if ( sscanf(v26, "%02X", &v275[j]) != 1 ) goto LABEL_209; v26 += 2; } v30 = 0; v31 = &v275[23]; do { v32 = *v31--; v274[v30++] = v32; } while ( v30 < 24 ); v227 = 0; zultoz((int)v274, 6, &v227); // sub_455880, v274 (first arg) is reversed hexdecoded a2 , which is serial zmul(v227, v227, &v245); // sub_4547E0, v245 = v227 * v227 = x * x = x**2 zmul(v227, v245, &v263); // sub_4547E0, v263 = v227 * v245 = x* x**2 = x**3 zmod(v263, v191, &v245); // sub_454440 zinvmod(v245, v191, &v245); // sub_4541F0, tmp2 = pow(tmp1, -3, pp) v266 = 6; ztoul(v245, (int *)v274, &v266); // sub_4556D0, now v274 (second arg) is serial ** (-3) % pp v33 = 0; v34 = 4 * v266; v266 = v34; if ( v34 > 0 ) { do { v275[v33] = v273[v34 - v33 + 255]; ++v33; } while ( v33 < v34 ); } ++*(_DWORD *)v275; // ?? tmp1 -> tmp2...0x56f67550f16a00390dcf0b2715708e61c5b3f23101862fc1 = 2132319876367679106148824069448800305036941072478331350977 = 45424490472579293708671645907 * 469420758314254285411875780110x56f67550f16a00390dcf0b2715708e61c5b3f23101862fc1 = 2132319876367679106148824069448800305036941072478331350977 = 45424490472579293708671645907 * 46942075831425428541187578011v35 = &v275[23];v220[0] = 0;for ( k = 0; k < 24; ++k ){ v37 = *v35--; *((_BYTE *)v221 + k) = v37;}v220[0] = 6;v220[2] = 0;sub_4065C0(v220);v38 = (_DWORD *)v220[0];v238[1] = bignumber_mul___((int)v221, (int)v221, v220[0], (int)v239, (int)v238);// sub_406450, v232 = v214 * v214 = x ** 2v238[2] = 0;sub_4065C0(v238);v256[1] = bignumber_mul___((int)v221, (int)v239, v238[0], (int)v257, (int)v256);// v250 = v214 * v232 = x ** 3v256[2] = v220[2] ^ v238[2];sub_4065C0(v256);v203[0] = bignumber_mul___((int)v257, (int)v257, v256[0], (int)v204, (int)&v202);// v199 = v250 * v250 = x ** 6v203[1] = 0;sub_4065C0(&v202);v256[1] = bignumber_mul___((int)v204, (int)v221, (int)v38, (int)v257, (int)v256);// v250 = v199 * v214 = x ** 7v256[2] = v220[2] ^ v203[1];sub_4065C0(v256);bignumber_mod(v256, v188, v238); // sub_4067B0, first arg v245 is x ** 7 , second arg v185 is pp (same as above)bignumber_invmod(v238, v188, v238); // sub_4074B0, tmp3 = pow(tmp2, -7, pp)v39 = (v238[1] + 7) >> 3;v266 = v39;sub_4065C0(v238);memset(v275, 0, v39);for ( m = 0; m < v39; v274[v42 + 255] = v41 ){ v41 = v239[m]; v42 = v39 - m++;}++*(_DWORD *)v275; // // ?? tmp3 -> tmp4v35 = &v275[23];v220[0] = 0;for ( k = 0; k < 24; ++k ){ v37 = *v35--; *((_BYTE *)v221 + k) = v37;}v220[0] = 6;v220[2] = 0;sub_4065C0(v220);v38 = (_DWORD *)v220[0];v238[1] = bignumber_mul___((int)v221, (int)v221, v220[0], (int)v239, (int)v238);// sub_406450, v232 = v214 * v214 = x ** 2v238[2] = 0;sub_4065C0(v238);v256[1] = bignumber_mul___((int)v221, (int)v239, v238[0], (int)v257, (int)v256);// v250 = v214 * v232 = x ** 3v256[2] = v220[2] ^ v238[2];sub_4065C0(v256);v203[0] = bignumber_mul___((int)v257, (int)v257, v256[0], (int)v204, (int)&v202);// v199 = v250 * v250 = x ** 6v203[1] = 0;sub_4065C0(&v202);v256[1] = bignumber_mul___((int)v204, (int)v221, (int)v38, (int)v257, (int)v256);// v250 = v199 * v214 = x ** 7v256[2] = v220[2] ^ v203[1];sub_4065C0(v256);bignumber_mod(v256, v188, v238); // sub_4067B0, first arg v245 is x ** 7 , second arg v185 is pp (same as above)bignumber_invmod(v238, v188, v238); // sub_4074B0, tmp3 = pow(tmp2, -7, pp)v39 = (v238[1] + 7) >> 3;v266 = v39;sub_4065C0(v238);[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!