本文描述wbc文件的使用过程
wbc主要包含了系统特征的哈希值,这些系统特征是CmAct证书私钥的生成参数。每个item节都表示一个系统特征以及CmAct证书私钥的部分结构。
以某份wbc文件为例,部分内容如下所示:
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
31
32
33
34
|
[WIBU
-
SYSTEMS Control
File
]
Guid
=
{
000B0002
-
0000
-
1100
-
8005
-
0000C06B5161
}
Description
=
WIBU
-
SYSTEMS CmAct Inventory
Version
=
1.00
Encoding
=
UTF
-
8
[Inventory]
Nonce
=
5B953B8DE30C80383851A1C4A0DCB4C08B5EA6DB5B57503C9F766B7EE75303B6
RedundancyData
=
4B76D4A6ADA098349EE74931AD4686F251B512998CB181346527C4625B2BA8ADEA16AEA16335F3ACC84F1C4E532D5DCC8EEE91901D9C7BCBC71506F16FF71CFBE893EBDA3FE75A0AFE8F94FB05150C26A2611DFDD9A369B326B493C2EF03A49FFA83743C2100
Version0
=
6
Version1
=
60
Version2
=
2869
Version3
=
0
Heuristic
=
2
Flags
=
0
ItemCount
=
3
[Item_0]
ID
=
1EC1787C328C4BE4883290757FB640BD6373E4C3CB3A85D5A13138A0FDFEB9E0
Position
=
0
Length
=
170
Params
=
AB1A401EC3FBA4070FA7CA569EB44DE8
[Item_1]
ID
=
CB53841BA7BF43DC643F5B8263F69FC439C7B4F1E2112B193D3AFD2B6A93F7EF
Position
=
170
Length
=
170
Params
=
8B4FC28F523E3B201B0A21075DA160C1
[Item_2]
ID
=
410912781D65FF2625587B62D115E9DB8F938B760CC33857BF4B6A54BE9CC235
Position
=
340
Length
=
172
Params
=
2B0E51706F32DC52323AF862ABABFE3A
|
在解说ID的生成过程前,首先需要介绍系统特征码。wibu软授权体系为每类(每种)系统特征都编了一个号码,我们姑且称之为系统特征码。每一个系统特征码都绑定着一个回调函数,调用时只要传入不同的系统特征码,最后就会调用对应的回调函数。
关于系统特征码的用途见附录。
ID的生成过程与系统特征码和系统特征有关。如上述item_0节的ID则由系统特征码6001生成,系统特征码6001表示的是系统的MAC。
wibu首先计算了系统特征码和系统特征的哈希值。但是在该计算过程,wibu错误使用sha256函数,导致无法直接使用python的hashlib库来复现该过程。任何鲁棒无bug的sha库都无法实现该过程,必须自己手动修改sha源码才能复现该过程。
接着,将上步骤的输出加上wbc文件中的nonce(随机值),再次sha256后即得到id值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
>>>
import
SHA256
>>>
import
struct
>>>
>>>
'''
wibu实现的sha256算法有bug
当一个上下文中执行两次final,第二次final输入与第一次final输入反转
'''
>>>
def
wibu_sha256(code, feature):
>>> h
=
SHA256.SHA256()
>>> h.update(struct.pack(
"I"
, code))
>>> h.update(feature)
>>> part1
=
h.final()
>>> h.update(part1)
>>> part2
=
h.final()
>>>
>>>
# print("%s %s" % (part1.hex().upper(), part2.hex().upper()))
>>>
return
part1
+
part2
>>>
>>> mac
=
b
"00:ff:52:9e:64:de"
>>> nonce
=
bytes.fromhex(
"5B953B8DE30C80383851A1C4A0DCB4C08B5EA6DB5B57503C9F766B7EE75303B6"
)
>>> h256_0
=
wibu_sha256(
6001
, mac)
>>> item_id
=
int
.from_bytes(SHA256(nonce
+
h256_0).final(),
"big"
)
>>>
print
(
"%X"
%
item_id)
1EC1787C328C4BE4883290757FB640BD6373E4C3CB3A85D5A13138A0FDFEB9E0
|
首先,根据同理计算item_1和item_2的id。
1
2
3
4
5
6
7
8
9
10
|
>>> hostname
=
b
"cpx1548"
>>> h256_1
=
wibu_sha256(
5001
, hostname)
>>> item_id
=
int
.from_bytes(SHA256.SHA256(nonce
+
h256_1).final(),
"big"
)
>>>
print
(
"%X"
%
item_id)
CB53841BA7BF43DC643F5B8263F69FC439C7B4F1E2112B193D3AFD2B6A93F7EF
>>> fs_root
=
b
"UUID=044527ca-a319-491b-a27f-6a25a97a44ea"
>>> h256_2
=
wibu_sha256(
5003
, fs_root)
>>> item_id
=
int
.from_bytes(SHA256.SHA256(nonce
+
h256_2).final(),
"big"
)
>>>
print
(
"%X"
%
item_id)
410912781D65FF2625587B62D115E9DB8F938B760CC33857BF4B6A54BE9CC235
|
确认所有item节都对上后,该份wbc文件会被wibu授权系统承认。
私钥主要来源于两部分数据,分别记为part1和part2,下面分别介绍这两部分的生成。
我们按照item节的pos和length取上述代码中的h256_0、h256_1和h256_2。
最终取完的结果长度为512比特,在取的过程中再次发生不符合程序员理解的过程。
我不清楚这是wibu故意为之还是开发者头脑一热弄出来的操作,但我认为一个简单的取前N比特的操作弄成这样子不符合我的程序之美,开发说明里还要特意描述这样结构。
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
|
>>>
print
(h256_0.
hex
())
2949fb78084c95a87ade867c4ab4f9b5418798269a01e6e8b3adcf660e02cfa2542fb60d640ab1868b8fb5d23b529b4fffdffd2bc1a38f7ea116888f394036ad
>>>
print
(h256_1.
hex
())
8d5cf77bc7d35c74d0596f2d2cbed1cfdd678778db400385c1e896fdbccc2272c4966f75d09e8e1f00867205954fbd3588d5443044643a3c5525e12fbd526537
>>>
print
(h256_2.
hex
())
b960bc22180606ff1fdc651947caab6df0f9adc4076b4cfe07fc1ee5393c4edfd7f1ce8f432cf2beb099e6f4262cb64506eec8eaa360b38e40e0856b353f2b65
>>>
'''
组比特的过程好SB呀,特别是it_len不是8的倍数时,不是用h256的前it_len个比特
'''
>>>
def
wibu_get_bits(h256, length):
>>> mask
=
1
>>> ret
=
""
>>>
for
i
in
range
(length):
>>>
if
((h256[i
/
/
8
] & mask) !
=
0
):
>>> ret
+
=
'1'
>>>
else
:
>>> ret
+
=
'0'
>>> mask
*
=
2
>>>
if
(mask
=
=
0x100
):
>>> mask
=
1
>>>
return
ret
>>> part1_bin
=
wibu_get_bits(h256_0,
170
)
+
wibu_get_bits(h256_1,
170
)
+
wibu_get_bits(h256_2,
170
)
>>> part1
=
''
>>>
# 每8个比特反转一下后输出
>>>
for
i
in
range
(
0
,
len
(part1_bin),
8
):
>>> part1
+
=
"%x"
%
int
(part1_bin[i:i
+
8
][::
-
1
],
2
)
>>>
print
(
"part1: %s"
%
part1)
part1:
2949fb7884c95a87ade867c4ab4f9b5418798269a3572ddef1d4f73d14167bdb5b0f8463f779f1de26d93bc62b826160f0ffc15d9671a4bcda69fdf4a7c30
|
part2是对wbc文件的哈希,过程很简单。部分代码见下图:
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
31
32
33
|
>>>
import
SHA256
>>>
import
struct
>>>
import
configparser
>>>
import
common
>>>
>>> cf
=
configparser.ConfigParser()
>>> wbc_content
=
common.read_file(
"test.wbc"
).replace(
"\x00"
, "")
>>> cf.read_string(wbc_content)
>>>
>>> h
=
SHA256.SHA256()
>>> h.update(bytes.fromhex(
"17051401040C14020402134414021346"
))
>>> h.update(bytes.fromhex(cf.get(
"Inventory"
,
"nonce"
)))
>>> h.update(bytes.fromhex(cf.get(
"Inventory"
,
"redundancydata"
))[
0
:
33
])
>>> h.update(struct.pack(
"I"
,
int
(cf.get(
"Inventory"
,
"version0"
),
10
)))
>>> h.update(struct.pack(
"I"
,
int
(cf.get(
"Inventory"
,
"version1"
),
10
)))
>>> h.update(struct.pack(
"I"
,
int
(cf.get(
"Inventory"
,
"version2"
),
10
)))
>>> h.update(struct.pack(
"I"
,
int
(cf.get(
"Inventory"
,
"version3"
),
10
)))
>>> h.update(struct.pack(
"I"
,
int
(cf.get(
"Inventory"
,
"Heuristic"
),
10
)))
>>> h.update(struct.pack(
"I"
,
int
(cf.get(
"Inventory"
,
"flags"
),
10
)))
>>>
>>>
for
i
in
range
(cf.getint(
"Inventory"
,
"itemcount"
)):
>>> item_name
=
"Item_%d"
%
i
>>> h.update(bytes.fromhex(cf.get(item_name,
"id"
)))
>>> h.update(struct.pack(
"I"
,
int
(cf.get(item_name,
"position"
),
10
)))
>>> h.update(struct.pack(
"I"
,
int
(cf.get(item_name,
"length"
),
10
)))
>>> h.update(bytes.fromhex(cf.get(item_name,
"params"
)))
>>>
>>> h.update(struct.pack(
"I"
,
int
(cf.get(
"Inventory"
,
"itemcount"
),
10
)))
>>> part2
=
''
>>>
for
i
in
h.final():
>>> part2
+
=
"%x"
%
i
>>>
print
(
"part2: %s"
%
part2)
part2:
57eceb1aa5768647a7ef6b775af2353d45f497321a2d2773ee551f3de35ca5
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
>>> data
=
"[%s-%s]"
%
(part1, part2)
>>> h
=
SHA256.SHA256()
>>> h.update(data.encode())
>>> h.update(struct.pack(
"I"
,
0x04000000
))
#todo: check it how to get
>>> h.update(b
"{1F4C32E0-34BB-414F-B1D6-C8D59E4B8004}\x00"
)
>>> out
=
h.final()
>>> out
+
=
SHA256.SHA256(out).final()[
0
:
8
]
>>>
>>> h
=
SHA256.SHA256()
>>> h.update(out)
>>> h.update(bytes.fromhex(
"C3649C300C6444438AB4DFDE047D25E1"
))
>>> k
=
h.final()[
0
:
28
]
>>>
print
(
"private: %s"
%
k.
hex
())
private:
33f0c397747ec06066c9943a2f1440d4bd7b589983bda88ee2f0b67a
|
由于CmAct证书使用的是ECC-224,所以其私钥为224比特(即28字节),故最后k只取了前28字节。
可以通过Q=dG来确认CmAct证书私钥是否正确。
得到CmAct证书私钥后,就可以去解析RAU和DYN文件。
私钥的计算是wibu软授权系统中十分重要的一个步骤,它的计算过程很复杂,并且具有多个分支路径,本文的代码只节选了其中一条路径。
更加详细的代码可以参考github仓库。
系统特征码 | 功能描述 | ||
---|---|---|---|
5001 | 读取/proc/sys/kernel/hostname | ||
5002 | 读取/sys/bus/i2c/drivers/i2c_adapter/module/srcversion | ||
5003 | 使用getfsfile函数获取挂载在/目录下的文件系统名,原理是读取/etc/fstab | ||
6001 | 使用ioctl的获取MAC | ||
6101 | 读取/sys/class/dmi/id/product_name | ||
6102 | 读取/sys/class/dmi/id/product_version | ||
6103 | 读取/sys/class/dmi/id/sys_vendor | ||
6104 | 读取/sys/class/dmi/id/board_version | ||
6105 | 读取/sys/class/dmi/id/board_vendor | ||
6106 | 读取/sys/class/dmi/id/board_name | ||
6107 | 读取/sys/class/dmi/id/bios_date | ||
6108 | 读取/sys/class/dmi/id/bios_vendor | ||
6109 | 读取/sys/class/dmi/id/bios_version | ||
6110 | 机器序列号,见补充说明1 | ||
7001 | vendor,硬盘相关,见补充说明3 | ||
7002 | rev,硬盘相关,见补充说明3 | ||
7003 | model,硬盘相关,见补充说明3 | ||
8001 | 读取/proc/cpuinfo信息,输出结构(`cpu family | model | stepping`)重复4次 |
8002 | 遍历/sys/class/power_supply/BAT*,输出结构为`manufacturer | model_name | serial_number` |
8003 | 读取/sys/class/drm/card*LVDS*/edid,注意*为通配符 | ||
8004 | 遍历/sys/bus/usb/devices/(非usb开头)文件夹,输出结构为`idVendor | idProduct | serial` |
8005 | 内存条相关,见补充说明2 |
运行以下其中一条命令,这三条命令理论上输出一样。
/usr/bin/codemeter-info -Z d6c11a123ca6b9e6290e1b85542d9a7ebf15a1f8c17e455ebc3ca734292b15e6
该命令实际是读取/sys/class/dmi/id/product_serial
/usr/bin/codemeter-info -Z 83f924b2cba312b3383aeaebd16d16af704a35ba0e8a83d8de1244b4c20e9de6
该命令实际是运行/usr/sbin/dmidecode -s system-serial-number
/usr/bin/codemeter-info -Z 825fabfb0a32da7981a7e3a4a0469c59c99d49b974ebc443e94fa9f5a96467b6
该命令实际是运行/usr/sbin/dmidecode -s system-serial-number
运行以下其中一条命令,这两条命令理论上输出一样。
/usr/bin/codemeter-info -Z efe29c248ac07cdb68fe09cc0f2913366406e5b4a47a9e6bda3eda14cc471f69
该命令实际是运行/usr/sbin/dmidecode -t 17,然后分析每一行输出,最后构成以下输出结构。
/usr/bin/codemeter-info -Z 9b0855c03c8977a941be48d9422fef6aa9821d7481e94ffe2d9d3737470a604e
该命令实际是运行/usr/sbin/dmidecode -t 17,然后分析每一行输出,最后构成以下输出结构。
输出结构为"%d|%s|%s|%s" % (size,Manufacturer,Part_Number,Serial_Number)
若/目录挂载源为/dev/hd*(通过/etc/fstab确定),则只有7003有效,读取/proc/ide/hd*/model
若/目录挂载源为/dev/sd*(通过/etc/fstab确定),则遍历/sys/bus/scsi/drivers/sd/*/block/*是否存在sd*相关的目录,若存在,则
7001有效,读取/sys/bus/scsi/drivers/sd/*/vendor
7002有效,读取/sys/bus/scsi/drivers/sd/*/rev
7003有效,读取/sys/bus/scsi/drivers/sd/*/model
先忽略,暂未处理。
更多【wibu软授权(三)】相关视频教程:www.yxfzedu.com