下篇:http://bbs.pediy.com/showthread.php?t=192245
这段时间花了点时间研究了下怎么替换ECC公钥(v9.2 ~ v11.11.1.1),在这里与感兴趣的朋友分享下。
==================================
生成用自定义公钥、私钥的keygen
==================================
要生成120位SIGN2的ECC license,只要在lm_code.h里加上
#define LM_SIGN_LEVEL LM_SIGN2
#define LM_STRENGTH LM_STRENGTH_239BIT
然后选定一组LM_SEED1,2,3(可以由lmrand1 -seed产生,这里我用的下面这组数),然后编译SDK就可以了。
#define LM_SEED1 0x11111111
#define LM_SEED2 0x22222222
#define LM_SEED3 0x33333333
作为参考,产生的lmcrypt.exe, testlmd.exe见附件,这里选用的是9.2版的SDK,因为有源码,讲解和调试起来都比较方便。
========================================
lmnewgen可以产生lmpubkey.h和lmprikey.h
========================================
请注意下lmprikey.h里的内容,后面会用到
static unsigned char lm_pubkey[2][3][40] = {{{0x77, 0x9c, 0x40, 0xa, 0x58, 0xe2, 0x7c, 0xef, 0xc3, 0xed, 0x93, 0x71, 0x20, 0xe0, 0x97, 0x36},
{0x76, 0xa1, 0xdd, 0x41, 0x32, 0x8c, 0x52, 0xb9, 0x2b, 0x9d, 0x5b, 0x68, 0xbf, 0x37, 0xbe, 0xa5, 0x82, 0x3f, 0x76, 0xca, 0x92, 0xd2},
{0x76, 0x9d, 0xe2, 0xab, 0x3b, 0x42, 0xd7, 0x6b, 0x20, 0xb5, 0x46, 0x39, 0xed, 0xbc, 0xa0, 0xd1, 0xcb, 0xb, 0x5d, 0x65, 0xdc, 0xe5, 0x39, 0x63, 0x44, 0x80, 0xcd, 0x78, 0x27, 0x8c, 0x17}}
,
{{0x77, 0x9b, 0x2f, 0xbb, 0x1f, 0x8, 0x7c, 0x42, 0x93, 0xb6, 0xcb, 0x1, 0x4c, 0x68, 0x8d, 0xb0},
{0x76, 0x9f, 0xeb, 0xa9, 0x94, 0x3d, 0x5a, 0xba, 0xc, 0x97, 0x1, 0x4a, 0xc8, 0xc2, 0x9f, 0x43, 0xdc, 0xe4, 0xd1, 0x5, 0xf9, 0x84},
{0x76, 0x9e, 0x6a, 0x55, 0xc2, 0x34, 0xfd, 0x24, 0xf, 0x95, 0xd1, 0x29, 0xbb, 0xf9, 0x1, 0x85, 0x1, 0x3a, 0x53, 0x17, 0x2a, 0x98, 0xbf, 0xb3, 0x62, 0x76, 0xec, 0x9a, 0x87, 0x69, 0x53}}
};
static unsigned int lm_pubsize[2][3] = {{0x10, 0x16, 0x1f}
,
{0x10, 0x16, 0x1f}
};
==============================================================================
1.我们先从获取public key开始(这里以testlmd为例,对实际应用,方法是一样的)
==============================================================================
打开SDK里src\l_prikey.c可以看到下面这段,那么第二个参数(&m->publicKey)存放的就应该是public key了。
if ((returnValue = sb_ecdsaVerifyEnd( m->global_data, &m->publicKey,
&ecc_sig, &verifyContext, &verificationResult)) != SB_SUCCESS)
{
l_pubkey_err(job, 10544, returnValue);
ret = LM_PUBKEY_ERR;
goto exit_verify;
}
OD里找到对应的位置下断,
.text:0041D5A9 loc_41D5A9: ; CODE XREF: _l_pubkey_verify+7A6j
.text:0041D5A9 ; _l_pubkey_verify+7D6j
.text:0041D5A9 lea edx, [ebp+verificationResult]
.text:0041D5AC push edx
.text:0041D5AD lea eax, [ebp+verifyContext]
.text:0041D5B3 push eax
.text:0041D5B4 lea ecx, [ebp+ecc_sig]
.text:0041D5BA push ecx
.text:0041D5BB mov edx, [ebp+m]
.text:0041D5C1 add edx, 44h
.text:0041D5C4 push edx <= check edx, will get the public key
.text:0041D5C5 mov eax, [ebp+m]
.text:0041D5CB mov ecx, [eax+4]
.text:0041D5CE push ecx
.text:0041D5CF call _sb_ecdsaVerifyEnd
查看0041D5C4处,edx里对应的就是public key
02 02 6F DF CF AF 73 DF BB C6 D2 A5 80 20 2C B4 58 7F F1 D2 78 91 D4 D6 D0 EC 60 1C B3 F1 A4

==================================
2.看看公钥在文件里是怎么存储的
==================================
函数_sb_ecdsaVerifyEnd是被_l_pubkey_verify调用的,好,我们回到函数l_pubkey_verify的起始点开始追踪[00C93B3C~00C93B5E]处是怎么变成public key的。
run过0041D143后,这块内存区域清零
0041D143 |. E8 E82A0100 CALL testlmd.l_malloc ; \l_malloc

在此处下内存写断点,如下图所示,

一路F9很容易来到0041D273处,再来看下内存中的这段数字:
76 9D E2 AB 3B 42 D7 6B 20 B5 46 39 ED BC A0 D1 CB 0B 5D 65 DC E5 39 63 44 80 CD 78 27 8C 17

回头再看下lmpubkey.h里的内容,有点明白了吧?
static unsigned char lm_pubkey[2][3][40] = {{{0x77, 0x9c, 0x40, 0xa, 0x58, 0xe2, 0x7c, 0xef, 0xc3, 0xed, 0x93, 0x71, 0x20, 0xe0, 0x97, 0x36},
{0x76, 0xa1, 0xdd, 0x41, 0x32, 0x8c, 0x52, 0xb9, 0x2b, 0x9d, 0x5b, 0x68, 0xbf, 0x37, 0xbe, 0xa5, 0x82, 0x3f, 0x76, 0xca, 0x92, 0xd2},
{0x76, 0x9d, 0xe2, 0xab, 0x3b, 0x42, 0xd7, 0x6b, 0x20, 0xb5, 0x46, 0x39, 0xed, 0xbc, 0xa0, 0xd1, 0xcb, 0xb, 0x5d, 0x65, 0xdc, 0xe5, 0x39, 0x63, 0x44, 0x80, 0xcd, 0x78, 0x27, 0x8c, 0x17}}
,
{{0x77, 0x9b, 0x2f, 0xbb, 0x1f, 0x8, 0x7c, 0x42, 0x93, 0xb6, 0xcb, 0x1, 0x4c, 0x68, 0x8d, 0xb0},
{0x76, 0x9f, 0xeb, 0xa9, 0x94, 0x3d, 0x5a, 0xba, 0xc, 0x97, 0x1, 0x4a, 0xc8, 0xc2, 0x9f, 0x43, 0xdc, 0xe4, 0xd1, 0x5, 0xf9, 0x84},
{0x76, 0x9e, 0x6a, 0x55, 0xc2, 0x34, 0xfd, 0x24, 0xf, 0x95, 0xd1, 0x29, 0xbb, 0xf9, 0x1, 0x85, 0x1, 0x3a, 0x53, 0x17, 0x2a, 0x98, 0xbf, 0xb3, 0x62, 0x76, 0xec, 0x9a, 0x87, 0x69, 0x53}}
};
static unsigned int lm_pubsize[2][3] = {{0x10, 0x16, 0x1f}
,
{0x10, 0x16, 0x1f}
};
好,让我们从0041D273处往回看看这串数字是怎么转换成最终的public key的。
0041D252 |. 0FBE08 |MOVSX ECX,BYTE PTR DS:[EAX]
这里的ECX里的内容是什么:testlmd (daemon name, 对应ASCII码74 65 73 74 6C 6D 64
)
再往下
0041D261 |. 0FB642 48 |MOVZX EAX,BYTE PTR DS:[EDX+48]
EAX里就是上面lmpubkey.h里的第一位数字"76",
0041D265 |. 2BC1 |SUB EAX,ECX
76h- 74h = 02

继续一路下去,
76 9D E2 AB 3B 42 D7 6B 20 B5 46 39 ED BC A0 D1 CB 0B 5D 65 DC E5 39 63 44 80 CD 78 27 8C 17 (lmpubkey.h里的public key)
=>
02 02 6F DF CF AF 73 DF BB C6 D2 A5 80 20 2C B4 58 7F F1 D2 78 91 D4 D6 D0 EC 60 1C B3 F1 A4 (真正的public key)
我们用IDA的F5反汇编可以看到这段转换对应的伪代码
for ( Gi = 0; Gi < pubkeysize[(_DWORD)offset]; ++Gi )
{
if ( !*cp )
cp = job->vendor;
if ( Gi % 2 )
{
if ( Gi % 3 )
m[Gi + 72] += *cp++;
else
m[Gi + 72] ^= *cp++;
}
else
{
m[Gi + 72] -= *cp++;
}
}
returnValue = sb_dataSize(ellipticCurve, m + 64);
再直观一点就是:
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 - (第0、2、4......26、28、30位用减法运算,比如第0位76-74=02h)
3 9 15 21 27 ^ (第3、9、15、21、27位用异或运算,例如第3位AB^74=DFh)
1 5 7 11 13 17 19 23 25 29 + (第1、5、7......23、25、29位用加法运算,例如第1位9D+65=02,注意取末两位)
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <= 0~30(共31 = (1F)h位
76 9D E2 AB 3B 42 D7 6B 20 B5 46 39 ED BC A0 D1 CB 0B 5D 65 DC E5 39 63 44 80 CD 78 27 8C 17 <= pubkey in pubkey.h
74 65 73 74 6C 6D 64 74 65 73 74 6C 6D 64 74 65 73 74 6C 6D 64 74 65 73 74 6C 6D 64 74 65 73 <= testlmd
02 02 6F DF CF AF 73 DF BB C6 D2 A5 80 20 2C B4 58 7F F1 D2 78 91 D4 D6 D0 EC 60 1C B3 F1 A4 <= real pubkey(for SIGN2=)
存储在硬盘上的是lmpubkey.h里的public key,经过上面的转化变成真正的public key,所以只要找到lmpubkey中key的存放规律,替换公钥就水到渠成了,这个后面再讲。
(未完待续)
[培训]科锐逆向工程师培训第53期2025年7月8日开班!