Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb1955779b | ||
|
|
2e5b4cc5a1 | ||
|
|
38b4f96437 | ||
|
|
173922d0ce | ||
|
|
99303a237e | ||
|
|
8f7baf2790 | ||
|
|
b673336b3f | ||
|
|
e37400a0f3 | ||
|
|
7b9474521d | ||
|
|
78c3f2c9b8 | ||
|
|
ad256abb7c | ||
|
|
86d160ae5f | ||
|
|
fde6afb39e | ||
|
|
7c3e78296a | ||
|
|
9cc06ff767 | ||
|
|
766b6e81d0 | ||
|
|
0349e01629 | ||
|
|
0bd9da2bbc | ||
|
|
f14c7c3f55 | ||
|
|
ef5793fb15 | ||
|
|
9f22fe6d97 | ||
|
|
3147d576ee | ||
|
|
2ceec2588c | ||
|
|
5b4d88b9c7 | ||
|
|
55043cca36 | ||
|
|
097ebe025f | ||
|
|
9f92307f64 | ||
|
|
c220035fd9 | ||
|
|
d7b98d3cde | ||
|
|
0ab876633f | ||
|
|
cabe76ca54 | ||
|
|
c57224e258 | ||
|
|
b56101d2e6 | ||
|
|
16211c28d1 | ||
|
|
139d3e0c3d | ||
|
|
f0652fc5ec | ||
|
|
55489cd634 | ||
|
|
742887c0e4 | ||
|
|
c3e5cd2c0d | ||
|
|
63e125627c | ||
|
|
c0189e9d05 | ||
|
|
133e3208ec | ||
|
|
17c6fd6d0f | ||
|
|
2aa7ea43b8 | ||
|
|
6d7dbfac31 | ||
|
|
d4942c6dd6 | ||
|
|
39ae3c24f6 | ||
|
|
a04d74a949 | ||
|
|
b93a7ebd94 | ||
|
|
cbc7b0340f | ||
|
|
ea7a197848 | ||
|
|
82d4df889e | ||
|
|
df3a2a1010 | ||
|
|
67cd6c2f59 | ||
|
|
b1788e7df9 | ||
|
|
1cb8a81518 | ||
|
|
e1b61f2675 | ||
|
|
834d44856c | ||
|
|
c088eeb5bc | ||
|
|
921510b7b7 | ||
|
|
17a54f7a8a | ||
|
|
43ff924efc | ||
|
|
f9bd4b6628 | ||
|
|
702aa22139 | ||
|
|
57d53f63e5 | ||
|
|
835ada4a53 | ||
|
|
7f204f3e1b | ||
|
|
70e13b8414 | ||
|
|
df9bb8cbb8 | ||
|
|
cebdb32502 | ||
|
|
58bd61343a | ||
|
|
90faf1007e | ||
|
|
f8dbd12b69 | ||
|
|
cf6ba66dd2 | ||
|
|
2080b5d10a | ||
|
|
dd180977d9 | ||
|
|
221dcbaf1b | ||
|
|
1a2098f4fb | ||
|
|
73eab17110 | ||
|
|
978a336358 | ||
|
|
6a0fd4e427 | ||
|
|
5fd4efd920 | ||
|
|
43be2987f5 | ||
|
|
06cc048fdc | ||
|
|
5752471420 | ||
|
|
3dfe12b90a | ||
|
|
ad1e3501b1 | ||
|
|
1b46572e4c | ||
|
|
9d8e42d34c | ||
|
|
3f6e9dab3e | ||
|
|
ad85f11875 | ||
|
|
9ae31834cc | ||
|
|
49a4dc6942 | ||
|
|
a9f58cc30f | ||
|
|
bdb2198582 | ||
|
|
f32a77ca8b | ||
|
|
121d3fb0ae | ||
|
|
1dd72f7600 | ||
|
|
c7c168b007 | ||
|
|
441b41c6af | ||
|
|
49a907283a | ||
|
|
0ffc52f9ba | ||
|
|
4e96ec49e7 | ||
|
|
dfb4646f51 | ||
|
|
a8e82c38c9 | ||
|
|
dba23289fd | ||
|
|
c2c1eb98d9 | ||
|
|
364176912d | ||
|
|
202245baad | ||
|
|
c3fa99640c | ||
|
|
a7c34b5a6a | ||
|
|
05de092642 | ||
|
|
1bfa40c841 | ||
|
|
c325435ae8 | ||
|
|
b978bd41c3 | ||
|
|
f03d8733b0 | ||
|
|
a624b3b759 | ||
|
|
d683398ba9 | ||
|
|
42f692b82d | ||
|
|
855fe9115e | ||
|
|
d6bd383822 | ||
|
|
b1b77b73ac | ||
|
|
8d87adcc50 | ||
|
|
49bf6c54e7 | ||
|
|
7e92e0b1d9 | ||
|
|
f0805d320c | ||
|
|
364f48a6f0 | ||
|
|
cfebf6df0e | ||
|
|
688855b72d | ||
|
|
77b275a04b | ||
|
|
98b142df12 | ||
|
|
c5fe845f18 | ||
|
|
e5e9629d31 | ||
|
|
b65b9e6bfb | ||
|
|
93ae8b02e3 | ||
|
|
5c183ea200 | ||
|
|
661765f08c | ||
|
|
60fa19c681 | ||
|
|
606eddba53 | ||
|
|
71605ade31 | ||
|
|
ee409e4872 | ||
|
|
91024abc4a | ||
|
|
99de36a6cf | ||
|
|
ac1e48dc30 | ||
|
|
494bab3380 | ||
|
|
6696ca93ce | ||
|
|
b7e80fb8ed | ||
|
|
ce5b642d3e | ||
|
|
76d414164a | ||
|
|
b6d13047ba | ||
|
|
f1ba295796 | ||
|
|
415abef4fc | ||
|
|
95af2014d8 | ||
|
|
f75b039daf | ||
|
|
65b1ac78f3 | ||
|
|
b067591882 | ||
|
|
7941d76fd7 | ||
|
|
7bff601beb | ||
|
|
7b4da32bee | ||
|
|
bbf2974703 | ||
|
|
f5abaf06de | ||
|
|
88023dd09e | ||
|
|
eec2996fab | ||
|
|
97ca200c29 | ||
|
|
f0600ea3a3 | ||
|
|
19b3eb51d7 | ||
|
|
871dabf142 | ||
|
|
58795b9349 | ||
|
|
1ef769842b | ||
|
|
6051570414 | ||
|
|
da5d5d0dc7 | ||
|
|
782aa22d4d | ||
|
|
0084867f38 | ||
|
|
5a27858f86 | ||
|
|
7905d8375f | ||
|
|
ba8aa63d75 | ||
|
|
ac82778e26 | ||
|
|
55d2661e38 | ||
|
|
4803b89294 | ||
|
|
e48adbd87d | ||
|
|
227314d83c | ||
|
|
3c0e21eb02 | ||
|
|
86d6153ef1 | ||
|
|
78cf8bded3 | ||
|
|
26d12181cd | ||
|
|
8d184b011c | ||
|
|
ed3a1444ec | ||
|
|
d3e70a47cd | ||
|
|
3171151219 | ||
|
|
e407867635 | ||
|
|
6d9cfab55f | ||
|
|
f0bce029f0 | ||
|
|
d47960a0cf | ||
|
|
8416018afe | ||
|
|
e93fc70169 | ||
|
|
5b3474c781 | ||
|
|
8d2a9f3b2b | ||
|
|
73d09f9b09 | ||
|
|
4e7c6efa2a | ||
|
|
81a884bc61 | ||
|
|
ac4f960352 | ||
|
|
e4b3e4e438 | ||
|
|
d6ee476296 | ||
|
|
c23741ba9a | ||
|
|
7bac5b4740 | ||
|
|
1fe440168e | ||
|
|
26ad57138b | ||
|
|
92e1fdaf0f | ||
|
|
357540d0ad | ||
|
|
405622a91e | ||
|
|
5686c73c56 | ||
|
|
76e2f787ac | ||
|
|
02b67f4e6a | ||
|
|
a2ab527761 | ||
|
|
f1142ae742 | ||
|
|
decb222dac | ||
|
|
00c598e6f7 | ||
|
|
e9843dd3a5 | ||
|
|
37f390742b | ||
|
|
533e7dfd15 | ||
|
|
ddcbe1cd51 | ||
|
|
4eabd04bc6 | ||
|
|
c8f2da6ac9 | ||
|
|
8820ac4bf3 | ||
|
|
236b200f39 | ||
|
|
6789e32674 | ||
|
|
9eb55deedf | ||
|
|
6e76b5b8eb | ||
|
|
491b0da83d | ||
|
|
5d2c17e940 | ||
|
|
0497c4a9e7 | ||
|
|
6d0765848b | ||
|
|
40c4ae71bd | ||
|
|
8edba10dff | ||
|
|
85aa7bc652 | ||
|
|
854ad1db07 | ||
|
|
955600a1e1 | ||
|
|
b739134d85 | ||
|
|
723152566d | ||
|
|
678b045e0e | ||
|
|
681f4df49a | ||
|
|
b19f7d7098 | ||
|
|
daaae3ec10 | ||
|
|
c14957afff | ||
|
|
843a4998f1 | ||
|
|
9637e74edc | ||
|
|
b1bdf4f671 | ||
|
|
238ed8e364 | ||
|
|
8aa1509863 | ||
|
|
aac182e30b | ||
|
|
ac7e953acc | ||
|
|
6ae0f27e05 | ||
|
|
f6e5a92400 | ||
|
|
5e5ff3428c | ||
|
|
1cdbb9c402 | ||
|
|
8caacd1870 | ||
|
|
cfbc4c9c20 | ||
|
|
76dbf1c5d0 | ||
|
|
c0e7c4afa5 | ||
|
|
9da0970d85 | ||
|
|
d6f2ad32a7 | ||
|
|
8dbc87a331 | ||
|
|
77eb1ce44e | ||
|
|
e12f4ae61a | ||
|
|
c23a73625f | ||
|
|
3a66946686 | ||
|
|
7fef43701f | ||
|
|
7f5eb34ac6 | ||
|
|
ba4369f829 | ||
|
|
81af2e51fc | ||
|
|
dff09064c8 | ||
|
|
ee0bb32cce | ||
|
|
4a215c308b | ||
|
|
79493a96ea | ||
|
|
3cca1c4671 | ||
|
|
72e9923d08 | ||
|
|
406e152891 | ||
|
|
e9eb569d50 | ||
|
|
640b389e44 | ||
|
|
314e3349d6 | ||
|
|
0ae4d72bc9 | ||
|
|
8920a5fb21 | ||
|
|
b338ff5dd9 | ||
|
|
87e2fcb37c | ||
|
|
bab43e3b17 | ||
|
|
e602088330 | ||
|
|
2f6db92077 | ||
|
|
cee33c0f77 | ||
|
|
4fcdaa10b6 | ||
|
|
949f74ab04 | ||
|
|
ef68a89b45 | ||
|
|
10ecb35cca | ||
|
|
696ac22f77 | ||
|
|
8d13f98030 | ||
|
|
1dddf13a9d | ||
|
|
855472239a | ||
|
|
dc5d317623 | ||
|
|
91011e9e2a | ||
|
|
bb09b75d3b | ||
|
|
b2f9adad66 | ||
|
|
0c2c84d2a9 | ||
|
|
1733e7cc86 | ||
|
|
831d9a9895 | ||
|
|
70ba3a629d | ||
|
|
29e9702d21 | ||
|
|
f8ef756bd4 | ||
|
|
233b28e8a5 | ||
|
|
da77c17be5 | ||
|
|
8dfa76822e | ||
|
|
4163991b66 | ||
|
|
78323c9a08 | ||
|
|
eb517e86f3 | ||
|
|
e24f3823cb | ||
|
|
fd3e8ee28a | ||
|
|
a980ea7bd6 | ||
|
|
20ce73c54f | ||
|
|
d8f22f7f28 | ||
|
|
591055edaf | ||
|
|
fbf0e06c04 | ||
|
|
9daf327e80 | ||
|
|
4441978e10 | ||
|
|
d8eb770735 | ||
|
|
cefbf79c6e | ||
|
|
fc8762094e | ||
|
|
801c3aadcc | ||
|
|
4296f1ec43 | ||
|
|
a7f469747c | ||
|
|
68d0352343 | ||
|
|
01e6d09f7c | ||
|
|
6582abfd4a | ||
|
|
f8f4b303d7 | ||
|
|
79f05a23a8 | ||
|
|
95845c5c85 | ||
|
|
cd1a24290e | ||
|
|
21261bfb17 | ||
|
|
79abf51cb8 | ||
|
|
e538b837d5 | ||
|
|
7b995e1223 | ||
|
|
c7826b58a3 | ||
|
|
5786dbdb75 | ||
|
|
0f42c19ec7 | ||
|
|
87d44a64f3 | ||
|
|
77dd4ace33 | ||
|
|
0907d71957 | ||
|
|
e7a30646e3 | ||
|
|
98d21b3391 | ||
|
|
35d9203b9c | ||
|
|
e6e384a7f7 | ||
|
|
7d954c14ae | ||
|
|
41388962e2 | ||
|
|
6d51f6b6a9 | ||
|
|
c7af9a3702 | ||
|
|
da7c61202c | ||
|
|
c24e872a33 | ||
|
|
d0809ec313 | ||
|
|
c1a796da38 | ||
|
|
98c27800e0 | ||
|
|
7a18eadd13 | ||
|
|
b07adb7b30 | ||
|
|
4d7c3cce4d | ||
|
|
4ff0ab9cd8 | ||
|
|
264dd50969 | ||
|
|
26d0ad1a6e | ||
|
|
90284018e5 | ||
|
|
46eeec16d4 | ||
|
|
083fb0dccc | ||
|
|
d46f145806 | ||
|
|
868eddfd2b | ||
|
|
8295e6bb5b | ||
|
|
424914b842 | ||
|
|
8c5027df0f | ||
|
|
0b7817d992 | ||
|
|
ca6c9bfd05 | ||
|
|
0a1abd8040 | ||
|
|
ed69a4fdf6 | ||
|
|
2ae19a6d4e | ||
|
|
404bc50dd4 | ||
|
|
cada5eeeaf | ||
|
|
5e9bd40e8b | ||
|
|
0788bab0dd | ||
|
|
4e8e0dbea7 | ||
|
|
b8c5279137 | ||
|
|
a3255b1681 | ||
|
|
f151379f8f | ||
|
|
7784544583 | ||
|
|
f742937296 | ||
|
|
5fd9242291 | ||
|
|
9f343196a9 | ||
|
|
5468eded04 | ||
|
|
1b97758fc9 | ||
|
|
b2e8e49447 | ||
|
|
3eec781139 | ||
|
|
b6f1977fa0 | ||
|
|
e0aa080f00 | ||
|
|
66aa76c836 | ||
|
|
bb9444e1ae | ||
|
|
6c9bfa7755 | ||
|
|
c4fa849290 | ||
|
|
4807e2c410 | ||
|
|
5388efdebc | ||
|
|
1d656de7ba | ||
|
|
eaa1d725a9 | ||
|
|
dea2150d63 | ||
|
|
80f9d6cf75 | ||
|
|
7430d38594 | ||
|
|
6804e4af9f | ||
|
|
138756c31d | ||
|
|
349bc293c6 | ||
|
|
2f87cd10f2 | ||
|
|
fc50eafcca | ||
|
|
b48e20a727 | ||
|
|
ce7ed2e6eb | ||
|
|
043d71e0ff | ||
|
|
1855ae0e1d | ||
|
|
77323a50df | ||
|
|
1c2f6b85ca | ||
|
|
2b5c478e4b | ||
|
|
90fd83a81f | ||
|
|
8fa2a166f3 | ||
|
|
e8cc45ebe7 | ||
|
|
eb87117b91 | ||
|
|
3799dfa1e8 | ||
|
|
163c7fd680 | ||
|
|
44ca762f69 | ||
|
|
8db311ba33 | ||
|
|
99d8604db4 | ||
|
|
90aa88daeb | ||
|
|
ae6eebad4d | ||
|
|
cd31ce1c38 | ||
|
|
ed3846e451 | ||
|
|
0c79cf8e37 | ||
|
|
81c7e16c0e | ||
|
|
16c03095b8 | ||
|
|
b5dab3aa22 | ||
|
|
e4a3c871f8 | ||
|
|
a2fe947b08 | ||
|
|
8df49ecfc1 | ||
|
|
526b9bd4c7 | ||
|
|
c67ff3d936 | ||
|
|
417b17da98 | ||
|
|
4ca19a4eb0 | ||
|
|
e834dfb7ee | ||
|
|
5e1ff74fb3 | ||
|
|
7a1d0f2ca7 | ||
|
|
b305522106 | ||
|
|
ff3495645a | ||
|
|
ddc000321f | ||
|
|
fe4e6af3b9 | ||
|
|
a1a83cc32c | ||
|
|
e37665218b | ||
|
|
2a876f4136 | ||
|
|
290d8e8a64 | ||
|
|
121bce529f | ||
|
|
1232d98573 | ||
|
|
208d9150b0 | ||
|
|
351bf7725e | ||
|
|
a547776766 | ||
|
|
86a8d20757 | ||
|
|
760c420f95 | ||
|
|
44c2edf58e | ||
|
|
e88207c232 | ||
|
|
f6b2450fcd | ||
|
|
a124ecd3ed | ||
|
|
34fcc3d29e | ||
|
|
5f3011eebc | ||
|
|
d4464edb6c | ||
|
|
1dc89a80db | ||
|
|
bd23599770 | ||
|
|
bda6ff80da | ||
|
|
16bee9825f | ||
|
|
5e4a4c4e8f | ||
|
|
0a03093333 | ||
|
|
960b1d53f7 | ||
|
|
f09dee6a52 | ||
|
|
301e1e56d1 | ||
|
|
6b2dbd382d | ||
|
|
e6f526c766 | ||
|
|
06cca669a0 | ||
|
|
f89ccea208 | ||
|
|
0728f2d995 | ||
|
|
c7a7a61c9a | ||
|
|
aca11e66a8 | ||
|
|
46c7a001c2 | ||
|
|
d6aa3c50a3 | ||
|
|
caaadc5cf3 | ||
|
|
14d703903c | ||
|
|
76fa14ab01 | ||
|
|
d1afe255e5 | ||
|
|
8f27739331 | ||
|
|
7a6aba5a2a | ||
|
|
ca0ec1f99d | ||
|
|
4e167486a1 | ||
|
|
503bbcd995 | ||
|
|
b160d2f634 | ||
|
|
2e7628a837 | ||
|
|
1259948ed7 | ||
|
|
81881ae1a4 | ||
|
|
0b5c581aca | ||
|
|
eb3614ed0c | ||
|
|
eb50513277 | ||
|
|
c3af73b8cf | ||
|
|
b552164a13 | ||
|
|
b6dc9ecadb | ||
|
|
0723dc8dbb | ||
|
|
0b1fffb5bf | ||
|
|
a39d87e5c5 | ||
|
|
0b0c3310cf | ||
|
|
f75c3750a5 | ||
|
|
3da760721b | ||
|
|
a9ad3feeec | ||
|
|
479c4ade67 | ||
|
|
84afa3e714 | ||
|
|
9a69665bab | ||
|
|
fea34ee937 | ||
|
|
ff2412c65d | ||
|
|
658953860a | ||
|
|
b473783527 | ||
|
|
7e590d4925 | ||
|
|
232fd3e2f2 | ||
|
|
9ea25fb77c | ||
|
|
6c47136de3 | ||
|
|
6fae29b488 | ||
|
|
7ed8cc9380 | ||
|
|
142e9d1028 | ||
|
|
c96c30ff9a | ||
|
|
337f1cc100 | ||
|
|
c3cb89dbea | ||
|
|
445bd1f668 | ||
|
|
1bded908c1 | ||
|
|
8d9dea4b08 | ||
|
|
4e98d6d8ce | ||
|
|
55b5cd939e | ||
|
|
d72f5a43c8 | ||
|
|
b406d8d30b | ||
|
|
f9c91c9ba0 | ||
|
|
0213d7eeb4 | ||
|
|
048c5c0970 | ||
|
|
728542fce8 | ||
|
|
81ca24c7a2 | ||
|
|
32ec272253 | ||
|
|
758982b660 | ||
|
|
a83bba10e3 | ||
|
|
4b9342fa8b | ||
|
|
a48162cf4a | ||
|
|
3fa61e777c | ||
|
|
01a2c55207 | ||
|
|
1a2376e6b7 | ||
|
|
dab0d6a47f | ||
|
|
b5d90b5cb7 | ||
|
|
25fd0bd2af | ||
|
|
a4bb63fcd4 | ||
|
|
9d85785c27 | ||
|
|
4206242627 | ||
|
|
f0149be9ad | ||
|
|
5d56a1f0e0 | ||
|
|
ad06c75058 | ||
|
|
d5b6e17e59 | ||
|
|
ffe2514e11 | ||
|
|
8621bed5d4 | ||
|
|
634031e6dc | ||
|
|
11fae36469 | ||
|
|
3d01e36a71 | ||
|
|
c2082b1ac5 | ||
|
|
c94bbf84fe | ||
|
|
930363cd3f | ||
|
|
085ca9eb58 | ||
|
|
f486d44804 | ||
|
|
1b4fbd3449 | ||
|
|
43ad7bf646 | ||
|
|
64921c3308 | ||
|
|
7a0e6a00c5 | ||
|
|
1332dd2c0e | ||
|
|
d52628bed2 | ||
|
|
981ec006b2 | ||
|
|
7da05cf678 | ||
|
|
316882bcd0 | ||
|
|
b204e52297 | ||
|
|
f82a63f4c8 | ||
|
|
cb0f2108a4 | ||
|
|
300b7a7a68 | ||
|
|
c3852ca60d | ||
|
|
a5fb735c93 | ||
|
|
d0b04ba84f | ||
|
|
9552742a39 | ||
|
|
8e9bdf6c0c | ||
|
|
818cc48502 | ||
|
|
adc4e28eb8 | ||
|
|
2e3a4d3bf1 | ||
|
|
d9bcac78e7 | ||
|
|
2342947849 | ||
|
|
19204dee8d | ||
|
|
863fa90b80 | ||
|
|
6fdef4a8f1 | ||
|
|
9469ceaa02 | ||
|
|
857f184409 | ||
|
|
a2f4a725f5 | ||
|
|
d12449bbb9 | ||
|
|
a41f4711dd | ||
|
|
3c2b018d49 | ||
|
|
e221a09709 | ||
|
|
26ff4bd843 | ||
|
|
d8e28f384a | ||
|
|
65e68c3cc4 | ||
|
|
7b76ab2a59 | ||
|
|
ae2a01b463 | ||
|
|
cda022a9f4 | ||
|
|
672e48c5f8 | ||
|
|
6fbdb1354f | ||
|
|
79bc7fd443 | ||
|
|
5578dd3339 | ||
|
|
0232dbd196 | ||
|
|
d44e192eaa | ||
|
|
4f12b19cf8 | ||
|
|
392ac1b1c7 | ||
|
|
8ad175aca3 | ||
|
|
6afa58c42b | ||
|
|
8be03cf232 | ||
|
|
23ea53d3ff | ||
|
|
294f40e7ac | ||
|
|
4f5f706ee5 | ||
|
|
27c1a7b4e4 | ||
|
|
cfaa5be6d7 | ||
|
|
a6942280a0 | ||
|
|
159124e6a3 | ||
|
|
6e63293a3c | ||
|
|
9a9dc0d788 | ||
|
|
e201cd074c | ||
|
|
0fd49a6e1c | ||
|
|
6169a85464 | ||
|
|
e583344fee | ||
|
|
dcee00394c | ||
|
|
e48dc99dc0 | ||
|
|
3ba3dda384 | ||
|
|
bbf1ce993c | ||
|
|
56245a8c9b | ||
|
|
4f2e65094c | ||
|
|
0bf456cbc8 | ||
|
|
9213d0da16 | ||
|
|
13f2ece0ff | ||
|
|
45c2a74c94 | ||
|
|
b1b3e90e9b | ||
|
|
90ef841dae | ||
|
|
b46000e8eb | ||
|
|
b24891355d | ||
|
|
14bde5a790 | ||
|
|
d6445f17e3 | ||
|
|
6124c3fe40 | ||
|
|
5531a4b194 | ||
|
|
839a0bbd42 | ||
|
|
1ba6272e4e | ||
|
|
fdd86815ef | ||
|
|
c8091fdc84 | ||
|
|
9d569bd9e3 | ||
|
|
1991664cd1 | ||
|
|
ee7064fafc | ||
|
|
cdaf861485 | ||
|
|
3e962b1da9 | ||
|
|
8398f1706c | ||
|
|
24d2d441b4 | ||
|
|
5700039d42 | ||
|
|
22445c79d4 | ||
|
|
2be05ac76c | ||
|
|
84e397aeed | ||
|
|
bb513b9115 | ||
|
|
01b680e278 | ||
|
|
ef7c1cba2e | ||
|
|
40bbc57c82 | ||
|
|
457e5bebbb | ||
|
|
99abc56177 | ||
|
|
9eeb0b6376 | ||
|
|
0b2da187ee | ||
|
|
06f5410a4d | ||
|
|
2551ada3b7 | ||
|
|
56df08879e | ||
|
|
d41685c86e | ||
|
|
14b942970e | ||
|
|
b6103c845d | ||
|
|
7927f12e84 | ||
|
|
7774ff77b2 | ||
|
|
aaa2ed7eef | ||
|
|
b83674e9b7 | ||
|
|
017356780c | ||
|
|
45916004d2 | ||
|
|
388c8b615e | ||
|
|
4219473632 | ||
|
|
f2283cdb7f | ||
|
|
72ce9cfd6b | ||
|
|
b26375d25a | ||
|
|
a2a823f358 | ||
|
|
62164cf628 | ||
|
|
0b5fc22181 | ||
|
|
0df905f777 | ||
|
|
a539a948f6 | ||
|
|
6bd8364b5f | ||
|
|
70b70bc3e7 | ||
|
|
ac50f8917e | ||
|
|
f935b2c0b6 | ||
|
|
245c9d1119 | ||
|
|
9476313725 | ||
|
|
2d27a74278 | ||
|
|
823a5115eb | ||
|
|
33ab1e1f5e | ||
|
|
8db984d1fd | ||
|
|
a00415657a | ||
|
|
527837cc56 | ||
|
|
ac9ddff8b1 | ||
|
|
4ebae2a167 | ||
|
|
11f1685f42 | ||
|
|
8199101891 | ||
|
|
4f3ce323ae | ||
|
|
76befc7c70 | ||
|
|
8b0ad06676 | ||
|
|
d7f091a3a9 | ||
|
|
679b00229c | ||
|
|
0cfc636fb6 | ||
|
|
bd3c31e150 | ||
|
|
8e9547b75a | ||
|
|
d38ecc0f96 | ||
|
|
85834497e0 | ||
|
|
0d8997db02 | ||
|
|
cb53450ca5 | ||
|
|
f38ae36eb6 | ||
|
|
8218b0c8a6 | ||
|
|
ea036d2d53 | ||
|
|
b163eb2cdb | ||
|
|
ba3568f312 | ||
|
|
80c9976832 | ||
|
|
b6326c25b9 | ||
|
|
35f591cf49 | ||
|
|
67e437bbd7 | ||
|
|
194dae8ce1 | ||
|
|
a89442ceb0 | ||
|
|
24debf6062 | ||
|
|
ff98846c99 | ||
|
|
e3f5a0bca1 | ||
|
|
7278e4f051 | ||
|
|
709a511c1d | ||
|
|
452027f085 | ||
|
|
8574802428 | ||
|
|
79fb044921 | ||
|
|
13ac634fea | ||
|
|
04a68bc262 | ||
|
|
47de15ab68 | ||
|
|
512af24a05 | ||
|
|
e5ff46d3bb | ||
|
|
c7a0979140 | ||
|
|
7fd7245df2 | ||
|
|
026301b011 | ||
|
|
5cc77f90ae | ||
|
|
0d4568ac8a | ||
|
|
a6ed491a39 | ||
|
|
90dfc3f1ae | ||
|
|
36388fb0d7 | ||
|
|
043166d0c8 | ||
|
|
b92d488851 | ||
|
|
d841a04a89 | ||
|
|
d8553baa78 | ||
|
|
8521d5aee3 | ||
|
|
ebe21d9c7f | ||
|
|
3b83abdbd3 | ||
|
|
aead2463c9 | ||
|
|
5cc5676672 | ||
|
|
ecc32ca18d | ||
|
|
661640de79 | ||
|
|
96dfdab7ed | ||
|
|
56bed5fd3b | ||
|
|
3fd3b18406 | ||
|
|
45bac97a16 | ||
|
|
2c6eca4a92 | ||
|
|
8109d59655 | ||
|
|
8a15571cd2 | ||
|
|
a15c8f2e60 | ||
|
|
cec3d6c04d | ||
|
|
d872604855 | ||
|
|
2b6fb933a4 | ||
|
|
1da892e863 | ||
|
|
19bfedd1a3 | ||
|
|
cb87392d9d | ||
|
|
7cd5bc9ec3 | ||
|
|
edcc1a30ab | ||
|
|
6f93f6ab06 | ||
|
|
9de1f44288 | ||
|
|
3d360b0881 | ||
|
|
cf1c415846 | ||
|
|
90133e2444 | ||
|
|
81246046b8 | ||
|
|
20e037ab9e | ||
|
|
bc27036ba7 | ||
|
|
1cd470b90b | ||
|
|
64471e34bc | ||
|
|
f4c5d3de09 | ||
|
|
0dc59405e3 | ||
|
|
7c775bdc9b | ||
|
|
61400ff4d4 | ||
|
|
e119e34ebe | ||
|
|
6f059747bf | ||
|
|
24d72641fa | ||
|
|
b91fca3aa6 | ||
|
|
a6f1947837 | ||
|
|
6919131cef | ||
|
|
9186d801fc | ||
|
|
76e2f200bd | ||
|
|
1400bbd9ce | ||
|
|
fd37628ac2 | ||
|
|
4396d5afc4 | ||
|
|
f5faf80883 | ||
|
|
ba32da97c8 | ||
|
|
ec2de73d3a | ||
|
|
f4d032e5c7 | ||
|
|
4114aac6c3 | ||
|
|
0c43c6bb5f | ||
|
|
7b3c965b88 | ||
|
|
d1035268f6 | ||
|
|
8968f30bf9 | ||
|
|
84c2c29600 | ||
|
|
b2a59f261c | ||
|
|
363a330f8e | ||
|
|
1d2aa059ee | ||
|
|
f2bfa908a1 | ||
|
|
02a8de4696 | ||
|
|
22fa3eff71 | ||
|
|
69361209c3 | ||
|
|
ff0aeaa4ef | ||
|
|
3c461dfd4b | ||
|
|
c6beda383e | ||
|
|
3d59f3befd | ||
|
|
e54f0db239 | ||
|
|
0f877f2749 | ||
|
|
b69ba165d9 | ||
|
|
965076fe52 | ||
|
|
f4d6cb0430 | ||
|
|
19d412188c | ||
|
|
798d85978a | ||
|
|
819742d753 | ||
|
|
9b32fa6219 | ||
|
|
2c93661726 | ||
|
|
bddaca2a1e | ||
|
|
2f06f2e051 | ||
|
|
1d26e46bd9 | ||
|
|
fa0ff826a3 | ||
|
|
e1b3114c83 | ||
|
|
a655f075db | ||
|
|
13d4ebbf6b | ||
|
|
913d10b1ee | ||
|
|
eb541a0104 | ||
|
|
2063186994 | ||
|
|
860d571039 | ||
|
|
c6d79adf9a | ||
|
|
5453900ba1 | ||
|
|
5029ff1e37 | ||
|
|
47fb691339 | ||
|
|
dff9dd17bf | ||
|
|
5bc4a16b90 | ||
|
|
b1918953b0 | ||
|
|
6e89ed7007 | ||
|
|
744e8bc941 | ||
|
|
0b98ce7ffd | ||
|
|
ca8b293b06 | ||
|
|
e28d99a257 | ||
|
|
488feb6108 | ||
|
|
6cdb106f07 | ||
|
|
a7c5ccd27a | ||
|
|
136f9dbf0a | ||
|
|
16b45c3450 | ||
|
|
68330fad52 | ||
|
|
dca167518d | ||
|
|
bcc44422ef | ||
|
|
e080e03466 | ||
|
|
b6e75490de | ||
|
|
196c014576 | ||
|
|
236110df5e | ||
|
|
b2ae192056 | ||
|
|
f080969a62 | ||
|
|
69d969898d | ||
|
|
a21a766926 | ||
|
|
f11fdb6b96 | ||
|
|
c7445df4c6 | ||
|
|
2c45c9bc5b | ||
|
|
ae5ed27cb6 | ||
|
|
11f9fe3c8b | ||
|
|
e93ad3bd33 | ||
|
|
20c26ea0a8 | ||
|
|
5925a80d3b | ||
|
|
56cb95e523 | ||
|
|
a6e101cc5d | ||
|
|
e8292b6f81 | ||
|
|
37a4392c94 | ||
|
|
a897f75ecf | ||
|
|
79055d3779 | ||
|
|
3d39aa7f97 | ||
|
|
2c1d09affc | ||
|
|
4775358a79 | ||
|
|
cb5f8e8191 | ||
|
|
8209b4ff01 | ||
|
|
e110134d24 | ||
|
|
c2248fdd49 | ||
|
|
58fea5b0ca | ||
|
|
7a36946545 | ||
|
|
563d9a8a36 | ||
|
|
bdbd0617b5 | ||
|
|
67006d88cb | ||
|
|
ded2083462 | ||
|
|
87b7d044f8 | ||
|
|
9781cef29b | ||
|
|
52c393148b | ||
|
|
b98d617d6f | ||
|
|
b475573e0e | ||
|
|
ba3c85d5b2 | ||
|
|
410777efb3 | ||
|
|
0b3fb452b4 | ||
|
|
aaff565172 | ||
|
|
dd0786648e | ||
|
|
e145ffa111 | ||
|
|
b322468c1c | ||
|
|
992d8b909c | ||
|
|
d5327c6370 | ||
|
|
0d13bb5f8f | ||
|
|
3ad08a33a9 | ||
|
|
533e0ed736 | ||
|
|
e7ba77b600 | ||
|
|
f3b84b04f7 | ||
|
|
f4c948aca9 | ||
|
|
1507f5df5a | ||
|
|
0562d11a71 | ||
|
|
b3a9f5cce0 | ||
|
|
5974a3c85a | ||
|
|
78c732d2b8 | ||
|
|
d087b2dc16 | ||
|
|
16c5d014aa | ||
|
|
e661ff400f | ||
|
|
b8c8e2d04d | ||
|
|
8fc36f2b20 | ||
|
|
4b73a09a09 | ||
|
|
65c4d5d5b7 | ||
|
|
19d0702aee | ||
|
|
b74e8330de | ||
|
|
334b837d43 | ||
|
|
169cb37f08 | ||
|
|
8f29c3f0da | ||
|
|
bec5cc4a04 | ||
|
|
af627ee202 | ||
|
|
1e091a3e7c | ||
|
|
8840da91d7 | ||
|
|
1f4ff04ed3 | ||
|
|
60974e8f72 | ||
|
|
86f68994c2 | ||
|
|
21c4918486 | ||
|
|
c6a426ec25 | ||
|
|
db75a22c1c | ||
|
|
7e85d22ecd | ||
|
|
67adadc0a4 | ||
|
|
6b78562454 | ||
|
|
4f13afc867 | ||
|
|
c34e8f193f | ||
|
|
4bcb507eae | ||
|
|
a50d457d75 | ||
|
|
9f119265a9 | ||
|
|
0b121a624b | ||
|
|
0f47f5ec8f | ||
|
|
f89896d041 | ||
|
|
5e335fd5e8 | ||
|
|
fd245dec14 | ||
|
|
774cdc5de3 | ||
|
|
633cfad1a8 | ||
|
|
cb7875c320 | ||
|
|
7960a57fc0 | ||
|
|
a23f693921 | ||
|
|
c49cb8bc4a | ||
|
|
02c425cfc0 | ||
|
|
be96ba0a0e | ||
|
|
df5e00977a | ||
|
|
f055650365 | ||
|
|
b0baddda3b | ||
|
|
69359cba05 | ||
|
|
8b0b29e39b | ||
|
|
21ac50667d | ||
|
|
b843a438ba | ||
|
|
441cc30c0b | ||
|
|
85adf5dc80 | ||
|
|
0c48f0981d | ||
|
|
88b646e41c | ||
|
|
dc4c3e0a99 | ||
|
|
262f6d55a0 | ||
|
|
2d68400670 | ||
|
|
8acaae3f0d | ||
|
|
a5cb26528f | ||
|
|
ac2520a03f | ||
|
|
9c15cc7b33 | ||
|
|
c38f460163 | ||
|
|
9bda8c9e77 | ||
|
|
558be68bad | ||
|
|
6b9d27ce9b | ||
|
|
412745eede | ||
|
|
91ea771af1 | ||
|
|
238ab03db3 | ||
|
|
2d63da193d | ||
|
|
fa4c20a241 | ||
|
|
6294fc33d1 | ||
|
|
c4cf48a94b | ||
|
|
2934c7eb57 | ||
|
|
46ce454105 | ||
|
|
9ab06a0ea9 | ||
|
|
e6f742380a | ||
|
|
2686812470 | ||
|
|
cd11b5b7b2 | ||
|
|
aae35dc640 | ||
|
|
f0b669ff85 | ||
|
|
237605bb9f | ||
|
|
1778046581 | ||
|
|
ff10adfabb | ||
|
|
1cb0f8d7ba | ||
|
|
5813bbdcd1 | ||
|
|
b38e5b177e | ||
|
|
9d66cf248c | ||
|
|
ebcf90d426 | ||
|
|
51904db187 | ||
|
|
4fc8755b8d | ||
|
|
7afcbfaa26 | ||
|
|
55cb96d978 | ||
|
|
a1de099974 | ||
|
|
a0238732fe | ||
|
|
6c100b0679 | ||
|
|
e4d159840f | ||
|
|
0375b3ff02 | ||
|
|
746bae5103 | ||
|
|
70eb2c4500 | ||
|
|
69175f3ae1 | ||
|
|
ed5fab2c54 | ||
|
|
1425ff8e6c | ||
|
|
832ab549a1 | ||
|
|
4ec1661c26 | ||
|
|
ea27712b62 | ||
|
|
6f65efe2d8 | ||
|
|
8edcf60baf | ||
|
|
74f6aec367 | ||
|
|
62c65a766a | ||
|
|
acbcaa337e | ||
|
|
f4dbe57244 | ||
|
|
b494c64a2f | ||
|
|
321e086b95 | ||
|
|
fb60d6b867 | ||
|
|
8ff2e2074a | ||
|
|
1151a1c43f | ||
|
|
13bc6cecc4 | ||
|
|
a4ea572458 | ||
|
|
880df4dee9 | ||
|
|
b75e86dd11 | ||
|
|
75c84092ae | ||
|
|
c69db97da8 | ||
|
|
9c2a8892ae | ||
|
|
f92111563c | ||
|
|
0abb3e78cb | ||
|
|
fb83071419 | ||
|
|
f593c6df8b | ||
|
|
9b452b25c2 | ||
|
|
c835fcf99a | ||
|
|
e4373edc54 | ||
|
|
686c05785f | ||
|
|
994358a793 | ||
|
|
b02ca3757c | ||
|
|
164ec25415 | ||
|
|
8ee85cd9ca | ||
|
|
c07bf05754 | ||
|
|
2e2a08a765 | ||
|
|
10be5f442f | ||
|
|
0fcd8565d8 | ||
|
|
e07eee86dd | ||
|
|
f25d5d73c6 | ||
|
|
28b4d00d3c | ||
|
|
c75cf8a539 | ||
|
|
ff7199b857 | ||
|
|
5613fd81b7 | ||
|
|
c1458502f7 | ||
|
|
b2f620bd3d | ||
|
|
0e1030f186 | ||
|
|
ff4ffb8ec5 | ||
|
|
941458a381 | ||
|
|
560eb924b8 | ||
|
|
c3fab9e48b | ||
|
|
fc4dc74f9e | ||
|
|
35f0362910 | ||
|
|
27dfaa5f06 | ||
|
|
cf1866710a | ||
|
|
b3a5d06c4a | ||
|
|
7c904ff331 | ||
|
|
c7855a9dd1 | ||
|
|
5faee6a05f | ||
|
|
9213f9f54f | ||
|
|
c5e8351d76 | ||
|
|
e8e245ac0b | ||
|
|
2d8d6851d1 | ||
|
|
54f3e2ceda | ||
|
|
81d98917c3 | ||
|
|
8651f1e5d6 | ||
|
|
db1a26424d | ||
|
|
16405810dd | ||
|
|
f4f7fc1b0c | ||
|
|
e1145a743a | ||
|
|
6e5b54bbfe | ||
|
|
a948219e1a | ||
|
|
5675e926d5 | ||
|
|
cb7f8bbaab | ||
|
|
61e7fd8f40 | ||
|
|
76fab2671a | ||
|
|
dbc2bd2d62 | ||
|
|
73e62935ff | ||
|
|
c9e28b5482 | ||
|
|
6bf58eae4a | ||
|
|
3e09574b3c | ||
|
|
a7a00b9ca6 | ||
|
|
7a62e2df27 | ||
|
|
317f1ccae7 | ||
|
|
884631409a | ||
|
|
14dca5e2cb | ||
|
|
3a0b856d47 | ||
|
|
e66f8af022 | ||
|
|
13e4560d86 | ||
|
|
5ea789df7f | ||
|
|
debda9ec11 | ||
|
|
b3f126181b | ||
|
|
6bd110cae7 | ||
|
|
635f4ccde3 | ||
|
|
01da2685a5 | ||
|
|
b040aad8d1 | ||
|
|
84a0f08618 | ||
|
|
1f64c0d672 | ||
|
|
5bfff87e21 | ||
|
|
47db0e1c46 | ||
|
|
9a9fdc418f | ||
|
|
c30c89ed40 | ||
|
|
d7bb89968f | ||
|
|
f9b431d158 | ||
|
|
a1364896af | ||
|
|
2870a999a2 | ||
|
|
98646a6bd9 | ||
|
|
8ec894f576 | ||
|
|
254a67b9a8 | ||
|
|
eebfb79c27 | ||
|
|
1a1d7ec783 | ||
|
|
4482144b4d | ||
|
|
827279c9a8 | ||
|
|
b1d3488842 | ||
|
|
90112ae1ae | ||
|
|
1f4b9390b9 | ||
|
|
cc142aca40 | ||
|
|
89513b2c0d | ||
|
|
bfcd00acec | ||
|
|
9833b28a50 | ||
|
|
d0404dd29c | ||
|
|
9d3761e7f6 | ||
|
|
03515acc68 | ||
|
|
f8413f4cfb | ||
|
|
24f7f67e63 | ||
|
|
85129cdfe1 | ||
|
|
6d4f8b2731 | ||
|
|
24a0efcce1 | ||
|
|
ce6be1b4d5 | ||
|
|
723ac47c48 | ||
|
|
720d163bdb | ||
|
|
a74047d4e5 | ||
|
|
f6538f93c7 | ||
|
|
49cb0b3ef5 | ||
|
|
c81845ba3a | ||
|
|
053ae64008 | ||
|
|
48989840b8 | ||
|
|
36c7bcd87f | ||
|
|
a50eca1600 | ||
|
|
f70dc8eb46 | ||
|
|
f13d816c5b | ||
|
|
3429e658b6 | ||
|
|
0c6f9d6041 | ||
|
|
4a668499b0 | ||
|
|
f1057252b3 | ||
|
|
4e3c866536 | ||
|
|
c585cb370c | ||
|
|
2211a2b1d4 | ||
|
|
42069e2aac | ||
|
|
fd499a17fe | ||
|
|
0cc56d3da7 | ||
|
|
2096f55ce8 | ||
|
|
a4afe0ac10 | ||
|
|
d523b66970 | ||
|
|
192335249c | ||
|
|
e02879b3d7 | ||
|
|
bf7157fb3e | ||
|
|
7c2ae87c73 | ||
|
|
c1357bb2e1 | ||
|
|
c298c3773e | ||
|
|
7d09c6501c | ||
|
|
1a051d42af | ||
|
|
22bbcc5217 | ||
|
|
017f153002 | ||
|
|
cf4762ad04 | ||
|
|
6ba920e26a | ||
|
|
a66acf0ef9 | ||
|
|
8ab982ac38 | ||
|
|
0c32a126a9 | ||
|
|
fd47881f88 | ||
|
|
61298235fd | ||
|
|
4a38277c3e | ||
|
|
fd7912f631 | ||
|
|
4f1b0e99e4 | ||
|
|
086e375cea | ||
|
|
f90b132ccb | ||
|
|
f4bd217445 | ||
|
|
5524e2f689 | ||
|
|
42b8201f37 | ||
|
|
f71c821128 | ||
|
|
28330d3ad6 | ||
|
|
5afd576a3c | ||
|
|
edcf82d139 | ||
|
|
69e5143a27 | ||
|
|
215d0faa53 | ||
|
|
470142a640 | ||
|
|
8e67a8d679 | ||
|
|
be5baf9c87 | ||
|
|
7a05e42f3f | ||
|
|
34240ec01f | ||
|
|
0bf6eccb8b | ||
|
|
1f3b694231 | ||
|
|
470358585d | ||
|
|
4c69b555ae | ||
|
|
aef60afcd8 | ||
|
|
ec753c50a5 | ||
|
|
cbebe878aa | ||
|
|
e9d930507e | ||
|
|
3eba768d06 | ||
|
|
76a5732997 | ||
|
|
32987c1274 | ||
|
|
8990b97a32 | ||
|
|
65b427121f | ||
|
|
81dd259672 | ||
|
|
05c2031d47 | ||
|
|
2b2de7550f | ||
|
|
f9a8244086 | ||
|
|
86f3904b8a | ||
|
|
017aab4b0a | ||
|
|
e33777c961 | ||
|
|
ac14acd042 | ||
|
|
e9f07df010 | ||
|
|
5d3855cdfa | ||
|
|
938e76fb4a | ||
|
|
c0f6722c18 | ||
|
|
54e8922bc3 | ||
|
|
81b47dbf45 | ||
|
|
312401d80f | ||
|
|
8927ab7e36 | ||
|
|
6d99576e36 | ||
|
|
5618cd19cb | ||
|
|
da11c6f5b6 | ||
|
|
6e370e776a | ||
|
|
ab6b319738 | ||
|
|
aca03cacf9 | ||
|
|
01b5e7ced0 | ||
|
|
6d8ef1d4a4 | ||
|
|
f029636757 | ||
|
|
168b396daa | ||
|
|
0873a59392 | ||
|
|
3c646f57e6 | ||
|
|
940db64678 | ||
|
|
da1c980743 | ||
|
|
e3a4b636f7 | ||
|
|
9434898efa | ||
|
|
5d1c17e6ff | ||
|
|
4f736b352a | ||
|
|
037e2fbab1 | ||
|
|
4b24858631 | ||
|
|
81904a3a7b | ||
|
|
6e63a789cb | ||
|
|
4b977ec4df | ||
|
|
fa692caaa8 | ||
|
|
7082003e4a | ||
|
|
dec35d9b8a | ||
|
|
90711465ba | ||
|
|
1bb74f20b4 | ||
|
|
b2cce37490 | ||
|
|
22e41eeacc | ||
|
|
600f2543a6 | ||
|
|
9e7185ae6d | ||
|
|
11725afa5a | ||
|
|
3bb94d05be | ||
|
|
7290db72e8 | ||
|
|
4dc92b081e | ||
|
|
68c614d9dd | ||
|
|
efca8d0161 | ||
|
|
6d86b15800 | ||
|
|
f43b2df6fc | ||
|
|
9f3eb593f8 | ||
|
|
9ca7c41fe7 | ||
|
|
f39746c116 | ||
|
|
88f1dc2c01 | ||
|
|
ee3daae665 | ||
|
|
9a47f06c2a | ||
|
|
6df173d30a | ||
|
|
f992394f6f | ||
|
|
98c19fcd7e | ||
|
|
12cc2439af | ||
|
|
2341100397 | ||
|
|
b15c9e4e39 | ||
|
|
dbf860ca7a | ||
|
|
aa481f415d | ||
|
|
59eeae791a | ||
|
|
0691a4420c | ||
|
|
66e744dd57 | ||
|
|
9f5eb72cec | ||
|
|
a021ab6e42 | ||
|
|
6a316e850c | ||
|
|
69413c1bed | ||
|
|
c152cd944e | ||
|
|
a055864676 | ||
|
|
cb64da2d5f | ||
|
|
5633e70a11 | ||
|
|
c1f6d0692b | ||
|
|
fe527f1668 | ||
|
|
774a2313fb | ||
|
|
a5f07706f5 | ||
|
|
2b6b8accf1 | ||
|
|
e6aca5533b | ||
|
|
dc60cc3ccb | ||
|
|
5613993019 | ||
|
|
1613101bd6 | ||
|
|
cb3440494e | ||
|
|
78b979f397 | ||
|
|
6742b71208 | ||
|
|
e0178ac2b5 | ||
|
|
e2141decaa | ||
|
|
4a68c62e72 | ||
|
|
bc7d5704f9 | ||
|
|
a984be2535 | ||
|
|
026f96d473 | ||
|
|
0dbca7895e | ||
|
|
0f390668f7 | ||
|
|
f4ced70d8e | ||
|
|
7b805df433 | ||
|
|
4b66171388 | ||
|
|
0cf80753ae | ||
|
|
6f3d315851 | ||
|
|
475d4b4a80 | ||
|
|
bd8633de0d | ||
|
|
5d2937c896 | ||
|
|
eb89284834 | ||
|
|
f5777b2a68 | ||
|
|
9431a4dd6c | ||
|
|
82a4dfdfd5 | ||
|
|
9bd63d15e9 | ||
|
|
f6e5c8e0b1 | ||
|
|
3b56f6dc6f | ||
|
|
2c24079552 | ||
|
|
f58a846826 | ||
|
|
8bc74a8833 | ||
|
|
5bd0033ff0 | ||
|
|
e45758be80 | ||
|
|
5033348eed | ||
|
|
2327fd099c | ||
|
|
6afa02ea71 | ||
|
|
9cf1b38e56 | ||
|
|
241a9ffea5 | ||
|
|
3e0749ad60 | ||
|
|
0f55ff4295 | ||
|
|
d8f8c34991 | ||
|
|
7ddd323b65 | ||
|
|
36bbb6e708 | ||
|
|
06638a8cdc | ||
|
|
42d8a8fde0 | ||
|
|
e2997fbbce | ||
|
|
5dd671eb4a | ||
|
|
59aff0f196 | ||
|
|
548def29b3 | ||
|
|
84d59eb7d4 | ||
|
|
1cc3b599a9 | ||
|
|
0e02b56d20 | ||
|
|
4e8d99f557 | ||
|
|
f3add6e3e3 | ||
|
|
32a2a93eb5 | ||
|
|
0090ee516f | ||
|
|
42e4e958df | ||
|
|
b6970d1da6 | ||
|
|
d7ce6c9a4a | ||
|
|
e6d18cbec8 | ||
|
|
3140f6b19f | ||
|
|
b493bf88a8 | ||
|
|
8877609f2a | ||
|
|
d3e5aa00df | ||
|
|
2b88f58e58 | ||
|
|
1f6fd659d5 | ||
|
|
42583407d0 | ||
|
|
cb6b30f4cf | ||
|
|
7a372bfbd1 | ||
|
|
adb8763706 | ||
|
|
ce709408af | ||
|
|
d577b58c52 | ||
|
|
d939b081ff | ||
|
|
b029ff0cfb | ||
|
|
2e473e7a65 | ||
|
|
8b0c2b7815 | ||
|
|
9eb9f83108 | ||
|
|
d724473bc2 | ||
|
|
ed3dac054a | ||
|
|
2c7294684f | ||
|
|
79016437e4 | ||
|
|
20d8131661 | ||
|
|
14b6bb18f3 | ||
|
|
c919b2bce3 | ||
|
|
bf4d65b179 | ||
|
|
6e7a50468b | ||
|
|
fd3c5900b0 | ||
|
|
b531fe4243 | ||
|
|
df39e73748 | ||
|
|
5478aeb96b | ||
|
|
4ef190261a | ||
|
|
164fd80065 | ||
|
|
03d437712c | ||
|
|
9b14c201c4 | ||
|
|
b4710bda0e | ||
|
|
1821ffbe7b | ||
|
|
7687328957 | ||
|
|
c02e1277d9 | ||
|
|
d1f0508391 | ||
|
|
ccf6395796 | ||
|
|
e9e1d47aff | ||
|
|
7e609e30a2 | ||
|
|
b97acbf8a5 | ||
|
|
cca1a2a270 | ||
|
|
ccbd7b7fe6 | ||
|
|
7e1865f676 | ||
|
|
da3c1f946c | ||
|
|
6c09cbc2d9 | ||
|
|
cfcb81b657 | ||
|
|
948458c404 | ||
|
|
2886672d20 | ||
|
|
3d0f2f2d80 | ||
|
|
a0b20127cb | ||
|
|
8e0092d647 | ||
|
|
347444edd2 | ||
|
|
559fa300a2 | ||
|
|
e1d6813fa7 | ||
|
|
fae99b9a29 | ||
|
|
75d2d58d0d | ||
|
|
fd2f9dc972 | ||
|
|
0697ca9bef | ||
|
|
759a7ba41f | ||
|
|
109cb8d0fd | ||
|
|
3d031b30b5 | ||
|
|
e95915d4be | ||
|
|
005e52c803 | ||
|
|
6d22f2c129 | ||
|
|
7fdadb14ae | ||
|
|
4d76ed5f00 | ||
|
|
37dc166adc | ||
|
|
014d3f234f | ||
|
|
0bb3b2a110 | ||
|
|
e3aa5ec75d | ||
|
|
81f2f57666 | ||
|
|
562c475d2b | ||
|
|
9366cb3de9 | ||
|
|
9a2bd94e4a | ||
|
|
cc61b16d3d | ||
|
|
b56b8063f3 | ||
|
|
9b08c8b27b | ||
|
|
50f4ca8d1e | ||
|
|
74fb2f3f57 | ||
|
|
e18e08a983 | ||
|
|
9967218524 | ||
|
|
44a9e0d6e0 | ||
|
|
91f153b109 | ||
|
|
ead845f7bc | ||
|
|
0908cacb9f | ||
|
|
9dd47fe9ae | ||
|
|
724acdaba8 | ||
|
|
08bdd870a5 | ||
|
|
54a43f6045 | ||
|
|
25a9cc3608 | ||
|
|
7f52a53e62 | ||
|
|
20e9c82854 | ||
|
|
fe92e53f7b | ||
|
|
130aeb01b9 | ||
|
|
7439b5e362 | ||
|
|
166059b9bd | ||
|
|
7e5fc40768 | ||
|
|
aadb2c1e40 | ||
|
|
f317452c80 | ||
|
|
cb98a00cfa | ||
|
|
75deeb247a | ||
|
|
b17eec0647 | ||
|
|
5ddcc24563 | ||
|
|
9b4ecdf7bf | ||
|
|
6ba4efe1bb | ||
|
|
84ae6b4247 | ||
|
|
7510c6a539 | ||
|
|
de45907c91 | ||
|
|
17e9546671 | ||
|
|
c0a382fefc | ||
|
|
bf7ca3e9b1 | ||
|
|
af1c23d72d | ||
|
|
1bdea5727b | ||
|
|
3dc573cce1 | ||
|
|
4d1b8ca9e8 | ||
|
|
aec1cff72c | ||
|
|
520a0e71eb | ||
|
|
9485d36565 | ||
|
|
48ce8a1b0e | ||
|
|
8a4c469e16 | ||
|
|
3ae7ba20d1 | ||
|
|
2b4adcf8bb | ||
|
|
d8a237f94f | ||
|
|
02e62465b7 | ||
|
|
c0db543d49 | ||
|
|
5a1f620d88 | ||
|
|
c418283845 | ||
|
|
44b00cc050 | ||
|
|
3d33e1a713 | ||
|
|
ccd1040ba6 | ||
|
|
35f7d6d71e | ||
|
|
be3da7d66c | ||
|
|
ccb706aef1 | ||
|
|
20d1c4fb23 | ||
|
|
99a459ae4f | ||
|
|
e8e4cb5ebe | ||
|
|
87175e6e89 | ||
|
|
301340b512 | ||
|
|
e4388201c2 | ||
|
|
ac3b2b7c80 | ||
|
|
5e79354705 | ||
|
|
d39ef0693f | ||
|
|
2577333d79 | ||
|
|
115c335916 | ||
|
|
4bfbdf476f | ||
|
|
faea8cd616 | ||
|
|
6f1db7f9ef | ||
|
|
204977cc49 | ||
|
|
8e825a58f0 | ||
|
|
3e567c6ffb | ||
|
|
8845f2a964 | ||
|
|
a1400992af | ||
|
|
fa721e2e51 | ||
|
|
c46a315280 | ||
|
|
81d6f34c35 | ||
|
|
6825e3d389 | ||
|
|
01e329234b | ||
|
|
cdac73a18a | ||
|
|
3469349da5 | ||
|
|
9d814f9b66 | ||
|
|
81bcf5d6df | ||
|
|
b5c595d1d6 | ||
|
|
484872cc11 | ||
|
|
c96f6df171 | ||
|
|
2a98c2e8b4 | ||
|
|
36e6ac3301 | ||
|
|
c20a0d3f32 | ||
|
|
a609812960 | ||
|
|
b6f3e66770 | ||
|
|
0f41e98e5e | ||
|
|
a808056229 | ||
|
|
314bdebde3 | ||
|
|
ab7afb342c | ||
|
|
691049617d | ||
|
|
f747d58c71 | ||
|
|
c193a38782 | ||
|
|
4e8b0ba168 | ||
|
|
fd37134fcd | ||
|
|
5acf1aaf0e | ||
|
|
d0e3bbc55f | ||
|
|
0c9dfb5829 | ||
|
|
987bd6bcc1 | ||
|
|
939cf7c0df | ||
|
|
9f2256b547 | ||
|
|
b301b9fb95 | ||
|
|
a61c49cf68 | ||
|
|
9ab2967129 | ||
|
|
1aefb38a7c | ||
|
|
c359312e9d | ||
|
|
fe24317a9e | ||
|
|
68996d56ee | ||
|
|
9e2843460e | ||
|
|
2e08287e1c | ||
|
|
ccf10e1444 | ||
|
|
8172f648ab | ||
|
|
113816f418 | ||
|
|
85bd807bf7 | ||
|
|
a17827dbf4 | ||
|
|
ef1f29a34f | ||
|
|
e283a6a955 | ||
|
|
7944627dab | ||
|
|
7757b10b4d | ||
|
|
2b182e182b | ||
|
|
d6fafd5b34 | ||
|
|
f36b3a0912 | ||
|
|
04cae30629 | ||
|
|
38cdeeed6a | ||
|
|
0d9ab96cf5 | ||
|
|
261420973e | ||
|
|
d058101e8c | ||
|
|
128f39dcb3 | ||
|
|
5d6cbf256a | ||
|
|
c904c43c22 | ||
|
|
401ec5eb6e | ||
|
|
9a54f676fd | ||
|
|
a7f3167776 | ||
|
|
b3928e9677 | ||
|
|
de0bbdffe2 | ||
|
|
0cf835a3fb | ||
|
|
527dbdaec3 | ||
|
|
3fee5ee3ee | ||
|
|
df62fa34d7 | ||
|
|
7a2709b612 | ||
|
|
508c6e1f66 | ||
|
|
2b4d282ec1 | ||
|
|
e977e27d79 | ||
|
|
1f0693cec4 | ||
|
|
6e0492a4a3 | ||
|
|
67eeaca923 | ||
|
|
5b589b26d8 | ||
|
|
65b821508b | ||
|
|
fc178439ed | ||
|
|
5c9b6016b2 | ||
|
|
32aad0d11a | ||
|
|
d81dfc2149 | ||
|
|
3aa1fea626 | ||
|
|
c4f029e7df | ||
|
|
81394de63a | ||
|
|
f986714cc0 | ||
|
|
f4fe6d65d5 | ||
|
|
2c38bd8f25 | ||
|
|
49909e65ff | ||
|
|
120fa556fe | ||
|
|
fa2d19f4d1 | ||
|
|
a538074890 | ||
|
|
06c698e7bb | ||
|
|
88622f9727 | ||
|
|
d6d22d9fd8 | ||
|
|
8c64ff8571 | ||
|
|
87691a0336 | ||
|
|
d59aaf2a38 | ||
|
|
a16d87a8d9 | ||
|
|
5f8f4e59bc | ||
|
|
9425d7244d | ||
|
|
9c88d9fa2c | ||
|
|
d24ae043f0 | ||
|
|
94f39bbabd | ||
|
|
2d290c0469 | ||
|
|
808716993e | ||
|
|
3b29cbf283 | ||
|
|
5d2c2bcdee | ||
|
|
0b638a802a | ||
|
|
3f7fb7f457 | ||
|
|
cdfc24c38a | ||
|
|
26adb5cb1e | ||
|
|
54ee48ce2a | ||
|
|
675fd2d16e | ||
|
|
f6dd77015a | ||
|
|
5ff8c774e4 | ||
|
|
7b314d93ad | ||
|
|
0627ef7814 | ||
|
|
eccdce1913 | ||
|
|
06fa3577e9 | ||
|
|
a6c77f18f0 | ||
|
|
534f816889 | ||
|
|
0cd67e7e34 | ||
|
|
552f36d15e | ||
|
|
5e6af38e0f | ||
|
|
6d49905ac2 | ||
|
|
e75e47dfda | ||
|
|
18c3f42c4b | ||
|
|
b431a9772c | ||
|
|
f55f4d54a0 | ||
|
|
08398d6fae | ||
|
|
86825baba8 | ||
|
|
79fd9b4778 | ||
|
|
5aa26b059b | ||
|
|
3db3e64bb1 | ||
|
|
c2384c1fb7 | ||
|
|
50954b5682 | ||
|
|
4fa9ddb9f8 | ||
|
|
a8e4373188 | ||
|
|
7d29c5f911 | ||
|
|
a4a87cf2ce | ||
|
|
8501aa319d | ||
|
|
89d59c1b75 | ||
|
|
cb40e6bc89 | ||
|
|
c5471ebca7 | ||
|
|
456885000e | ||
|
|
03a9b6336b | ||
|
|
c01a93635f | ||
|
|
5b9fe1ac1c | ||
|
|
cb205164a8 | ||
|
|
0ac5eb8840 | ||
|
|
0ed0b38ea9 | ||
|
|
e6c2d4521f | ||
|
|
7e4c038354 | ||
|
|
c4bd8c4584 | ||
|
|
bc6702acc4 | ||
|
|
21b12114ab | ||
|
|
d571a18f7b | ||
|
|
2233f3a8f2 | ||
|
|
2895be61be | ||
|
|
8a79bc9510 | ||
|
|
f6be201e66 | ||
|
|
974dd9b519 | ||
|
|
939f943580 | ||
|
|
c12a1ef16d | ||
|
|
c51173651e | ||
|
|
96a2a93c8c | ||
|
|
794baab0c9 | ||
|
|
0ac7b80eae | ||
|
|
6795d1c6a4 | ||
|
|
d9eac56f1b | ||
|
|
325e5cda21 | ||
|
|
a8f8a8ddb4 | ||
|
|
3507802eb9 | ||
|
|
9588bdb7cc | ||
|
|
97ca2dcc79 | ||
|
|
44df3e9344 | ||
|
|
668c12bac7 | ||
|
|
f843bf7c08 |
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,744 @@
|
||||
import re, os, sys, math, time, zlib, datetime, hashlib
|
||||
import ctypes, ctypes.wintypes, pefile, multiprocessing
|
||||
import numpy as np
|
||||
|
||||
####################################################################################################
|
||||
|
||||
try:
|
||||
import orjson
|
||||
JSON_DUMPS = lambda x: orjson.dumps(x).decode('utf-8')
|
||||
JSON_LOADS = orjson.loads
|
||||
HAS_ORJSON = True
|
||||
|
||||
except ImportError:
|
||||
import json
|
||||
JSON_DUMPS = json.dumps
|
||||
JSON_LOADS = json.loads
|
||||
HAS_ORJSON = False
|
||||
|
||||
####################################################################################################
|
||||
|
||||
JSONL_PATH = "pe_features.jsonl"
|
||||
BATCH_SIZE = 1000
|
||||
MAX_FILE_SIZE = 4 * 1024 * 1024 * 1024
|
||||
MAX_WORKERS = os.cpu_count() - 2
|
||||
|
||||
####################################################################################################
|
||||
|
||||
class WINTRUST_FILE_INFO(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("cbStruct", ctypes.wintypes.DWORD),
|
||||
("pcwszFilePath", ctypes.wintypes.LPCWSTR),
|
||||
("hFile", ctypes.wintypes.HANDLE),
|
||||
("pgKnownSubject", ctypes.wintypes.LPVOID)
|
||||
]
|
||||
|
||||
class WINTRUST_DATA_UNION(ctypes.Union):
|
||||
_fields_ = [
|
||||
("pFile", ctypes.POINTER(WINTRUST_FILE_INFO)),
|
||||
("pCatalog", ctypes.wintypes.LPVOID),
|
||||
("pBlob", ctypes.wintypes.LPVOID),
|
||||
("pSgnr", ctypes.wintypes.LPVOID),
|
||||
("pCert", ctypes.wintypes.LPVOID)
|
||||
]
|
||||
|
||||
class WINTRUST_DATA(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("cbStruct", ctypes.wintypes.DWORD),
|
||||
("pPolicyCallbackData", ctypes.wintypes.LPVOID),
|
||||
("pSIPClientData", ctypes.wintypes.LPVOID),
|
||||
("dwUIChoice", ctypes.wintypes.DWORD),
|
||||
("fdwRevocationChecks", ctypes.wintypes.DWORD),
|
||||
("dwUnionChoice", ctypes.wintypes.DWORD),
|
||||
("u", WINTRUST_DATA_UNION),
|
||||
("dwStateAction", ctypes.wintypes.DWORD),
|
||||
("hWVTStateData", ctypes.wintypes.HANDLE),
|
||||
("pwszURLReference", ctypes.wintypes.LPCWSTR),
|
||||
("dwProvFlags", ctypes.wintypes.DWORD),
|
||||
("dwUIContext", ctypes.wintypes.DWORD),
|
||||
("pSignatureSettings", ctypes.wintypes.LPVOID)
|
||||
]
|
||||
|
||||
class GUID(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("Data1", ctypes.wintypes.DWORD),
|
||||
("Data2", ctypes.wintypes.WORD),
|
||||
("Data3", ctypes.wintypes.WORD),
|
||||
("Data4", ctypes.c_ubyte * 8)
|
||||
]
|
||||
|
||||
V2_GUID = GUID(0x00AAC56B, 0xCD44, 0x11D0, (ctypes.c_ubyte * 8)(0x8C, 0xC2, 0x00, 0xC0, 0x4F, 0xC2, 0x95, 0xEE))
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def verify_signature(file_path):
|
||||
if os.name != 'nt':
|
||||
return 0
|
||||
try:
|
||||
wintrust = ctypes.windll.wintrust
|
||||
wintrust.WinVerifyTrust.argtypes = [ctypes.wintypes.HWND, ctypes.POINTER(GUID), ctypes.wintypes.LPVOID]
|
||||
wintrust.WinVerifyTrust.restype = ctypes.wintypes.LONG
|
||||
|
||||
abs_path = os.path.abspath(file_path)
|
||||
|
||||
fi = WINTRUST_FILE_INFO()
|
||||
fi.cbStruct = ctypes.sizeof(WINTRUST_FILE_INFO)
|
||||
fi.pcwszFilePath = abs_path
|
||||
fi.hFile = None
|
||||
fi.pgKnownSubject = None
|
||||
|
||||
wt_union = WINTRUST_DATA_UNION()
|
||||
wt_union.pFile = ctypes.pointer(fi)
|
||||
|
||||
td = WINTRUST_DATA()
|
||||
td.cbStruct = ctypes.sizeof(WINTRUST_DATA)
|
||||
td.pPolicyCallbackData = None
|
||||
td.pSIPClientData = None
|
||||
td.dwUIChoice = 2
|
||||
td.fdwRevocationChecks = 0
|
||||
td.dwUnionChoice = 1
|
||||
td.u = wt_union
|
||||
td.dwStateAction = 0
|
||||
td.hWVTStateData = None
|
||||
td.pwszURLReference = None
|
||||
td.dwProvFlags = 0
|
||||
td.dwUIContext = 0
|
||||
td.pSignatureSettings = None
|
||||
|
||||
return 1 if wintrust.WinVerifyTrust(None, ctypes.byref(V2_GUID), ctypes.byref(td)) == 0 else 0
|
||||
except Exception:
|
||||
return 0
|
||||
|
||||
####################################################################################################
|
||||
|
||||
class FeatureExtractor:
|
||||
_STRING_PATTERN = re.compile(b'[\x20-\x7E]{5,}')
|
||||
|
||||
_BYTE_HIST_KEYS = [f'ByteHist_{i:02X}' for i in range(256)]
|
||||
_BYTE_ENT_KEYS = [f'ByteEnt_{i:02X}' for i in range(256)]
|
||||
_STRING_KEYS = [f'StringHist_{i:02d}' for i in range(95)]
|
||||
_SECTION_HASH_KEYS = [f'SectionHash_{i:02d}' for i in range(50)]
|
||||
_CHAR_FLAGS = [0x00000020, 0x00000040, 0x00000080, 0x02000000, 0x20000000, 0x40000000, 0x80000000]
|
||||
_CHAR_COUNT_KEYS = {flag: f'Char_{flag:08X}_Count' for flag in _CHAR_FLAGS}
|
||||
_CHAR_ENT_KEYS = {flag: f'Char_{flag:08X}_MeanEntropy' for flag in _CHAR_FLAGS}
|
||||
|
||||
_C_LOG_C_TABLE = np.zeros(2049, dtype=np.float64)
|
||||
_C_LOG_C_TABLE[1:] = np.arange(1, 2049) * np.log2(np.arange(1, 2049))
|
||||
|
||||
@staticmethod
|
||||
def _safe_float(val):
|
||||
try:
|
||||
f = float(val)
|
||||
return f if not (math.isinf(f) or math.isnan(f)) else 0.0
|
||||
|
||||
except Exception:
|
||||
return 0.0
|
||||
|
||||
@staticmethod
|
||||
def _calc_entropy(data):
|
||||
if not data:
|
||||
return 0.0
|
||||
|
||||
arr = np.frombuffer(data, dtype=np.uint8)
|
||||
sz = len(arr)
|
||||
counts = np.bincount(arr, minlength=256)
|
||||
counts = counts[counts > 0]
|
||||
|
||||
return float(np.log2(sz) - np.sum(counts * np.log2(counts)) / sz)
|
||||
|
||||
@classmethod
|
||||
def _extract_strings(cls, file_bytes):
|
||||
res = {"StringCount": 0.0, "StringMeanLength": 0.0, "StringEntropy": 0.0}
|
||||
res.update(dict.fromkeys(cls._STRING_KEYS, 0.0))
|
||||
|
||||
matches = cls._STRING_PATTERN.findall(file_bytes)
|
||||
if not matches:
|
||||
return res
|
||||
|
||||
combined_data = b''.join(matches)
|
||||
count = len(matches)
|
||||
total_len = len(combined_data)
|
||||
|
||||
res["StringCount"] = float(count)
|
||||
res["StringMeanLength"] = float(total_len) / count
|
||||
res["StringEntropy"] = cls._calc_entropy(combined_data)
|
||||
|
||||
arr = np.frombuffer(combined_data, dtype=np.uint8)
|
||||
counts = np.bincount(arr, minlength=127)
|
||||
char_counts = counts[0x20:0x7F]
|
||||
|
||||
res.update(dict(zip(cls._STRING_KEYS, char_counts.astype(np.float64) / total_len)))
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
def _extract_histograms(cls, file_bytes):
|
||||
arr = np.frombuffer(file_bytes, dtype=np.uint8)
|
||||
sz = len(arr)
|
||||
res = {}
|
||||
|
||||
if sz == 0:
|
||||
res.update(dict.fromkeys(cls._BYTE_HIST_KEYS, 0.0))
|
||||
res.update(dict.fromkeys(cls._BYTE_ENT_KEYS, 0.0))
|
||||
return res
|
||||
|
||||
byte_counts = np.bincount(arr, minlength=256)
|
||||
byte_hist = byte_counts.astype(np.float64) / sz
|
||||
res.update(dict(zip(cls._BYTE_HIST_KEYS, byte_hist)))
|
||||
|
||||
ent_hist = np.zeros(256, dtype=np.float64)
|
||||
window = 2048
|
||||
step = 1024
|
||||
|
||||
if sz < window:
|
||||
w_counts = byte_counts
|
||||
p = w_counts[w_counts > 0] / float(sz)
|
||||
entropy = float(-np.sum(p * np.log2(p)))
|
||||
ent_bin = min(int(entropy * 2.0), 15)
|
||||
byte_bins = w_counts.reshape(16, 16).sum(axis=1)
|
||||
idx_start = ent_bin * 16
|
||||
ent_hist[idx_start : idx_start+16] += byte_bins
|
||||
|
||||
else:
|
||||
num_windows = (sz - window) // step + 1
|
||||
batch_windows = 10000
|
||||
|
||||
for w_start in range(0, num_windows, batch_windows):
|
||||
w_end = min(w_start + batch_windows, num_windows)
|
||||
b_num = w_end - w_start
|
||||
|
||||
start_byte = w_start * step
|
||||
end_byte = start_byte + b_num * step + step
|
||||
b_arr = arr[start_byte:end_byte]
|
||||
|
||||
arr_2d = b_arr.reshape(b_num + 1, step)
|
||||
offsets = (np.arange(b_num + 1, dtype=np.int32)[:, None] * 256)
|
||||
flat_idx = (arr_2d.astype(np.int32) + offsets).ravel()
|
||||
|
||||
chunk_counts = np.bincount(flat_idx, minlength=(b_num + 1) * 256).reshape(b_num + 1, 256)
|
||||
window_counts = chunk_counts[:-1] + chunk_counts[1:]
|
||||
|
||||
sum_c_log_c = np.sum(cls._C_LOG_C_TABLE[window_counts], axis=1)
|
||||
entropies = 11.0 - sum_c_log_c / 2048.0
|
||||
|
||||
ent_bins = (entropies * 2.0).astype(np.int32)
|
||||
np.clip(ent_bins, 0, 15, out=ent_bins)
|
||||
|
||||
byte_bins = window_counts.reshape(b_num, 16, 16).sum(axis=2)
|
||||
flat_ent_bins = (ent_bins[:, None] * 16 + np.arange(16)).ravel()
|
||||
np.add.at(ent_hist, flat_ent_bins, byte_bins.ravel())
|
||||
|
||||
ent_sum = np.sum(ent_hist)
|
||||
if ent_sum > 0:
|
||||
ent_hist /= ent_sum
|
||||
|
||||
res.update(dict(zip(cls._BYTE_ENT_KEYS, ent_hist)))
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
def _extract_overlay_features(cls, pe, fsize):
|
||||
overlay_offset = pe.get_overlay_data_start_offset()
|
||||
if not overlay_offset or overlay_offset >= fsize:
|
||||
return {"HasOverlay": 0.0, "OverlaySize": 0.0, "OverlayRatio": 0.0, "OverlayEntropy": 0.0}
|
||||
|
||||
overlay_data = pe.get_overlay()
|
||||
if not overlay_data:
|
||||
return {"HasOverlay": 0.0, "OverlaySize": 0.0, "OverlayRatio": 0.0, "OverlayEntropy": 0.0}
|
||||
|
||||
sz = len(overlay_data)
|
||||
return {
|
||||
"HasOverlay": 1.0,
|
||||
"OverlaySize": float(sz),
|
||||
"OverlayRatio": float(sz) / float(fsize),
|
||||
"OverlayEntropy": cls._calc_entropy(overlay_data)
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def _extract_rich_header(cls, pe):
|
||||
if not hasattr(pe, 'RICH_HEADER') or not pe.RICH_HEADER:
|
||||
return {"HasRichHeader": 0.0, "RichHeaderCount": 0.0}
|
||||
|
||||
return {
|
||||
"HasRichHeader": 1.0,
|
||||
"RichHeaderCount": float(len(pe.RICH_HEADER.values) // 2) if hasattr(pe.RICH_HEADER, 'values') else 0.0
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def _extract_ep_anomalies(cls, pe):
|
||||
if not hasattr(pe, 'OPTIONAL_HEADER') or not hasattr(pe, 'sections') or not pe.sections:
|
||||
return {"EntryPointSectionIndex": -1.0, "EntryPointInExecutable": 0.0, "EntryPointInLastSection": 0.0}
|
||||
|
||||
ep = pe.OPTIONAL_HEADER.AddressOfEntryPoint
|
||||
ep_section_idx = -1
|
||||
ep_in_exec = 0.0
|
||||
|
||||
for idx, sec in enumerate(pe.sections):
|
||||
if sec.VirtualAddress <= ep < (sec.VirtualAddress + sec.Misc_VirtualSize):
|
||||
ep_section_idx = idx
|
||||
if sec.Characteristics & 0x20000000:
|
||||
ep_in_exec = 1.0
|
||||
break
|
||||
|
||||
return {
|
||||
"EntryPointSectionIndex": float(ep_section_idx),
|
||||
"EntryPointInExecutable": ep_in_exec,
|
||||
"EntryPointInLastSection": 1.0 if ep_section_idx == len(pe.sections) - 1 else 0.0
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def _extract_advanced_resources(cls, pe):
|
||||
res_data = {
|
||||
"ResourceMaxEntropy": 0.0,
|
||||
"ResourceMinEntropy": 0.0,
|
||||
"ResourceMeanEntropy": 0.0,
|
||||
"ResourceLangCount": 0.0,
|
||||
"ResourceRCDataCount": 0.0
|
||||
}
|
||||
|
||||
if not hasattr(pe, 'DIRECTORY_ENTRY_RESOURCE'):
|
||||
return res_data
|
||||
|
||||
entropies = []
|
||||
langs = set()
|
||||
rcdata_count = 0
|
||||
|
||||
for entry_type in pe.DIRECTORY_ENTRY_RESOURCE.entries:
|
||||
if hasattr(entry_type, 'id') and entry_type.id == 10:
|
||||
rcdata_count += len(getattr(entry_type.directory, 'entries', []))
|
||||
|
||||
if not hasattr(entry_type, 'directory'):
|
||||
continue
|
||||
|
||||
for entry_id in entry_type.directory.entries:
|
||||
if not hasattr(entry_id, 'directory'):
|
||||
continue
|
||||
|
||||
for entry_lang in entry_id.directory.entries:
|
||||
if hasattr(entry_lang, 'id'):
|
||||
langs.add(entry_lang.id)
|
||||
|
||||
if hasattr(entry_lang, 'data'):
|
||||
try:
|
||||
data = pe.get_data(entry_lang.data.struct.OffsetToData, entry_lang.data.struct.Size)
|
||||
entropies.append(cls._calc_entropy(data))
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
res_data["ResourceLangCount"] = float(len(langs))
|
||||
res_data["ResourceRCDataCount"] = float(rcdata_count)
|
||||
|
||||
if entropies:
|
||||
res_data["ResourceMaxEntropy"] = max(entropies)
|
||||
res_data["ResourceMinEntropy"] = min(entropies)
|
||||
res_data["ResourceMeanEntropy"] = sum(entropies) / len(entropies)
|
||||
|
||||
return res_data
|
||||
|
||||
@classmethod
|
||||
def _extract_load_config(cls, pe):
|
||||
cfg = {"HasLoadConfig": 0.0, "HasCFG": 0.0, "HasSEHTable": 0.0}
|
||||
if hasattr(pe, 'DIRECTORY_ENTRY_LOAD_CONFIG'):
|
||||
cfg["HasLoadConfig"] = 1.0
|
||||
struct = pe.DIRECTORY_ENTRY_LOAD_CONFIG.struct
|
||||
|
||||
if getattr(struct, 'GuardCFFunctionTable', 0) != 0:
|
||||
cfg["HasCFG"] = 1.0
|
||||
|
||||
if getattr(struct, 'SEHandlerTable', 0) != 0:
|
||||
cfg["HasSEHTable"] = 1.0
|
||||
|
||||
return cfg
|
||||
|
||||
@classmethod
|
||||
def _extract_security_directory(cls, pe, file_bytes, fsize):
|
||||
res = {"HasSignature": 0.0, "SignatureCount": 0.0}
|
||||
if not hasattr(pe, 'OPTIONAL_HEADER') or not hasattr(pe.OPTIONAL_HEADER, 'DATA_DIRECTORY'):
|
||||
return res
|
||||
|
||||
sec_idx = 4
|
||||
directories = pe.OPTIONAL_HEADER.DATA_DIRECTORY
|
||||
if len(directories) <= sec_idx:
|
||||
return res
|
||||
|
||||
sec_dir = directories[sec_idx]
|
||||
if sec_dir.Size == 0 or sec_dir.VirtualAddress == 0:
|
||||
return res
|
||||
|
||||
res["HasSignature"] = 1.0
|
||||
offset = sec_dir.VirtualAddress
|
||||
size = sec_dir.Size
|
||||
|
||||
if offset + size > fsize or offset < 0 or size < 0:
|
||||
return res
|
||||
|
||||
count = 0
|
||||
curr = offset
|
||||
end = offset + size
|
||||
|
||||
while curr + 8 <= end:
|
||||
dw_length = int.from_bytes(file_bytes[curr:curr+4], 'little')
|
||||
if dw_length < 8:
|
||||
break
|
||||
|
||||
count += 1
|
||||
curr += (dw_length + 7) & ~7
|
||||
|
||||
res["SignatureCount"] = float(count)
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
def extract(cls, file_bytes, file_path, fsize):
|
||||
if fsize == 0:
|
||||
return None
|
||||
|
||||
base, dlls, apis, pe = {}, set(), set(), None
|
||||
try:
|
||||
pe = pefile.PE(data=file_bytes, fast_load=True)
|
||||
|
||||
try:
|
||||
pe.parse_rich_header()
|
||||
pe.parse_data_directories(directories=[
|
||||
pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_IMPORT'],
|
||||
pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_EXPORT'],
|
||||
pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_RESOURCE'],
|
||||
pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_DEBUG'],
|
||||
pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_TLS'],
|
||||
pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG']
|
||||
])
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
base['TrustSigned'] = float(verify_signature(file_path))
|
||||
base['FileEntropy'] = cls._calc_entropy(file_bytes)
|
||||
base['FileSize'] = float(fsize)
|
||||
|
||||
fh = pe.FILE_HEADER
|
||||
base['Machine'] = getattr(fh, 'Machine', 0)
|
||||
base['NumberOfSections'] = getattr(fh, 'NumberOfSections', 0)
|
||||
base['TimeDateStamp'] = getattr(fh, 'TimeDateStamp', 0)
|
||||
base['PointerToSymbolTable'] = getattr(fh, 'PointerToSymbolTable', 0)
|
||||
base['NumberOfSymbols'] = getattr(fh, 'NumberOfSymbols', 0)
|
||||
base['SizeOfOptionalHeader'] = getattr(fh, 'SizeOfOptionalHeader', 0)
|
||||
base['Characteristics'] = getattr(fh, 'Characteristics', 0)
|
||||
|
||||
curr_ts = datetime.datetime.utcnow().timestamp()
|
||||
base['HasInvalidTimestamp'] = 1.0 if (base['TimeDateStamp'] < 631152000 or base['TimeDateStamp'] > curr_ts + 2592000) else 0.0
|
||||
base['FileTimeException'] = 1.0 if base['TimeDateStamp'] == 0 else 0.0
|
||||
base['Is64Bit'] = 1.0 if base['Machine'] in (0x8664, 0xAA64, 0x0200) else 0.0
|
||||
base['IsExe'] = 1.0 if pe.is_exe() else 0.0
|
||||
base['IsDll'] = 1.0 if pe.is_dll() else 0.0
|
||||
base['ExceptionCount'] = 0.0
|
||||
|
||||
if hasattr(pe, 'OPTIONAL_HEADER'):
|
||||
op = pe.OPTIONAL_HEADER
|
||||
fields = ['Magic', 'MajorLinkerVersion', 'MinorLinkerVersion', 'SizeOfCode', 'SizeOfInitializedData', 'SizeOfUninitializedData', 'AddressOfEntryPoint', 'BaseOfCode', 'ImageBase', 'SectionAlignment', 'FileAlignment', 'MajorOperatingSystemVersion', 'MinorOperatingSystemVersion', 'MajorImageVersion', 'MinorImageVersion', 'MajorSubsystemVersion', 'MinorSubsystemVersion', 'SizeOfImage', 'SizeOfHeaders', 'CheckSum', 'Subsystem', 'DllCharacteristics', 'SizeOfStackReserve', 'SizeOfStackCommit', 'SizeOfHeapReserve', 'SizeOfHeapCommit', 'LoaderFlags', 'NumberOfRvaAndSizes']
|
||||
for f in fields: base[f] = getattr(op, f, 0)
|
||||
base['IsDriver'] = 1.0 if base.get('Subsystem') == 1 else 0.0
|
||||
|
||||
if hasattr(op, 'DATA_DIRECTORY'):
|
||||
for i, directory in enumerate(op.DATA_DIRECTORY):
|
||||
base[f'DataDirectory_{i}_Size'] = directory.Size
|
||||
base[f'DataDirectory_{i}_VA'] = directory.VirtualAddress
|
||||
|
||||
exc_idx = pefile.DIRECTORY_ENTRY.get('IMAGE_DIRECTORY_ENTRY_EXCEPTION', 3)
|
||||
if len(op.DATA_DIRECTORY) > exc_idx and op.DATA_DIRECTORY[exc_idx].Size > 0:
|
||||
base['ExceptionCount'] = op.DATA_DIRECTORY[exc_idx].Size // 12 if base['Machine'] in (0x8664, 0xAA64) else 0.0
|
||||
|
||||
res_flags_count = dict.fromkeys(cls._CHAR_COUNT_KEYS.values(), 0.0)
|
||||
res_flags_ent = dict.fromkeys(cls._CHAR_ENT_KEYS.values(), 0.0)
|
||||
res_sec_hash = dict.fromkeys(cls._SECTION_HASH_KEYS, 0.0)
|
||||
|
||||
if hasattr(pe, 'sections'):
|
||||
base['SectionCount'] = len(pe.sections)
|
||||
entropies, raw_sizes, v_sizes = [], [], []
|
||||
exec_sec, write_sec, read_sec, sec_exc = 0, 0, 0, 0
|
||||
|
||||
for section in pe.sections:
|
||||
sec_name = section.Name.rstrip(b'\x00')
|
||||
hash_val = zlib.crc32(sec_name) % 50
|
||||
res_sec_hash[cls._SECTION_HASH_KEYS[hash_val]] += 1.0
|
||||
|
||||
try:
|
||||
s_data = section.get_data()
|
||||
s_entropy = cls._calc_entropy(s_data)
|
||||
except Exception:
|
||||
s_entropy = 0.0
|
||||
|
||||
entropies.append(s_entropy)
|
||||
raw_sizes.append(section.SizeOfRawData)
|
||||
v_sizes.append(section.Misc_VirtualSize)
|
||||
|
||||
if section.Characteristics & 0x20000000:
|
||||
exec_sec += 1
|
||||
if section.Characteristics & 0x80000000:
|
||||
write_sec += 1
|
||||
if section.Characteristics & 0x40000000:
|
||||
read_sec += 1
|
||||
if section.SizeOfRawData + section.PointerToRawData > fsize:
|
||||
sec_exc = 1
|
||||
|
||||
for flag in cls._CHAR_FLAGS:
|
||||
if section.Characteristics & flag:
|
||||
res_flags_count[cls._CHAR_COUNT_KEYS[flag]] += 1.0
|
||||
res_flags_ent[cls._CHAR_ENT_KEYS[flag]] += s_entropy
|
||||
|
||||
base['SectionMaxEntropy'] = max(entropies) if entropies else 0.0
|
||||
base['SectionMinEntropy'] = min(entropies) if entropies else 0.0
|
||||
base['SectionMeanEntropy'] = sum(entropies) / len(entropies) if entropies else 0.0
|
||||
base['SectionMaxRawSize'] = max(raw_sizes) if raw_sizes else 0.0
|
||||
base['SectionMinRawSize'] = min(raw_sizes) if raw_sizes else 0.0
|
||||
base['SectionMeanRawSize'] = sum(raw_sizes) / len(raw_sizes) if raw_sizes else 0.0
|
||||
base['SectionMaxVSize'] = max(v_sizes) if v_sizes else 0.0
|
||||
base['SectionMinVSize'] = min(v_sizes) if v_sizes else 0.0
|
||||
base['SectionMeanVSize'] = sum(v_sizes) / len(v_sizes) if v_sizes else 0.0
|
||||
base['ExecutableSections'] = float(exec_sec)
|
||||
base['WritableSections'] = float(write_sec)
|
||||
base['ReadableSections'] = float(read_sec)
|
||||
base['SectionException'] = float(sec_exc)
|
||||
|
||||
for flag in cls._CHAR_FLAGS:
|
||||
cnt_key = cls._CHAR_COUNT_KEYS[flag]
|
||||
if res_flags_count[cnt_key] > 0:
|
||||
res_flags_ent[cls._CHAR_ENT_KEYS[flag]] /= res_flags_count[cnt_key]
|
||||
|
||||
base.update(res_flags_count)
|
||||
base.update(res_flags_ent)
|
||||
base.update(res_sec_hash)
|
||||
|
||||
string_keys = ['FileDescription', 'FileVersion', 'ProductName', 'ProductVersion', 'CompanyName', 'LegalCopyright', 'Comments', 'InternalName', 'LegalTrademarks', 'SpecialBuild', 'PrivateBuild']
|
||||
for key in string_keys:
|
||||
base[f'{key}Length'] = 0.0
|
||||
|
||||
if hasattr(pe, 'FileInfo'):
|
||||
for fileinfo_list in pe.FileInfo:
|
||||
for fileinfo in fileinfo_list:
|
||||
if getattr(fileinfo, 'name', '') in ('StringFileInfo', b'StringFileInfo'):
|
||||
for st in getattr(fileinfo, 'StringTable', []):
|
||||
for key, val in st.entries.items():
|
||||
|
||||
try:
|
||||
k = key.decode('utf-8', 'ignore') if isinstance(key, bytes) else str(key)
|
||||
if k in string_keys:
|
||||
v = val.decode('utf-8', 'ignore') if isinstance(val, bytes) else str(val)
|
||||
base[f'{k}Length'] = float(len(v))
|
||||
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
if hasattr(pe, 'DIRECTORY_ENTRY_TLS') and hasattr(pe.DIRECTORY_ENTRY_TLS, 'struct'):
|
||||
base['HasTlsCallbacks'] = 1.0 if getattr(pe.DIRECTORY_ENTRY_TLS.struct, 'AddressOfCallBacks', 0) != 0 else 0.0
|
||||
|
||||
if hasattr(pe, 'VS_FIXEDFILEINFO') and len(pe.VS_FIXEDFILEINFO) > 0:
|
||||
flags = getattr(pe.VS_FIXEDFILEINFO[0], 'FileFlags', 0)
|
||||
base['IsDebug'] = 1.0 if flags & 0x1 else 0.0
|
||||
base['IsPreRelease'] = 1.0 if flags & 0x2 else 0.0
|
||||
base['IsPatched'] = 1.0 if flags & 0x4 else 0.0
|
||||
base['IsPrivateBuild'] = 1.0 if flags & 0x8 else 0.0
|
||||
base['IsSpecialBuild'] = 1.0 if flags & 0x20 else 0.0
|
||||
|
||||
if hasattr(pe, 'DIRECTORY_ENTRY_IMPORT'):
|
||||
base['ImportCount'] = float(len(pe.DIRECTORY_ENTRY_IMPORT))
|
||||
func_count = 0
|
||||
for entry in pe.DIRECTORY_ENTRY_IMPORT:
|
||||
if getattr(entry, 'dll', None):
|
||||
try:
|
||||
dlls.add(entry.dll.decode('ascii', 'ignore').lower())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
for imp in getattr(entry, 'imports', []):
|
||||
func_count += 1
|
||||
if getattr(imp, 'name', None):
|
||||
try:
|
||||
apis.add(imp.name.decode('ascii', 'ignore'))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
base['ImportFunctionCount'] = float(func_count)
|
||||
|
||||
base['ExportCount'] = float(len(pe.DIRECTORY_ENTRY_EXPORT.symbols)) if (hasattr(pe, 'DIRECTORY_ENTRY_EXPORT') and hasattr(pe.DIRECTORY_ENTRY_EXPORT, 'symbols')) else 0.0
|
||||
|
||||
if hasattr(pe, 'DIRECTORY_ENTRY_RESOURCE'):
|
||||
icon_count = 0
|
||||
for entry in pe.DIRECTORY_ENTRY_RESOURCE.entries:
|
||||
if getattr(entry, 'id', None) == 3 and hasattr(entry, 'directory'):
|
||||
icon_count += len(getattr(entry.directory, 'entries', []))
|
||||
base['IconCount'] = float(icon_count)
|
||||
else:
|
||||
base['IconCount'] = 0.0
|
||||
|
||||
base['DebugCount'] = float(len(pe.DIRECTORY_ENTRY_DEBUG)) if hasattr(pe, 'DIRECTORY_ENTRY_DEBUG') else 0.0
|
||||
|
||||
base.update(cls._extract_strings(file_bytes))
|
||||
base.update(cls._extract_histograms(file_bytes))
|
||||
base.update(cls._extract_overlay_features(pe, fsize))
|
||||
base.update(cls._extract_rich_header(pe))
|
||||
base.update(cls._extract_ep_anomalies(pe))
|
||||
base.update(cls._extract_advanced_resources(pe))
|
||||
base.update(cls._extract_load_config(pe))
|
||||
base.update(cls._extract_security_directory(pe, file_bytes, fsize))
|
||||
|
||||
for k in base:
|
||||
base[k] = cls._safe_float(base[k])
|
||||
|
||||
return {"Base": base, "DLLs": list(dlls), "APIs": list(apis)}
|
||||
|
||||
except Exception:
|
||||
return None
|
||||
finally:
|
||||
if pe: pe.close()
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def load_existing_hashes():
|
||||
hashes = set()
|
||||
if not os.path.exists(JSONL_PATH):
|
||||
return hashes
|
||||
|
||||
pattern = re.compile(rb'"FileHash"\s*:\s*"([a-fA-F0-9]{64})"')
|
||||
with open(JSONL_PATH, 'rb') as f:
|
||||
for line in f:
|
||||
match = pattern.search(line)
|
||||
if match:
|
||||
hashes.add(match.group(1).decode('ascii'))
|
||||
|
||||
return hashes
|
||||
|
||||
GLOBAL_HASHES = set()
|
||||
|
||||
def worker_init(shared_hashes):
|
||||
global GLOBAL_HASHES
|
||||
GLOBAL_HASHES = shared_hashes
|
||||
|
||||
def _extract_worker_wrapper(args):
|
||||
return _extract_worker(*args)
|
||||
|
||||
def _extract_worker(file_path, label):
|
||||
try:
|
||||
fsize = os.path.getsize(file_path)
|
||||
if fsize == 0 or fsize > MAX_FILE_SIZE:
|
||||
return 'error', None
|
||||
|
||||
with open(file_path, "rb") as f:
|
||||
file_bytes = f.read()
|
||||
|
||||
sha256 = hashlib.sha256(file_bytes).hexdigest()
|
||||
|
||||
if sha256 in GLOBAL_HASHES:
|
||||
return 'skipped', None
|
||||
|
||||
res = FeatureExtractor.extract(file_bytes, file_path, fsize)
|
||||
if res:
|
||||
final_res = {"Label": label, "FileHash": sha256, **res}
|
||||
return 'success', (sha256, JSON_DUMPS(final_res) + "\n")
|
||||
|
||||
return 'error', None
|
||||
except Exception:
|
||||
return 'error', None
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def scan_and_save(path, label, existing_hashes):
|
||||
print(f"\n[*] Scanning path: {path} (Label={label})")
|
||||
files = [path] if os.path.isfile(path) else [os.path.join(r, f) for r, _, fs in os.walk(path) for f in fs]
|
||||
|
||||
total = len(files)
|
||||
if total == 0:
|
||||
print("[-] No files found.")
|
||||
return
|
||||
|
||||
print(f"[*] Found {total} files. Starting {MAX_WORKERS}-process extraction...\n")
|
||||
if HAS_ORJSON:
|
||||
print("[+] orjson acceleration enabled.")
|
||||
|
||||
count, errors, skipped, processed_files = 0, 0, 0, 0
|
||||
start_time = time.time()
|
||||
batch_buffer = []
|
||||
|
||||
pool = multiprocessing.Pool(processes=MAX_WORKERS, initializer=worker_init, initargs=(existing_hashes,))
|
||||
tasks = [(fpath, label) for fpath in files]
|
||||
|
||||
try:
|
||||
for status, data in pool.imap_unordered(_extract_worker_wrapper, tasks):
|
||||
processed_files += 1
|
||||
|
||||
if status == 'error':
|
||||
errors += 1
|
||||
elif status == 'skipped':
|
||||
skipped += 1
|
||||
elif status == 'success':
|
||||
sha256, json_str = data
|
||||
|
||||
if sha256 not in existing_hashes:
|
||||
existing_hashes.add(sha256)
|
||||
batch_buffer.append(json_str)
|
||||
count += 1
|
||||
else:
|
||||
skipped += 1
|
||||
|
||||
if len(batch_buffer) >= BATCH_SIZE:
|
||||
with open(JSONL_PATH, 'a', encoding='utf-8') as f_out:
|
||||
f_out.writelines(batch_buffer)
|
||||
batch_buffer.clear()
|
||||
|
||||
elapsed = time.time() - start_time
|
||||
rate = processed_files / elapsed if elapsed > 0 else 0
|
||||
sys.stdout.write(f"\r[{processed_files}/{total}] Added: {count} | Skipped: {skipped} | Errors: {errors} | Speed: {rate:.1f} files/s")
|
||||
sys.stdout.flush()
|
||||
|
||||
pool.close()
|
||||
pool.join()
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n[-] User aborted. Force terminating all child processes...")
|
||||
pool.terminate()
|
||||
pool.join()
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n[-] Critical error: {e}")
|
||||
pool.terminate()
|
||||
pool.join()
|
||||
|
||||
finally:
|
||||
if batch_buffer:
|
||||
try:
|
||||
with open(JSONL_PATH, 'a', encoding='utf-8') as f_out:
|
||||
f_out.writelines(batch_buffer)
|
||||
except Exception as save_err:
|
||||
print(f"\n[-] Failed to save remaining buffer: {save_err}")
|
||||
|
||||
print(f"\n\n[+] Scan complete. Total added: {count}, Total skipped: {skipped}")
|
||||
|
||||
####################################################################################################
|
||||
|
||||
if __name__ == "__main__":
|
||||
multiprocessing.freeze_support()
|
||||
|
||||
print("\n---------------- PE Dataset Builder v4.0 ----------------\n")
|
||||
|
||||
existing_hashes = load_existing_hashes()
|
||||
print(f"[+] Found {len(existing_hashes)} existing hashes.\n")
|
||||
|
||||
try:
|
||||
while True:
|
||||
print("-" * 57)
|
||||
target_path = input("\n[*] Enter path (file or directory): ").strip().strip('"').strip("'")
|
||||
if target_path.lower() in ['exit', 'q']:
|
||||
break
|
||||
|
||||
if not target_path or not os.path.exists(target_path):
|
||||
print("[-] Invalid path.")
|
||||
continue
|
||||
|
||||
while True:
|
||||
label_input = input("[*] Enter label (0=Safe, 1=Malware): ").strip()
|
||||
if label_input.lower() in ['exit', 'q']:
|
||||
sys.exit()
|
||||
if label_input in ['0', '1']:
|
||||
break
|
||||
|
||||
print("[-] Please enter 0 or 1.")
|
||||
|
||||
scan_and_save(target_path, int(label_input), existing_hashes)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n[-] Aborted.")
|
||||
@@ -0,0 +1,731 @@
|
||||
import os, re, sys, math, json, zlib, pefile, datetime
|
||||
import ctypes, ctypes.wintypes
|
||||
import numpy as np
|
||||
import onnxruntime as ort
|
||||
|
||||
####################################################################################################
|
||||
|
||||
MODEL_FILE = "Pefile_General_S1.onnx"
|
||||
FEATURE_FILE = "features.json"
|
||||
MAX_FILE_SIZE = 4 * 1024 * 1024 * 1024
|
||||
|
||||
####################################################################################################
|
||||
|
||||
class WINTRUST_FILE_INFO(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("cbStruct", ctypes.wintypes.DWORD),
|
||||
("pcwszFilePath", ctypes.wintypes.LPCWSTR),
|
||||
("hFile", ctypes.wintypes.HANDLE),
|
||||
("pgKnownSubject", ctypes.wintypes.LPVOID)
|
||||
]
|
||||
|
||||
class WINTRUST_DATA_UNION(ctypes.Union):
|
||||
_fields_ = [
|
||||
("pFile", ctypes.POINTER(WINTRUST_FILE_INFO)),
|
||||
("pCatalog", ctypes.wintypes.LPVOID),
|
||||
("pBlob", ctypes.wintypes.LPVOID),
|
||||
("pSgnr", ctypes.wintypes.LPVOID),
|
||||
("pCert", ctypes.wintypes.LPVOID)
|
||||
]
|
||||
|
||||
class WINTRUST_DATA(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("cbStruct", ctypes.wintypes.DWORD),
|
||||
("pPolicyCallbackData", ctypes.wintypes.LPVOID),
|
||||
("pSIPClientData", ctypes.wintypes.LPVOID),
|
||||
("dwUIChoice", ctypes.wintypes.DWORD),
|
||||
("fdwRevocationChecks", ctypes.wintypes.DWORD),
|
||||
("dwUnionChoice", ctypes.wintypes.DWORD),
|
||||
("u", WINTRUST_DATA_UNION),
|
||||
("dwStateAction", ctypes.wintypes.DWORD),
|
||||
("hWVTStateData", ctypes.wintypes.HANDLE),
|
||||
("pwszURLReference", ctypes.wintypes.LPCWSTR),
|
||||
("dwProvFlags", ctypes.wintypes.DWORD),
|
||||
("dwUIContext", ctypes.wintypes.DWORD),
|
||||
("pSignatureSettings", ctypes.wintypes.LPVOID)
|
||||
]
|
||||
|
||||
class GUID(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("Data1", ctypes.wintypes.DWORD),
|
||||
("Data2", ctypes.wintypes.WORD),
|
||||
("Data3", ctypes.wintypes.WORD),
|
||||
("Data4", ctypes.c_ubyte * 8)
|
||||
]
|
||||
|
||||
V2_GUID = GUID(0x00AAC56B, 0xCD44, 0x11D0, (ctypes.c_ubyte * 8)(0x8C, 0xC2, 0x00, 0xC0, 0x4F, 0xC2, 0x95, 0xEE))
|
||||
|
||||
####################################################################################################
|
||||
|
||||
class WinTrust:
|
||||
@staticmethod
|
||||
def verify(file_path):
|
||||
if os.name != 'nt':
|
||||
return 0
|
||||
try:
|
||||
wintrust = ctypes.windll.wintrust
|
||||
wintrust.WinVerifyTrust.argtypes = [ctypes.wintypes.HWND, ctypes.POINTER(GUID), ctypes.wintypes.LPVOID]
|
||||
wintrust.WinVerifyTrust.restype = ctypes.wintypes.LONG
|
||||
|
||||
fi = WINTRUST_FILE_INFO()
|
||||
fi.cbStruct = ctypes.sizeof(WINTRUST_FILE_INFO)
|
||||
fi.pcwszFilePath = os.path.abspath(file_path)
|
||||
fi.hFile = None
|
||||
fi.pgKnownSubject = None
|
||||
|
||||
wt_union = WINTRUST_DATA_UNION()
|
||||
wt_union.pFile = ctypes.pointer(fi)
|
||||
|
||||
td = WINTRUST_DATA()
|
||||
td.cbStruct = ctypes.sizeof(WINTRUST_DATA)
|
||||
td.pPolicyCallbackData = None
|
||||
td.pSIPClientData = None
|
||||
td.dwUIChoice = 2
|
||||
td.fdwRevocationChecks = 0
|
||||
td.dwUnionChoice = 1
|
||||
td.u = wt_union
|
||||
td.dwStateAction = 0
|
||||
td.hWVTStateData = None
|
||||
td.pwszURLReference = None
|
||||
td.dwProvFlags = 0
|
||||
td.dwUIContext = 0
|
||||
td.pSignatureSettings = None
|
||||
|
||||
return 1 if wintrust.WinVerifyTrust(None, ctypes.byref(V2_GUID), ctypes.byref(td)) == 0 else 0
|
||||
except Exception:
|
||||
return 0
|
||||
|
||||
####################################################################################################
|
||||
|
||||
class FeatureExtractor:
|
||||
_STRING_PATTERN = re.compile(b'[\x20-\x7E]{5,}')
|
||||
|
||||
_BYTE_HIST_KEYS = [f'ByteHist_{i:02X}' for i in range(256)]
|
||||
_BYTE_ENT_KEYS = [f'ByteEnt_{i:02X}' for i in range(256)]
|
||||
_STRING_KEYS = [f'StringHist_{i:02d}' for i in range(95)]
|
||||
_SECTION_HASH_KEYS = [f'SectionHash_{i:02d}' for i in range(50)]
|
||||
_CHAR_FLAGS = [0x00000020, 0x00000040, 0x00000080, 0x02000000, 0x20000000, 0x40000000, 0x80000000]
|
||||
_CHAR_COUNT_KEYS = {flag: f'Char_{flag:08X}_Count' for flag in _CHAR_FLAGS}
|
||||
_CHAR_ENT_KEYS = {flag: f'Char_{flag:08X}_MeanEntropy' for flag in _CHAR_FLAGS}
|
||||
|
||||
_C_LOG_C_TABLE = np.zeros(2049, dtype=np.float64)
|
||||
_C_LOG_C_TABLE[1:] = np.arange(1, 2049) * np.log2(np.arange(1, 2049))
|
||||
|
||||
@staticmethod
|
||||
def _safe_float(val):
|
||||
try:
|
||||
f = float(val)
|
||||
return f if not (math.isinf(f) or math.isnan(f)) else 0.0
|
||||
|
||||
except Exception:
|
||||
return 0.0
|
||||
|
||||
@staticmethod
|
||||
def _calc_entropy(data):
|
||||
if not data:
|
||||
return 0.0
|
||||
|
||||
arr = np.frombuffer(data, dtype=np.uint8)
|
||||
sz = len(arr)
|
||||
counts = np.bincount(arr, minlength=256)
|
||||
counts = counts[counts > 0]
|
||||
|
||||
return float(np.log2(sz) - np.sum(counts * np.log2(counts)) / sz)
|
||||
|
||||
@classmethod
|
||||
def _extract_strings(cls, file_bytes):
|
||||
res = {"StringCount": 0.0, "StringMeanLength": 0.0, "StringEntropy": 0.0}
|
||||
res.update(dict.fromkeys(cls._STRING_KEYS, 0.0))
|
||||
|
||||
matches = cls._STRING_PATTERN.findall(file_bytes)
|
||||
if not matches:
|
||||
return res
|
||||
|
||||
combined_data = b''.join(matches)
|
||||
count = len(matches)
|
||||
total_len = len(combined_data)
|
||||
|
||||
res["StringCount"] = float(count)
|
||||
res["StringMeanLength"] = float(total_len) / count
|
||||
res["StringEntropy"] = cls._calc_entropy(combined_data)
|
||||
|
||||
arr = np.frombuffer(combined_data, dtype=np.uint8)
|
||||
counts = np.bincount(arr, minlength=127)
|
||||
char_counts = counts[0x20:0x7F]
|
||||
|
||||
res.update(dict(zip(cls._STRING_KEYS, char_counts.astype(np.float64) / total_len)))
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
def _extract_histograms(cls, file_bytes):
|
||||
arr = np.frombuffer(file_bytes, dtype=np.uint8)
|
||||
sz = len(arr)
|
||||
res = {}
|
||||
|
||||
if sz == 0:
|
||||
res.update(dict.fromkeys(cls._BYTE_HIST_KEYS, 0.0))
|
||||
res.update(dict.fromkeys(cls._BYTE_ENT_KEYS, 0.0))
|
||||
return res
|
||||
|
||||
byte_counts = np.bincount(arr, minlength=256)
|
||||
byte_hist = byte_counts.astype(np.float64) / sz
|
||||
res.update(dict(zip(cls._BYTE_HIST_KEYS, byte_hist)))
|
||||
|
||||
ent_hist = np.zeros(256, dtype=np.float64)
|
||||
window = 2048
|
||||
step = 1024
|
||||
|
||||
if sz < window:
|
||||
w_counts = byte_counts
|
||||
p = w_counts[w_counts > 0] / float(sz)
|
||||
entropy = float(-np.sum(p * np.log2(p)))
|
||||
ent_bin = min(int(entropy * 2.0), 15)
|
||||
byte_bins = w_counts.reshape(16, 16).sum(axis=1)
|
||||
idx_start = ent_bin * 16
|
||||
ent_hist[idx_start : idx_start+16] += byte_bins
|
||||
else:
|
||||
num_windows = (sz - window) // step + 1
|
||||
batch_windows = 10000
|
||||
|
||||
for w_start in range(0, num_windows, batch_windows):
|
||||
w_end = min(w_start + batch_windows, num_windows)
|
||||
b_num = w_end - w_start
|
||||
|
||||
start_byte = w_start * step
|
||||
end_byte = start_byte + b_num * step + step
|
||||
b_arr = arr[start_byte:end_byte]
|
||||
|
||||
arr_2d = b_arr.reshape(b_num + 1, step)
|
||||
offsets = (np.arange(b_num + 1, dtype=np.int32)[:, None] * 256)
|
||||
flat_idx = (arr_2d.astype(np.int32) + offsets).ravel()
|
||||
|
||||
chunk_counts = np.bincount(flat_idx, minlength=(b_num + 1) * 256).reshape(b_num + 1, 256)
|
||||
window_counts = chunk_counts[:-1] + chunk_counts[1:]
|
||||
|
||||
sum_c_log_c = np.sum(cls._C_LOG_C_TABLE[window_counts], axis=1)
|
||||
entropies = 11.0 - sum_c_log_c / 2048.0
|
||||
|
||||
ent_bins = (entropies * 2.0).astype(np.int32)
|
||||
np.clip(ent_bins, 0, 15, out=ent_bins)
|
||||
|
||||
byte_bins = window_counts.reshape(b_num, 16, 16).sum(axis=2)
|
||||
flat_ent_bins = (ent_bins[:, None] * 16 + np.arange(16)).ravel()
|
||||
np.add.at(ent_hist, flat_ent_bins, byte_bins.ravel())
|
||||
|
||||
ent_sum = np.sum(ent_hist)
|
||||
if ent_sum > 0:
|
||||
ent_hist /= ent_sum
|
||||
|
||||
res.update(dict(zip(cls._BYTE_ENT_KEYS, ent_hist)))
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
def _extract_overlay_features(cls, pe, fsize):
|
||||
overlay_offset = pe.get_overlay_data_start_offset()
|
||||
if not overlay_offset or overlay_offset >= fsize:
|
||||
return {"HasOverlay": 0.0, "OverlaySize": 0.0, "OverlayRatio": 0.0, "OverlayEntropy": 0.0}
|
||||
|
||||
overlay_data = pe.get_overlay()
|
||||
if not overlay_data:
|
||||
return {"HasOverlay": 0.0, "OverlaySize": 0.0, "OverlayRatio": 0.0, "OverlayEntropy": 0.0}
|
||||
|
||||
sz = len(overlay_data)
|
||||
return {
|
||||
"HasOverlay": 1.0,
|
||||
"OverlaySize": float(sz),
|
||||
"OverlayRatio": float(sz) / float(fsize),
|
||||
"OverlayEntropy": cls._calc_entropy(overlay_data)
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def _extract_rich_header(cls, pe):
|
||||
if not hasattr(pe, 'RICH_HEADER') or not pe.RICH_HEADER:
|
||||
return {"HasRichHeader": 0.0, "RichHeaderCount": 0.0}
|
||||
return {
|
||||
"HasRichHeader": 1.0,
|
||||
"RichHeaderCount": float(len(pe.RICH_HEADER.values) // 2) if hasattr(pe.RICH_HEADER, 'values') else 0.0
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def _extract_ep_anomalies(cls, pe):
|
||||
if not hasattr(pe, 'OPTIONAL_HEADER') or not hasattr(pe, 'sections') or not pe.sections:
|
||||
return {"EntryPointSectionIndex": -1.0, "EntryPointInExecutable": 0.0, "EntryPointInLastSection": 0.0}
|
||||
|
||||
ep = pe.OPTIONAL_HEADER.AddressOfEntryPoint
|
||||
ep_section_idx = -1
|
||||
ep_in_exec = 0.0
|
||||
|
||||
for idx, sec in enumerate(pe.sections):
|
||||
if sec.VirtualAddress <= ep < (sec.VirtualAddress + sec.Misc_VirtualSize):
|
||||
ep_section_idx = idx
|
||||
if sec.Characteristics & 0x20000000:
|
||||
ep_in_exec = 1.0
|
||||
break
|
||||
|
||||
return {
|
||||
"EntryPointSectionIndex": float(ep_section_idx),
|
||||
"EntryPointInExecutable": ep_in_exec,
|
||||
"EntryPointInLastSection": 1.0 if ep_section_idx == len(pe.sections) - 1 else 0.0
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def _extract_advanced_resources(cls, pe):
|
||||
res_data = {
|
||||
"ResourceMaxEntropy": 0.0,
|
||||
"ResourceMinEntropy": 0.0,
|
||||
"ResourceMeanEntropy": 0.0,
|
||||
"ResourceLangCount": 0.0,
|
||||
"ResourceRCDataCount": 0.0
|
||||
}
|
||||
|
||||
if not hasattr(pe, 'DIRECTORY_ENTRY_RESOURCE'):
|
||||
return res_data
|
||||
|
||||
entropies = []
|
||||
langs = set()
|
||||
rcdata_count = 0
|
||||
|
||||
for entry_type in pe.DIRECTORY_ENTRY_RESOURCE.entries:
|
||||
if hasattr(entry_type, 'id') and entry_type.id == 10:
|
||||
rcdata_count += len(getattr(entry_type.directory, 'entries', []))
|
||||
|
||||
if not hasattr(entry_type, 'directory'):
|
||||
continue
|
||||
|
||||
for entry_id in entry_type.directory.entries:
|
||||
if not hasattr(entry_id, 'directory'):
|
||||
continue
|
||||
|
||||
for entry_lang in entry_id.directory.entries:
|
||||
if hasattr(entry_lang, 'id'):
|
||||
langs.add(entry_lang.id)
|
||||
|
||||
if hasattr(entry_lang, 'data'):
|
||||
try:
|
||||
data = pe.get_data(entry_lang.data.struct.OffsetToData, entry_lang.data.struct.Size)
|
||||
entropies.append(cls._calc_entropy(data))
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
res_data["ResourceLangCount"] = float(len(langs))
|
||||
res_data["ResourceRCDataCount"] = float(rcdata_count)
|
||||
|
||||
if entropies:
|
||||
res_data["ResourceMaxEntropy"] = max(entropies)
|
||||
res_data["ResourceMinEntropy"] = min(entropies)
|
||||
res_data["ResourceMeanEntropy"] = sum(entropies) / len(entropies)
|
||||
|
||||
return res_data
|
||||
|
||||
@classmethod
|
||||
def _extract_load_config(cls, pe):
|
||||
cfg = {"HasLoadConfig": 0.0, "HasCFG": 0.0, "HasSEHTable": 0.0}
|
||||
if hasattr(pe, 'DIRECTORY_ENTRY_LOAD_CONFIG'):
|
||||
cfg["HasLoadConfig"] = 1.0
|
||||
struct = pe.DIRECTORY_ENTRY_LOAD_CONFIG.struct
|
||||
|
||||
if getattr(struct, 'GuardCFFunctionTable', 0) != 0:
|
||||
cfg["HasCFG"] = 1.0
|
||||
|
||||
if getattr(struct, 'SEHandlerTable', 0) != 0:
|
||||
cfg["HasSEHTable"] = 1.0
|
||||
|
||||
return cfg
|
||||
|
||||
@classmethod
|
||||
def _extract_security_directory(cls, pe, file_bytes, fsize):
|
||||
res = {"HasSignature": 0.0, "SignatureCount": 0.0}
|
||||
if not hasattr(pe, 'OPTIONAL_HEADER') or not hasattr(pe.OPTIONAL_HEADER, 'DATA_DIRECTORY'):
|
||||
return res
|
||||
|
||||
sec_idx = 4
|
||||
directories = pe.OPTIONAL_HEADER.DATA_DIRECTORY
|
||||
if len(directories) <= sec_idx:
|
||||
return res
|
||||
|
||||
sec_dir = directories[sec_idx]
|
||||
if sec_dir.Size == 0 or sec_dir.VirtualAddress == 0:
|
||||
return res
|
||||
|
||||
res["HasSignature"] = 1.0
|
||||
offset = sec_dir.VirtualAddress
|
||||
size = sec_dir.Size
|
||||
|
||||
if offset + size > fsize or offset < 0 or size < 0:
|
||||
return res
|
||||
|
||||
count = 0
|
||||
curr = offset
|
||||
end = offset + size
|
||||
|
||||
while curr + 8 <= end:
|
||||
dw_length = int.from_bytes(file_bytes[curr:curr+4], 'little')
|
||||
if dw_length < 8:
|
||||
break
|
||||
|
||||
count += 1
|
||||
curr += (dw_length + 7) & ~7
|
||||
|
||||
res["SignatureCount"] = float(count)
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
def extract(cls, file_path):
|
||||
pe = None
|
||||
try:
|
||||
fsize = os.path.getsize(file_path)
|
||||
if fsize == 0 or fsize > MAX_FILE_SIZE:
|
||||
return None
|
||||
|
||||
with open(file_path, "rb") as f:
|
||||
file_bytes = f.read()
|
||||
|
||||
base, dlls, apis = {}, set(), set()
|
||||
pe = pefile.PE(data=file_bytes, fast_load=True)
|
||||
|
||||
try:
|
||||
pe.parse_rich_header()
|
||||
pe.parse_data_directories(directories=[
|
||||
pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_IMPORT'],
|
||||
pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_EXPORT'],
|
||||
pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_RESOURCE'],
|
||||
pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_DEBUG'],
|
||||
pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_TLS'],
|
||||
pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG']
|
||||
])
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
base['TrustSigned'] = float(WinTrust.verify(file_path))
|
||||
base['FileEntropy'] = cls._calc_entropy(file_bytes)
|
||||
base['FileSize'] = float(fsize)
|
||||
|
||||
fh = pe.FILE_HEADER
|
||||
base['Machine'] = getattr(fh, 'Machine', 0)
|
||||
base['NumberOfSections'] = getattr(fh, 'NumberOfSections', 0)
|
||||
base['TimeDateStamp'] = getattr(fh, 'TimeDateStamp', 0)
|
||||
base['PointerToSymbolTable'] = getattr(fh, 'PointerToSymbolTable', 0)
|
||||
base['NumberOfSymbols'] = getattr(fh, 'NumberOfSymbols', 0)
|
||||
base['SizeOfOptionalHeader'] = getattr(fh, 'SizeOfOptionalHeader', 0)
|
||||
base['Characteristics'] = getattr(fh, 'Characteristics', 0)
|
||||
|
||||
curr_ts = datetime.datetime.utcnow().timestamp()
|
||||
base['HasInvalidTimestamp'] = 1.0 if (base['TimeDateStamp'] < 631152000 or base['TimeDateStamp'] > curr_ts + 2592000) else 0.0
|
||||
base['FileTimeException'] = 1.0 if base['TimeDateStamp'] == 0 else 0.0
|
||||
base['Is64Bit'] = 1.0 if base['Machine'] in (0x8664, 0xAA64, 0x0200) else 0.0
|
||||
base['IsExe'] = 1.0 if pe.is_exe() else 0.0
|
||||
base['IsDll'] = 1.0 if pe.is_dll() else 0.0
|
||||
base['ExceptionCount'] = 0.0
|
||||
|
||||
if hasattr(pe, 'OPTIONAL_HEADER'):
|
||||
op = pe.OPTIONAL_HEADER
|
||||
fields = ['Magic', 'MajorLinkerVersion', 'MinorLinkerVersion', 'SizeOfCode', 'SizeOfInitializedData', 'SizeOfUninitializedData', 'AddressOfEntryPoint', 'BaseOfCode', 'ImageBase', 'SectionAlignment', 'FileAlignment', 'MajorOperatingSystemVersion', 'MinorOperatingSystemVersion', 'MajorImageVersion', 'MinorImageVersion', 'MajorSubsystemVersion', 'MinorSubsystemVersion', 'SizeOfImage', 'SizeOfHeaders', 'CheckSum', 'Subsystem', 'DllCharacteristics', 'SizeOfStackReserve', 'SizeOfStackCommit', 'SizeOfHeapReserve', 'SizeOfHeapCommit', 'LoaderFlags', 'NumberOfRvaAndSizes']
|
||||
for f in fields: base[f] = getattr(op, f, 0)
|
||||
base['IsDriver'] = 1.0 if base.get('Subsystem') == 1 else 0.0
|
||||
|
||||
if hasattr(op, 'DATA_DIRECTORY'):
|
||||
for i, directory in enumerate(op.DATA_DIRECTORY):
|
||||
base[f'DataDirectory_{i}_Size'] = directory.Size
|
||||
base[f'DataDirectory_{i}_VA'] = directory.VirtualAddress
|
||||
|
||||
exc_idx = pefile.DIRECTORY_ENTRY.get('IMAGE_DIRECTORY_ENTRY_EXCEPTION', 3)
|
||||
if len(op.DATA_DIRECTORY) > exc_idx and op.DATA_DIRECTORY[exc_idx].Size > 0:
|
||||
base['ExceptionCount'] = op.DATA_DIRECTORY[exc_idx].Size // 12 if base['Machine'] in (0x8664, 0xAA64) else 0.0
|
||||
|
||||
res_flags_count = dict.fromkeys(cls._CHAR_COUNT_KEYS.values(), 0.0)
|
||||
res_flags_ent = dict.fromkeys(cls._CHAR_ENT_KEYS.values(), 0.0)
|
||||
res_sec_hash = dict.fromkeys(cls._SECTION_HASH_KEYS, 0.0)
|
||||
|
||||
if hasattr(pe, 'sections'):
|
||||
base['SectionCount'] = len(pe.sections)
|
||||
entropies, raw_sizes, v_sizes = [], [], []
|
||||
exec_sec, write_sec, read_sec, sec_exc = 0, 0, 0, 0
|
||||
|
||||
for section in pe.sections:
|
||||
sec_name = section.Name.rstrip(b'\x00')
|
||||
hash_val = zlib.crc32(sec_name) % 50
|
||||
res_sec_hash[cls._SECTION_HASH_KEYS[hash_val]] += 1.0
|
||||
|
||||
try:
|
||||
s_data = section.get_data()
|
||||
s_entropy = cls._calc_entropy(s_data)
|
||||
except Exception:
|
||||
s_entropy = 0.0
|
||||
|
||||
entropies.append(s_entropy)
|
||||
raw_sizes.append(section.SizeOfRawData)
|
||||
v_sizes.append(section.Misc_VirtualSize)
|
||||
|
||||
if section.Characteristics & 0x20000000:
|
||||
exec_sec += 1
|
||||
if section.Characteristics & 0x80000000:
|
||||
write_sec += 1
|
||||
if section.Characteristics & 0x40000000:
|
||||
read_sec += 1
|
||||
if section.SizeOfRawData + section.PointerToRawData > fsize:
|
||||
sec_exc = 1
|
||||
|
||||
for flag in cls._CHAR_FLAGS:
|
||||
if section.Characteristics & flag:
|
||||
res_flags_count[cls._CHAR_COUNT_KEYS[flag]] += 1.0
|
||||
res_flags_ent[cls._CHAR_ENT_KEYS[flag]] += s_entropy
|
||||
|
||||
base['SectionMaxEntropy'] = max(entropies) if entropies else 0.0
|
||||
base['SectionMinEntropy'] = min(entropies) if entropies else 0.0
|
||||
base['SectionMeanEntropy'] = sum(entropies) / len(entropies) if entropies else 0.0
|
||||
base['SectionMaxRawSize'] = max(raw_sizes) if raw_sizes else 0.0
|
||||
base['SectionMinRawSize'] = min(raw_sizes) if raw_sizes else 0.0
|
||||
base['SectionMeanRawSize'] = sum(raw_sizes) / len(raw_sizes) if raw_sizes else 0.0
|
||||
base['SectionMaxVSize'] = max(v_sizes) if v_sizes else 0.0
|
||||
base['SectionMinVSize'] = min(v_sizes) if v_sizes else 0.0
|
||||
base['SectionMeanVSize'] = sum(v_sizes) / len(v_sizes) if v_sizes else 0.0
|
||||
base['ExecutableSections'] = float(exec_sec)
|
||||
base['WritableSections'] = float(write_sec)
|
||||
base['ReadableSections'] = float(read_sec)
|
||||
base['SectionException'] = float(sec_exc)
|
||||
|
||||
for flag in cls._CHAR_FLAGS:
|
||||
cnt_key = cls._CHAR_COUNT_KEYS[flag]
|
||||
if res_flags_count[cnt_key] > 0:
|
||||
res_flags_ent[cls._CHAR_ENT_KEYS[flag]] /= res_flags_count[cnt_key]
|
||||
|
||||
base.update(res_flags_count)
|
||||
base.update(res_flags_ent)
|
||||
base.update(res_sec_hash)
|
||||
|
||||
string_keys = ['FileDescription', 'FileVersion', 'ProductName', 'ProductVersion', 'CompanyName', 'LegalCopyright', 'Comments', 'InternalName', 'LegalTrademarks', 'SpecialBuild', 'PrivateBuild']
|
||||
for key in string_keys:
|
||||
base[f'{key}Length'] = 0.0
|
||||
|
||||
if hasattr(pe, 'FileInfo'):
|
||||
for fileinfo_list in pe.FileInfo:
|
||||
for fileinfo in fileinfo_list:
|
||||
if getattr(fileinfo, 'name', '') in ('StringFileInfo', b'StringFileInfo'):
|
||||
for st in getattr(fileinfo, 'StringTable', []):
|
||||
for key, val in st.entries.items():
|
||||
|
||||
try:
|
||||
k = key.decode('utf-8', 'ignore') if isinstance(key, bytes) else str(key)
|
||||
if k in string_keys:
|
||||
v = val.decode('utf-8', 'ignore') if isinstance(val, bytes) else str(val)
|
||||
base[f'{k}Length'] = float(len(v))
|
||||
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
if hasattr(pe, 'DIRECTORY_ENTRY_TLS') and hasattr(pe.DIRECTORY_ENTRY_TLS, 'struct'):
|
||||
base['HasTlsCallbacks'] = 1.0 if getattr(pe.DIRECTORY_ENTRY_TLS.struct, 'AddressOfCallBacks', 0) != 0 else 0.0
|
||||
|
||||
if hasattr(pe, 'VS_FIXEDFILEINFO') and len(pe.VS_FIXEDFILEINFO) > 0:
|
||||
flags = getattr(pe.VS_FIXEDFILEINFO[0], 'FileFlags', 0)
|
||||
base['IsDebug'] = 1.0 if flags & 0x1 else 0.0
|
||||
base['IsPreRelease'] = 1.0 if flags & 0x2 else 0.0
|
||||
base['IsPatched'] = 1.0 if flags & 0x4 else 0.0
|
||||
base['IsPrivateBuild'] = 1.0 if flags & 0x8 else 0.0
|
||||
base['IsSpecialBuild'] = 1.0 if flags & 0x20 else 0.0
|
||||
|
||||
if hasattr(pe, 'DIRECTORY_ENTRY_IMPORT'):
|
||||
base['ImportCount'] = float(len(pe.DIRECTORY_ENTRY_IMPORT))
|
||||
func_count = 0
|
||||
for entry in pe.DIRECTORY_ENTRY_IMPORT:
|
||||
if getattr(entry, 'dll', None):
|
||||
try:
|
||||
dlls.add(entry.dll.decode('ascii', 'ignore').lower())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
for imp in getattr(entry, 'imports', []):
|
||||
func_count += 1
|
||||
if getattr(imp, 'name', None):
|
||||
try:
|
||||
apis.add(imp.name.decode('ascii', 'ignore'))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
base['ImportFunctionCount'] = float(func_count)
|
||||
|
||||
base['ExportCount'] = float(len(pe.DIRECTORY_ENTRY_EXPORT.symbols)) if (hasattr(pe, 'DIRECTORY_ENTRY_EXPORT') and hasattr(pe.DIRECTORY_ENTRY_EXPORT, 'symbols')) else 0.0
|
||||
|
||||
if hasattr(pe, 'DIRECTORY_ENTRY_RESOURCE'):
|
||||
icon_count = 0
|
||||
for entry in pe.DIRECTORY_ENTRY_RESOURCE.entries:
|
||||
if getattr(entry, 'id', None) == 3 and hasattr(entry, 'directory'):
|
||||
icon_count += len(getattr(entry.directory, 'entries', []))
|
||||
base['IconCount'] = float(icon_count)
|
||||
else:
|
||||
base['IconCount'] = 0.0
|
||||
|
||||
base['DebugCount'] = float(len(pe.DIRECTORY_ENTRY_DEBUG)) if hasattr(pe, 'DIRECTORY_ENTRY_DEBUG') else 0.0
|
||||
|
||||
base.update(cls._extract_strings(file_bytes))
|
||||
base.update(cls._extract_histograms(file_bytes))
|
||||
base.update(cls._extract_overlay_features(pe, fsize))
|
||||
base.update(cls._extract_rich_header(pe))
|
||||
base.update(cls._extract_ep_anomalies(pe))
|
||||
base.update(cls._extract_advanced_resources(pe))
|
||||
base.update(cls._extract_load_config(pe))
|
||||
base.update(cls._extract_security_directory(pe, file_bytes, fsize))
|
||||
|
||||
for k in base:
|
||||
base[k] = cls._safe_float(base[k])
|
||||
|
||||
return {"Base": base, "DLLs": list(dlls), "APIs": list(apis)}
|
||||
|
||||
except Exception:
|
||||
return None
|
||||
finally:
|
||||
if pe:
|
||||
pe.close()
|
||||
|
||||
####################################################################################################
|
||||
|
||||
class ModelPredictor:
|
||||
def __init__(self, model_path, feature_path):
|
||||
self.model_features = self._load_features(feature_path)
|
||||
self.sess = ort.InferenceSession(model_path, providers=['CPUExecutionProvider'])
|
||||
self.input_name = self.sess.get_inputs()[0].name
|
||||
|
||||
expected_dim = self.sess.get_inputs()[0].shape[1]
|
||||
current_dim = len(self.model_features)
|
||||
if current_dim != expected_dim:
|
||||
raise ValueError(f"Dimension mismatch! Model expects {expected_dim} features, but feature file lists {current_dim}.")
|
||||
|
||||
self.feat_map = {feat: i for i, feat in enumerate(self.model_features)}
|
||||
|
||||
def _load_features(self, path):
|
||||
if not os.path.exists(path):
|
||||
raise FileNotFoundError(f"Feature file not found: {path}")
|
||||
|
||||
with open(path, 'r') as f:
|
||||
return json.load(f)
|
||||
|
||||
def predict(self, raw_data):
|
||||
vec = np.zeros((1, len(self.model_features)), dtype=np.float32)
|
||||
base = raw_data.get('Base', {})
|
||||
dlls = raw_data.get('DLLs', [])
|
||||
apis = raw_data.get('APIs', [])
|
||||
|
||||
for k, v in base.items():
|
||||
if k in self.feat_map:
|
||||
vec[0, self.feat_map[k]] = v
|
||||
|
||||
for d in dlls:
|
||||
h = zlib.crc32(d.encode('utf-8', 'ignore')) % 256
|
||||
feat_name = f"DllHash_{h:03d}"
|
||||
if feat_name in self.feat_map:
|
||||
vec[0, self.feat_map[feat_name]] += 1.0
|
||||
|
||||
for a in apis:
|
||||
h = zlib.crc32(a.encode('utf-8', 'ignore')) % 1024
|
||||
feat_name = f"ApiHash_{h:04d}"
|
||||
if feat_name in self.feat_map:
|
||||
vec[0, self.feat_map[feat_name]] += 1.0
|
||||
|
||||
outputs = self.sess.run(None, {self.input_name: vec})
|
||||
|
||||
if len(outputs) > 1:
|
||||
result = outputs[1]
|
||||
if isinstance(result, list) and len(result) > 0:
|
||||
prob_dict = result[0]
|
||||
if hasattr(prob_dict, 'get'):
|
||||
return float(prob_dict.get(1, prob_dict.get('1', 0.0)))
|
||||
|
||||
elif isinstance(result, np.ndarray):
|
||||
if result.ndim == 2 and result.shape[1] > 1:
|
||||
return float(result[0][1])
|
||||
|
||||
return 0.0
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def scan_target(target, predictor):
|
||||
files = []
|
||||
if os.path.isfile(target):
|
||||
files.append(target)
|
||||
|
||||
elif os.path.isdir(target):
|
||||
for r, _, fs in os.walk(target):
|
||||
for f in fs:
|
||||
files.append(os.path.join(r, f))
|
||||
|
||||
if not files:
|
||||
print("[-] No valid PE files found.")
|
||||
return
|
||||
|
||||
print(f"\n[*] Scanning {len(files)} files...\n")
|
||||
print(f"{'RESULT':<10} | {'PROB':<8} | {'FILE'}")
|
||||
print("-" * 80)
|
||||
|
||||
safe_count = 0
|
||||
mal_count = 0
|
||||
|
||||
for fpath in files:
|
||||
try:
|
||||
data = FeatureExtractor.extract(fpath)
|
||||
if not data:
|
||||
print(f"{'ERROR':<10} | {'----':<8} | {fpath}")
|
||||
continue
|
||||
|
||||
prob = predictor.predict(data)
|
||||
is_malware = prob > 0.5
|
||||
|
||||
label = "MALWARE" if is_malware else "SAFE"
|
||||
if is_malware:
|
||||
mal_count += 1
|
||||
else:
|
||||
safe_count += 1
|
||||
|
||||
print(f"{label:<10} | {prob:.4f} | {fpath}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"{'FAIL':<10} | {'----':<8} | {fpath} ({str(e)})")
|
||||
|
||||
print("-" * 80)
|
||||
print(f"[*] Summary: Safe={safe_count}, Malware={mal_count}, Total={len(files)}")
|
||||
|
||||
####################################################################################################
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("\n-------------------------- PE Malware Predictor v4.0 --------------------------\n")
|
||||
print(f"[*] © 2020-2026 87owo (PYAS Security)")
|
||||
print(f"[*] Loading model features and weights...")
|
||||
|
||||
base_dir = os.path.dirname(sys.executable) if getattr(sys, 'frozen', False) else os.path.dirname(os.path.abspath(__file__))
|
||||
model_path = os.path.join(base_dir, MODEL_FILE)
|
||||
feature_path = os.path.join(base_dir, FEATURE_FILE)
|
||||
|
||||
try:
|
||||
predictor = ModelPredictor(model_path, feature_path)
|
||||
except Exception as e:
|
||||
print(f"[-] Critical Error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
for path in sys.argv[1:]:
|
||||
target = path.strip('"').strip("'")
|
||||
if os.path.exists(target):
|
||||
print()
|
||||
print("-" * 80)
|
||||
scan_target(target, predictor)
|
||||
|
||||
else:
|
||||
print(f"[-] Path does not exist: {target}")
|
||||
else:
|
||||
while True:
|
||||
try:
|
||||
print()
|
||||
print("-" * 80)
|
||||
path = input("\n[?] Enter File or Folder Path (or 'q' to exit): ").strip().strip('"').strip("'")
|
||||
if path.lower() in ['q', 'exit']:
|
||||
break
|
||||
|
||||
if not os.path.exists(path):
|
||||
print("[-] Path does not exist.")
|
||||
continue
|
||||
|
||||
scan_target(path, predictor)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
break
|
||||
except Exception as e:
|
||||
print(f"[-] Error: {e}")
|
||||
@@ -0,0 +1,310 @@
|
||||
import os, sys, time, json, zlib, array, onnxmltools, multiprocessing
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import lightgbm as lgb
|
||||
import scipy.sparse as sp
|
||||
|
||||
from collections import defaultdict
|
||||
from onnxmltools.convert.common.data_types import FloatTensorType
|
||||
from sklearn.model_selection import train_test_split
|
||||
from sklearn.metrics import accuracy_score, roc_auc_score
|
||||
|
||||
try:
|
||||
import orjson
|
||||
JSON_LOADS = orjson.loads
|
||||
|
||||
except ImportError:
|
||||
JSON_LOADS = json.loads
|
||||
|
||||
####################################################################################################
|
||||
|
||||
JSONL_PATH = "pe_features.jsonl"
|
||||
MODEL_FILE = "model.txt"
|
||||
ONNX_FILE = "Pefile_General_S1.onnx"
|
||||
FEATURE_FILE = "features.json"
|
||||
|
||||
TEST_SIZE = 0.0001
|
||||
RANDOM_SEED = 42
|
||||
DLL_HASH_DIM = 256
|
||||
API_HASH_DIM = 1024
|
||||
|
||||
EXCLUDE_FEATURES = {
|
||||
'label', 'filehash',
|
||||
}
|
||||
|
||||
LGBM_PARAMS = {
|
||||
'objective': 'binary',
|
||||
'metric': ['binary_logloss', 'auc'],
|
||||
'boosting_type': 'gbdt',
|
||||
'num_leaves': 256,
|
||||
'feature_fraction': 0.8,
|
||||
'bagging_fraction': 1.0,
|
||||
'bagging_freq': 5,
|
||||
'verbose': -1,
|
||||
'seed': RANDOM_SEED,
|
||||
'n_jobs': -1,
|
||||
'max_depth': -1,
|
||||
'min_data_in_leaf': 50,
|
||||
}
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def analyze_schema(jsonl_path):
|
||||
print("[*] Stage 1: Scanning global schema...")
|
||||
base_keys = set()
|
||||
num_lines = 0
|
||||
|
||||
with open(jsonl_path, 'rb') as f:
|
||||
for line in f:
|
||||
num_lines += 1
|
||||
|
||||
if not base_keys:
|
||||
try:
|
||||
data = JSON_LOADS(line)
|
||||
base_data = data.get('Base')
|
||||
if base_data:
|
||||
base_keys.update(base_data.keys())
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
base_schema = sorted([k for k in base_keys if k.lower().strip() not in EXCLUDE_FEATURES])
|
||||
dll_schema = [f"DllHash_{i:03d}" for i in range(DLL_HASH_DIM)]
|
||||
api_schema = [f"ApiHash_{i:04d}" for i in range(API_HASH_DIM)]
|
||||
|
||||
return base_schema, dll_schema, api_schema, num_lines
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def load_data_efficiently(jsonl_path, base_schema, dll_schema, api_schema, num_lines):
|
||||
print(f"[*] Stage 2: Building sparse feature matrix dynamically via Multiprocessing...")
|
||||
final_features = base_schema + dll_schema + api_schema
|
||||
|
||||
num_features = len(final_features)
|
||||
offset_dll = len(base_schema)
|
||||
offset_api = offset_dll + DLL_HASH_DIM
|
||||
|
||||
file_size = os.path.getsize(jsonl_path)
|
||||
workers = min(os.cpu_count() or 4, 16)
|
||||
chunk_size = file_size // workers
|
||||
|
||||
tasks = []
|
||||
for i in range(workers):
|
||||
start = i * chunk_size
|
||||
end = file_size if i == workers - 1 else (i + 1) * chunk_size
|
||||
tasks.append((jsonl_path, start, end, base_schema, num_features, offset_dll, offset_api))
|
||||
|
||||
with multiprocessing.Pool(workers) as pool:
|
||||
results = pool.map(_chunk_worker, tasks)
|
||||
|
||||
Xs = [r[0] for r in results if r[0].shape[0] > 0]
|
||||
ys = [r[1] for r in results if r[1].shape[0] > 0]
|
||||
|
||||
X_final = sp.vstack(Xs, format='csr') if Xs else sp.csr_matrix((0, num_features), dtype=np.float32)
|
||||
y_final = np.concatenate(ys) if ys else np.array([], dtype=np.int8)
|
||||
|
||||
return X_final, pd.Series(y_final), final_features
|
||||
|
||||
def _chunk_worker(args):
|
||||
filepath, start_byte, end_byte, base_schema, num_features, offset_dll, offset_api = args
|
||||
|
||||
base_idx = {feat: i for i, feat in enumerate(base_schema)}
|
||||
base_idx_get = base_idx.get
|
||||
|
||||
rows = array.array('i')
|
||||
cols = array.array('i')
|
||||
vals = array.array('f')
|
||||
y_list = array.array('b')
|
||||
|
||||
with open(filepath, 'rb') as f:
|
||||
if start_byte > 0:
|
||||
f.seek(start_byte - 1)
|
||||
if f.read(1) != b'\n':
|
||||
f.seek(start_byte)
|
||||
f.readline()
|
||||
|
||||
else:
|
||||
f.seek(start_byte)
|
||||
else:
|
||||
f.seek(0)
|
||||
|
||||
row_idx = 0
|
||||
while f.tell() < end_byte:
|
||||
line = f.readline()
|
||||
if not line:
|
||||
break
|
||||
|
||||
try:
|
||||
data = JSON_LOADS(line)
|
||||
y_list.append(data.get('Label', 0))
|
||||
|
||||
base_data = data.get('Base')
|
||||
if base_data:
|
||||
for k, v in base_data.items():
|
||||
idx = base_idx_get(k)
|
||||
if idx is not None:
|
||||
val = float(v)
|
||||
if val != 0.0:
|
||||
rows.append(row_idx)
|
||||
cols.append(idx)
|
||||
vals.append(val)
|
||||
|
||||
dll_data = data.get('DLLs')
|
||||
if dll_data:
|
||||
for d in dll_data:
|
||||
idx = zlib.crc32(d.encode('utf-8', 'ignore')) % DLL_HASH_DIM
|
||||
rows.append(row_idx)
|
||||
cols.append(offset_dll + idx)
|
||||
vals.append(1.0)
|
||||
|
||||
api_data = data.get('APIs')
|
||||
if api_data:
|
||||
for a in api_data:
|
||||
idx = zlib.crc32(a.encode('utf-8', 'ignore')) % API_HASH_DIM
|
||||
rows.append(row_idx)
|
||||
cols.append(offset_api + idx)
|
||||
vals.append(1.0)
|
||||
|
||||
row_idx += 1
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
y_arr = np.frombuffer(y_list, dtype=np.int8)
|
||||
np_rows = np.frombuffer(rows, dtype=np.int32)
|
||||
np_cols = np.frombuffer(cols, dtype=np.int32)
|
||||
np_vals = np.frombuffer(vals, dtype=np.float32)
|
||||
|
||||
X_local = sp.csr_matrix((np_vals, (np_rows, np_cols)), shape=(row_idx, num_features), dtype=np.float32)
|
||||
|
||||
return X_local, y_arr
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def _calculate_dynamic_lr(current_iter: int) -> float:
|
||||
return max(0.01, 0.1 - (current_iter // 80) * 0.01)
|
||||
|
||||
def train_process(X, y, feature_names):
|
||||
print(f"[*] Dataset shape: {X.shape}")
|
||||
|
||||
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=TEST_SIZE, random_state=RANDOM_SEED, stratify=y)
|
||||
|
||||
n_pos = (y_train == 1).sum()
|
||||
n_neg = (y_train == 0).sum()
|
||||
|
||||
base_weight = n_neg / n_pos if n_pos > 0 else 1.0
|
||||
weight_ratio_target = 0.005
|
||||
final_pos_weight = base_weight * weight_ratio_target
|
||||
|
||||
print(f"[*] Sample distribution: Safe={n_neg}, Malware={n_pos}")
|
||||
print(f"[*] Weight correction: Base={base_weight:.4f}, Final={final_pos_weight:.4f}")
|
||||
|
||||
train_data = lgb.Dataset(X_train, label=y_train, feature_name=feature_names)
|
||||
valid_data = lgb.Dataset(X_test, label=y_test, reference=train_data, feature_name=feature_names)
|
||||
|
||||
params = LGBM_PARAMS.copy()
|
||||
params['scale_pos_weight'] = final_pos_weight
|
||||
|
||||
print("\n[*] Starting LightGBM training...")
|
||||
start_time = time.time()
|
||||
|
||||
model = lgb.train(
|
||||
params,
|
||||
train_data,
|
||||
valid_sets=[valid_data],
|
||||
num_boost_round=500,
|
||||
callbacks=[
|
||||
lgb.log_evaluation(period=50),
|
||||
lgb.reset_parameter(learning_rate=_calculate_dynamic_lr)
|
||||
]
|
||||
)
|
||||
|
||||
elapsed = time.time() - start_time
|
||||
print(f"\n[+] Training complete, time elapsed {elapsed:.2f} seconds")
|
||||
|
||||
return model, X_test, y_test
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def export_onnx_model(model, feature_names):
|
||||
print(f"[*] Exporting ONNX model to {ONNX_FILE}...")
|
||||
try:
|
||||
initial_type = [('float_input', FloatTensorType([None, len(feature_names)]))]
|
||||
onnx_model = onnxmltools.convert_lightgbm(model, initial_types=initial_type)
|
||||
onnxmltools.utils.save_model(onnx_model, ONNX_FILE)
|
||||
|
||||
with open(FEATURE_FILE, 'w') as f:
|
||||
json.dump(feature_names, f)
|
||||
|
||||
print(f"[+] ONNX export successful. Feature order saved to {FEATURE_FILE}")
|
||||
except Exception as e:
|
||||
print(f"[-] ONNX export failed: {e}")
|
||||
|
||||
def evaluate_and_save(model, X_test, y_test):
|
||||
print("\n------------------- Evaluation Report -------------------\n")
|
||||
y_prob = model.predict(X_test, num_iteration=model.best_iteration)
|
||||
y_pred = (y_prob > 0.5).astype(int)
|
||||
|
||||
acc = accuracy_score(y_test, y_pred)
|
||||
auc = roc_auc_score(y_test, y_prob)
|
||||
|
||||
tp = np.sum((y_test == 1) & (y_pred == 1))
|
||||
fn = np.sum((y_test == 1) & (y_pred == 0))
|
||||
tn = np.sum((y_test == 0) & (y_pred == 0))
|
||||
fp = np.sum((y_test == 0) & (y_pred == 1))
|
||||
|
||||
total_malware = tp + fn
|
||||
total_safe = tn + fp
|
||||
|
||||
detection_rate = (tp / total_malware * 100) if total_malware > 0 else 0.0
|
||||
fpr = (fp / total_safe * 100) if total_safe > 0 else 0.0
|
||||
|
||||
print(f"Accuracy : {acc:.6f}")
|
||||
print(f"ROC AUC : {auc:.6f}\n")
|
||||
|
||||
print(f"True Positive (Class 1) : {detection_rate:.3f}% ({tp}/{total_malware})")
|
||||
print(f"False Positive (Class 0) : {fpr:.3f}% ({fp}/{total_safe})")
|
||||
|
||||
print("\n---------------------------------------------------------\n")
|
||||
print("[*] Top 20 Features:")
|
||||
|
||||
feature_names = model.feature_name()
|
||||
importance = model.feature_importance(importance_type='gain')
|
||||
feature_imp = pd.DataFrame(sorted(zip(importance, feature_names), reverse=True), columns=['Value', 'Feature'])
|
||||
print(feature_imp.head(20).to_string(index=False))
|
||||
|
||||
print("\n---------------------------------------------------------\n")
|
||||
|
||||
model.save_model(MODEL_FILE)
|
||||
print(f"[+] Native model saved to: {os.path.abspath(MODEL_FILE)}")
|
||||
|
||||
export_onnx_model(model, feature_names)
|
||||
|
||||
####################################################################################################
|
||||
|
||||
if __name__ == "__main__":
|
||||
multiprocessing.freeze_support()
|
||||
print("\n---------------- PE Malware Trainer v4.0 ----------------\n")
|
||||
|
||||
if not os.path.exists(JSONL_PATH):
|
||||
print(f"[-] Error: Feature file {JSONL_PATH} not found.")
|
||||
sys.exit(1)
|
||||
|
||||
base_schema, dll_schema, api_schema, num_lines = analyze_schema(JSONL_PATH)
|
||||
if num_lines == 0:
|
||||
print("[-] Error: Feature file is empty.")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"[*] Detected Base features: {len(base_schema)}")
|
||||
print(f"[*] Hashing DLL features: {len(dll_schema)}")
|
||||
print(f"[*] Hashing API features: {len(api_schema)}")
|
||||
|
||||
X, y, final_features = load_data_efficiently(JSONL_PATH, base_schema, dll_schema, api_schema, num_lines)
|
||||
|
||||
try:
|
||||
model, X_test, y_test = train_process(X, y, final_features)
|
||||
evaluate_and_save(model, X_test, y_test)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n[-] Training aborted.")
|
||||
except Exception as e:
|
||||
print(f"\n[-] System exception: {e}")
|
||||
@@ -0,0 +1,310 @@
|
||||
import os, sys, time, json, zlib, array, onnxmltools, multiprocessing
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import lightgbm as lgb
|
||||
import scipy.sparse as sp
|
||||
|
||||
from collections import defaultdict
|
||||
from onnxmltools.convert.common.data_types import FloatTensorType
|
||||
from sklearn.model_selection import train_test_split
|
||||
from sklearn.metrics import accuracy_score, roc_auc_score
|
||||
|
||||
try:
|
||||
import orjson
|
||||
JSON_LOADS = orjson.loads
|
||||
|
||||
except ImportError:
|
||||
JSON_LOADS = json.loads
|
||||
|
||||
####################################################################################################
|
||||
|
||||
JSONL_PATH = "pe_features.jsonl"
|
||||
MODEL_FILE = "model.txt"
|
||||
ONNX_FILE = "Pefile_General_T1.onnx"
|
||||
FEATURE_FILE = "features.json"
|
||||
|
||||
TEST_SIZE = 0.0001
|
||||
RANDOM_SEED = 42
|
||||
DLL_HASH_DIM = 256
|
||||
API_HASH_DIM = 1024
|
||||
|
||||
EXCLUDE_FEATURES = {
|
||||
'label', 'filehash',
|
||||
}
|
||||
|
||||
LGBM_PARAMS = {
|
||||
'objective': 'binary',
|
||||
'metric': ['binary_logloss', 'auc'],
|
||||
'boosting_type': 'gbdt',
|
||||
'num_leaves': 128,
|
||||
'feature_fraction': 0.8,
|
||||
'bagging_fraction': 1.0,
|
||||
'bagging_freq': 5,
|
||||
'verbose': -1,
|
||||
'seed': RANDOM_SEED,
|
||||
'n_jobs': -1,
|
||||
'max_depth': -1,
|
||||
'min_data_in_leaf': 50,
|
||||
}
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def analyze_schema(jsonl_path):
|
||||
print("[*] Stage 1: Scanning global schema...")
|
||||
base_keys = set()
|
||||
num_lines = 0
|
||||
|
||||
with open(jsonl_path, 'rb') as f:
|
||||
for line in f:
|
||||
num_lines += 1
|
||||
|
||||
if not base_keys:
|
||||
try:
|
||||
data = JSON_LOADS(line)
|
||||
base_data = data.get('Base')
|
||||
if base_data:
|
||||
base_keys.update(base_data.keys())
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
base_schema = sorted([k for k in base_keys if k.lower().strip() not in EXCLUDE_FEATURES])
|
||||
dll_schema = [f"DllHash_{i:03d}" for i in range(DLL_HASH_DIM)]
|
||||
api_schema = [f"ApiHash_{i:04d}" for i in range(API_HASH_DIM)]
|
||||
|
||||
return base_schema, dll_schema, api_schema, num_lines
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def load_data_efficiently(jsonl_path, base_schema, dll_schema, api_schema, num_lines):
|
||||
print(f"[*] Stage 2: Building sparse feature matrix dynamically via Multiprocessing...")
|
||||
final_features = base_schema + dll_schema + api_schema
|
||||
|
||||
num_features = len(final_features)
|
||||
offset_dll = len(base_schema)
|
||||
offset_api = offset_dll + DLL_HASH_DIM
|
||||
|
||||
file_size = os.path.getsize(jsonl_path)
|
||||
workers = min(os.cpu_count() or 4, 16)
|
||||
chunk_size = file_size // workers
|
||||
|
||||
tasks = []
|
||||
for i in range(workers):
|
||||
start = i * chunk_size
|
||||
end = file_size if i == workers - 1 else (i + 1) * chunk_size
|
||||
tasks.append((jsonl_path, start, end, base_schema, num_features, offset_dll, offset_api))
|
||||
|
||||
with multiprocessing.Pool(workers) as pool:
|
||||
results = pool.map(_chunk_worker, tasks)
|
||||
|
||||
Xs = [r[0] for r in results if r[0].shape[0] > 0]
|
||||
ys = [r[1] for r in results if r[1].shape[0] > 0]
|
||||
|
||||
X_final = sp.vstack(Xs, format='csr') if Xs else sp.csr_matrix((0, num_features), dtype=np.float32)
|
||||
y_final = np.concatenate(ys) if ys else np.array([], dtype=np.int8)
|
||||
|
||||
return X_final, pd.Series(y_final), final_features
|
||||
|
||||
def _chunk_worker(args):
|
||||
filepath, start_byte, end_byte, base_schema, num_features, offset_dll, offset_api = args
|
||||
|
||||
base_idx = {feat: i for i, feat in enumerate(base_schema)}
|
||||
base_idx_get = base_idx.get
|
||||
|
||||
rows = array.array('i')
|
||||
cols = array.array('i')
|
||||
vals = array.array('f')
|
||||
y_list = array.array('b')
|
||||
|
||||
with open(filepath, 'rb') as f:
|
||||
if start_byte > 0:
|
||||
f.seek(start_byte - 1)
|
||||
if f.read(1) != b'\n':
|
||||
f.seek(start_byte)
|
||||
f.readline()
|
||||
|
||||
else:
|
||||
f.seek(start_byte)
|
||||
else:
|
||||
f.seek(0)
|
||||
|
||||
row_idx = 0
|
||||
while f.tell() < end_byte:
|
||||
line = f.readline()
|
||||
if not line:
|
||||
break
|
||||
|
||||
try:
|
||||
data = JSON_LOADS(line)
|
||||
y_list.append(data.get('Label', 0))
|
||||
|
||||
base_data = data.get('Base')
|
||||
if base_data:
|
||||
for k, v in base_data.items():
|
||||
idx = base_idx_get(k)
|
||||
if idx is not None:
|
||||
val = float(v)
|
||||
if val != 0.0:
|
||||
rows.append(row_idx)
|
||||
cols.append(idx)
|
||||
vals.append(val)
|
||||
|
||||
dll_data = data.get('DLLs')
|
||||
if dll_data:
|
||||
for d in dll_data:
|
||||
idx = zlib.crc32(d.encode('utf-8', 'ignore')) % DLL_HASH_DIM
|
||||
rows.append(row_idx)
|
||||
cols.append(offset_dll + idx)
|
||||
vals.append(1.0)
|
||||
|
||||
api_data = data.get('APIs')
|
||||
if api_data:
|
||||
for a in api_data:
|
||||
idx = zlib.crc32(a.encode('utf-8', 'ignore')) % API_HASH_DIM
|
||||
rows.append(row_idx)
|
||||
cols.append(offset_api + idx)
|
||||
vals.append(1.0)
|
||||
|
||||
row_idx += 1
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
y_arr = np.frombuffer(y_list, dtype=np.int8)
|
||||
np_rows = np.frombuffer(rows, dtype=np.int32)
|
||||
np_cols = np.frombuffer(cols, dtype=np.int32)
|
||||
np_vals = np.frombuffer(vals, dtype=np.float32)
|
||||
|
||||
X_local = sp.csr_matrix((np_vals, (np_rows, np_cols)), shape=(row_idx, num_features), dtype=np.float32)
|
||||
|
||||
return X_local, y_arr
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def _calculate_dynamic_lr(current_iter: int) -> float:
|
||||
return max(0.01, 0.1 - (current_iter // 80) * 0.01)
|
||||
|
||||
def train_process(X, y, feature_names):
|
||||
print(f"[*] Dataset shape: {X.shape}")
|
||||
|
||||
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=TEST_SIZE, random_state=RANDOM_SEED, stratify=y)
|
||||
|
||||
n_pos = (y_train == 1).sum()
|
||||
n_neg = (y_train == 0).sum()
|
||||
|
||||
base_weight = n_neg / n_pos if n_pos > 0 else 1.0
|
||||
weight_ratio_target = 0.001
|
||||
final_pos_weight = base_weight * weight_ratio_target
|
||||
|
||||
print(f"[*] Sample distribution: Safe={n_neg}, Malware={n_pos}")
|
||||
print(f"[*] Weight correction: Base={base_weight:.4f}, Final={final_pos_weight:.4f}")
|
||||
|
||||
train_data = lgb.Dataset(X_train, label=y_train, feature_name=feature_names)
|
||||
valid_data = lgb.Dataset(X_test, label=y_test, reference=train_data, feature_name=feature_names)
|
||||
|
||||
params = LGBM_PARAMS.copy()
|
||||
params['scale_pos_weight'] = final_pos_weight
|
||||
|
||||
print("\n[*] Starting LightGBM training...")
|
||||
start_time = time.time()
|
||||
|
||||
model = lgb.train(
|
||||
params,
|
||||
train_data,
|
||||
valid_sets=[valid_data],
|
||||
num_boost_round=500,
|
||||
callbacks=[
|
||||
lgb.log_evaluation(period=50),
|
||||
lgb.reset_parameter(learning_rate=_calculate_dynamic_lr)
|
||||
]
|
||||
)
|
||||
|
||||
elapsed = time.time() - start_time
|
||||
print(f"\n[+] Training complete, time elapsed {elapsed:.2f} seconds")
|
||||
|
||||
return model, X_test, y_test
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def export_onnx_model(model, feature_names):
|
||||
print(f"[*] Exporting ONNX model to {ONNX_FILE}...")
|
||||
try:
|
||||
initial_type = [('float_input', FloatTensorType([None, len(feature_names)]))]
|
||||
onnx_model = onnxmltools.convert_lightgbm(model, initial_types=initial_type)
|
||||
onnxmltools.utils.save_model(onnx_model, ONNX_FILE)
|
||||
|
||||
with open(FEATURE_FILE, 'w') as f:
|
||||
json.dump(feature_names, f)
|
||||
|
||||
print(f"[+] ONNX export successful. Feature order saved to {FEATURE_FILE}")
|
||||
except Exception as e:
|
||||
print(f"[-] ONNX export failed: {e}")
|
||||
|
||||
def evaluate_and_save(model, X_test, y_test):
|
||||
print("\n------------------- Evaluation Report -------------------\n")
|
||||
y_prob = model.predict(X_test, num_iteration=model.best_iteration)
|
||||
y_pred = (y_prob > 0.5).astype(int)
|
||||
|
||||
acc = accuracy_score(y_test, y_pred)
|
||||
auc = roc_auc_score(y_test, y_prob)
|
||||
|
||||
tp = np.sum((y_test == 1) & (y_pred == 1))
|
||||
fn = np.sum((y_test == 1) & (y_pred == 0))
|
||||
tn = np.sum((y_test == 0) & (y_pred == 0))
|
||||
fp = np.sum((y_test == 0) & (y_pred == 1))
|
||||
|
||||
total_malware = tp + fn
|
||||
total_safe = tn + fp
|
||||
|
||||
detection_rate = (tp / total_malware * 100) if total_malware > 0 else 0.0
|
||||
fpr = (fp / total_safe * 100) if total_safe > 0 else 0.0
|
||||
|
||||
print(f"Accuracy : {acc:.6f}")
|
||||
print(f"ROC AUC : {auc:.6f}\n")
|
||||
|
||||
print(f"True Positive (Class 1) : {detection_rate:.3f}% ({tp}/{total_malware})")
|
||||
print(f"False Positive (Class 0) : {fpr:.3f}% ({fp}/{total_safe})")
|
||||
|
||||
print("\n---------------------------------------------------------\n")
|
||||
print("[*] Top 20 Features:")
|
||||
|
||||
feature_names = model.feature_name()
|
||||
importance = model.feature_importance(importance_type='gain')
|
||||
feature_imp = pd.DataFrame(sorted(zip(importance, feature_names), reverse=True), columns=['Value', 'Feature'])
|
||||
print(feature_imp.head(20).to_string(index=False))
|
||||
|
||||
print("\n---------------------------------------------------------\n")
|
||||
|
||||
model.save_model(MODEL_FILE)
|
||||
print(f"[+] Native model saved to: {os.path.abspath(MODEL_FILE)}")
|
||||
|
||||
export_onnx_model(model, feature_names)
|
||||
|
||||
####################################################################################################
|
||||
|
||||
if __name__ == "__main__":
|
||||
multiprocessing.freeze_support()
|
||||
print("\n---------------- PE Malware Trainer v4.0 ----------------\n")
|
||||
|
||||
if not os.path.exists(JSONL_PATH):
|
||||
print(f"[-] Error: Feature file {JSONL_PATH} not found.")
|
||||
sys.exit(1)
|
||||
|
||||
base_schema, dll_schema, api_schema, num_lines = analyze_schema(JSONL_PATH)
|
||||
if num_lines == 0:
|
||||
print("[-] Error: Feature file is empty.")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"[*] Detected Base features: {len(base_schema)}")
|
||||
print(f"[*] Hashing DLL features: {len(dll_schema)}")
|
||||
print(f"[*] Hashing API features: {len(api_schema)}")
|
||||
|
||||
X, y, final_features = load_data_efficiently(JSONL_PATH, base_schema, dll_schema, api_schema, num_lines)
|
||||
|
||||
try:
|
||||
model, X_test, y_test = train_process(X, y, final_features)
|
||||
evaluate_and_save(model, X_test, y_test)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n[-] Training aborted.")
|
||||
except Exception as e:
|
||||
print(f"\n[-] System exception: {e}")
|
||||
@@ -0,0 +1,841 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
:root {
|
||||
--bg-window: rgb(245, 245, 247);
|
||||
--bg-nav: rgb(255, 255, 255);
|
||||
--bg-panel: rgb(255, 255, 255);
|
||||
--bg-hover: rgba(0, 0, 0, 0.05);
|
||||
--text-primary: rgb(29, 29, 31);
|
||||
--text-secondary: rgb(134, 134, 139);
|
||||
--border-color: rgb(210, 210, 215);
|
||||
--accent-color: rgb(0, 122, 255);
|
||||
--accent-alpha: rgba(0, 122, 255, 0.1);
|
||||
--accent-shadow: rgba(0, 122, 255, 0.2);
|
||||
--accent-hover: rgba(0, 122, 255, 0.3);
|
||||
--shadow-sm: 0 2px 12px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: transparent;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.app-container {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
background-color: var(--bg-window);
|
||||
}
|
||||
|
||||
.navigate-window {
|
||||
height: 44px;
|
||||
background-color: var(--bg-nav);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 10px 0 20px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.navigate-title {
|
||||
font-size: 17px;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.2px;
|
||||
}
|
||||
|
||||
.window-controls {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
.ctrl-btn {
|
||||
width: 46px;
|
||||
height: 32px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transition: background-color 0.15s;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.ctrl-btn:hover { background-color: var(--bg-hover); color: var(--text-primary); }
|
||||
#close_button:hover { background-color: #ff3b30; color: white; }
|
||||
|
||||
.theme-svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.main-layout {
|
||||
display: flex;
|
||||
height: calc(100% - 44px);
|
||||
}
|
||||
|
||||
.options-window {
|
||||
width: 64px;
|
||||
background-color: var(--bg-nav);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 16px 0;
|
||||
gap: 12px;
|
||||
border-right: 1px solid var(--border-color);
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.nav-btn {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--text-secondary);
|
||||
transition: all 0.2s;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nav-btn svg { width: 22px; height: 22px; }
|
||||
.nav-btn:hover { background-color: var(--bg-hover); color: var(--text-primary); }
|
||||
.nav-btn.active { background-color: var(--accent-alpha); color: var(--accent-color); }
|
||||
|
||||
.spacer { flex-grow: 1; }
|
||||
|
||||
.content-window {
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.page {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 28px 32px;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transform: translateY(8px);
|
||||
transition: opacity 0.25s ease, transform 0.25s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.page.active {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.state-widget {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.state-title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
|
||||
.log-widget {
|
||||
flex-grow: 1;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.license-widget {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.log-text, .license-text {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
background-color: var(--bg-panel);
|
||||
color: var(--text-primary);
|
||||
resize: none;
|
||||
padding: 16px;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
white-space: pre;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.progress-widget {
|
||||
height: 70px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.header-group {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.header-group h2 {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.5px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.header-group p {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 4px;
|
||||
white-space: nowrap;
|
||||
overflow-x: auto;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.modern-action-group {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.modern-select {
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
.modern-select:hover, .modern-select:focus {
|
||||
border-color: var(--accent-color);
|
||||
box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.2);
|
||||
}
|
||||
|
||||
.modern-btn {
|
||||
padding: 8px 20px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 10px;
|
||||
background-color: transparent;
|
||||
color: var(--text-primary);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.modern-btn:hover { background-color: var(--bg-hover); }
|
||||
|
||||
.primary-btn {
|
||||
background-color: var(--accent-color);
|
||||
color: white;
|
||||
border: none;
|
||||
box-shadow: 0 2px 4px var(--accent-shadow);
|
||||
}
|
||||
.primary-btn:hover {
|
||||
box-shadow: 0 4px 8px var(--accent-hover);
|
||||
transform: translateY(-1px);
|
||||
background-color: var(--accent-color);
|
||||
opacity: 0.95;
|
||||
}
|
||||
.primary-btn:active {
|
||||
transform: translateY(0);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.danger-btn { color: #ff3b30; border-color: #ff3b30; }
|
||||
.danger-btn:hover { background-color: rgba(255, 59, 48, 0.1); }
|
||||
|
||||
.hidden { display: none !important; }
|
||||
|
||||
.scan-content {
|
||||
flex-grow: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.virus-widget {
|
||||
width: 100%;
|
||||
flex-grow: 1;
|
||||
min-height: 0;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
background-color: var(--bg-panel);
|
||||
overflow-y: auto;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.virus-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
min-width: max-content;
|
||||
}
|
||||
|
||||
.list-widget {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
padding-right: 12px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 18px 24px;
|
||||
border-radius: 16px;
|
||||
background-color: var(--bg-panel);
|
||||
border: 1px solid var(--border-color);
|
||||
transition: background-color 0.2s;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.item-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow-x: auto;
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
|
||||
.list-item[data-target]:hover, .list-item[data-action]:hover {
|
||||
background-color: var(--bg-hover);
|
||||
}
|
||||
|
||||
.item-info h2 {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.2px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.item-info p {
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 4px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.toggle-switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
height: 28px;
|
||||
}
|
||||
.toggle-switch input { opacity: 0; width: 0; height: 0; }
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background-color: var(--border-color);
|
||||
transition: .3s;
|
||||
border-radius: 28px;
|
||||
}
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 24px; width: 24px;
|
||||
left: 2px; bottom: 2px;
|
||||
background-color: white;
|
||||
transition: .3s;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.toggle-switch input:checked + .slider {
|
||||
background-color: var(--accent-color);
|
||||
}
|
||||
.toggle-switch input:checked + .slider:before {
|
||||
transform: translateX(22px);
|
||||
}
|
||||
|
||||
.icon-action-btn {
|
||||
width: 32px; height: 32px;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
display: flex; justify-content: center; align-items: center;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
.icon-action-btn:hover { background-color: var(--bg-hover); color: var(--text-primary); }
|
||||
.icon-action-btn svg { width: 20px; height: 20px; }
|
||||
|
||||
.sub-header-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sub-header-toolbar h2 {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.5px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.icon-btn {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
padding: 6px;
|
||||
border-radius: 8px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
.icon-btn:hover { background-color: var(--bg-hover); color: var(--text-primary); }
|
||||
.icon-btn svg { width: 24px; height: 24px; }
|
||||
|
||||
.search-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: var(--bg-panel);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 10px;
|
||||
padding: 0 14px;
|
||||
width: 200px;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.search-box:focus-within {
|
||||
border-color: var(--accent-color);
|
||||
box-shadow: 0 0 0 2px var(--accent-shadow);
|
||||
}
|
||||
|
||||
.search-box svg {
|
||||
width: 18px; height: 18px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.search-box input {
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--text-primary);
|
||||
padding: 10px;
|
||||
outline: none;
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.flex-grow {
|
||||
flex-grow: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.manage-widget {
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
background-color: var(--bg-panel);
|
||||
overflow-y: auto;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.manage-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
min-width: max-content;
|
||||
}
|
||||
|
||||
.manage-list-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.manage-list-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.manage-list-item:hover {
|
||||
background-color: var(--bg-hover);
|
||||
}
|
||||
|
||||
.manage-list-item input[type="checkbox"] {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
accent-color: var(--accent-color);
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.manage-list-item-content {
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
font-size: 14px;
|
||||
color: var(--text-primary);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.custom-select-wrapper {
|
||||
position: relative;
|
||||
min-width: 100px;
|
||||
display: inline-grid;
|
||||
vertical-align: middle;
|
||||
user-select: none;
|
||||
}
|
||||
select.hidden + .custom-select-wrapper {
|
||||
display: none !important;
|
||||
}
|
||||
.custom-select-trigger {
|
||||
grid-area: 1 / 1;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 16px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--border-color);
|
||||
background-color: var(--bg-panel);
|
||||
color: var(--text-primary);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
gap: 12px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.custom-select-phantom {
|
||||
grid-area: 1 / 1;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
display: grid;
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.custom-select-phantom span {
|
||||
grid-area: 1 / 1;
|
||||
white-space: nowrap;
|
||||
padding-right: 28px;
|
||||
}
|
||||
|
||||
.custom-select-wrapper.open .custom-select-trigger,
|
||||
.custom-select-trigger:hover {
|
||||
border-color: var(--accent-color);
|
||||
box-shadow: 0 0 0 2px var(--accent-shadow);
|
||||
}
|
||||
.custom-select-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--text-secondary);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
.custom-select-wrapper.open .custom-select-icon {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
.custom-select-icon svg { width: 16px; height: 16px; }
|
||||
.custom-select-options {
|
||||
position: absolute;
|
||||
top: calc(100% + 8px);
|
||||
left: 0;
|
||||
min-width: 100%;
|
||||
width: max-content;
|
||||
white-space: nowrap;
|
||||
|
||||
background-color: var(--bg-panel);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 10px;
|
||||
box-shadow: var(--shadow-sm);
|
||||
z-index: 100;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
max-height: 0;
|
||||
padding: 0 6px;
|
||||
border-width: 0;
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transform: translateY(-10px);
|
||||
transition: opacity 0.2s ease, transform 0.2s ease, visibility 0s 0.2s, max-height 0s 0.2s, padding 0s 0.2s, border-width 0s 0.2s, box-shadow 0s 0.2s;
|
||||
}
|
||||
.custom-select-wrapper.open .custom-select-options {
|
||||
max-height: 260px;
|
||||
padding: 6px;
|
||||
border-width: 1px;
|
||||
overflow-y: auto;
|
||||
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translateY(0);
|
||||
transition: opacity 0.2s ease, transform 0.2s ease, visibility 0s 0s, max-height 0s 0s, padding 0s 0s, border-width 0s 0s, box-shadow 0s 0s;
|
||||
}
|
||||
.custom-select-option {
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
color: var(--text-primary);
|
||||
transition: background-color 0.2s, color 0.2s;
|
||||
text-align: center;
|
||||
}
|
||||
.custom-select-option:hover {
|
||||
background-color: var(--bg-hover);
|
||||
}
|
||||
.custom-select-option.selected {
|
||||
background-color: var(--accent-alpha);
|
||||
color: var(--accent-color);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.home-top-widget {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 200px;
|
||||
gap: 24px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.quick-actions {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#loading_overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: #e0e0e0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 9999;
|
||||
transition: opacity 0.4s ease, visibility 0.4s ease;
|
||||
}
|
||||
|
||||
#loading_overlay.fade-out {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
animation: rotate 2s linear infinite;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.spinner .path {
|
||||
stroke: #86868b;
|
||||
stroke-linecap: round;
|
||||
animation: dash 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes dash {
|
||||
0% { stroke-dasharray: 1, 150; stroke-dashoffset: 0; }
|
||||
50% { stroke-dasharray: 90, 150; stroke-dashoffset: -35; }
|
||||
100% { stroke-dasharray: 90, 150; stroke-dashoffset: -124; }
|
||||
}
|
||||
|
||||
.app-container.fade-in {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.manage-list-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
background-color: var(--bg-panel);
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.manage-list-header input[type="checkbox"] {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
accent-color: var(--accent-color);
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.header-cols-container, .manage-list-item-content {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.col-header {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 8px;
|
||||
border-right: 1px solid var(--border-color);
|
||||
background-color: var(--bg-panel);
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
.col-header:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.header-text {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.manage-list-header, .col-header, .col-resizer {
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
.col-resizer {
|
||||
position: absolute;
|
||||
right: -5px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 10px;
|
||||
cursor: col-resize;
|
||||
z-index: 10;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.col-resizer::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 1px;
|
||||
background-color: transparent;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.col-resizer:hover::after,
|
||||
.col-resizer.resizing::after {
|
||||
background-color: var(--text-secondary) !important;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
body.resizing-active,
|
||||
body.resizing-active * {
|
||||
user-select: none !important;
|
||||
-webkit-user-select: none !important;
|
||||
cursor: col-resize !important;
|
||||
}
|
||||
|
||||
.col-resizer:hover {
|
||||
background-color: var(--accent-color);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.col-header:hover {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.sort-icon {
|
||||
font-size: 10px;
|
||||
margin-left: 4px;
|
||||
color: var(--accent-color);
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
.row-col {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 14px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.custom-context-menu {
|
||||
position: absolute;
|
||||
z-index: 10000;
|
||||
background-color: var(--bg-panel);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
padding: 4px;
|
||||
display: none;
|
||||
min-width: 160px;
|
||||
}
|
||||
|
||||
.custom-context-menu.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.custom-context-menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
color: var(--text-primary);
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.custom-context-menu-item:hover {
|
||||
background-color: var(--bg-hover);
|
||||
}
|
||||
|
||||
.custom-context-menu-item svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar { width: 6px; height: 6px; }
|
||||
::-webkit-scrollbar-track { background: transparent; }
|
||||
::-webkit-scrollbar-thumb { background: var(--border-color); border-radius: 10px; }
|
||||
::-webkit-scrollbar-thumb:hover { background: var(--text-secondary); }
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 6.6 KiB |
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,528 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-TW">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>PYAS Security</title>
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
<script defer src="/static/js/i18n.js"></script>
|
||||
<script defer src="/static/js/main.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="loading_overlay">
|
||||
<svg class="spinner" viewBox="0 0 50 50">
|
||||
<circle class="path" cx="25" cy="25" r="20" fill="none" stroke-width="5"></circle>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="app-container" style="opacity: 0; transition: opacity 0.5s ease;">
|
||||
<header class="navigate-window pywebview-drag-region">
|
||||
<div class="navigate-title" data-i18n>PYAS Security</div>
|
||||
<div class="window-controls">
|
||||
<button class="ctrl-btn" id="about_button" data-target="about_window">
|
||||
<svg viewBox="0 0 24 24" class="theme-svg" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>
|
||||
</button>
|
||||
<button class="ctrl-btn" id="minimize_button">
|
||||
<svg viewBox="0 0 24 24" class="theme-svg" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"></line></svg>
|
||||
</button>
|
||||
<button class="ctrl-btn" id="close_button">
|
||||
<svg viewBox="0 0 24 24" class="theme-svg" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="main-layout">
|
||||
<aside class="options-window pywebview-drag-region">
|
||||
<button class="nav-btn active" data-target="home_window">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>
|
||||
</button>
|
||||
<button class="nav-btn" data-target="scan_window">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
|
||||
</button>
|
||||
<button class="nav-btn" data-target="tool_window">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"></rect><rect x="14" y="3" width="7" height="7"></rect><rect x="14" y="14" width="7" height="7"></rect><rect x="3" y="14" width="7" height="7"></rect></svg>
|
||||
</button>
|
||||
<button class="nav-btn" data-target="protect_window">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg>
|
||||
</button>
|
||||
<div class="spacer"></div>
|
||||
<button class="nav-btn" data-target="setting_window">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
|
||||
</button>
|
||||
</aside>
|
||||
|
||||
<main class="content-window">
|
||||
<section id="home_window" class="page active">
|
||||
<div class="home-top-widget">
|
||||
<div class="state-widget">
|
||||
<svg viewBox="0 0 24 24" class="state-icon theme-svg" style="width: 120px; height: 120px;" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path><polyline points="9 12 11 14 15 10"></polyline></svg>
|
||||
<h2 class="state-title" data-i18n>此裝置已受到防護</h2>
|
||||
</div>
|
||||
<div class="quick-actions">
|
||||
<div class="list-item" style="cursor: pointer;" id="quick_scan_btn" data-action="scan">
|
||||
<div class="item-info">
|
||||
<h2 data-i18n>智能掃描</h2><p data-i18n>此選項可以快速掃描裝置安全性</p>
|
||||
</div>
|
||||
<button class="icon-action-btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg></button>
|
||||
</div>
|
||||
<div class="list-item" style="cursor: pointer;" data-target="log_export_window">
|
||||
<div class="item-info">
|
||||
<h2 data-i18n>日誌導出</h2><p data-i18n>此選項可以篩選日誌並匯出存檔</p>
|
||||
</div>
|
||||
<button class="icon-action-btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-widget">
|
||||
<textarea class="log-text" readonly></textarea>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="scan_window" class="page">
|
||||
<div class="progress-widget">
|
||||
<div class="header-group">
|
||||
<h2 class="section-title" data-i18n>病毒掃描</h2>
|
||||
<p class="section-desc" id="progress_text" data-i18n>此選項可以選擇進階掃描方式</p>
|
||||
</div>
|
||||
<div class="action-group modern-action-group">
|
||||
<select id="scan_method_select" class="modern-select">
|
||||
<option value="none" selected disabled data-i18n>選擇</option>
|
||||
<option value="smart" data-i18n>智能掃描</option>
|
||||
<option value="file" data-i18n>檔案掃描</option>
|
||||
<option value="path" data-i18n>路徑掃描</option>
|
||||
<option value="full" data-i18n>全盤掃描</option>
|
||||
</select>
|
||||
<button class="action-btn modern-btn hidden" id="stop_btn" data-i18n>停止</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scan-content">
|
||||
<div class="virus-widget">
|
||||
<ul class="virus-list" id="virus_list"></ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="protect_window" class="page">
|
||||
<div class="list-widget">
|
||||
<div class="list-item">
|
||||
<div class="item-info">
|
||||
<h2 data-i18n>進程防護</h2><p data-i18n>此選項可以終止惡意進程運行</p>
|
||||
</div>
|
||||
<label class="toggle-switch"><input type="checkbox" checked><span class="slider"></span></label>
|
||||
</div>
|
||||
<div class="list-item">
|
||||
<div class="item-info">
|
||||
<h2 data-i18n>檔案防護</h2><p data-i18n>此選項可以隔離惡意檔案建立</p>
|
||||
</div>
|
||||
<label class="toggle-switch"><input type="checkbox" checked><span class="slider"></span></label>
|
||||
</div>
|
||||
<div class="list-item">
|
||||
<div class="item-info">
|
||||
<h2 data-i18n>系統防護</h2><p data-i18n>此選項可以實時修復系統異常</p>
|
||||
</div>
|
||||
<label class="toggle-switch"><input type="checkbox" checked><span class="slider"></span></label>
|
||||
</div>
|
||||
<div class="list-item">
|
||||
<div class="item-info">
|
||||
<h2 data-i18n>驅動防護</h2><p data-i18n>此選項可以實時攔截惡意行為</p>
|
||||
</div>
|
||||
<label class="toggle-switch"><input type="checkbox" checked><span class="slider"></span></label>
|
||||
</div>
|
||||
<div class="list-item">
|
||||
<div class="item-info">
|
||||
<h2 data-i18n>網路防護</h2><p data-i18n>此選項可以攔截惡意網路連接</p>
|
||||
</div>
|
||||
<label class="toggle-switch"><input type="checkbox" checked><span class="slider"></span></label>
|
||||
</div>
|
||||
<div class="list-item" style="cursor: pointer;" data-target="custom_protect_window">
|
||||
<div class="item-info">
|
||||
<h2 data-i18n>自訂防護</h2><p data-i18n>此選項可以導入自訂防護規則</p>
|
||||
</div>
|
||||
<button class="icon-action-btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg></button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="tool_window" class="page">
|
||||
<div class="list-widget">
|
||||
<div class="list-item" style="cursor: pointer;" data-target="taskmgr_window">
|
||||
<div class="item-info">
|
||||
<h2 data-i18n>進程管理</h2><p data-i18n>此選項可以實時查看系統進程</p>
|
||||
</div>
|
||||
<button class="icon-action-btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg></button>
|
||||
</div>
|
||||
<div class="list-item" style="cursor: pointer;" data-target="startup_window">
|
||||
<div class="item-info">
|
||||
<h2 data-i18n>啟動項管理</h2><p data-i18n>此選項可以管理各應用啟動項</p>
|
||||
</div>
|
||||
<button class="icon-action-btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg></button>
|
||||
</div>
|
||||
<div class="list-item" style="cursor: pointer;" data-target="junk_window">
|
||||
<div class="item-info">
|
||||
<h2 data-i18n>垃圾清理</h2><p data-i18n>此選項可以清理暫存垃圾檔案</p>
|
||||
</div>
|
||||
<button class="icon-action-btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg></button>
|
||||
</div>
|
||||
<div class="list-item" style="cursor: pointer;" data-target="repair_window">
|
||||
<div class="item-info">
|
||||
<h2 data-i18n>系統修復</h2><p data-i18n>此選項可以修復系統登錄檔項目</p>
|
||||
</div>
|
||||
<button class="icon-action-btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg></button>
|
||||
</div>
|
||||
<div class="list-item" style="cursor: pointer;" data-target="popup_window">
|
||||
<div class="item-info">
|
||||
<h2 data-i18n>彈窗攔截</h2><p data-i18n>此選項可以選擇指定窗口攔截</p>
|
||||
</div>
|
||||
<button class="icon-action-btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg></button>
|
||||
</div>
|
||||
<div class="list-item" style="cursor: pointer;" data-target="netmon_window">
|
||||
<div class="item-info">
|
||||
<h2 data-i18n>流量監控</h2><p data-i18n>此選項可以實時監控網路流量</p>
|
||||
</div>
|
||||
<button class="icon-action-btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg></button>
|
||||
</div>
|
||||
<div class="list-item" style="cursor: pointer;" data-target="whitelist_window">
|
||||
<div class="item-info">
|
||||
<h2 data-i18n>白名單</h2><p data-i18n>此選項可以查看並管理白名單</p>
|
||||
</div>
|
||||
<button class="icon-action-btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg></button>
|
||||
</div>
|
||||
<div class="list-item" style="cursor: pointer;" data-target="quarantine_window">
|
||||
<div class="item-info">
|
||||
<h2 data-i18n>隔離區</h2><p data-i18n>此選項可以查看並管理隔離區</p>
|
||||
</div>
|
||||
<button class="icon-action-btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg></button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="setting_window" class="page">
|
||||
<div class="list-widget">
|
||||
<div class="list-item">
|
||||
<div class="item-info">
|
||||
<h2 data-i18n>增強模式</h2><p data-i18n>此選項可以增強靜態掃描引擎</p>
|
||||
</div>
|
||||
<label class="toggle-switch"><input type="checkbox"><span class="slider"></span></label>
|
||||
</div>
|
||||
<div class="list-item">
|
||||
<div class="item-info">
|
||||
<h2 data-i18n>擴展引擎</h2><p data-i18n>此選項可以使用擴展規則引擎</p>
|
||||
</div>
|
||||
<label class="toggle-switch"><input type="checkbox"><span class="slider"></span></label>
|
||||
</div>
|
||||
<div class="list-item">
|
||||
<div class="item-info">
|
||||
<h2 data-i18n>雲端服務</h2><p data-i18n>此選項可以自動上報可疑檔案</p>
|
||||
</div>
|
||||
<label class="toggle-switch"><input type="checkbox" checked><span class="slider"></span></label>
|
||||
</div>
|
||||
<div class="list-item">
|
||||
<div class="item-info">
|
||||
<h2 data-i18n>選單掃描</h2><p data-i18n>此選項可以啟用右鍵掃描功能</p>
|
||||
</div>
|
||||
<label class="toggle-switch"><input type="checkbox"><span class="slider"></span></label>
|
||||
</div>
|
||||
<div class="list-item">
|
||||
<div class="item-info">
|
||||
<h2 data-i18n>顯色主題</h2><p data-i18n>此選項可以選擇主題顏色</p>
|
||||
</div>
|
||||
<select id="theme_select" class="modern-select">
|
||||
<option value="system_switch" selected data-i18n>跟隨系統</option>
|
||||
<option value="white_switch" data-i18n>白色主題</option>
|
||||
<option value="red_switch" data-i18n>紅色主題</option>
|
||||
<option value="yellow_switch" data-i18n>黃色主題</option>
|
||||
<option value="green_switch" data-i18n>綠色主題</option>
|
||||
<option value="blue_switch" data-i18n>藍色主題</option>
|
||||
<option value="black_switch" data-i18n>黑色主題</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="list-item">
|
||||
<div class="item-info">
|
||||
<h2 data-i18n>顯示語言</h2><p data-i18n>此選項可以選擇顯示語言</p>
|
||||
</div>
|
||||
<select id="lang_select" class="modern-select">
|
||||
<option value="english_switch">English</option>
|
||||
<option value="traditional_switch">繁體中文</option>
|
||||
<option value="simplified_switch">简体中文</option>
|
||||
<option value="japanese_switch">日本語</option>
|
||||
<option value="korean_switch">한국어</option>
|
||||
<option value="french_switch">Français</option>
|
||||
<option value="spanish_switch">Español</option>
|
||||
<option value="hindi_switch">हिन्दी</option>
|
||||
<option value="arabic_switch">العربية</option>
|
||||
<option value="russian_switch">Русский</option>
|
||||
<option value="slovenian_switch">Slovenščina</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="list-item">
|
||||
<div class="item-info">
|
||||
<h2 data-i18n>重置選項</h2><p data-i18n>此選項可以重置所有設定</p>
|
||||
</div>
|
||||
<button class="action-btn modern-btn danger-btn" data-i18n>執行</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="about_window" class="page">
|
||||
<div class="list-widget">
|
||||
<div class="list-item">
|
||||
<div class="item-info">
|
||||
<h2 data-i18n>產品金鑰</h2><p data-i18n>此選項可以啟用產品金鑰</p>
|
||||
</div>
|
||||
<button class="icon-action-btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"></path></svg></button>
|
||||
</div>
|
||||
<div class="list-item">
|
||||
<div class="item-info">
|
||||
<h2 data-i18n>官方網站</h2><p data-i18n>此選項可以前往官方網站</p>
|
||||
</div>
|
||||
<button class="icon-action-btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="2" y1="12" x2="22" y2="12"></line><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path></svg></button>
|
||||
</div>
|
||||
<div class="list-item">
|
||||
<div class="item-info">
|
||||
<h2 data-i18n>檢查更新</h2><p data-i18n>此選項可以檢查可用更新</p>
|
||||
</div>
|
||||
<button class="icon-action-btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"></polyline><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"></path></svg></button>
|
||||
</div>
|
||||
<div class="license-widget">
|
||||
<textarea class="license-text" readonly>87owo Publicly Available Source Code License Agreement
|
||||
Last updated: 2025-12-19
|
||||
|
||||
1. License and Ownership
|
||||
- This software and its source code are source available for public view.
|
||||
- Access or execution of certain features may require an activation key.
|
||||
- 87owo reserves all legal rights, no intellectual property is transferred.
|
||||
- All project archives, source code, and binaries remain fully proprietary.
|
||||
|
||||
2. Use and Prohibitions
|
||||
- You may view, download, and modify the code for local and personal use.
|
||||
- Redistribution of any original or modified code requires prior consent.
|
||||
- Commercial hosting or SaaS deployment is prohibited without permission.
|
||||
- Reverse engineering, license removal, or illegal use is strictly barred.
|
||||
|
||||
3. Data and Privacy
|
||||
- This project may collect operational data for optimization and analysis.
|
||||
- All collected data is strictly anonymized to ensure the user's privacy.
|
||||
- Transmission of information is encrypted to prevent unauthorized access.
|
||||
|
||||
4. Updates and Terms
|
||||
- New releases may include critical patches, enhancements, or UI changes.
|
||||
- Continued use of these services constitutes acceptance of updated terms.
|
||||
- The author reserves the right to modify these license terms at any time.
|
||||
|
||||
5. Warranty and Liability
|
||||
- All services and assets are provided "as is" without any form of warranty.
|
||||
- 87owo is not liable for any direct or indirect damages from software use.
|
||||
- Users assume all risks regarding performance, data, and system integrity.
|
||||
|
||||
© 2020-2026 87owo (PYAS Security). All rights reserved.</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="taskmgr_window" class="page sub-page">
|
||||
<div class="sub-header-toolbar">
|
||||
<button class="icon-btn" data-target="tool_window"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg></button>
|
||||
<h2 data-i18n>進程管理</h2>
|
||||
<div class="spacer"></div>
|
||||
<div class="search-box">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
|
||||
<input type="text" data-i18n-placeholder="搜尋進程..." placeholder="搜尋進程...">
|
||||
</div>
|
||||
<div class="action-group modern-action-group">
|
||||
<select id="taskmgr_select" class="modern-select">
|
||||
<option value="none" selected disabled data-i18n="btn_select">選擇</option>
|
||||
<option value="view" data-i18n="btn_view_item">查看項目</option>
|
||||
<option value="end" data-i18n="btn_end">結束進程</option>
|
||||
<option value="quarantine" data-i18n="act_quarantine">加入隔離區</option>
|
||||
<option value="whitelist" data-i18n="act_white">加入白名單</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="manage-widget flex-grow"><ul class="manage-list"></ul></div>
|
||||
</section>
|
||||
|
||||
<section id="startup_window" class="page sub-page">
|
||||
<div class="sub-header-toolbar">
|
||||
<button class="icon-btn" data-target="tool_window"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg></button>
|
||||
<h2 data-i18n>啟動項管理</h2>
|
||||
<div class="spacer"></div>
|
||||
<div class="search-box">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
|
||||
<input type="text" data-i18n-placeholder="搜尋項目..." placeholder="搜尋項目...">
|
||||
</div>
|
||||
<div class="action-group modern-action-group">
|
||||
<select id="startup_select" class="modern-select">
|
||||
<option value="none" selected disabled data-i18n="btn_select">選擇</option>
|
||||
<option value="enable" data-i18n="btn_enable_item">啟用項目</option>
|
||||
<option value="disable" data-i18n="btn_disable_item">禁用項目</option>
|
||||
<option value="delete" data-i18n="btn_delete_item">刪除項目</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="manage-widget flex-grow"><ul class="manage-list"></ul></div>
|
||||
</section>
|
||||
|
||||
<section id="whitelist_window" class="page sub-page">
|
||||
<div class="sub-header-toolbar">
|
||||
<button class="icon-btn" data-target="tool_window"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg></button>
|
||||
<h2 data-i18n>白名單管理</h2>
|
||||
<div class="spacer"></div>
|
||||
<div class="search-box">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
|
||||
<input type="text" data-i18n-placeholder="搜尋檔案..." placeholder="搜尋檔案...">
|
||||
</div>
|
||||
<div class="action-group modern-action-group">
|
||||
<select id="whitelist_select" class="modern-select">
|
||||
<option value="none" selected disabled data-i18n="btn_select">選擇</option>
|
||||
<option value="add_file" data-i18n="btn_add_file">新增檔案</option>
|
||||
<option value="add_path" data-i18n="btn_add_path">新增路徑</option>
|
||||
<option value="extract" data-i18n="btn_extract_item">提取項目</option>
|
||||
<option value="remove" data-i18n="btn_remove_item">移除項目</option>
|
||||
<option value="quarantine" data-i18n="act_quarantine">加入隔離區</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="manage-widget flex-grow"><ul class="manage-list"></ul></div>
|
||||
</section>
|
||||
|
||||
<section id="quarantine_window" class="page sub-page">
|
||||
<div class="sub-header-toolbar">
|
||||
<button class="icon-btn" data-target="tool_window"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg></button>
|
||||
<h2 data-i18n>隔離區管理</h2>
|
||||
<div class="spacer"></div>
|
||||
<div class="search-box">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
|
||||
<input type="text" data-i18n-placeholder="搜尋檔案..." placeholder="搜尋檔案...">
|
||||
</div>
|
||||
<div class="action-group modern-action-group">
|
||||
<select id="quarantine_select" class="modern-select">
|
||||
<option value="none" selected disabled data-i18n="btn_select">選擇</option>
|
||||
<option value="add_file" data-i18n="btn_add_file">新增檔案</option>
|
||||
<option value="add_path" data-i18n="btn_add_path">新增路徑</option>
|
||||
<option value="extract" data-i18n="btn_extract_item">提取項目</option>
|
||||
<option value="remove" data-i18n="btn_remove_item">移除項目</option>
|
||||
<option value="whitelist" data-i18n="act_white">加入白名單</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="manage-widget flex-grow"><ul class="manage-list"></ul></div>
|
||||
</section>
|
||||
|
||||
<section id="junk_window" class="page sub-page">
|
||||
<div class="sub-header-toolbar">
|
||||
<button class="icon-btn" data-target="tool_window"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg></button>
|
||||
<h2 data-i18n>垃圾清理</h2>
|
||||
<div class="spacer"></div>
|
||||
<div class="search-box">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
|
||||
<input type="text" data-i18n-placeholder="搜尋檔案..." placeholder="搜尋檔案...">
|
||||
</div>
|
||||
<div class="action-group modern-action-group">
|
||||
<select id="junk_select" class="modern-select">
|
||||
<option value="none" selected disabled data-i18n="btn_select">選擇</option>
|
||||
<option value="view" data-i18n="btn_view_item">查看項目</option>
|
||||
<option value="delete" data-i18n="btn_delete_item">刪除項目</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="manage-widget flex-grow"><ul class="manage-list"></ul></div>
|
||||
</section>
|
||||
|
||||
<section id="repair_window" class="page sub-page">
|
||||
<div class="sub-header-toolbar">
|
||||
<button class="icon-btn" data-target="tool_window"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg></button>
|
||||
<h2 data-i18n>系統修復</h2>
|
||||
<div class="spacer"></div>
|
||||
<div class="action-group modern-action-group">
|
||||
<select id="repair_select" class="modern-select">
|
||||
<option value="none" selected disabled data-i18n="btn_select">選擇</option>
|
||||
<option value="repair" data-i18n="btn_repair_item">修復項目</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="manage-widget flex-grow"><ul class="manage-list"></ul></div>
|
||||
</section>
|
||||
|
||||
<section id="popup_window" class="page sub-page">
|
||||
<div class="sub-header-toolbar">
|
||||
<button class="icon-btn" data-target="tool_window"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg></button>
|
||||
<h2 data-i18n>彈窗攔截</h2>
|
||||
<div class="spacer"></div>
|
||||
<div class="search-box">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
|
||||
<input type="text" data-i18n-placeholder="搜尋窗口..." placeholder="搜尋窗口...">
|
||||
</div>
|
||||
<div class="action-group modern-action-group">
|
||||
<select id="popup_select" class="modern-select">
|
||||
<option value="none" selected disabled data-i18n="btn_select">選擇</option>
|
||||
<option value="add" data-i18n="btn_add_item">新增項目</option>
|
||||
<option value="remove" data-i18n="btn_remove_item">移除項目</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="manage-widget flex-grow"><ul class="manage-list"></ul></div>
|
||||
</section>
|
||||
|
||||
<section id="netmon_window" class="page sub-page">
|
||||
<div class="sub-header-toolbar">
|
||||
<button class="icon-btn" data-target="tool_window"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg></button>
|
||||
<h2 data-i18n>流量監控</h2>
|
||||
<div class="spacer"></div>
|
||||
<div class="search-box">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
|
||||
<input type="text" data-i18n-placeholder="搜尋進程..." placeholder="搜尋進程...">
|
||||
</div>
|
||||
<div class="action-group modern-action-group">
|
||||
<select id="netmon_select" class="modern-select">
|
||||
<option value="none" selected disabled data-i18n="btn_select">選擇</option>
|
||||
<option value="view" data-i18n="btn_view_item">查看項目</option>
|
||||
<option value="end" data-i18n="btn_end">結束進程</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="manage-widget flex-grow"><ul class="manage-list"></ul></div>
|
||||
</section>
|
||||
|
||||
<section id="custom_protect_window" class="page sub-page">
|
||||
<div class="sub-header-toolbar">
|
||||
<button class="icon-btn" data-target="protect_window"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg></button>
|
||||
<h2 data-i18n>自訂防護</h2>
|
||||
<div class="spacer"></div>
|
||||
<div class="search-box">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
|
||||
<input type="text" data-i18n-placeholder="搜尋規則..." placeholder="搜尋規則...">
|
||||
</div>
|
||||
<div class="action-group modern-action-group">
|
||||
<select id="custom_protect_select" class="modern-select">
|
||||
<option value="none" selected disabled data-i18n="btn_select">選擇</option>
|
||||
<option value="add" data-i18n="btn_add_item">新增項目</option>
|
||||
<option value="remove" data-i18n="btn_remove_item">移除項目</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="manage-widget flex-grow"><ul class="manage-list"></ul></div>
|
||||
</section>
|
||||
|
||||
<section id="log_export_window" class="page sub-page">
|
||||
<div class="sub-header-toolbar">
|
||||
<button class="icon-btn" data-target="home_window"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg></button>
|
||||
<h2 data-i18n>日誌導出</h2>
|
||||
<div class="spacer"></div>
|
||||
<div class="search-box">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
|
||||
<input type="text" data-i18n-placeholder="搜尋日誌..." placeholder="搜尋日誌...">
|
||||
</div>
|
||||
<div class="action-group modern-action-group">
|
||||
<select id="log_export_select" class="modern-select">
|
||||
<option value="none" selected disabled data-i18n="btn_select">選擇</option>
|
||||
<option value="export" data-i18n="btn_export">導出項目</option>
|
||||
<option value="delete" data-i18n="btn_remove_item">移除項目</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="manage-widget flex-grow"><ul class="manage-list"></ul></div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
87owo Publicly Available Source Code License Agreement
|
||||
Last updated: 2025-12-19
|
||||
|
||||
1. License and Ownership
|
||||
- This software and its source code are source available for public view.
|
||||
- Access or execution of certain features may require an activation key.
|
||||
- 87owo reserves all legal rights, no intellectual property is transferred.
|
||||
- All project archives, source code, and binaries remain fully proprietary.
|
||||
|
||||
2. Use and Prohibitions
|
||||
- You may view, download, and modify the code for local and personal use.
|
||||
- Redistribution of any original or modified code requires prior consent.
|
||||
- Commercial hosting or SaaS deployment is prohibited without permission.
|
||||
- Reverse engineering, license removal, or illegal use is strictly barred.
|
||||
|
||||
3. Data and Privacy
|
||||
- This project may collect operational data for optimization and analysis.
|
||||
- All collected data is strictly anonymized to ensure the user's privacy.
|
||||
- Transmission of information is encrypted to prevent unauthorized access.
|
||||
|
||||
4. Updates and Terms
|
||||
- New releases may include critical patches, enhancements, or UI changes.
|
||||
- Continued use of these services constitutes acceptance of updated terms.
|
||||
- The author reserves the right to modify these license terms at any time.
|
||||
|
||||
5. Warranty and Liability
|
||||
- All services and assets are provided "as is" without any form of warranty.
|
||||
- 87owo is not liable for any direct or indirect damages from software use.
|
||||
- Users assume all risks regarding performance, data, and system integrity.
|
||||
|
||||
© 2020-2026 87owo (PYAS Security). All rights reserved.
|
||||
-177
@@ -1,177 +0,0 @@
|
||||
import hashlib
|
||||
import os
|
||||
from functools import partial
|
||||
import json
|
||||
import zipfile
|
||||
|
||||
def md5_file():
|
||||
for i in range(410):
|
||||
x = 5 - len(str(i))
|
||||
y = '0'*x+str(i)
|
||||
bytes = zipfile.ZipFile('Library/MD5.zip', 'r').read(str(y)+'.md5')
|
||||
|
||||
|
||||
#def pyae_scan():
|
||||
|
||||
def scan_sha256(file):
|
||||
try:
|
||||
virus_found = False
|
||||
with open(file,"rb") as f:
|
||||
bytes = f.read()
|
||||
readable_hash = hashlib.sha256(bytes).hexdigest();
|
||||
print("SHA256: " + readable_hash)
|
||||
with open("Library/SHA256.txt",'r') as f:
|
||||
lines = [line.rstrip() for line in f]
|
||||
for line in lines:
|
||||
if str(readable_hash) == str(line.split(";")[0]):
|
||||
virus_found = True
|
||||
f.close()
|
||||
if not virus_found:
|
||||
print("File is safe!")
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
#print("Virus detected! File quarentined")
|
||||
#os.remove(file)
|
||||
except:
|
||||
pass
|
||||
|
||||
def scan_md5(file):
|
||||
#try:
|
||||
virus_found = False
|
||||
with open(file,"rb") as f:
|
||||
bytes = f.read()
|
||||
readable_hash = hashlib.md5(bytes).hexdigest();
|
||||
#print("MD5: " + readable_hash)
|
||||
for i in range(410):
|
||||
x = 5 - len(str(i))
|
||||
y = '0'*x+str(i)
|
||||
with open('Library/MD5/'+str(y)+'.md5','r') as f:
|
||||
#print(str(f))
|
||||
lines = [line.rstrip() for line in f]
|
||||
for line in lines:
|
||||
if str(readable_hash) == str(line.split(";")[0]):
|
||||
#print(str(f))
|
||||
virus_found = True
|
||||
f.close()
|
||||
if not virus_found:
|
||||
#print("File is safe!")
|
||||
return False
|
||||
else:
|
||||
#print("Virus detected! File quarentined")
|
||||
#os.remove(file)
|
||||
return True
|
||||
#except Exception as e:
|
||||
# print(e)
|
||||
|
||||
|
||||
def scan(file):
|
||||
try:
|
||||
virus_found = False
|
||||
with open(file,"rb") as f:
|
||||
bytes = f.read()
|
||||
readable_hash = hashlib.sha1(bytes).hexdigest();
|
||||
print("SHA1: " + readable_hash)
|
||||
with open('Library/SHA1 HASHES.json', 'r') as f:
|
||||
dataset = json.loads(f.read())
|
||||
for index, item in enumerate(dataset["data"]):
|
||||
if str(item['hash']) == str(readable_hash):
|
||||
virus_found = True
|
||||
f.close()
|
||||
if not virus_found:
|
||||
print("File is safe!")
|
||||
return False
|
||||
else:
|
||||
#print("Virus detected! File quarentined")
|
||||
#os.remove(file)
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
|
||||
def pyae_scan_full(filepath):
|
||||
try:
|
||||
fss = 0
|
||||
start = time.time()
|
||||
findfile('A:/',ffile,fss,start)
|
||||
findfile('B:/',ffile,fss,start)
|
||||
findfile('C:/',ffile,fss,start)
|
||||
findfile('D:/',ffile,fss,start)
|
||||
findfile('E:/',ffile,fss,start)
|
||||
findfile('F:/',ffile,fss,start)
|
||||
findfile('G:/',ffile,fss,start)
|
||||
findfile('H:/',ffile,fss,start)
|
||||
findfile('I:/',ffile,fss,start)
|
||||
findfile('J:/',ffile,fss,start)
|
||||
findfile('K:/',ffile,fss,start)
|
||||
findfile('L:/',ffile,fss,start)
|
||||
findfile('M:/',ffile,fss,start)
|
||||
findfile('N:/',ffile,fss,start)
|
||||
findfile('O:/',ffile,fss,start)
|
||||
findfile('P:/',ffile,fss,start)
|
||||
findfile('Q:/',ffile,fss,start)
|
||||
findfile('R:/',ffile,fss,start)
|
||||
findfile('S:/',ffile,fss,start)
|
||||
findfile('T:/',ffile,fss,start)
|
||||
findfile('U:/',ffile,fss,start)
|
||||
findfile('V:/',ffile,fss,start)
|
||||
findfile('W:/',ffile,fss,start)
|
||||
findfile('X:/',ffile,fss,start)
|
||||
findfile('Y:/',ffile,fss,start)
|
||||
findfile('Z:/',ffile,fss,start)
|
||||
end = time.time()
|
||||
ft = open('PYASF.txt','r')
|
||||
fe = ft.read()
|
||||
ft.close()
|
||||
textPad.insert("insert", '''
|
||||
尋找結果: '''+'''
|
||||
============================================================================
|
||||
|
||||
'''+str(fe)+'''============================================================================
|
||||
總共耗時: '''+str(end - start)+''' 秒''')
|
||||
os.remove('PYASF.txt')
|
||||
except:
|
||||
pass
|
||||
|
||||
def findfile(path,ffile,fss,start):
|
||||
textPad.delete(1.0,END)
|
||||
try:
|
||||
textPad.insert("insert", '正在尋找: '+str(path))
|
||||
for fd in os.listdir(path):
|
||||
root.update()
|
||||
fullpath = os.path.join(path,fd)
|
||||
if os.path.isdir(fullpath):
|
||||
#print('正在掃描: ',fullpath)
|
||||
findfile(fullpath,ffile,fss,start)
|
||||
else:
|
||||
fss = fss + 1
|
||||
if ffile in str(fd):
|
||||
date = time.ctime(os.path.getmtime(fullpath))
|
||||
#try:
|
||||
#f = open(fullpath, 'r')
|
||||
#text = f.readline()
|
||||
#f.close()
|
||||
#print('預覽內容: '+text)
|
||||
#except:
|
||||
#print('預覽內容: ✖錯誤,這個檔案不支援預覽')
|
||||
ft = open('PYASF.txt','a')
|
||||
ft.write('''找到檔案: '''+str(fullpath)+'''
|
||||
建立日期: '''+str(date)+'''
|
||||
|
||||
''')
|
||||
ft.close()
|
||||
continue
|
||||
except:
|
||||
pass
|
||||
|
||||
#scan(filepath)
|
||||
#scan_md5(filepath)
|
||||
#scan_sha256(filepath)
|
||||
#ed01ebfbc9eb5bbea545af4d01bf5f1071661840480439c6e5babe8e080e41aa.exe
|
||||
def pyae_file_scan(file):
|
||||
if scan_md5(file):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
#if scan(file) or scan_sha256(file) or scan_md5(file):
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
at_list_winf = ['CreateFileW',
|
||||
'DeleteFileW',
|
||||
'FindExistFileW',
|
||||
'FindNextFileW',
|
||||
'ReadFile',
|
||||
'WriteFile',
|
||||
'MapViewOfFile',
|
||||
'SetFilePointer',
|
||||
'CreateDirectoryW',
|
||||
'FindClose',
|
||||
'SetWindowHookEx',
|
||||
'ExitProcess',
|
||||
'GetCurrentProcess',
|
||||
'GetProcessHeap',
|
||||
'OpenProcess',
|
||||
'IsDebuggerPresent',
|
||||
'CreateThread',
|
||||
'GetCurrentThread',
|
||||
'GetModuleHandleW',
|
||||
'BeginDeferWindowPos',
|
||||
'CreateDialogParaW',
|
||||
'CreateWindowExW',
|
||||
'GetWindowLongW',
|
||||
'GetWindowRect',
|
||||
'GetWindowTextW',
|
||||
'InvalidRect',
|
||||
'SetWindowTextW',
|
||||
'ShowWindow',
|
||||
'RegisterClassExW',
|
||||
'RegisterHotKey',
|
||||
'RegOpenKeyExW',
|
||||
'RegOpenCurrentUser',
|
||||
'RegSetValueExW',
|
||||
'MessageBoxW']
|
||||
|
||||
ai_list_file = ['CreateFileW'
|
||||
'DeleteFileW'
|
||||
'FindExistFileW'
|
||||
'FindNextFileW'
|
||||
'ReadFile'
|
||||
'WriteFile'
|
||||
'MapViewOfFile'
|
||||
'SetFilePointer'
|
||||
'CreateDirectoryW'
|
||||
'FindClose'
|
||||
'GetModuleHandleW']
|
||||
|
||||
ai_list_process = ['ExitProcess',
|
||||
'GetCurrentProcess',
|
||||
'GetProcessHeap',
|
||||
'OpenProcess']
|
||||
|
||||
ai_list_Antidebug = ['IsDebuggerPresent']
|
||||
|
||||
ai_list_thread = ['CreateThread',
|
||||
'GetCurrentThread']
|
||||
|
||||
ai_list_windows = ['MessageBoxW',
|
||||
'BeginDeferWindowPos',
|
||||
'CreateDialogParaW',
|
||||
'CreateWindowExW',
|
||||
'GetWindowLongW',
|
||||
'GetWindowRect',
|
||||
'GetWindowTextW',
|
||||
'InvalidRect'
|
||||
'SetWindowTextW'
|
||||
'ShowWindow']
|
||||
|
||||
ai_list_regkey = ['RegisterClassExW'
|
||||
'RegisterHotKey',
|
||||
'RegOpenKeyExW',
|
||||
'RegOpenCurrentUser',
|
||||
'RegSetValueExW']
|
||||
|
||||
ai_list_hook = ['SetWindowHookEx']
|
||||
|
||||
at = 34
|
||||
t = 35
|
||||
ti = 35
|
||||
+919
@@ -0,0 +1,919 @@
|
||||
import os, re, yara, time, math, json, zlib, numpy, base64, requests, datetime
|
||||
import ctypes, ctypes.wintypes, hashlib, pefile, threading, onnxruntime
|
||||
|
||||
####################################################################################################
|
||||
|
||||
class GUID(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("Data1", ctypes.wintypes.DWORD),
|
||||
("Data2", ctypes.wintypes.WORD),
|
||||
("Data3", ctypes.wintypes.WORD),
|
||||
("Data4", ctypes.c_ubyte * 8)
|
||||
]
|
||||
|
||||
class WINTRUST_FILE_INFO(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("cbStruct", ctypes.wintypes.DWORD),
|
||||
("pcwszFilePath", ctypes.wintypes.LPCWSTR),
|
||||
("hFile", ctypes.wintypes.HANDLE),
|
||||
("pgKnownSubject", ctypes.wintypes.LPVOID)
|
||||
]
|
||||
|
||||
class WINTRUST_DATA_UNION(ctypes.Union):
|
||||
_fields_ = [
|
||||
("pFile", ctypes.POINTER(WINTRUST_FILE_INFO)),
|
||||
("pCatalog", ctypes.wintypes.LPVOID),
|
||||
("pBlob", ctypes.wintypes.LPVOID),
|
||||
("pSgnr", ctypes.wintypes.LPVOID),
|
||||
("pCert", ctypes.wintypes.LPVOID)
|
||||
]
|
||||
|
||||
class WINTRUST_DATA(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("cbStruct", ctypes.wintypes.DWORD),
|
||||
("pPolicyCallbackData", ctypes.wintypes.LPVOID),
|
||||
("pSIPClientData", ctypes.wintypes.LPVOID),
|
||||
("dwUIChoice", ctypes.wintypes.DWORD),
|
||||
("fdwRevocationChecks", ctypes.wintypes.DWORD),
|
||||
("dwUnionChoice", ctypes.wintypes.DWORD),
|
||||
("u", WINTRUST_DATA_UNION),
|
||||
("dwStateAction", ctypes.wintypes.DWORD),
|
||||
("hWVTStateData", ctypes.wintypes.HANDLE),
|
||||
("pwszURLReference", ctypes.wintypes.LPCWSTR),
|
||||
("dwProvFlags", ctypes.wintypes.DWORD),
|
||||
("dwUIContext", ctypes.wintypes.DWORD),
|
||||
("pSignatureSettings", ctypes.wintypes.LPVOID)
|
||||
]
|
||||
|
||||
####################################################################################################
|
||||
|
||||
class sign_scanner:
|
||||
def __init__(self):
|
||||
self.verify = GUID(0x00AAC56B, 0xCD44, 0x11D0, (ctypes.c_ubyte * 8)(0x8C, 0xC2, 0x00, 0xC0, 0x4F, 0xC2, 0x95, 0xEE))
|
||||
|
||||
def init_windll(self, path):
|
||||
for name in path:
|
||||
try:
|
||||
setattr(self, name.lower(), ctypes.WinDLL(name, use_last_error=True))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
self.WinVerifyTrust = self.wintrust.WinVerifyTrust
|
||||
self.WinVerifyTrust.restype = ctypes.wintypes.LONG
|
||||
self.WinVerifyTrust.argtypes = [ctypes.wintypes.HWND, ctypes.POINTER(GUID), ctypes.c_void_p]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def sign_verify(self, file_path):
|
||||
if os.name != 'nt':
|
||||
return False
|
||||
try:
|
||||
fi = WINTRUST_FILE_INFO()
|
||||
fi.cbStruct = ctypes.sizeof(WINTRUST_FILE_INFO)
|
||||
fi.pcwszFilePath = os.path.abspath(file_path)
|
||||
fi.hFile = None
|
||||
fi.pgKnownSubject = None
|
||||
|
||||
wt_union = WINTRUST_DATA_UNION()
|
||||
wt_union.pFile = ctypes.pointer(fi)
|
||||
|
||||
td = WINTRUST_DATA()
|
||||
td.cbStruct = ctypes.sizeof(WINTRUST_DATA)
|
||||
td.pPolicyCallbackData = None
|
||||
td.pSIPClientData = None
|
||||
td.dwUIChoice = 2
|
||||
td.fdwRevocationChecks = 0
|
||||
td.dwUnionChoice = 1
|
||||
td.u = wt_union
|
||||
td.dwStateAction = 0
|
||||
td.hWVTStateData = None
|
||||
td.pwszURLReference = None
|
||||
td.dwProvFlags = 0
|
||||
td.dwUIContext = 0
|
||||
td.pSignatureSettings = None
|
||||
|
||||
return self.WinVerifyTrust(None, ctypes.byref(self.verify), ctypes.byref(td)) == 0
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
####################################################################################################
|
||||
|
||||
class rule_scanner:
|
||||
def __init__(self):
|
||||
self.rules = None
|
||||
self.network = []
|
||||
|
||||
def load_path(self, path, callback=None):
|
||||
yara_files = {}
|
||||
for root, _, files in os.walk(path):
|
||||
for file in files:
|
||||
|
||||
full_path = os.path.join(root, file)
|
||||
if callback:
|
||||
callback(full_path)
|
||||
|
||||
ext = os.path.splitext(file)[1].lower()
|
||||
if ext in ('.yara', '.yar'):
|
||||
namespace = os.path.relpath(full_path, path).replace(os.sep, '_')
|
||||
yara_files[namespace] = full_path
|
||||
|
||||
elif ext in ('.yc', '.yrc'):
|
||||
self.load_compiled_rule(full_path)
|
||||
|
||||
elif ext in ('.ip', '.txt'):
|
||||
self.load_network_list(full_path)
|
||||
|
||||
if yara_files:
|
||||
self.compile_all_rules(yara_files)
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def load_compiled_rule(self, file):
|
||||
try:
|
||||
self.rules = yara.load(file)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def load_network_list(self, file):
|
||||
try:
|
||||
with open(file, "r", encoding="utf-8", errors="ignore") as f:
|
||||
self.network.extend(line.strip() for line in f if line.strip())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def compile_all_rules(self, file_map):
|
||||
try:
|
||||
self.rules = yara.compile(filepaths=file_map)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def yara_scan(self, file_path):
|
||||
try:
|
||||
if not self.rules:
|
||||
return False, False
|
||||
|
||||
matches = self.rules.match(filepath=file_path)
|
||||
if matches:
|
||||
rule_name = str(matches[0])
|
||||
label = rule_name.split("_")[0]
|
||||
level = rule_name.split("_")[-1]
|
||||
return f"{label}:WinPE/Unknown.{level}!yr", level
|
||||
|
||||
return False, False
|
||||
except Exception:
|
||||
return False, False
|
||||
|
||||
def yara_mem_scan(self, pid):
|
||||
try:
|
||||
if not self.rules:
|
||||
return False, False
|
||||
|
||||
matches = self.rules.match(pid=pid)
|
||||
if matches:
|
||||
rule_name = str(matches[0])
|
||||
label = rule_name.split("_")[0]
|
||||
level = rule_name.split("_")[-1]
|
||||
return f"{label}:WinPE/Unknown.{level}!ym", level
|
||||
|
||||
return False, False
|
||||
except Exception:
|
||||
return False, False
|
||||
|
||||
####################################################################################################
|
||||
|
||||
class pe_scanner:
|
||||
_STRING_PATTERN = re.compile(b'[\x20-\x7E]{5,}')
|
||||
|
||||
_BYTE_HIST_KEYS = [f'ByteHist_{i:02X}' for i in range(256)]
|
||||
_BYTE_ENT_KEYS = [f'ByteEnt_{i:02X}' for i in range(256)]
|
||||
_STRING_KEYS = [f'StringHist_{i:02d}' for i in range(95)]
|
||||
_SECTION_HASH_KEYS = [f'SectionHash_{i:02d}' for i in range(50)]
|
||||
|
||||
_CHAR_FLAGS = [0x00000020, 0x00000040, 0x00000080, 0x02000000, 0x20000000, 0x40000000, 0x80000000]
|
||||
_CHAR_COUNT_KEYS = {flag: f'Char_{flag:08X}_Count' for flag in _CHAR_FLAGS}
|
||||
_CHAR_ENT_KEYS = {flag: f'Char_{flag:08X}_MeanEntropy' for flag in _CHAR_FLAGS}
|
||||
|
||||
_C_LOG_C_TABLE = numpy.zeros(2049, dtype=numpy.float64)
|
||||
_C_LOG_C_TABLE[1:] = numpy.arange(1, 2049) * numpy.log2(numpy.arange(1, 2049))
|
||||
|
||||
def __init__(self):
|
||||
self.model = None
|
||||
self.input_name = None
|
||||
self.feature_order = []
|
||||
self.signer = sign_scanner()
|
||||
self.signer.init_windll(["wintrust"])
|
||||
|
||||
def load_path(self, path, callback=None):
|
||||
for root, _, files in os.walk(path):
|
||||
for file in files:
|
||||
full_path = os.path.join(root, file)
|
||||
|
||||
if callback:
|
||||
callback(full_path)
|
||||
|
||||
if full_path.endswith('.onnx'):
|
||||
self.load_model(full_path)
|
||||
|
||||
def load_model(self, model_path):
|
||||
if not os.path.exists(model_path):
|
||||
return
|
||||
|
||||
try:
|
||||
self.model = onnxruntime.InferenceSession(model_path, providers=['CPUExecutionProvider'])
|
||||
self.input_name = self.model.get_inputs()[0].name
|
||||
|
||||
feat_path = os.path.join(os.path.dirname(model_path), "features.json")
|
||||
if os.path.exists(feat_path):
|
||||
with open(feat_path, 'r', encoding='utf-8') as f:
|
||||
self.feature_order = json.load(f)
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _safe_float(self, val):
|
||||
try:
|
||||
f = float(val)
|
||||
if math.isinf(f) or math.isnan(f):
|
||||
return 0.0
|
||||
return f
|
||||
|
||||
except Exception:
|
||||
return 0.0
|
||||
|
||||
def _calc_entropy(self, data):
|
||||
if not data:
|
||||
return 0.0
|
||||
|
||||
arr = numpy.frombuffer(data, dtype=numpy.uint8)
|
||||
sz = len(arr)
|
||||
counts = numpy.bincount(arr, minlength=256)
|
||||
counts = counts[counts > 0]
|
||||
|
||||
return float(numpy.log2(sz) - numpy.sum(counts * numpy.log2(counts)) / sz)
|
||||
|
||||
def _extract_strings(self, file_bytes):
|
||||
res = {"StringCount": 0.0, "StringMeanLength": 0.0, "StringEntropy": 0.0}
|
||||
res.update(dict.fromkeys(self._STRING_KEYS, 0.0))
|
||||
|
||||
matches = self._STRING_PATTERN.findall(file_bytes)
|
||||
if not matches:
|
||||
return res
|
||||
|
||||
combined_data = b''.join(matches)
|
||||
count = len(matches)
|
||||
total_len = len(combined_data)
|
||||
|
||||
res["StringCount"] = float(count)
|
||||
res["StringMeanLength"] = float(total_len) / count
|
||||
res["StringEntropy"] = self._calc_entropy(combined_data)
|
||||
|
||||
arr = numpy.frombuffer(combined_data, dtype=numpy.uint8)
|
||||
counts = numpy.bincount(arr, minlength=127)
|
||||
char_counts = counts[0x20:0x7F]
|
||||
|
||||
res.update(dict(zip(self._STRING_KEYS, char_counts.astype(numpy.float64) / total_len)))
|
||||
return res
|
||||
|
||||
def _extract_histograms(self, file_bytes):
|
||||
arr = numpy.frombuffer(file_bytes, dtype=numpy.uint8)
|
||||
sz = len(arr)
|
||||
res = {}
|
||||
|
||||
if sz == 0:
|
||||
res.update(dict.fromkeys(self._BYTE_HIST_KEYS, 0.0))
|
||||
res.update(dict.fromkeys(self._BYTE_ENT_KEYS, 0.0))
|
||||
return res
|
||||
|
||||
byte_counts = numpy.bincount(arr, minlength=256)
|
||||
byte_hist = byte_counts.astype(numpy.float64) / sz
|
||||
res.update(dict(zip(self._BYTE_HIST_KEYS, byte_hist)))
|
||||
|
||||
ent_hist = numpy.zeros(256, dtype=numpy.float64)
|
||||
window = 2048
|
||||
step = 1024
|
||||
|
||||
if sz < window:
|
||||
w_counts = byte_counts
|
||||
p = w_counts[w_counts > 0] / float(sz)
|
||||
entropy = float(-numpy.sum(p * numpy.log2(p)))
|
||||
ent_bin = min(int(entropy * 2.0), 15)
|
||||
byte_bins = w_counts.reshape(16, 16).sum(axis=1)
|
||||
idx_start = ent_bin * 16
|
||||
ent_hist[idx_start : idx_start+16] += byte_bins
|
||||
else:
|
||||
num_windows = (sz - window) // step + 1
|
||||
batch_windows = 10000
|
||||
|
||||
for w_start in range(0, num_windows, batch_windows):
|
||||
w_end = min(w_start + batch_windows, num_windows)
|
||||
b_num = w_end - w_start
|
||||
|
||||
start_byte = w_start * step
|
||||
end_byte = start_byte + b_num * step + step
|
||||
b_arr = arr[start_byte:end_byte]
|
||||
|
||||
arr_2d = b_arr.reshape(b_num + 1, step)
|
||||
offsets = (numpy.arange(b_num + 1, dtype=numpy.int32)[:, None] * 256)
|
||||
flat_idx = (arr_2d.astype(numpy.int32) + offsets).ravel()
|
||||
|
||||
chunk_counts = numpy.bincount(flat_idx, minlength=(b_num + 1) * 256).reshape(b_num + 1, 256)
|
||||
window_counts = chunk_counts[:-1] + chunk_counts[1:]
|
||||
|
||||
sum_c_log_c = numpy.sum(self._C_LOG_C_TABLE[window_counts], axis=1)
|
||||
entropies = 11.0 - sum_c_log_c / 2048.0
|
||||
|
||||
ent_bins = (entropies * 2.0).astype(numpy.int32)
|
||||
numpy.clip(ent_bins, 0, 15, out=ent_bins)
|
||||
|
||||
byte_bins = window_counts.reshape(b_num, 16, 16).sum(axis=2)
|
||||
flat_ent_bins = (ent_bins[:, None] * 16 + numpy.arange(16)).ravel()
|
||||
numpy.add.at(ent_hist, flat_ent_bins, byte_bins.ravel())
|
||||
|
||||
ent_sum = numpy.sum(ent_hist)
|
||||
if ent_sum > 0:
|
||||
ent_hist /= ent_sum
|
||||
|
||||
res.update(dict(zip(self._BYTE_ENT_KEYS, ent_hist)))
|
||||
return res
|
||||
|
||||
def _extract_overlay_features(self, pe, fsize):
|
||||
overlay_offset = pe.get_overlay_data_start_offset()
|
||||
if not overlay_offset or overlay_offset >= fsize:
|
||||
return {"HasOverlay": 0.0, "OverlaySize": 0.0, "OverlayRatio": 0.0, "OverlayEntropy": 0.0}
|
||||
|
||||
overlay_data = pe.get_overlay()
|
||||
if not overlay_data:
|
||||
return {"HasOverlay": 0.0, "OverlaySize": 0.0, "OverlayRatio": 0.0, "OverlayEntropy": 0.0}
|
||||
|
||||
sz = len(overlay_data)
|
||||
return {
|
||||
"HasOverlay": 1.0,
|
||||
"OverlaySize": float(sz),
|
||||
"OverlayRatio": float(sz) / float(fsize),
|
||||
"OverlayEntropy": self._calc_entropy(overlay_data)
|
||||
}
|
||||
|
||||
def _extract_rich_header(self, pe):
|
||||
if not hasattr(pe, 'RICH_HEADER') or not pe.RICH_HEADER:
|
||||
return {"HasRichHeader": 0.0, "RichHeaderCount": 0.0}
|
||||
|
||||
return {
|
||||
"HasRichHeader": 1.0,
|
||||
"RichHeaderCount": float(len(pe.RICH_HEADER.values) // 2) if hasattr(pe.RICH_HEADER, 'values') else 0.0
|
||||
}
|
||||
|
||||
def _extract_ep_anomalies(self, pe):
|
||||
if not hasattr(pe, 'OPTIONAL_HEADER') or not hasattr(pe, 'sections') or not pe.sections:
|
||||
return {"EntryPointSectionIndex": -1.0, "EntryPointInExecutable": 0.0, "EntryPointInLastSection": 0.0}
|
||||
|
||||
ep = pe.OPTIONAL_HEADER.AddressOfEntryPoint
|
||||
ep_section_idx = -1
|
||||
ep_in_exec = 0.0
|
||||
|
||||
for idx, sec in enumerate(pe.sections):
|
||||
if sec.VirtualAddress <= ep < (sec.VirtualAddress + sec.Misc_VirtualSize):
|
||||
ep_section_idx = idx
|
||||
if sec.Characteristics & 0x20000000:
|
||||
ep_in_exec = 1.0
|
||||
break
|
||||
|
||||
return {
|
||||
"EntryPointSectionIndex": float(ep_section_idx),
|
||||
"EntryPointInExecutable": ep_in_exec,
|
||||
"EntryPointInLastSection": 1.0 if ep_section_idx == len(pe.sections) - 1 else 0.0
|
||||
}
|
||||
|
||||
def _extract_advanced_resources(self, pe):
|
||||
res_data = {
|
||||
"ResourceMaxEntropy": 0.0,
|
||||
"ResourceMinEntropy": 0.0,
|
||||
"ResourceMeanEntropy": 0.0,
|
||||
"ResourceLangCount": 0.0,
|
||||
"ResourceRCDataCount": 0.0
|
||||
}
|
||||
|
||||
if not hasattr(pe, 'DIRECTORY_ENTRY_RESOURCE'):
|
||||
return res_data
|
||||
|
||||
entropies = []
|
||||
langs = set()
|
||||
rcdata_count = 0
|
||||
|
||||
for entry_type in pe.DIRECTORY_ENTRY_RESOURCE.entries:
|
||||
if hasattr(entry_type, 'id') and entry_type.id == 10:
|
||||
rcdata_count += len(getattr(entry_type.directory, 'entries', []))
|
||||
|
||||
if not hasattr(entry_type, 'directory'):
|
||||
continue
|
||||
|
||||
for entry_id in entry_type.directory.entries:
|
||||
if not hasattr(entry_id, 'directory'):
|
||||
continue
|
||||
|
||||
for entry_lang in entry_id.directory.entries:
|
||||
if hasattr(entry_lang, 'id'):
|
||||
langs.add(entry_lang.id)
|
||||
|
||||
if hasattr(entry_lang, 'data'):
|
||||
try:
|
||||
data = pe.get_data(entry_lang.data.struct.OffsetToData, entry_lang.data.struct.Size)
|
||||
entropies.append(self._calc_entropy(data))
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
res_data["ResourceLangCount"] = float(len(langs))
|
||||
res_data["ResourceRCDataCount"] = float(rcdata_count)
|
||||
|
||||
if entropies:
|
||||
res_data["ResourceMaxEntropy"] = max(entropies)
|
||||
res_data["ResourceMinEntropy"] = min(entropies)
|
||||
res_data["ResourceMeanEntropy"] = sum(entropies) / len(entropies)
|
||||
|
||||
return res_data
|
||||
|
||||
def _extract_load_config(self, pe):
|
||||
cfg = {"HasLoadConfig": 0.0, "HasCFG": 0.0, "HasSEHTable": 0.0}
|
||||
|
||||
if hasattr(pe, 'DIRECTORY_ENTRY_LOAD_CONFIG'):
|
||||
cfg["HasLoadConfig"] = 1.0
|
||||
struct = pe.DIRECTORY_ENTRY_LOAD_CONFIG.struct
|
||||
|
||||
if getattr(struct, 'GuardCFFunctionTable', 0) != 0:
|
||||
cfg["HasCFG"] = 1.0
|
||||
if getattr(struct, 'SEHandlerTable', 0) != 0:
|
||||
cfg["HasSEHTable"] = 1.0
|
||||
|
||||
return cfg
|
||||
|
||||
def _extract_security_directory(self, pe, file_bytes, fsize):
|
||||
res = {"HasSignature": 0.0, "SignatureCount": 0.0}
|
||||
if not hasattr(pe, 'OPTIONAL_HEADER') or not hasattr(pe.OPTIONAL_HEADER, 'DATA_DIRECTORY'):
|
||||
return res
|
||||
|
||||
sec_idx = 4
|
||||
directories = pe.OPTIONAL_HEADER.DATA_DIRECTORY
|
||||
if len(directories) <= sec_idx:
|
||||
return res
|
||||
|
||||
sec_dir = directories[sec_idx]
|
||||
if sec_dir.Size == 0 or sec_dir.VirtualAddress == 0:
|
||||
return res
|
||||
|
||||
res["HasSignature"] = 1.0
|
||||
offset = sec_dir.VirtualAddress
|
||||
size = sec_dir.Size
|
||||
|
||||
if offset + size > fsize or offset < 0 or size < 0:
|
||||
return res
|
||||
|
||||
count = 0
|
||||
curr = offset
|
||||
end = offset + size
|
||||
|
||||
while curr + 8 <= end:
|
||||
dw_length = int.from_bytes(file_bytes[curr:curr+4], 'little')
|
||||
if dw_length < 8:
|
||||
break
|
||||
|
||||
count += 1
|
||||
curr += (dw_length + 7) & ~7
|
||||
|
||||
res["SignatureCount"] = float(count)
|
||||
return res
|
||||
|
||||
def extract_features(self, file_path):
|
||||
pe = None
|
||||
try:
|
||||
fsize = os.path.getsize(file_path)
|
||||
if fsize == 0 or fsize > 536870912:
|
||||
return None
|
||||
|
||||
with open(file_path, "rb") as f:
|
||||
file_bytes = f.read()
|
||||
|
||||
base = {}
|
||||
dlls = set()
|
||||
apis = set()
|
||||
pe = pefile.PE(data=file_bytes, fast_load=True)
|
||||
|
||||
try:
|
||||
pe.parse_rich_header()
|
||||
pe.parse_data_directories(directories=[
|
||||
pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_IMPORT'],
|
||||
pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_EXPORT'],
|
||||
pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_RESOURCE'],
|
||||
pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_DEBUG'],
|
||||
pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_TLS'],
|
||||
pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG']
|
||||
])
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
base['TrustSigned'] = 1.0 if self.signer.sign_verify(file_path) else 0.0
|
||||
base['FileEntropy'] = self._calc_entropy(file_bytes)
|
||||
base['FileSize'] = float(fsize)
|
||||
|
||||
fh = pe.FILE_HEADER
|
||||
base['Machine'] = getattr(fh, 'Machine', 0)
|
||||
base['NumberOfSections'] = getattr(fh, 'NumberOfSections', 0)
|
||||
base['TimeDateStamp'] = getattr(fh, 'TimeDateStamp', 0)
|
||||
base['PointerToSymbolTable'] = getattr(fh, 'PointerToSymbolTable', 0)
|
||||
base['NumberOfSymbols'] = getattr(fh, 'NumberOfSymbols', 0)
|
||||
base['SizeOfOptionalHeader'] = getattr(fh, 'SizeOfOptionalHeader', 0)
|
||||
base['Characteristics'] = getattr(fh, 'Characteristics', 0)
|
||||
|
||||
curr_ts = datetime.datetime.utcnow().timestamp()
|
||||
base['HasInvalidTimestamp'] = 1.0 if (base['TimeDateStamp'] < 631152000 or base['TimeDateStamp'] > curr_ts + 2592000) else 0.0
|
||||
base['FileTimeException'] = 1.0 if base['TimeDateStamp'] == 0 else 0.0
|
||||
base['Is64Bit'] = 1.0 if base['Machine'] in (0x8664, 0xAA64, 0x0200) else 0.0
|
||||
base['IsExe'] = 1.0 if pe.is_exe() else 0.0
|
||||
base['IsDll'] = 1.0 if pe.is_dll() else 0.0
|
||||
base['ExceptionCount'] = 0.0
|
||||
|
||||
if hasattr(pe, 'OPTIONAL_HEADER'):
|
||||
op = pe.OPTIONAL_HEADER
|
||||
fields = [
|
||||
'Magic', 'MajorLinkerVersion', 'MinorLinkerVersion', 'SizeOfCode',
|
||||
'SizeOfInitializedData', 'SizeOfUninitializedData', 'AddressOfEntryPoint',
|
||||
'BaseOfCode', 'ImageBase', 'SectionAlignment', 'FileAlignment',
|
||||
'MajorOperatingSystemVersion', 'MinorOperatingSystemVersion',
|
||||
'MajorImageVersion', 'MinorImageVersion', 'MajorSubsystemVersion',
|
||||
'MinorSubsystemVersion', 'SizeOfImage', 'SizeOfHeaders', 'CheckSum',
|
||||
'Subsystem', 'DllCharacteristics', 'SizeOfStackReserve', 'SizeOfStackCommit',
|
||||
'SizeOfHeapReserve', 'SizeOfHeapCommit', 'LoaderFlags', 'NumberOfRvaAndSizes'
|
||||
]
|
||||
for f_field in fields:
|
||||
base[f_field] = getattr(op, f_field, 0)
|
||||
|
||||
base['IsDriver'] = 1.0 if base.get('Subsystem') == 1 else 0.0
|
||||
|
||||
if hasattr(op, 'DATA_DIRECTORY'):
|
||||
for i, directory in enumerate(op.DATA_DIRECTORY):
|
||||
base[f'DataDirectory_{i}_Size'] = directory.Size
|
||||
base[f'DataDirectory_{i}_VA'] = directory.VirtualAddress
|
||||
|
||||
exc_idx = pefile.DIRECTORY_ENTRY.get('IMAGE_DIRECTORY_ENTRY_EXCEPTION', 3)
|
||||
if len(op.DATA_DIRECTORY) > exc_idx and op.DATA_DIRECTORY[exc_idx].Size > 0:
|
||||
base['ExceptionCount'] = op.DATA_DIRECTORY[exc_idx].Size // 12 if base['Machine'] in (0x8664, 0xAA64) else 0.0
|
||||
|
||||
res_flags_count = dict.fromkeys(self._CHAR_COUNT_KEYS.values(), 0.0)
|
||||
res_flags_ent = dict.fromkeys(self._CHAR_ENT_KEYS.values(), 0.0)
|
||||
res_sec_hash = dict.fromkeys(self._SECTION_HASH_KEYS, 0.0)
|
||||
|
||||
if hasattr(pe, 'sections'):
|
||||
base['SectionCount'] = len(pe.sections)
|
||||
entropies = []
|
||||
raw_sizes = []
|
||||
v_sizes = []
|
||||
exec_sec = write_sec = read_sec = sec_exc = 0
|
||||
|
||||
for section in pe.sections:
|
||||
sec_name = section.Name.rstrip(b'\x00')
|
||||
hash_val = zlib.crc32(sec_name) % 50
|
||||
res_sec_hash[self._SECTION_HASH_KEYS[hash_val]] += 1.0
|
||||
|
||||
try:
|
||||
s_data = section.get_data()
|
||||
s_entropy = self._calc_entropy(s_data)
|
||||
except Exception:
|
||||
s_entropy = 0.0
|
||||
|
||||
entropies.append(s_entropy)
|
||||
raw_sizes.append(section.SizeOfRawData)
|
||||
v_sizes.append(section.Misc_VirtualSize)
|
||||
|
||||
if section.Characteristics & 0x20000000:
|
||||
exec_sec += 1
|
||||
if section.Characteristics & 0x80000000:
|
||||
write_sec += 1
|
||||
if section.Characteristics & 0x40000000:
|
||||
read_sec += 1
|
||||
if section.SizeOfRawData + section.PointerToRawData > fsize:
|
||||
sec_exc = 1
|
||||
|
||||
for flag in self._CHAR_FLAGS:
|
||||
if section.Characteristics & flag:
|
||||
res_flags_count[self._CHAR_COUNT_KEYS[flag]] += 1.0
|
||||
res_flags_ent[self._CHAR_ENT_KEYS[flag]] += s_entropy
|
||||
|
||||
base['SectionMaxEntropy'] = max(entropies) if entropies else 0.0
|
||||
base['SectionMinEntropy'] = min(entropies) if entropies else 0.0
|
||||
base['SectionMeanEntropy'] = sum(entropies) / len(entropies) if entropies else 0.0
|
||||
base['SectionMaxRawSize'] = max(raw_sizes) if raw_sizes else 0.0
|
||||
base['SectionMinRawSize'] = min(raw_sizes) if raw_sizes else 0.0
|
||||
base['SectionMeanRawSize'] = sum(raw_sizes) / len(raw_sizes) if raw_sizes else 0.0
|
||||
base['SectionMaxVSize'] = max(v_sizes) if v_sizes else 0.0
|
||||
base['SectionMinVSize'] = min(v_sizes) if v_sizes else 0.0
|
||||
base['SectionMeanVSize'] = sum(v_sizes) / len(v_sizes) if v_sizes else 0.0
|
||||
base['ExecutableSections'] = float(exec_sec)
|
||||
base['WritableSections'] = float(write_sec)
|
||||
base['ReadableSections'] = float(read_sec)
|
||||
base['SectionException'] = float(sec_exc)
|
||||
|
||||
for flag in self._CHAR_FLAGS:
|
||||
cnt_key = self._CHAR_COUNT_KEYS[flag]
|
||||
if res_flags_count[cnt_key] > 0:
|
||||
res_flags_ent[self._CHAR_ENT_KEYS[flag]] /= res_flags_count[cnt_key]
|
||||
|
||||
base.update(res_flags_count)
|
||||
base.update(res_flags_ent)
|
||||
base.update(res_sec_hash)
|
||||
|
||||
string_keys = ['FileDescription', 'FileVersion', 'ProductName', 'ProductVersion', 'CompanyName', 'LegalCopyright', 'Comments', 'InternalName', 'LegalTrademarks', 'SpecialBuild', 'PrivateBuild']
|
||||
for key in string_keys:
|
||||
base[f'{key}Length'] = 0.0
|
||||
|
||||
if hasattr(pe, 'FileInfo'):
|
||||
for fileinfo_list in pe.FileInfo:
|
||||
for fileinfo in fileinfo_list:
|
||||
if getattr(fileinfo, 'name', '') in ('StringFileInfo', b'StringFileInfo'):
|
||||
for st in getattr(fileinfo, 'StringTable', []):
|
||||
|
||||
for key, val in st.entries.items():
|
||||
try:
|
||||
k = key.decode('utf-8', 'ignore') if isinstance(key, bytes) else str(key)
|
||||
if k in string_keys:
|
||||
v = val.decode('utf-8', 'ignore') if isinstance(val, bytes) else str(val)
|
||||
base[f'{k}Length'] = float(len(v))
|
||||
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
if hasattr(pe, 'DIRECTORY_ENTRY_TLS') and hasattr(pe.DIRECTORY_ENTRY_TLS, 'struct'):
|
||||
if getattr(pe.DIRECTORY_ENTRY_TLS.struct, 'AddressOfCallBacks', 0) != 0:
|
||||
base['HasTlsCallbacks'] = 1.0
|
||||
|
||||
if hasattr(pe, 'VS_FIXEDFILEINFO') and len(pe.VS_FIXEDFILEINFO) > 0:
|
||||
flags = getattr(pe.VS_FIXEDFILEINFO[0], 'FileFlags', 0)
|
||||
base['IsDebug'] = 1.0 if flags & 0x1 else 0.0
|
||||
base['IsPreRelease'] = 1.0 if flags & 0x2 else 0.0
|
||||
base['IsPatched'] = 1.0 if flags & 0x4 else 0.0
|
||||
base['IsPrivateBuild'] = 1.0 if flags & 0x8 else 0.0
|
||||
base['IsSpecialBuild'] = 1.0 if flags & 0x20 else 0.0
|
||||
|
||||
if hasattr(pe, 'DIRECTORY_ENTRY_IMPORT'):
|
||||
base['ImportCount'] = float(len(pe.DIRECTORY_ENTRY_IMPORT))
|
||||
func_count = 0
|
||||
|
||||
for entry in pe.DIRECTORY_ENTRY_IMPORT:
|
||||
if getattr(entry, 'dll', None):
|
||||
try:
|
||||
dlls.add(entry.dll.decode('ascii', 'ignore').lower())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
for imp in getattr(entry, 'imports', []):
|
||||
func_count += 1
|
||||
if getattr(imp, 'name', None):
|
||||
try:
|
||||
apis.add(imp.name.decode('ascii', 'ignore'))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
base['ImportFunctionCount'] = float(func_count)
|
||||
else:
|
||||
base['ImportCount'] = 0.0
|
||||
base['ImportFunctionCount'] = 0.0
|
||||
|
||||
if hasattr(pe, 'DIRECTORY_ENTRY_EXPORT') and hasattr(pe.DIRECTORY_ENTRY_EXPORT, 'symbols'):
|
||||
base['ExportCount'] = float(len(pe.DIRECTORY_ENTRY_EXPORT.symbols))
|
||||
else:
|
||||
base['ExportCount'] = 0.0
|
||||
|
||||
if hasattr(pe, 'DIRECTORY_ENTRY_RESOURCE'):
|
||||
icon_count = 0
|
||||
for entry in pe.DIRECTORY_ENTRY_RESOURCE.entries:
|
||||
if getattr(entry, 'id', None) == 3 and hasattr(entry, 'directory'):
|
||||
icon_count += len(getattr(entry.directory, 'entries', []))
|
||||
base['IconCount'] = float(icon_count)
|
||||
else:
|
||||
base['IconCount'] = 0.0
|
||||
|
||||
if hasattr(pe, 'DIRECTORY_ENTRY_DEBUG'):
|
||||
base['DebugCount'] = float(len(pe.DIRECTORY_ENTRY_DEBUG))
|
||||
else:
|
||||
base['DebugCount'] = 0.0
|
||||
|
||||
base.update(self._extract_strings(file_bytes))
|
||||
base.update(self._extract_histograms(file_bytes))
|
||||
base.update(self._extract_overlay_features(pe, fsize))
|
||||
base.update(self._extract_rich_header(pe))
|
||||
base.update(self._extract_ep_anomalies(pe))
|
||||
base.update(self._extract_advanced_resources(pe))
|
||||
base.update(self._extract_load_config(pe))
|
||||
base.update(self._extract_security_directory(pe, file_bytes, fsize))
|
||||
|
||||
for k in base:
|
||||
base[k] = self._safe_float(base[k])
|
||||
|
||||
return {"Base": base, "DLLs": list(dlls), "APIs": list(apis)}
|
||||
|
||||
except Exception:
|
||||
return None
|
||||
finally:
|
||||
if pe:
|
||||
pe.close()
|
||||
|
||||
def pe_scan(self, file_path, enhanced_mode=False):
|
||||
if not self.model or not self.feature_order:
|
||||
return False, False
|
||||
try:
|
||||
raw_data = self.extract_features(file_path)
|
||||
if not raw_data:
|
||||
return False, False
|
||||
|
||||
vec = numpy.zeros((1, len(self.feature_order)), dtype=numpy.float32)
|
||||
base = raw_data.get('Base', {})
|
||||
dlls = set(raw_data.get('DLLs', []))
|
||||
apis = set(raw_data.get('APIs', []))
|
||||
feat_map = {feat: i for i, feat in enumerate(self.feature_order)}
|
||||
|
||||
for k, v in base.items():
|
||||
if k in feat_map:
|
||||
vec[0, feat_map[k]] = v
|
||||
|
||||
for d in dlls:
|
||||
old_fn = f"Dll_{d}"
|
||||
if old_fn in feat_map:
|
||||
vec[0, feat_map[old_fn]] = 1.0
|
||||
|
||||
h = zlib.crc32(d.encode('utf-8', 'ignore')) % 256
|
||||
new_fn = f"DllHash_{h:03d}"
|
||||
if new_fn in feat_map:
|
||||
vec[0, feat_map[new_fn]] += 1.0
|
||||
|
||||
for a in apis:
|
||||
old_fn = f"Api_{a}"
|
||||
if old_fn in feat_map:
|
||||
vec[0, feat_map[old_fn]] = 1.0
|
||||
|
||||
h = zlib.crc32(a.encode('utf-8', 'ignore')) % 1024
|
||||
new_fn = f"ApiHash_{h:04d}"
|
||||
if new_fn in feat_map:
|
||||
vec[0, feat_map[new_fn]] += 1.0
|
||||
|
||||
outputs = self.model.run(None, {self.input_name: vec})
|
||||
result = outputs[1]
|
||||
prob = 0.0
|
||||
|
||||
if isinstance(result, list) and len(result) > 0:
|
||||
prob_dict = result[0]
|
||||
if hasattr(prob_dict, 'get'):
|
||||
prob = float(prob_dict.get(1, prob_dict.get('1', 0.0)))
|
||||
|
||||
elif isinstance(result, numpy.ndarray):
|
||||
if result.ndim == 2 and result.shape[1] > 1:
|
||||
prob = float(result[0][1])
|
||||
|
||||
score = int(prob * 100)
|
||||
|
||||
if score >= 80:
|
||||
return f"General:WinPE/Malware.{score}!ml", score
|
||||
elif enhanced_mode and score >= 50:
|
||||
return f"General:WinPE/Suspicious.{score}!ml", score
|
||||
|
||||
return False, False
|
||||
except Exception:
|
||||
return False, False
|
||||
|
||||
####################################################################################################
|
||||
|
||||
class cloud_scanner:
|
||||
def __init__(self):
|
||||
self.api_host = None
|
||||
self.api_key = None
|
||||
self.timeout = 30
|
||||
self.lock = threading.RLock()
|
||||
self.local = threading.local()
|
||||
|
||||
def _get_session(self, api_host, api_key):
|
||||
if not hasattr(self.local, 'session') or getattr(self.local, 'host', None) != api_host:
|
||||
self.local.session = requests.Session()
|
||||
self.local.session.headers.update({"X-API-Key": api_key, "User-Agent": "PYAS-Engine/1.1"})
|
||||
self.local.host = api_host
|
||||
|
||||
return self.local.session
|
||||
|
||||
def _request(self, method, endpoint, api_host, api_key, **kwargs):
|
||||
session = self._get_session(api_host, api_key)
|
||||
try:
|
||||
r = session.request(method, f"{api_host}{endpoint}", timeout=self.timeout, **kwargs)
|
||||
if r.status_code == 200:
|
||||
return r
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def rescan(self, sha256, api_host, api_key):
|
||||
r = self._request("POST", f"/api/rescan/{sha256}", api_host, api_key)
|
||||
return r is not None and r.json().get('status') == 'success'
|
||||
|
||||
def upload_file(self, file_path, api_host, api_key, chunk_size=4194304, need_rescan=False, max_retries=3, file_hash=None):
|
||||
try:
|
||||
sha256 = file_hash
|
||||
if not sha256:
|
||||
return False, None
|
||||
|
||||
status_req = self._request("GET", f"/api/processing_status/{sha256}", api_host, api_key)
|
||||
if status_req:
|
||||
current_status = status_req.json().get('status')
|
||||
|
||||
if current_status == 'done':
|
||||
if need_rescan:
|
||||
self.rescan(sha256, api_host, api_key)
|
||||
return True, sha256
|
||||
|
||||
elif current_status in ('queued', 'processing'):
|
||||
return True, sha256
|
||||
|
||||
file_size = os.path.getsize(file_path)
|
||||
if file_size > 104857600:
|
||||
return False, sha256
|
||||
|
||||
total_chunks = max(1, (file_size + chunk_size - 1) // chunk_size)
|
||||
upload_id = os.urandom(16).hex()
|
||||
|
||||
with open(file_path, 'rb') as f:
|
||||
for i in range(total_chunks):
|
||||
chunk_data = f.read(chunk_size)
|
||||
headers = {"X-Chunk-Index": str(i), "X-Total-Chunks": str(total_chunks), "X-Upload-ID": upload_id}
|
||||
|
||||
chunk_success = False
|
||||
for attempt in range(max_retries):
|
||||
r = self._request("POST", "/api/upload", api_host, api_key, files={'file': (os.path.basename(file_path), chunk_data)}, headers=headers)
|
||||
if r:
|
||||
if i == total_chunks - 1:
|
||||
try:
|
||||
resp = r.json()
|
||||
if 'url' in resp:
|
||||
sha256 = resp.get('url', '').split('/')[-1]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
chunk_success = True
|
||||
break
|
||||
time.sleep(2 ** attempt)
|
||||
|
||||
if not chunk_success:
|
||||
return False, sha256
|
||||
|
||||
return True, sha256
|
||||
except Exception:
|
||||
return False, None
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def get_result(self, sha256, api_host, api_key, max_retries=6, interval=10):
|
||||
try:
|
||||
if not sha256:
|
||||
return False
|
||||
|
||||
is_done = False
|
||||
for _ in range(max_retries):
|
||||
r = self._request("GET", f"/api/processing_status/{sha256}", api_host, api_key)
|
||||
if r:
|
||||
st = r.json().get('status', 'error')
|
||||
if st == 'done':
|
||||
is_done = True
|
||||
break
|
||||
|
||||
if st in ['error', 'failed']:
|
||||
return False
|
||||
|
||||
time.sleep(interval)
|
||||
|
||||
if not is_done:
|
||||
return False
|
||||
|
||||
r = self._request("GET", f"/api/report/{sha256}", api_host, api_key)
|
||||
if r:
|
||||
data = r.json().get('data', {})
|
||||
metadata = data.get('metadata', {})
|
||||
label = metadata.get('label', 'Unsupport')
|
||||
score = metadata.get('score', 0)
|
||||
sims = data.get('similar', [])
|
||||
|
||||
is_malicious = 'General' in label
|
||||
sim_malicious_count = 0
|
||||
valid_sim_count = 0
|
||||
|
||||
for s in sims:
|
||||
if s.get('similarity', 0) > 80:
|
||||
valid_sim_count += 1
|
||||
if "General" in s.get('label', ''):
|
||||
sim_malicious_count += 1
|
||||
|
||||
if is_malicious and (valid_sim_count == 0 or sim_malicious_count == valid_sim_count):
|
||||
return f"General:WinPE/Malware.{score}!cl"
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
+253
@@ -0,0 +1,253 @@
|
||||
#define AppId "{{a7d7bac3-93b8-4630-8308-c7a56bf7fdf4}"
|
||||
#define AppName "PYAS"
|
||||
#define AppVersion "3.5.9.0"
|
||||
#define AppPublisher "PYAS Security"
|
||||
#define AppURL "https://github.com/87owo/PYAS"
|
||||
#define AppExeName "PYAS.exe"
|
||||
|
||||
[Setup]
|
||||
AppId={#AppId}
|
||||
AppName={#AppName}
|
||||
AppVersion={#AppVersion}
|
||||
AppVerName={#AppName} {#AppVersion}
|
||||
AppPublisher={#AppPublisher}
|
||||
AppPublisherURL={#AppURL}
|
||||
AppSupportURL={#AppURL}
|
||||
AppUpdatesURL={#AppURL}
|
||||
VersionInfoVersion={#AppVersion}
|
||||
VersionInfoCompany={#AppPublisher}
|
||||
VersionInfoDescription={#AppName} Setup
|
||||
VersionInfoProductName={#AppName} Setup
|
||||
VersionInfoProductVersion={#AppVersion}
|
||||
DefaultDirName={autopf}\{#AppName}
|
||||
DefaultGroupName={#AppPublisher}\{#AppName}
|
||||
AllowNoIcons=yes
|
||||
LicenseFile=SetupResources\licence.rtf
|
||||
ShowLanguageDialog=yes
|
||||
WizardStyle=modern
|
||||
WizardImageFile=SetupResources\wizardImage.bmp
|
||||
WizardSmallImageFile=SetupResources\headerImage.png
|
||||
SetupIconFile=Payload\Interface\static\img\icon.ico
|
||||
UninstallDisplayIcon={app}\{#AppExeName}
|
||||
PrivilegesRequired=admin
|
||||
ArchitecturesAllowed=x64compatible
|
||||
ArchitecturesInstallIn64BitMode=x64compatible
|
||||
MinVersion=10.0
|
||||
Compression=lzma2/max
|
||||
SolidCompression=yes
|
||||
OutputDir=Output
|
||||
OutputBaseFilename=PYAS_Setup
|
||||
UsePreviousTasks=no
|
||||
|
||||
[Languages]
|
||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
Name: "chinesesimplified"; MessagesFile: "SetupResources\ChineseSimplified.isl"
|
||||
Name: "chinesetraditional"; MessagesFile: "SetupResources\ChineseTraditional.isl"
|
||||
Name: "japanese"; MessagesFile: "SetupResources\Japanese.isl"
|
||||
Name: "korean"; MessagesFile: "SetupResources\Korean.isl"
|
||||
Name: "french"; MessagesFile: "SetupResources\French.isl"
|
||||
Name: "spanish"; MessagesFile: "SetupResources\Spanish.isl"
|
||||
Name: "hindi"; MessagesFile: "SetupResources\Hindi.isl"
|
||||
Name: "arabic"; MessagesFile: "SetupResources\Arabic.isl"
|
||||
Name: "russian"; MessagesFile: "SetupResources\Russian.isl"
|
||||
Name: "slovenian"; MessagesFile: "SetupResources\Slovenian.isl"
|
||||
|
||||
[Tasks]
|
||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"
|
||||
Name: "autostart"; Description: "{cm:AutoStartTask}"; GroupDescription: "{cm:AdditionalIcons}"
|
||||
Name: "install_webview2"; Description: "{cm:InstallWebView2}"; GroupDescription: "{cm:Dependencies}"
|
||||
Name: "install_vcredist"; Description: "{cm:InstallVCRedist}"; GroupDescription: "{cm:Dependencies}"
|
||||
|
||||
[Files]
|
||||
Source: "Payload\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
Source: "Redist\VC_redist.x64.exe"; DestDir: "{tmp}\PYAS_Redist"; Flags: deleteafterinstall ignoreversion
|
||||
Source: "Redist\MicrosoftEdgeWebview2Setup.exe"; DestDir: "{tmp}\PYAS_Redist"; Flags: deleteafterinstall ignoreversion
|
||||
|
||||
[Registry]
|
||||
Root: HKCU; Subkey: "Software\Classes\*\shell\PYAS_Scan"; Flags: uninsdeletekey dontcreatekey
|
||||
Root: HKCU; Subkey: "Software\Classes\Directory\shell\PYAS_Scan"; Flags: uninsdeletekey dontcreatekey
|
||||
|
||||
[UninstallDelete]
|
||||
Type: filesandordirs; Name: "{commonappdata}\PYAS"
|
||||
|
||||
[Icons]
|
||||
Name: "{autodesktop}\{#AppName}"; Filename: "{app}\{#AppExeName}"; Tasks: desktopicon
|
||||
Name: "{group}\{#AppName}"; Filename: "{app}\{#AppExeName}"
|
||||
Name: "{group}\Uninstall {#AppName}"; Filename: "{uninstallexe}"
|
||||
|
||||
[Run]
|
||||
Filename: "{tmp}\PYAS_Redist\VC_redist.x64.exe"; Parameters: "/quiet /norestart"; Flags: waituntilterminated runhidden; StatusMsg: "{cm:InstallingVCRuntime}"; Tasks: install_vcredist
|
||||
Filename: "{tmp}\PYAS_Redist\MicrosoftEdgeWebview2Setup.exe"; Parameters: "/silent /install"; Flags: waituntilterminated runhidden; StatusMsg: "{cm:InstallingWebView2Runtime}"; Tasks: install_webview2
|
||||
Filename: "{app}\{#AppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(AppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent runascurrentuser
|
||||
|
||||
[CustomMessages]
|
||||
english.InstallingVCRuntime=Installing Microsoft Visual C++ Runtime...
|
||||
chinesesimplified.InstallingVCRuntime=正在安装 Microsoft Visual C++ 运行库...
|
||||
chinesetraditional.InstallingVCRuntime=正在安裝 Microsoft Visual C++ 執行庫...
|
||||
english.InstallingWebView2Runtime=Installing Microsoft Edge WebView2 Runtime...
|
||||
chinesesimplified.InstallingWebView2Runtime=正在安装 Microsoft Edge WebView2 Runtime...
|
||||
chinesetraditional.InstallingWebView2Runtime=正在安裝 Microsoft Edge WebView2 Runtime...
|
||||
english.AutoStartTask=Run PYAS automatically at system startup
|
||||
chinesesimplified.AutoStartTask=开机时自动运行 PYAS
|
||||
chinesetraditional.AutoStartTask=開機時自動執行 PYAS
|
||||
english.Dependencies=Dependencies:
|
||||
chinesesimplified.Dependencies=运行环境:
|
||||
chinesetraditional.Dependencies=執行環境:
|
||||
english.InstallWebView2=Install Microsoft Edge WebView2 Runtime
|
||||
chinesesimplified.InstallWebView2=安装 Microsoft Edge WebView2 运行环境
|
||||
chinesetraditional.InstallWebView2=安裝 Microsoft Edge WebView2 執行環境
|
||||
english.InstallVCRedist=Install Microsoft Visual C++ Runtime
|
||||
chinesesimplified.InstallVCRedist=安装 Microsoft Visual C++ 运行库
|
||||
chinesetraditional.InstallVCRedist=安裝 Microsoft Visual C++ 執行庫
|
||||
english.LegacyVersionDetected=An older version of PYAS was detected. Please uninstall it manually before installing.
|
||||
chinesesimplified.LegacyVersionDetected=检测到存在旧版 PYAS。请先手动卸载旧版后,再运行本安装程序。
|
||||
chinesetraditional.LegacyVersionDetected=檢測到存在舊版 PYAS。請先手動卸載舊版後,再執行本安裝程式。
|
||||
|
||||
[Code]
|
||||
var
|
||||
TasksInitialized: Boolean;
|
||||
|
||||
function IsWebView2Installed: Boolean;
|
||||
var
|
||||
RegKeyPath: string;
|
||||
Version: string;
|
||||
begin
|
||||
RegKeyPath := 'SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}';
|
||||
Result := False;
|
||||
if RegQueryStringValue(HKLM, 'SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}', 'pv', Version) then
|
||||
if Length(Version) > 0 then Result := True;
|
||||
if not Result and RegQueryStringValue(HKCU, RegKeyPath, 'pv', Version) then
|
||||
if Length(Version) > 0 then Result := True;
|
||||
if not Result and RegQueryStringValue(HKLM, RegKeyPath, 'pv', Version) then
|
||||
if Length(Version) > 0 then Result := True;
|
||||
end;
|
||||
|
||||
function IsVCRedistInstalled: Boolean;
|
||||
var
|
||||
Bld: Cardinal;
|
||||
begin
|
||||
Result := False;
|
||||
if RegQueryDWordValue(HKLM, 'SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64', 'Bld', Bld) then
|
||||
if Bld > 0 then Result := True;
|
||||
end;
|
||||
|
||||
procedure CurPageChanged(CurPageID: Integer);
|
||||
var
|
||||
I: Integer;
|
||||
ItemText: string;
|
||||
begin
|
||||
if (CurPageID = wpSelectTasks) and not TasksInitialized then
|
||||
begin
|
||||
TasksInitialized := True;
|
||||
for I := 0 to WizardForm.TasksList.Items.Count - 1 do
|
||||
begin
|
||||
ItemText := WizardForm.TasksList.Items[I];
|
||||
if Pos('Visual C++', ItemText) > 0 then
|
||||
WizardForm.TasksList.Checked[I] := not IsVCRedistInstalled
|
||||
else if Pos('WebView2', ItemText) > 0 then
|
||||
WizardForm.TasksList.Checked[I] := not IsWebView2Installed;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure QuitOldInstance(InstallPath: string);
|
||||
var
|
||||
ResultCode: Integer;
|
||||
ExePath: string;
|
||||
begin
|
||||
ExePath := InstallPath + '\{#AppExeName}';
|
||||
if FileExists(ExePath) then
|
||||
begin
|
||||
Exec(ExePath, '-quit', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
|
||||
Sleep(500);
|
||||
Exec(ExpandConstant('{sys}\taskkill.exe'), '/F /IM {#AppExeName} /T', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
|
||||
end;
|
||||
end;
|
||||
|
||||
function InitializeSetup(): Boolean;
|
||||
var
|
||||
OldInstallPath: string;
|
||||
LegacyPath: string;
|
||||
begin
|
||||
LegacyPath := ExpandConstant('{pf32}\{#AppName}');
|
||||
if DirExists(LegacyPath) and FileExists(LegacyPath + '\{#AppExeName}') then
|
||||
begin
|
||||
MsgBox(CustomMessage('LegacyVersionDetected'), mbCriticalError, MB_OK);
|
||||
Result := False;
|
||||
Exit;
|
||||
end;
|
||||
if RegQueryStringValue(HKLM, 'Software\Microsoft\Windows\CurrentVersion\Uninstall\' + ExpandConstant('{#AppId}') + '_is1', 'InstallLocation', OldInstallPath) or
|
||||
RegQueryStringValue(HKCU, 'Software\Microsoft\Windows\CurrentVersion\Uninstall\' + ExpandConstant('{#AppId}') + '_is1', 'InstallLocation', OldInstallPath) then
|
||||
begin
|
||||
QuitOldInstance(OldInstallPath);
|
||||
end;
|
||||
|
||||
Result := True;
|
||||
end;
|
||||
|
||||
function InitializeUninstall(): Boolean;
|
||||
begin
|
||||
QuitOldInstance(ExpandConstant('{app}'));
|
||||
Result := True;
|
||||
end;
|
||||
|
||||
procedure TryCreateStartupTask;
|
||||
var
|
||||
ResultCode: Integer;
|
||||
PSExe: string;
|
||||
PSCommand: string;
|
||||
AppPath: string;
|
||||
begin
|
||||
AppPath := ExpandConstant('{app}\{#AppExeName}');
|
||||
PSExe := ExpandConstant('{sys}\WindowsPowerShell\v1.0\powershell.exe');
|
||||
PSCommand := '-NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -Command ' +
|
||||
'$Action = New-ScheduledTaskAction -Execute ''' + AppPath + ''' -Argument ''-hide''; ' +
|
||||
'$Trigger = New-ScheduledTaskTrigger -AtLogOn; ' +
|
||||
'$Settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries; ' +
|
||||
'Register-ScheduledTask -TaskName ''PYAS_Security_ATS'' -Action $Action -Trigger $Trigger -Settings $Settings -RunLevel Highest -Force';
|
||||
if not Exec(PSExe, PSCommand, '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then
|
||||
begin
|
||||
Log(Format('Failed to create scheduled task, Exec error code: %d', [ResultCode]));
|
||||
end
|
||||
else if ResultCode <> 0 then
|
||||
begin
|
||||
Log(Format('powershell returned exit code %d while creating the scheduled task', [ResultCode]));
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TryDeleteStartupTask;
|
||||
var
|
||||
ResultCode: Integer;
|
||||
begin
|
||||
if not Exec(ExpandConstant('{sys}\schtasks.exe'), '/Delete /TN "PYAS_Security_ATS" /F', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then
|
||||
begin
|
||||
Log(Format('Failed to delete scheduled task, Exec error code: %d', [ResultCode]));
|
||||
end
|
||||
else if ResultCode <> 0 then
|
||||
begin
|
||||
Log(Format('schtasks returned exit code %d while deleting the scheduled task', [ResultCode]));
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure CurStepChanged(CurStep: TSetupStep);
|
||||
begin
|
||||
if CurStep = ssPostInstall then
|
||||
begin
|
||||
if WizardIsTaskSelected('autostart') then
|
||||
begin
|
||||
TryCreateStartupTask;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
|
||||
begin
|
||||
if CurUninstallStep = usUninstall then
|
||||
begin
|
||||
TryDeleteStartupTask;
|
||||
end
|
||||
else if CurUninstallStep = usPostUninstall then
|
||||
begin
|
||||
DelTree(ExpandConstant('{app}'), True, True, True);
|
||||
end;
|
||||
end;
|
||||
@@ -0,0 +1,11 @@
|
||||
import pyinstaller_versionfile
|
||||
|
||||
pyinstaller_versionfile.create_versionfile(
|
||||
output_file="versionfile.txt",
|
||||
version='3.5.9.0',
|
||||
company_name="PYAS Security",
|
||||
file_description="PYAS Security Antivirus",
|
||||
internal_name="PYAS",
|
||||
legal_copyright="PYAS Security",
|
||||
original_filename="PYAS.exe",
|
||||
product_name="PYAS")
|
||||
@@ -0,0 +1,171 @@
|
||||
#pragma once
|
||||
#include <fltKernel.h>
|
||||
#include <ntddk.h>
|
||||
#include <ntdddisk.h>
|
||||
#include <ntddscsi.h>
|
||||
#include <ntstrsafe.h>
|
||||
|
||||
constexpr auto PYAS_PORT_NAME = L"\\PYAS_Output_Pipe";
|
||||
constexpr auto MAX_PATH_LEN = 1024;
|
||||
constexpr auto PYAS_POOL_TAG = 'SAYP';
|
||||
|
||||
#ifndef PROCESS_TERMINATE
|
||||
#define PROCESS_TERMINATE (0x0001)
|
||||
#endif
|
||||
#ifndef PROCESS_CREATE_THREAD
|
||||
#define PROCESS_CREATE_THREAD (0x0002)
|
||||
#endif
|
||||
#ifndef PROCESS_VM_OPERATION
|
||||
#define PROCESS_VM_OPERATION (0x0008)
|
||||
#endif
|
||||
#ifndef PROCESS_VM_READ
|
||||
#define PROCESS_VM_READ (0x0010)
|
||||
#endif
|
||||
#ifndef PROCESS_VM_WRITE
|
||||
#define PROCESS_VM_WRITE (0x0020)
|
||||
#endif
|
||||
#ifndef PROCESS_DUP_HANDLE
|
||||
#define PROCESS_DUP_HANDLE (0x0040)
|
||||
#endif
|
||||
#ifndef PROCESS_CREATE_PROCESS
|
||||
#define PROCESS_CREATE_PROCESS (0x0080)
|
||||
#endif
|
||||
#ifndef PROCESS_SET_INFORMATION
|
||||
#define PROCESS_SET_INFORMATION (0x0200)
|
||||
#endif
|
||||
#ifndef PROCESS_QUERY_INFORMATION
|
||||
#define PROCESS_QUERY_INFORMATION (0x0400)
|
||||
#endif
|
||||
#ifndef PROCESS_SUSPEND_RESUME
|
||||
#define PROCESS_SUSPEND_RESUME (0x0800)
|
||||
#endif
|
||||
#ifndef PROCESS_QUERY_LIMITED_INFORMATION
|
||||
#define PROCESS_QUERY_LIMITED_INFORMATION (0x1000)
|
||||
#endif
|
||||
#ifndef PROCESS_SET_QUOTA
|
||||
#define PROCESS_SET_QUOTA (0x0100)
|
||||
#endif
|
||||
#ifndef THREAD_TERMINATE
|
||||
#define THREAD_TERMINATE (0x0001)
|
||||
#endif
|
||||
#ifndef THREAD_SUSPEND_RESUME
|
||||
#define THREAD_SUSPEND_RESUME (0x0002)
|
||||
#endif
|
||||
#ifndef THREAD_SET_CONTEXT
|
||||
#define THREAD_SET_CONTEXT (0x0010)
|
||||
#endif
|
||||
#ifndef THREAD_SET_INFORMATION
|
||||
#define THREAD_SET_INFORMATION (0x0020)
|
||||
#endif
|
||||
#ifndef THREAD_SET_THREAD_TOKEN
|
||||
#define THREAD_SET_THREAD_TOKEN (0x0080)
|
||||
#endif
|
||||
#ifndef POOL_FLAG_NON_PAGED
|
||||
#define POOL_FLAG_NON_PAGED 0x0000000000000040UI64
|
||||
#endif
|
||||
|
||||
#ifndef FSCTL_MANAGE_BYPASS_IO
|
||||
#define FSCTL_MANAGE_BYPASS_IO CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 188, METHOD_NEITHER, FILE_ANY_ACCESS)
|
||||
|
||||
typedef enum _FS_BPIO_OPERATIONS {
|
||||
FS_BPIO_OP_ENABLE = 1,
|
||||
FS_BPIO_OP_DISABLE = 2,
|
||||
FS_BPIO_OP_QUERY = 3,
|
||||
FS_BPIO_OP_VOLUME_STACK_PAUSE = 4,
|
||||
FS_BPIO_OP_VOLUME_STACK_RESUME = 5,
|
||||
FS_BPIO_OP_STREAM_PAUSE = 6,
|
||||
FS_BPIO_OP_STREAM_RESUME = 7,
|
||||
FS_BPIO_OP_GET_INFO = 8
|
||||
} FS_BPIO_OPERATIONS;
|
||||
|
||||
typedef struct _FS_BPIO_INPUT {
|
||||
FS_BPIO_OPERATIONS Operation;
|
||||
ULONG InFlags;
|
||||
ULONGLONG Reserved1;
|
||||
ULONGLONG Reserved2;
|
||||
} FS_BPIO_INPUT, * PFS_BPIO_INPUT;
|
||||
|
||||
typedef struct _FS_BPIO_OUTPUT {
|
||||
FS_BPIO_OPERATIONS Operation;
|
||||
ULONG OutFlags;
|
||||
ULONGLONG Reserved1;
|
||||
ULONGLONG Reserved2;
|
||||
NTSTATUS Status;
|
||||
} FS_BPIO_OUTPUT, * PFS_BPIO_OUTPUT;
|
||||
|
||||
#endif
|
||||
|
||||
typedef struct _PYAS_MESSAGE {
|
||||
ULONG MessageCode;
|
||||
ULONG ProcessId;
|
||||
WCHAR Path[MAX_PATH_LEN];
|
||||
} PYAS_MESSAGE, * PPYAS_MESSAGE;
|
||||
|
||||
typedef struct _PYAS_USER_MESSAGE {
|
||||
ULONG Command;
|
||||
WCHAR Path[MAX_PATH_LEN];
|
||||
} PYAS_USER_MESSAGE, * PPYAS_USER_MESSAGE;
|
||||
|
||||
typedef struct _DRIVER_DATA {
|
||||
PDRIVER_OBJECT DriverObject;
|
||||
PFLT_FILTER FilterHandle;
|
||||
PFLT_PORT ServerPort;
|
||||
PFLT_PORT ClientPort;
|
||||
PEPROCESS PyasProcess;
|
||||
ULONG PyasPid;
|
||||
LARGE_INTEGER Cookie;
|
||||
KSPIN_LOCK PortMutex;
|
||||
KSPIN_LOCK TrackerMutex;
|
||||
EX_RUNDOWN_REF PortRundown;
|
||||
} DRIVER_DATA, * PDRIVER_DATA;
|
||||
|
||||
typedef struct _RULE_NODE {
|
||||
struct _RULE_NODE* Next;
|
||||
UNICODE_STRING Pattern;
|
||||
} RULE_NODE, * PRULE_NODE;
|
||||
|
||||
extern DRIVER_DATA GlobalData;
|
||||
|
||||
FLT_PREOP_CALLBACK_STATUS ProtectFile_PreCreate(PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID* CompletionContext);
|
||||
FLT_PREOP_CALLBACK_STATUS ProtectFile_PreWrite(PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID* CompletionContext);
|
||||
FLT_PREOP_CALLBACK_STATUS ProtectFile_PreSetInfo(PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID* CompletionContext);
|
||||
FLT_PREOP_CALLBACK_STATUS ProtectFile_FileSystemControl(PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID* CompletionContext);
|
||||
FLT_PREOP_CALLBACK_STATUS ProtectFile_SetSecurity(PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID* CompletionContext);
|
||||
FLT_PREOP_CALLBACK_STATUS ProtectBoot_PreDeviceControl(PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID* CompletionContext);
|
||||
|
||||
NTSTATUS InitializeProcessProtection();
|
||||
VOID UninitializeProcessProtection();
|
||||
NTSTATUS InitializeRegistryProtection(PDRIVER_OBJECT DriverObject);
|
||||
VOID UninitializeRegistryProtection();
|
||||
|
||||
NTSTATUS LoadRulesFromDisk(PUNICODE_STRING RegistryPath);
|
||||
VOID UnloadRules();
|
||||
|
||||
VOID InitializeRulesEngine();
|
||||
VOID UninitializeRulesEngine();
|
||||
|
||||
VOID AddDynamicWhitelist(PUNICODE_STRING RuleStr);
|
||||
VOID RemoveDynamicWhitelist(PUNICODE_STRING RuleStr);
|
||||
|
||||
VOID ImageLoadNotify(PUNICODE_STRING FullImageName, HANDLE ProcessId, PIMAGE_INFO ImageInfo);
|
||||
|
||||
NTSTATUS GetProcessImageName(HANDLE ProcessId, PUNICODE_STRING* ImageName);
|
||||
|
||||
BOOLEAN IsProcessTrusted(HANDLE ProcessId);
|
||||
BOOLEAN IsTargetProtected(HANDLE ProcessId);
|
||||
BOOLEAN WildcardMatch(PCWSTR Pattern, PCWSTR String, USHORT StringLengthBytes);
|
||||
|
||||
BOOLEAN CheckRegistryRule(PCUNICODE_STRING KeyName);
|
||||
BOOLEAN CheckFileExtensionRule(PCUNICODE_STRING FileName);
|
||||
BOOLEAN CheckProtectedPathRule(PCUNICODE_STRING FileName);
|
||||
BOOLEAN CheckRansomActivity(HANDLE ProcessId, PUNICODE_STRING FileName, PVOID WriteBuffer, ULONG WriteLength, BOOLEAN IsWrite);
|
||||
|
||||
NTSTATUS SendMessageToUser(ULONG Code, ULONG Pid, PWCHAR Path, USHORT PathSize);
|
||||
|
||||
FORCEINLINE PVOID PyasAllocate(SIZE_T Size) {
|
||||
return ExAllocatePool2(POOL_FLAG_NON_PAGED, Size, PYAS_POOL_TAG);
|
||||
}
|
||||
|
||||
FORCEINLINE VOID PyasFree(PVOID Ptr) {
|
||||
if (Ptr) ExFreePoolWithTag(Ptr, PYAS_POOL_TAG);
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
#include "DriverCommon.h"
|
||||
|
||||
DRIVER_DATA GlobalData;
|
||||
static BOOLEAN g_ImageNotifyRegistered = FALSE;
|
||||
|
||||
static NTSTATUS InstanceSetup(PCFLT_RELATED_OBJECTS FltObjects, FLT_INSTANCE_SETUP_FLAGS Flags, DEVICE_TYPE VolumeDeviceType, FLT_FILESYSTEM_TYPE VolumeFilesystemType) {
|
||||
UNREFERENCED_PARAMETER(FltObjects);
|
||||
UNREFERENCED_PARAMETER(Flags);
|
||||
UNREFERENCED_PARAMETER(VolumeDeviceType);
|
||||
UNREFERENCED_PARAMETER(VolumeFilesystemType);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static NTSTATUS InstanceQueryTeardown(PCFLT_RELATED_OBJECTS FltObjects, FLT_INSTANCE_QUERY_TEARDOWN_FLAGS Flags) {
|
||||
UNREFERENCED_PARAMETER(FltObjects);
|
||||
UNREFERENCED_PARAMETER(Flags);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static VOID InstanceTeardownStart(PCFLT_RELATED_OBJECTS FltObjects, FLT_INSTANCE_TEARDOWN_FLAGS Flags) {
|
||||
UNREFERENCED_PARAMETER(FltObjects);
|
||||
UNREFERENCED_PARAMETER(Flags);
|
||||
}
|
||||
|
||||
static VOID InstanceTeardownComplete(PCFLT_RELATED_OBJECTS FltObjects, FLT_INSTANCE_TEARDOWN_FLAGS Flags) {
|
||||
UNREFERENCED_PARAMETER(FltObjects);
|
||||
UNREFERENCED_PARAMETER(Flags);
|
||||
}
|
||||
|
||||
static NTSTATUS DriverUnload(FLT_FILTER_UNLOAD_FLAGS Flags) {
|
||||
UNREFERENCED_PARAMETER(Flags);
|
||||
|
||||
if (GlobalData.ServerPort) {
|
||||
FltCloseCommunicationPort(GlobalData.ServerPort);
|
||||
GlobalData.ServerPort = NULL;
|
||||
}
|
||||
|
||||
if (GlobalData.FilterHandle) {
|
||||
FltUnregisterFilter(GlobalData.FilterHandle);
|
||||
GlobalData.FilterHandle = NULL;
|
||||
}
|
||||
|
||||
if (g_ImageNotifyRegistered) {
|
||||
PsRemoveLoadImageNotifyRoutine(ImageLoadNotify);
|
||||
g_ImageNotifyRegistered = FALSE;
|
||||
}
|
||||
|
||||
UninitializeRegistryProtection();
|
||||
UninitializeProcessProtection();
|
||||
|
||||
UnloadRules();
|
||||
UninitializeRulesEngine();
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static NTSTATUS PortMessage(PVOID PortCookie, PVOID InputBuffer, ULONG InputBufferLength, PVOID OutputBuffer, ULONG OutputBufferLength, PULONG ReturnOutputBufferLength) {
|
||||
UNREFERENCED_PARAMETER(PortCookie);
|
||||
UNREFERENCED_PARAMETER(OutputBuffer);
|
||||
UNREFERENCED_PARAMETER(OutputBufferLength);
|
||||
|
||||
if (InputBuffer && InputBufferLength >= sizeof(PYAS_USER_MESSAGE)) {
|
||||
PPYAS_USER_MESSAGE msg = (PPYAS_USER_MESSAGE)InputBuffer;
|
||||
if (msg->Command == 1 || msg->Command == 2) {
|
||||
msg->Path[MAX_PATH_LEN - 1] = L'\0';
|
||||
UNICODE_STRING us;
|
||||
RtlInitUnicodeString(&us, msg->Path);
|
||||
if (us.Length > 0) {
|
||||
if (msg->Command == 1) {
|
||||
AddDynamicWhitelist(&us);
|
||||
}
|
||||
else {
|
||||
RemoveDynamicWhitelist(&us);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ReturnOutputBufferLength) {
|
||||
*ReturnOutputBufferLength = 0;
|
||||
}
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static NTSTATUS PortConnect(PFLT_PORT ClientPort, PVOID ServerPortCookie, PVOID ConnectionContext, ULONG SizeOfContext, PVOID* ConnectionPortCookie) {
|
||||
UNREFERENCED_PARAMETER(ServerPortCookie);
|
||||
UNREFERENCED_PARAMETER(ConnectionContext);
|
||||
UNREFERENCED_PARAMETER(SizeOfContext);
|
||||
UNREFERENCED_PARAMETER(ConnectionPortCookie);
|
||||
|
||||
KIRQL OldIrql;
|
||||
KeAcquireSpinLock(&GlobalData.PortMutex, &OldIrql);
|
||||
|
||||
GlobalData.ClientPort = ClientPort;
|
||||
GlobalData.PyasPid = (ULONG)(ULONG_PTR)PsGetCurrentProcessId();
|
||||
GlobalData.PyasProcess = PsGetCurrentProcess();
|
||||
|
||||
ExReInitializeRundownProtection(&GlobalData.PortRundown);
|
||||
|
||||
KeReleaseSpinLock(&GlobalData.PortMutex, OldIrql);
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static VOID PortDisconnect(PVOID ConnectionCookie) {
|
||||
UNREFERENCED_PARAMETER(ConnectionCookie);
|
||||
|
||||
KIRQL OldIrql;
|
||||
KeAcquireSpinLock(&GlobalData.PortMutex, &OldIrql);
|
||||
|
||||
GlobalData.ClientPort = NULL;
|
||||
GlobalData.PyasPid = 0;
|
||||
GlobalData.PyasProcess = NULL;
|
||||
|
||||
KeReleaseSpinLock(&GlobalData.PortMutex, OldIrql);
|
||||
|
||||
ExWaitForRundownProtectionRelease(&GlobalData.PortRundown);
|
||||
}
|
||||
|
||||
CONST FLT_OPERATION_REGISTRATION Callbacks[] = {
|
||||
{ IRP_MJ_CREATE, 0, ProtectFile_PreCreate, NULL },
|
||||
{ IRP_MJ_WRITE, 0, ProtectFile_PreWrite, NULL },
|
||||
{ IRP_MJ_SET_INFORMATION, 0, ProtectFile_PreSetInfo, NULL },
|
||||
{ IRP_MJ_SET_SECURITY, 0, ProtectFile_SetSecurity, NULL },
|
||||
{ IRP_MJ_FILE_SYSTEM_CONTROL, 0, ProtectFile_FileSystemControl, NULL },
|
||||
{ IRP_MJ_DEVICE_CONTROL, 0, ProtectBoot_PreDeviceControl, NULL },
|
||||
{ IRP_MJ_OPERATION_END }
|
||||
};
|
||||
|
||||
CONST FLT_REGISTRATION FilterRegistration = {
|
||||
sizeof(FLT_REGISTRATION),
|
||||
FLT_REGISTRATION_VERSION,
|
||||
0,
|
||||
NULL,
|
||||
Callbacks,
|
||||
DriverUnload,
|
||||
InstanceSetup,
|
||||
InstanceQueryTeardown,
|
||||
InstanceTeardownStart,
|
||||
InstanceTeardownComplete,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL
|
||||
};
|
||||
|
||||
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {
|
||||
NTSTATUS status = STATUS_SUCCESS;
|
||||
PSECURITY_DESCRIPTOR sd = NULL;
|
||||
OBJECT_ATTRIBUTES oa = { 0 };
|
||||
UNICODE_STRING name = { 0 };
|
||||
|
||||
RtlZeroMemory(&GlobalData, sizeof(GlobalData));
|
||||
GlobalData.DriverObject = DriverObject;
|
||||
KeInitializeSpinLock(&GlobalData.PortMutex);
|
||||
KeInitializeSpinLock(&GlobalData.TrackerMutex);
|
||||
|
||||
ExInitializeRundownProtection(&GlobalData.PortRundown);
|
||||
|
||||
InitializeRulesEngine();
|
||||
|
||||
status = LoadRulesFromDisk(RegistryPath);
|
||||
|
||||
status = FltRegisterFilter(DriverObject, &FilterRegistration, &GlobalData.FilterHandle);
|
||||
if (!NT_SUCCESS(status)) {
|
||||
UnloadRules();
|
||||
UninitializeRulesEngine();
|
||||
return status;
|
||||
}
|
||||
|
||||
status = FltBuildDefaultSecurityDescriptor(&sd, FLT_PORT_ALL_ACCESS);
|
||||
if (NT_SUCCESS(status)) {
|
||||
RtlInitUnicodeString(&name, PYAS_PORT_NAME);
|
||||
InitializeObjectAttributes(&oa, &name, OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, NULL, sd);
|
||||
status = FltCreateCommunicationPort(GlobalData.FilterHandle, &GlobalData.ServerPort, &oa, NULL, PortConnect, PortDisconnect, PortMessage, 1);
|
||||
FltFreeSecurityDescriptor(sd);
|
||||
}
|
||||
|
||||
if (!NT_SUCCESS(status)) {
|
||||
FltUnregisterFilter(GlobalData.FilterHandle);
|
||||
GlobalData.FilterHandle = NULL;
|
||||
UnloadRules();
|
||||
UninitializeRulesEngine();
|
||||
return status;
|
||||
}
|
||||
|
||||
InitializeProcessProtection();
|
||||
|
||||
InitializeRegistryProtection(DriverObject);
|
||||
|
||||
NTSTATUS imageNotifyStatus = PsSetLoadImageNotifyRoutine(ImageLoadNotify);
|
||||
if (NT_SUCCESS(imageNotifyStatus)) {
|
||||
g_ImageNotifyRegistered = TRUE;
|
||||
}
|
||||
|
||||
status = FltStartFiltering(GlobalData.FilterHandle);
|
||||
if (!NT_SUCCESS(status)) {
|
||||
if (g_ImageNotifyRegistered) {
|
||||
PsRemoveLoadImageNotifyRoutine(ImageLoadNotify);
|
||||
g_ImageNotifyRegistered = FALSE;
|
||||
}
|
||||
|
||||
UninitializeRegistryProtection();
|
||||
UninitializeProcessProtection();
|
||||
|
||||
if (GlobalData.ServerPort) {
|
||||
FltCloseCommunicationPort(GlobalData.ServerPort);
|
||||
GlobalData.ServerPort = NULL;
|
||||
}
|
||||
|
||||
if (GlobalData.FilterHandle) {
|
||||
FltUnregisterFilter(GlobalData.FilterHandle);
|
||||
GlobalData.FilterHandle = NULL;
|
||||
}
|
||||
|
||||
UnloadRules();
|
||||
UninitializeRulesEngine();
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
[Version]
|
||||
Signature = "$WINDOWS NT$"
|
||||
Class = ActivityMonitor
|
||||
ClassGuid = {b86dff51-a31e-4bac-b3cf-e8cfe75c9fc2}
|
||||
Provider = %ManufacturerName%
|
||||
DriverVer = 04/26/2026,3.5.1.0
|
||||
CatalogFile = PYAS_Driver.cat
|
||||
PnpLockdown = 1
|
||||
|
||||
[DestinationDirs]
|
||||
DefaultDestDir = 12
|
||||
PYAS_Driver.DriverFiles = 12
|
||||
|
||||
[SourceDisksNames]
|
||||
1 = %DiskName%,,,""
|
||||
|
||||
[SourceDisksFiles]
|
||||
PYAS_Driver.sys = 1,,
|
||||
|
||||
[DefaultInstall.NTamd64]
|
||||
OptionDesc = %ServiceDescription%
|
||||
CopyFiles = PYAS_Driver.DriverFiles
|
||||
|
||||
[DefaultInstall.NTamd64.Services]
|
||||
AddService = %ServiceName%,,PYAS_Driver.Service
|
||||
|
||||
[PYAS_Driver.DriverFiles]
|
||||
PYAS_Driver.sys
|
||||
|
||||
[PYAS_Driver.Service]
|
||||
DisplayName = %ServiceName%
|
||||
Description = %ServiceDescription%
|
||||
ServiceBinary = %12%\PYAS_Driver.sys
|
||||
Dependencies = FltMgr
|
||||
ServiceType = 2
|
||||
StartType = 3
|
||||
ErrorControl = 1
|
||||
LoadOrderGroup = "FSFilter Activity Monitor"
|
||||
AddReg = PYAS_Driver.AddReg
|
||||
|
||||
[PYAS_Driver.AddReg]
|
||||
HKR,"Parameters","DebugFlags",0x00010001,0x0
|
||||
HKR,"Parameters","SupportedFeatures",0x00010001,0x3
|
||||
HKR,"Parameters\Instances","DefaultInstance",0x00000000,"PYAS Instance"
|
||||
HKR,"Parameters\Instances\PYAS Instance","Altitude",0x00000000,"320000"
|
||||
HKR,"Parameters\Instances\PYAS Instance","Flags",0x00010001,0x0
|
||||
|
||||
[Strings]
|
||||
ManufacturerName = "PYAS Security"
|
||||
DiskName = "PYAS_Driver Source Disk"
|
||||
ServiceName = "PYAS_Driver"
|
||||
ServiceDescription = "PYAS Security Driver"
|
||||
@@ -0,0 +1,35 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.14.36518.9 d17.14
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PYAS_Driver", "PYAS_Driver.vcxproj", "{699C79A8-A904-1976-F72A-CDD7E811CFD2}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|ARM64 = Debug|ARM64
|
||||
Debug|x64 = Debug|x64
|
||||
Release|ARM64 = Release|ARM64
|
||||
Release|x64 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{699C79A8-A904-1976-F72A-CDD7E811CFD2}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{699C79A8-A904-1976-F72A-CDD7E811CFD2}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{699C79A8-A904-1976-F72A-CDD7E811CFD2}.Debug|ARM64.Deploy.0 = Debug|ARM64
|
||||
{699C79A8-A904-1976-F72A-CDD7E811CFD2}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{699C79A8-A904-1976-F72A-CDD7E811CFD2}.Debug|x64.Build.0 = Debug|x64
|
||||
{699C79A8-A904-1976-F72A-CDD7E811CFD2}.Debug|x64.Deploy.0 = Debug|x64
|
||||
{699C79A8-A904-1976-F72A-CDD7E811CFD2}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{699C79A8-A904-1976-F72A-CDD7E811CFD2}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{699C79A8-A904-1976-F72A-CDD7E811CFD2}.Release|ARM64.Deploy.0 = Release|ARM64
|
||||
{699C79A8-A904-1976-F72A-CDD7E811CFD2}.Release|x64.ActiveCfg = Release|x64
|
||||
{699C79A8-A904-1976-F72A-CDD7E811CFD2}.Release|x64.Build.0 = Release|x64
|
||||
{699C79A8-A904-1976-F72A-CDD7E811CFD2}.Release|x64.Deploy.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {D166699A-50D5-470E-9559-39B8E842528D}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,128 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|ARM64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|ARM64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{699C79A8-A904-1976-F72A-CDD7E811CFD2}</ProjectGuid>
|
||||
<TemplateGuid>{dd38f7fc-d7bd-488b-9242-7d8754cde80d}</TemplateGuid>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<MinimumVisualStudioVersion>12.0</MinimumVisualStudioVersion>
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform Condition="'$(Platform)' == ''">x64</Platform>
|
||||
<RootNamespace>PYAS_Driver</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>$(LatestTargetPlatformVersion)</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<TargetVersion>Windows10</TargetVersion>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
|
||||
<ConfigurationType>Driver</ConfigurationType>
|
||||
<DriverType>WDM</DriverType>
|
||||
<Driver_SpectreMitigation>false</Driver_SpectreMitigation>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<TargetVersion>Windows10</TargetVersion>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
|
||||
<ConfigurationType>Driver</ConfigurationType>
|
||||
<DriverType>WDM</DriverType>
|
||||
<Driver_SpectreMitigation>false</Driver_SpectreMitigation>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">
|
||||
<TargetVersion>Windows10</TargetVersion>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
|
||||
<ConfigurationType>Driver</ConfigurationType>
|
||||
<DriverType>WDM</DriverType>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">
|
||||
<TargetVersion>Windows10</TargetVersion>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
|
||||
<ConfigurationType>Driver</ConfigurationType>
|
||||
<DriverType>WDM</DriverType>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor>
|
||||
<EnableInf2cat>false</EnableInf2cat>
|
||||
<Inf2CatUseLocalTime>true</Inf2CatUseLocalTime>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor>
|
||||
<Inf2CatUseLocalTime>true</Inf2CatUseLocalTime>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
|
||||
<DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
|
||||
<DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<DriverSign>
|
||||
<FileDigestAlgorithm>sha256</FileDigestAlgorithm>
|
||||
</DriverSign>
|
||||
<ClCompile>
|
||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||
<TreatWarningAsError>false</TreatWarningAsError>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>$(DDK_LIB_PATH)fltmgr.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<DriverSign>
|
||||
<FileDigestAlgorithm>sha256</FileDigestAlgorithm>
|
||||
</DriverSign>
|
||||
<ClCompile>
|
||||
<TreatWarningAsError>false</TreatWarningAsError>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>$(DDK_LIB_PATH)fltmgr.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<Inf Include="PYAS_Driver.inf" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<FilesToPackage Include="$(TargetPath)" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="DriverCommon.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="DriverEntry.cpp" />
|
||||
<ClCompile Include="ProtectBoot.cpp" />
|
||||
<ClCompile Include="ProtectFile.cpp" />
|
||||
<ClCompile Include="ProtectProcess.cpp" />
|
||||
<ClCompile Include="ProtectRegistry.cpp" />
|
||||
<ClCompile Include="ProtectRules.cpp" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Driver Files">
|
||||
<UniqueIdentifier>{8E41214B-6785-4CFE-B992-037D68949A14}</UniqueIdentifier>
|
||||
<Extensions>inf;inv;inx;mof;mc;</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Inf Include="PYAS_Driver.inf">
|
||||
<Filter>Driver Files</Filter>
|
||||
</Inf>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="DriverCommon.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="ProtectRules.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ProtectFile.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ProtectBoot.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ProtectRegistry.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ProtectProcess.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="DriverEntry.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,33 @@
|
||||
#include "DriverCommon.h"
|
||||
|
||||
#ifndef IOCTL_DISK_FORMAT_TRACKS
|
||||
#define IOCTL_DISK_FORMAT_TRACKS CTL_CODE(IOCTL_DISK_BASE, 0x0006, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
|
||||
#endif
|
||||
#ifndef IOCTL_DISK_FORMAT_TRACKS_EX
|
||||
#define IOCTL_DISK_FORMAT_TRACKS_EX CTL_CODE(IOCTL_DISK_BASE, 0x000b, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
|
||||
#endif
|
||||
|
||||
FLT_PREOP_CALLBACK_STATUS ProtectBoot_PreDeviceControl(PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID* CompletionContext) {
|
||||
UNREFERENCED_PARAMETER(FltObjects);
|
||||
UNREFERENCED_PARAMETER(CompletionContext);
|
||||
|
||||
if (Data->RequestorMode == KernelMode) return FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
if (KeGetCurrentIrql() > APC_LEVEL) return FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
|
||||
ULONG IoControlCode = Data->Iopb->Parameters.DeviceIoControl.Common.IoControlCode;
|
||||
|
||||
if (IoControlCode == IOCTL_DISK_SET_DRIVE_LAYOUT_EX ||
|
||||
IoControlCode == IOCTL_SCSI_PASS_THROUGH_DIRECT ||
|
||||
IoControlCode == IOCTL_DISK_FORMAT_TRACKS ||
|
||||
IoControlCode == IOCTL_DISK_FORMAT_TRACKS_EX) {
|
||||
|
||||
HANDLE Pid = PsGetCurrentProcessId();
|
||||
if (!IsProcessTrusted(Pid)) {
|
||||
UNICODE_STRING MsgStr = RTL_CONSTANT_STRING(L"Disk_Wiper_Attempt");
|
||||
SendMessageToUser(4001, (ULONG)(ULONG_PTR)Pid, MsgStr.Buffer, MsgStr.Length);
|
||||
Data->IoStatus.Status = STATUS_ACCESS_DENIED;
|
||||
return FLT_PREOP_COMPLETE;
|
||||
}
|
||||
}
|
||||
return FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
#include "DriverCommon.h"
|
||||
|
||||
static BOOLEAN IsHoneyToken(PUNICODE_STRING Path) {
|
||||
if (!Path || !Path->Buffer) return FALSE;
|
||||
if (WildcardMatch(L"*PYAS_Honey*", Path->Buffer, Path->Length) ||
|
||||
WildcardMatch(L"*Backup_Secret*", Path->Buffer, Path->Length)) {
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
FLT_PREOP_CALLBACK_STATUS ProtectFile_PreCreate(PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID* CompletionContext) {
|
||||
UNREFERENCED_PARAMETER(FltObjects);
|
||||
UNREFERENCED_PARAMETER(CompletionContext);
|
||||
|
||||
if (Data->RequestorMode == KernelMode) return FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
if (KeGetCurrentIrql() > APC_LEVEL) return FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
|
||||
PFLT_FILE_NAME_INFORMATION nameInfo = NULL;
|
||||
UNICODE_STRING FileName = { 0 };
|
||||
NTSTATUS status = STATUS_SUCCESS;
|
||||
FLT_PREOP_CALLBACK_STATUS callbackStatus = FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
|
||||
status = FltGetFileNameInformation(Data, FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT, &nameInfo);
|
||||
if (!NT_SUCCESS(status) || !nameInfo) {
|
||||
return FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
}
|
||||
|
||||
status = FltParseFileNameInformation(nameInfo);
|
||||
if (!NT_SUCCESS(status)) {
|
||||
FltReleaseFileNameInformation(nameInfo);
|
||||
return FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
}
|
||||
|
||||
FileName = nameInfo->Name;
|
||||
if (!FileName.Buffer || FileName.Length == 0) {
|
||||
FltReleaseFileNameInformation(nameInfo);
|
||||
return FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
}
|
||||
|
||||
HANDLE Pid = PsGetCurrentProcessId();
|
||||
BOOLEAN Trusted = IsProcessTrusted(Pid);
|
||||
|
||||
ULONG CreateDisposition = (Data->Iopb->Parameters.Create.Options >> 24) & 0xFF;
|
||||
BOOLEAN IsCreateAction = (CreateDisposition == FILE_CREATE ||
|
||||
CreateDisposition == FILE_SUPERSEDE ||
|
||||
CreateDisposition == FILE_OVERWRITE ||
|
||||
CreateDisposition == FILE_OVERWRITE_IF ||
|
||||
CreateDisposition == FILE_OPEN_IF);
|
||||
|
||||
if (IsHoneyToken(&FileName)) {
|
||||
if (!Trusted) {
|
||||
if ((Data->Iopb->Parameters.Create.SecurityContext->DesiredAccess & (FILE_WRITE_DATA | DELETE)) || IsCreateAction) {
|
||||
SendMessageToUser(5001, (ULONG)(ULONG_PTR)Pid, FileName.Buffer, FileName.Length);
|
||||
Data->IoStatus.Status = STATUS_ACCESS_DENIED;
|
||||
callbackStatus = FLT_PREOP_COMPLETE;
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (WildcardMatch(L"\\Device\\PhysicalDrive*", FileName.Buffer, FileName.Length) ||
|
||||
(WildcardMatch(L"\\Device\\Harddisk*", FileName.Buffer, FileName.Length) && !WildcardMatch(L"*Volume*", FileName.Buffer, FileName.Length))) {
|
||||
|
||||
if (Data->Iopb->Parameters.Create.SecurityContext->DesiredAccess &
|
||||
(FILE_WRITE_DATA | FILE_APPEND_DATA | GENERIC_WRITE | WRITE_DAC | WRITE_OWNER)) {
|
||||
|
||||
if (!Trusted) {
|
||||
UNICODE_STRING MsgStr = RTL_CONSTANT_STRING(L"Disk_Wiper_Attempt");
|
||||
SendMessageToUser(4001, (ULONG)(ULONG_PTR)Pid, MsgStr.Buffer, MsgStr.Length);
|
||||
Data->IoStatus.Status = STATUS_ACCESS_DENIED;
|
||||
callbackStatus = FLT_PREOP_COMPLETE;
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (CheckProtectedPathRule(&FileName)) {
|
||||
if (!Trusted) {
|
||||
if ((Data->Iopb->Parameters.Create.SecurityContext->DesiredAccess &
|
||||
(FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_WRITE_ATTRIBUTES | DELETE | WRITE_DAC | GENERIC_WRITE)) || IsCreateAction) {
|
||||
SendMessageToUser(2001, (ULONG)(ULONG_PTR)Pid, FileName.Buffer, FileName.Length);
|
||||
Data->IoStatus.Status = STATUS_ACCESS_DENIED;
|
||||
callbackStatus = FLT_PREOP_COMPLETE;
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (CheckFileExtensionRule(&FileName)) {
|
||||
if (!Trusted) {
|
||||
if (Data->Iopb->Parameters.Create.SecurityContext->DesiredAccess & (DELETE | FILE_DELETE_CHILD)) {
|
||||
if (CheckRansomActivity(Pid, &FileName, NULL, 0, FALSE)) {
|
||||
SendMessageToUser(5001, (ULONG)(ULONG_PTR)Pid, FileName.Buffer, FileName.Length);
|
||||
Data->IoStatus.Status = STATUS_ACCESS_DENIED;
|
||||
callbackStatus = FLT_PREOP_COMPLETE;
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
if (nameInfo) {
|
||||
FltReleaseFileNameInformation(nameInfo);
|
||||
}
|
||||
return callbackStatus;
|
||||
}
|
||||
|
||||
FLT_PREOP_CALLBACK_STATUS ProtectFile_PreSetInfo(PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID* CompletionContext) {
|
||||
UNREFERENCED_PARAMETER(FltObjects);
|
||||
UNREFERENCED_PARAMETER(CompletionContext);
|
||||
|
||||
if (Data->RequestorMode == KernelMode) return FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
if (KeGetCurrentIrql() > APC_LEVEL) return FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
|
||||
FILE_INFORMATION_CLASS infoClass = Data->Iopb->Parameters.SetFileInformation.FileInformationClass;
|
||||
|
||||
if (infoClass != FileDispositionInformation &&
|
||||
infoClass != FileRenameInformation &&
|
||||
infoClass != FileRenameInformationEx) {
|
||||
return FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
}
|
||||
|
||||
PFLT_FILE_NAME_INFORMATION nameInfo = NULL;
|
||||
FLT_PREOP_CALLBACK_STATUS callbackStatus = FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
NTSTATUS status = FltGetFileNameInformation(Data, FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT, &nameInfo);
|
||||
|
||||
if (!NT_SUCCESS(status) || !nameInfo) goto cleanup;
|
||||
|
||||
status = FltParseFileNameInformation(nameInfo);
|
||||
if (!NT_SUCCESS(status) || !nameInfo->Name.Buffer) goto cleanup;
|
||||
|
||||
HANDLE Pid = PsGetCurrentProcessId();
|
||||
if (!IsProcessTrusted(Pid)) {
|
||||
if (CheckProtectedPathRule(&nameInfo->Name)) {
|
||||
SendMessageToUser(2001, (ULONG)(ULONG_PTR)Pid, nameInfo->Name.Buffer, nameInfo->Name.Length);
|
||||
Data->IoStatus.Status = STATUS_ACCESS_DENIED;
|
||||
callbackStatus = FLT_PREOP_COMPLETE;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (CheckFileExtensionRule(&nameInfo->Name)) {
|
||||
if (CheckRansomActivity(Pid, &nameInfo->Name, NULL, 0, FALSE)) {
|
||||
SendMessageToUser(5001, (ULONG)(ULONG_PTR)Pid, nameInfo->Name.Buffer, nameInfo->Name.Length);
|
||||
Data->IoStatus.Status = STATUS_ACCESS_DENIED;
|
||||
callbackStatus = FLT_PREOP_COMPLETE;
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
if (nameInfo) {
|
||||
FltReleaseFileNameInformation(nameInfo);
|
||||
}
|
||||
return callbackStatus;
|
||||
}
|
||||
|
||||
FLT_PREOP_CALLBACK_STATUS ProtectFile_SetSecurity(PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID* CompletionContext) {
|
||||
UNREFERENCED_PARAMETER(FltObjects);
|
||||
UNREFERENCED_PARAMETER(CompletionContext);
|
||||
|
||||
if (Data->RequestorMode == KernelMode) return FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
if (KeGetCurrentIrql() > APC_LEVEL) return FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
|
||||
PFLT_FILE_NAME_INFORMATION nameInfo = NULL;
|
||||
FLT_PREOP_CALLBACK_STATUS callbackStatus = FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
NTSTATUS status = FltGetFileNameInformation(Data, FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT, &nameInfo);
|
||||
|
||||
if (!NT_SUCCESS(status) || !nameInfo) goto cleanup;
|
||||
|
||||
status = FltParseFileNameInformation(nameInfo);
|
||||
if (!NT_SUCCESS(status) || !nameInfo->Name.Buffer) goto cleanup;
|
||||
|
||||
SECURITY_INFORMATION SecurityInfo = Data->Iopb->Parameters.SetSecurity.SecurityInformation;
|
||||
|
||||
if (SecurityInfo & (OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION)) {
|
||||
if (CheckProtectedPathRule(&nameInfo->Name)) {
|
||||
HANDLE Pid = PsGetCurrentProcessId();
|
||||
if (!IsProcessTrusted(Pid)) {
|
||||
SendMessageToUser(2001, (ULONG)(ULONG_PTR)Pid, nameInfo->Name.Buffer, nameInfo->Name.Length);
|
||||
Data->IoStatus.Status = STATUS_ACCESS_DENIED;
|
||||
callbackStatus = FLT_PREOP_COMPLETE;
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
if (nameInfo) {
|
||||
FltReleaseFileNameInformation(nameInfo);
|
||||
}
|
||||
return callbackStatus;
|
||||
}
|
||||
|
||||
FLT_PREOP_CALLBACK_STATUS ProtectFile_PreWrite(PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID* CompletionContext) {
|
||||
UNREFERENCED_PARAMETER(CompletionContext);
|
||||
UNREFERENCED_PARAMETER(FltObjects);
|
||||
|
||||
if (Data->RequestorMode == KernelMode) return FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
if (KeGetCurrentIrql() > APC_LEVEL) return FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
|
||||
if (Data->Iopb->IrpFlags & (IRP_PAGING_IO | IRP_SYNCHRONOUS_PAGING_IO | IRP_NOCACHE)) {
|
||||
return FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
}
|
||||
|
||||
ULONG WriteLength = Data->Iopb->Parameters.Write.Length;
|
||||
if (WriteLength == 0) return FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
|
||||
PFLT_FILE_NAME_INFORMATION nameInfo = NULL;
|
||||
FLT_PREOP_CALLBACK_STATUS callbackStatus = FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
NTSTATUS status = FltGetFileNameInformation(Data, FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT, &nameInfo);
|
||||
|
||||
if (!NT_SUCCESS(status) || !nameInfo) goto cleanup;
|
||||
|
||||
status = FltParseFileNameInformation(nameInfo);
|
||||
if (!NT_SUCCESS(status) || !nameInfo->Name.Buffer) goto cleanup;
|
||||
|
||||
if (CheckFileExtensionRule(&nameInfo->Name)) {
|
||||
HANDLE Pid = PsGetCurrentProcessId();
|
||||
|
||||
if (!IsProcessTrusted(Pid)) {
|
||||
PVOID SystemBuffer = NULL;
|
||||
PMDL Mdl = Data->Iopb->Parameters.Write.MdlAddress;
|
||||
|
||||
if (!Mdl) {
|
||||
if (Data->Iopb->Parameters.Write.WriteBuffer && KeGetCurrentIrql() <= APC_LEVEL) {
|
||||
if (NT_SUCCESS(FltLockUserBuffer(Data))) {
|
||||
Mdl = Data->Iopb->Parameters.Write.MdlAddress;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Mdl) {
|
||||
SystemBuffer = MmGetSystemAddressForMdlSafe(Mdl, NormalPagePriority | MdlMappingNoExecute);
|
||||
}
|
||||
|
||||
if (CheckRansomActivity(Pid, &nameInfo->Name, SystemBuffer, WriteLength, TRUE)) {
|
||||
SendMessageToUser(5001, (ULONG)(ULONG_PTR)Pid, nameInfo->Name.Buffer, nameInfo->Name.Length);
|
||||
Data->IoStatus.Status = STATUS_ACCESS_DENIED;
|
||||
callbackStatus = FLT_PREOP_COMPLETE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
if (nameInfo) {
|
||||
FltReleaseFileNameInformation(nameInfo);
|
||||
}
|
||||
return callbackStatus;
|
||||
}
|
||||
|
||||
FLT_PREOP_CALLBACK_STATUS ProtectFile_FileSystemControl(PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID* CompletionContext) {
|
||||
UNREFERENCED_PARAMETER(FltObjects);
|
||||
UNREFERENCED_PARAMETER(CompletionContext);
|
||||
|
||||
if (Data->Iopb->Parameters.FileSystemControl.Common.FsControlCode != FSCTL_MANAGE_BYPASS_IO) {
|
||||
return FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
}
|
||||
|
||||
PFS_BPIO_INPUT InputBuffer = (PFS_BPIO_INPUT)Data->Iopb->Parameters.FileSystemControl.Neither.InputBuffer;
|
||||
if (!InputBuffer) return FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
|
||||
FS_BPIO_OPERATIONS Operation;
|
||||
|
||||
__try {
|
||||
Operation = InputBuffer->Operation;
|
||||
}
|
||||
__except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
return FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
}
|
||||
|
||||
PFLT_FILE_NAME_INFORMATION nameInfo = NULL;
|
||||
FLT_PREOP_CALLBACK_STATUS callbackStatus = FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
|
||||
if (Operation == FS_BPIO_OP_ENABLE) {
|
||||
if (NT_SUCCESS(FltGetFileNameInformation(Data, FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT, &nameInfo)) && nameInfo) {
|
||||
if (NT_SUCCESS(FltParseFileNameInformation(nameInfo))) {
|
||||
if (CheckFileExtensionRule(&nameInfo->Name) || CheckProtectedPathRule(&nameInfo->Name)) {
|
||||
Data->IoStatus.Status = STATUS_NOT_SUPPORTED;
|
||||
callbackStatus = FLT_PREOP_COMPLETE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nameInfo) {
|
||||
FltReleaseFileNameInformation(nameInfo);
|
||||
}
|
||||
return callbackStatus;
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
#include "DriverCommon.h"
|
||||
|
||||
static PVOID ObRegistrationHandle = NULL;
|
||||
static OB_CALLBACK_REGISTRATION ObRegistration;
|
||||
static OB_OPERATION_REGISTRATION ObCallbacks[2];
|
||||
|
||||
static BOOLEAN IsSystemImage(PUNICODE_STRING FullImageName) {
|
||||
if (!FullImageName || !FullImageName->Buffer) return FALSE;
|
||||
|
||||
if (WildcardMatch(L"*\\Windows\\System32\\*", FullImageName->Buffer, FullImageName->Length) ||
|
||||
WildcardMatch(L"*\\Windows\\SysWOW64\\*", FullImageName->Buffer, FullImageName->Length) ||
|
||||
WildcardMatch(L"*\\Windows\\WinSxS\\*", FullImageName->Buffer, FullImageName->Length) ||
|
||||
WildcardMatch(L"*\\Windows\\Microsoft.NET\\*", FullImageName->Buffer, FullImageName->Length) ||
|
||||
WildcardMatch(L"*\\Common Files\\Microsoft Shared\\*", FullImageName->Buffer, FullImageName->Length) ||
|
||||
WildcardMatch(L"*\\Program Files*\\*", FullImageName->Buffer, FullImageName->Length)) {
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
VOID ImageLoadNotify(PUNICODE_STRING FullImageName, HANDLE ProcessId, PIMAGE_INFO ImageInfo) {
|
||||
UNREFERENCED_PARAMETER(ImageInfo);
|
||||
|
||||
if (KeGetCurrentIrql() > APC_LEVEL) return;
|
||||
if (!FullImageName || !FullImageName->Buffer) return;
|
||||
if (ProcessId == (HANDLE)0) return;
|
||||
|
||||
if (IsSystemImage(FullImageName)) return;
|
||||
|
||||
if (IsTargetProtected(ProcessId)) {
|
||||
if (CheckFileExtensionRule(FullImageName)) {
|
||||
SendMessageToUser(6001, (ULONG)(ULONG_PTR)ProcessId, FullImageName->Buffer, FullImageName->Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static BOOLEAN IsCriticalSystemProcess(HANDLE ProcessId) {
|
||||
if (ProcessId == (HANDLE)4) return TRUE;
|
||||
|
||||
if (KeGetCurrentIrql() != PASSIVE_LEVEL) return FALSE;
|
||||
|
||||
NTSTATUS status;
|
||||
BOOLEAN isCritical = FALSE;
|
||||
PEPROCESS Process = NULL;
|
||||
PUNICODE_STRING imageFileName = NULL;
|
||||
|
||||
status = PsLookupProcessByProcessId(ProcessId, &Process);
|
||||
if (!NT_SUCCESS(status)) return FALSE;
|
||||
|
||||
status = SeLocateProcessImageName(Process, &imageFileName);
|
||||
|
||||
if (NT_SUCCESS(status) && imageFileName && imageFileName->Buffer) {
|
||||
if (WildcardMatch(L"*\\Windows\\System32\\csrss.exe", imageFileName->Buffer, imageFileName->Length) ||
|
||||
WildcardMatch(L"*\\Windows\\System32\\lsass.exe", imageFileName->Buffer, imageFileName->Length) ||
|
||||
WildcardMatch(L"*\\Windows\\System32\\services.exe", imageFileName->Buffer, imageFileName->Length) ||
|
||||
WildcardMatch(L"*\\Windows\\System32\\wininit.exe", imageFileName->Buffer, imageFileName->Length) ||
|
||||
WildcardMatch(L"*\\Windows\\System32\\smss.exe", imageFileName->Buffer, imageFileName->Length)) {
|
||||
isCritical = TRUE;
|
||||
}
|
||||
ExFreePool(imageFileName);
|
||||
}
|
||||
|
||||
ObDereferenceObject(Process);
|
||||
return isCritical;
|
||||
}
|
||||
|
||||
static BOOLEAN IsBlacklistedAdminTool(HANDLE ProcessId) {
|
||||
if (KeGetCurrentIrql() != PASSIVE_LEVEL) return FALSE;
|
||||
|
||||
NTSTATUS status;
|
||||
BOOLEAN isBlacklisted = FALSE;
|
||||
PEPROCESS Process = NULL;
|
||||
PUNICODE_STRING imageFileName = NULL;
|
||||
|
||||
status = PsLookupProcessByProcessId(ProcessId, &Process);
|
||||
if (!NT_SUCCESS(status)) return FALSE;
|
||||
|
||||
status = SeLocateProcessImageName(Process, &imageFileName);
|
||||
|
||||
if (NT_SUCCESS(status) && imageFileName) {
|
||||
if (imageFileName->Buffer) {
|
||||
if (WildcardMatch(L"*Taskmgr.exe", imageFileName->Buffer, imageFileName->Length) ||
|
||||
WildcardMatch(L"*taskkill.exe", imageFileName->Buffer, imageFileName->Length) ||
|
||||
WildcardMatch(L"*ProcessHacker.exe", imageFileName->Buffer, imageFileName->Length) ||
|
||||
WildcardMatch(L"*procmon.exe", imageFileName->Buffer, imageFileName->Length)) {
|
||||
isBlacklisted = TRUE;
|
||||
}
|
||||
}
|
||||
ExFreePool(imageFileName);
|
||||
}
|
||||
|
||||
ObDereferenceObject(Process);
|
||||
return isBlacklisted;
|
||||
}
|
||||
|
||||
static OB_PREOP_CALLBACK_STATUS PreOpenProcess(PVOID RegistrationContext, POB_PRE_OPERATION_INFORMATION OperationInformation) {
|
||||
UNREFERENCED_PARAMETER(RegistrationContext);
|
||||
|
||||
if (OperationInformation->KernelHandle) return OB_PREOP_SUCCESS;
|
||||
|
||||
PEPROCESS TargetProcess = (PEPROCESS)OperationInformation->Object;
|
||||
if (!TargetProcess) return OB_PREOP_SUCCESS;
|
||||
|
||||
HANDLE TargetPid = PsGetProcessId(TargetProcess);
|
||||
HANDLE SourcePid = PsGetCurrentProcessId();
|
||||
|
||||
if (SourcePid == TargetPid) return OB_PREOP_SUCCESS;
|
||||
if ((ULONG)(ULONG_PTR)SourcePid == GlobalData.PyasPid) return OB_PREOP_SUCCESS;
|
||||
if (SourcePid == (HANDLE)4) return OB_PREOP_SUCCESS;
|
||||
|
||||
if (IsTargetProtected(TargetPid)) {
|
||||
if (IsCriticalSystemProcess(SourcePid)) return OB_PREOP_SUCCESS;
|
||||
|
||||
BOOLEAN bIsTrusted = IsProcessTrusted(SourcePid);
|
||||
|
||||
if (bIsTrusted) {
|
||||
if (IsBlacklistedAdminTool(SourcePid)) {
|
||||
bIsTrusted = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
if (bIsTrusted) return OB_PREOP_SUCCESS;
|
||||
|
||||
ACCESS_MASK DenyMask = PROCESS_TERMINATE |
|
||||
PROCESS_VM_OPERATION |
|
||||
PROCESS_VM_WRITE |
|
||||
PROCESS_CREATE_THREAD |
|
||||
PROCESS_VM_READ |
|
||||
PROCESS_DUP_HANDLE |
|
||||
PROCESS_SUSPEND_RESUME |
|
||||
PROCESS_SET_INFORMATION |
|
||||
PROCESS_SET_QUOTA;
|
||||
|
||||
OperationInformation->Parameters->CreateHandleInformation.DesiredAccess &= (ACCESS_MASK)(~DenyMask);
|
||||
|
||||
if (OperationInformation->Operation == OB_OPERATION_HANDLE_DUPLICATE) {
|
||||
OperationInformation->Parameters->DuplicateHandleInformation.DesiredAccess &= (ACCESS_MASK)(~DenyMask);
|
||||
}
|
||||
}
|
||||
|
||||
return OB_PREOP_SUCCESS;
|
||||
}
|
||||
|
||||
static OB_PREOP_CALLBACK_STATUS PreOpenThread(PVOID RegistrationContext, POB_PRE_OPERATION_INFORMATION OperationInformation) {
|
||||
UNREFERENCED_PARAMETER(RegistrationContext);
|
||||
|
||||
if (OperationInformation->KernelHandle) return OB_PREOP_SUCCESS;
|
||||
|
||||
PETHREAD TargetThread = (PETHREAD)OperationInformation->Object;
|
||||
if (!TargetThread) return OB_PREOP_SUCCESS;
|
||||
|
||||
PEPROCESS TargetProcess = IoThreadToProcess(TargetThread);
|
||||
if (!TargetProcess) return OB_PREOP_SUCCESS;
|
||||
|
||||
HANDLE TargetPid = PsGetProcessId(TargetProcess);
|
||||
HANDLE SourcePid = PsGetCurrentProcessId();
|
||||
|
||||
if (SourcePid == TargetPid) return OB_PREOP_SUCCESS;
|
||||
if ((ULONG)(ULONG_PTR)SourcePid == GlobalData.PyasPid) return OB_PREOP_SUCCESS;
|
||||
if (SourcePid == (HANDLE)4) return OB_PREOP_SUCCESS;
|
||||
|
||||
if (IsTargetProtected(TargetPid)) {
|
||||
if (IsCriticalSystemProcess(SourcePid)) return OB_PREOP_SUCCESS;
|
||||
|
||||
BOOLEAN bIsTrusted = IsProcessTrusted(SourcePid);
|
||||
|
||||
if (bIsTrusted) {
|
||||
if (IsBlacklistedAdminTool(SourcePid)) {
|
||||
bIsTrusted = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
if (bIsTrusted) return OB_PREOP_SUCCESS;
|
||||
|
||||
ACCESS_MASK DenyMask = THREAD_TERMINATE |
|
||||
THREAD_SUSPEND_RESUME |
|
||||
THREAD_SET_CONTEXT |
|
||||
THREAD_SET_INFORMATION |
|
||||
THREAD_SET_THREAD_TOKEN;
|
||||
|
||||
OperationInformation->Parameters->CreateHandleInformation.DesiredAccess &= (ACCESS_MASK)(~DenyMask);
|
||||
|
||||
if (OperationInformation->Operation == OB_OPERATION_HANDLE_DUPLICATE) {
|
||||
OperationInformation->Parameters->DuplicateHandleInformation.DesiredAccess &= (ACCESS_MASK)(~DenyMask);
|
||||
}
|
||||
}
|
||||
|
||||
return OB_PREOP_SUCCESS;
|
||||
}
|
||||
|
||||
NTSTATUS InitializeProcessProtection() {
|
||||
static UNICODE_STRING Altitude = RTL_CONSTANT_STRING(L"320000.PYAS.Ob");
|
||||
|
||||
ObCallbacks[0].ObjectType = PsProcessType;
|
||||
ObCallbacks[0].Operations = OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE;
|
||||
ObCallbacks[0].PreOperation = PreOpenProcess;
|
||||
ObCallbacks[0].PostOperation = NULL;
|
||||
|
||||
ObCallbacks[1].ObjectType = PsThreadType;
|
||||
ObCallbacks[1].Operations = OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE;
|
||||
ObCallbacks[1].PreOperation = PreOpenThread;
|
||||
ObCallbacks[1].PostOperation = NULL;
|
||||
|
||||
ObRegistration.Version = OB_FLT_REGISTRATION_VERSION;
|
||||
ObRegistration.OperationRegistrationCount = 2;
|
||||
ObRegistration.Altitude = Altitude;
|
||||
ObRegistration.RegistrationContext = NULL;
|
||||
ObRegistration.OperationRegistration = ObCallbacks;
|
||||
|
||||
NTSTATUS status = ObRegisterCallbacks(&ObRegistration, &ObRegistrationHandle);
|
||||
|
||||
if (!NT_SUCCESS(status) && (status == STATUS_FLT_INSTANCE_ALTITUDE_COLLISION || status == STATUS_OBJECT_NAME_COLLISION)) {
|
||||
static UNICODE_STRING FallbackAltitude = RTL_CONSTANT_STRING(L"320000.PYAS.Ob.Fallback");
|
||||
ObRegistration.Altitude = FallbackAltitude;
|
||||
status = ObRegisterCallbacks(&ObRegistration, &ObRegistrationHandle);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
VOID UninitializeProcessProtection() {
|
||||
if (ObRegistrationHandle) {
|
||||
ObUnRegisterCallbacks(ObRegistrationHandle);
|
||||
ObRegistrationHandle = NULL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
#include "DriverCommon.h"
|
||||
|
||||
static LARGE_INTEGER Cookie;
|
||||
|
||||
static BOOLEAN IsBamRegistryPath(PCUNICODE_STRING KeyName) {
|
||||
if (!KeyName || !KeyName->Buffer) return FALSE;
|
||||
if (WildcardMatch(L"*\\Services\\bam\\*", KeyName->Buffer, KeyName->Length)) {
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static BOOLEAN IsWriteAccess(ACCESS_MASK Access) {
|
||||
if (Access & (KEY_SET_VALUE | KEY_CREATE_SUB_KEY | KEY_CREATE_LINK |
|
||||
DELETE | WRITE_DAC | WRITE_OWNER | GENERIC_WRITE)) {
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static PWCHAR GetFullPath(PVOID RootObject, PUNICODE_STRING CompleteName, PULONG OutLength) {
|
||||
PWCHAR Buffer = NULL;
|
||||
ULONG TotalSize = 0;
|
||||
PCUNICODE_STRING RootName = NULL;
|
||||
BOOLEAN NeedFreeRootName = FALSE;
|
||||
|
||||
if (RootObject) {
|
||||
NTSTATUS status = CmCallbackGetKeyObjectIDEx(&Cookie, RootObject, NULL, &RootName, 0);
|
||||
if (NT_SUCCESS(status) && RootName) {
|
||||
NeedFreeRootName = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
ULONG RootLen = (RootName && RootName->Buffer) ? RootName->Length : 0;
|
||||
ULONG RelLen = (CompleteName && CompleteName->Buffer) ? CompleteName->Length : 0;
|
||||
|
||||
TotalSize = RootLen + sizeof(WCHAR) + RelLen + sizeof(WCHAR);
|
||||
|
||||
Buffer = (PWCHAR)PyasAllocate(TotalSize);
|
||||
if (!Buffer) {
|
||||
if (NeedFreeRootName) CmCallbackReleaseKeyObjectIDEx(RootName);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
RtlZeroMemory(Buffer, TotalSize);
|
||||
|
||||
PWCHAR Current = Buffer;
|
||||
if (RootLen > 0) {
|
||||
RtlCopyMemory(Current, RootName->Buffer, RootLen);
|
||||
Current += (RootLen / sizeof(WCHAR));
|
||||
if (Current > Buffer && *(Current - 1) != L'\\') {
|
||||
*Current = L'\\';
|
||||
Current++;
|
||||
}
|
||||
}
|
||||
|
||||
if (RelLen > 0) {
|
||||
if (RootLen > 0 && CompleteName->Buffer[0] == L'\\') {
|
||||
RtlCopyMemory(Current, CompleteName->Buffer + 1, RelLen - sizeof(WCHAR));
|
||||
}
|
||||
else {
|
||||
RtlCopyMemory(Current, CompleteName->Buffer, RelLen);
|
||||
}
|
||||
}
|
||||
|
||||
if (NeedFreeRootName) CmCallbackReleaseKeyObjectIDEx(RootName);
|
||||
|
||||
if (OutLength) *OutLength = (ULONG)wcslen(Buffer) * sizeof(WCHAR);
|
||||
return Buffer;
|
||||
}
|
||||
|
||||
static NTSTATUS RegistryCallback(PVOID CallbackContext, PVOID Argument1, PVOID Argument2) {
|
||||
UNREFERENCED_PARAMETER(CallbackContext);
|
||||
|
||||
if (KeGetCurrentIrql() > APC_LEVEL) return STATUS_SUCCESS;
|
||||
|
||||
REG_NOTIFY_CLASS Class = (REG_NOTIFY_CLASS)(ULONG_PTR)Argument1;
|
||||
NTSTATUS status = STATUS_SUCCESS;
|
||||
|
||||
if (Class == RegNtPreCreateKey || Class == RegNtPreOpenKey || Class == RegNtPreOpenKeyEx) {
|
||||
PVOID RootObject = NULL;
|
||||
PUNICODE_STRING CompleteName = NULL;
|
||||
ACCESS_MASK DesiredAccess = 0;
|
||||
|
||||
if (Class == RegNtPreCreateKey) {
|
||||
REG_CREATE_KEY_INFORMATION* Info = (REG_CREATE_KEY_INFORMATION*)Argument2;
|
||||
RootObject = Info->RootObject;
|
||||
CompleteName = Info->CompleteName;
|
||||
DesiredAccess = Info->DesiredAccess;
|
||||
}
|
||||
else {
|
||||
REG_OPEN_KEY_INFORMATION* Info = (REG_OPEN_KEY_INFORMATION*)Argument2;
|
||||
RootObject = Info->RootObject;
|
||||
CompleteName = Info->CompleteName;
|
||||
DesiredAccess = Info->DesiredAccess;
|
||||
}
|
||||
|
||||
if (!IsWriteAccess(DesiredAccess)) {
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
ULONG PathLen = 0;
|
||||
PWCHAR FullPathBuffer = GetFullPath(RootObject, CompleteName, &PathLen);
|
||||
|
||||
if (FullPathBuffer) {
|
||||
UNICODE_STRING FullPath;
|
||||
RtlInitUnicodeString(&FullPath, FullPathBuffer);
|
||||
|
||||
if (CheckRegistryRule(&FullPath)) {
|
||||
HANDLE Pid = PsGetCurrentProcessId();
|
||||
if (!IsProcessTrusted(Pid)) {
|
||||
SendMessageToUser(3001, (ULONG)(ULONG_PTR)Pid, FullPath.Buffer, FullPath.Length);
|
||||
status = STATUS_ACCESS_DENIED;
|
||||
}
|
||||
}
|
||||
PyasFree(FullPathBuffer);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
if (Class == RegNtPreDeleteKey) {
|
||||
REG_DELETE_KEY_INFORMATION* Info = (REG_DELETE_KEY_INFORMATION*)Argument2;
|
||||
PCUNICODE_STRING KeyName = NULL;
|
||||
|
||||
if (NT_SUCCESS(CmCallbackGetKeyObjectIDEx(&Cookie, Info->Object, NULL, &KeyName, 0))) {
|
||||
if (KeyName) {
|
||||
if (CheckRegistryRule(KeyName)) {
|
||||
HANDLE Pid = PsGetCurrentProcessId();
|
||||
if (!IsProcessTrusted(Pid)) {
|
||||
SendMessageToUser(3001, (ULONG)(ULONG_PTR)Pid, KeyName->Buffer, KeyName->Length);
|
||||
status = STATUS_ACCESS_DENIED;
|
||||
}
|
||||
}
|
||||
CmCallbackReleaseKeyObjectIDEx(KeyName);
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
if (Class == RegNtPreSetValueKey) {
|
||||
REG_SET_VALUE_KEY_INFORMATION* Info = (REG_SET_VALUE_KEY_INFORMATION*)Argument2;
|
||||
PCUNICODE_STRING KeyName = NULL;
|
||||
|
||||
if (NT_SUCCESS(CmCallbackGetKeyObjectIDEx(&Cookie, Info->Object, NULL, &KeyName, 0))) {
|
||||
if (KeyName) {
|
||||
if (KeyName->Buffer) {
|
||||
if (!IsBamRegistryPath(KeyName)) {
|
||||
ULONG FullSize = KeyName->Length + sizeof(WCHAR) +
|
||||
(Info->ValueName ? Info->ValueName->Length : 0) +
|
||||
sizeof(WCHAR);
|
||||
|
||||
if (FullSize <= 0xFFFF) {
|
||||
PWCHAR Buffer = (PWCHAR)PyasAllocate(FullSize);
|
||||
if (Buffer) {
|
||||
UNICODE_STRING FullPath;
|
||||
FullPath.Buffer = Buffer;
|
||||
FullPath.Length = 0;
|
||||
FullPath.MaximumLength = (USHORT)FullSize;
|
||||
|
||||
RtlCopyUnicodeString(&FullPath, KeyName);
|
||||
|
||||
if (Info->ValueName && Info->ValueName->Length > 0) {
|
||||
if (FullPath.Length > 0 && FullPath.Buffer[(FullPath.Length / sizeof(WCHAR)) - 1] != L'\\') {
|
||||
RtlAppendUnicodeToString(&FullPath, L"\\");
|
||||
}
|
||||
RtlAppendUnicodeStringToString(&FullPath, Info->ValueName);
|
||||
}
|
||||
|
||||
if (CheckRegistryRule(&FullPath)) {
|
||||
HANDLE Pid = PsGetCurrentProcessId();
|
||||
if (!IsProcessTrusted(Pid)) {
|
||||
SendMessageToUser(3001, (ULONG)(ULONG_PTR)Pid, FullPath.Buffer, FullPath.Length);
|
||||
status = STATUS_ACCESS_DENIED;
|
||||
}
|
||||
}
|
||||
PyasFree(Buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
CmCallbackReleaseKeyObjectIDEx(KeyName);
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
if (Class == RegNtPreDeleteValueKey) {
|
||||
REG_DELETE_VALUE_KEY_INFORMATION* Info = (REG_DELETE_VALUE_KEY_INFORMATION*)Argument2;
|
||||
PCUNICODE_STRING KeyName = NULL;
|
||||
|
||||
if (NT_SUCCESS(CmCallbackGetKeyObjectIDEx(&Cookie, Info->Object, NULL, &KeyName, 0))) {
|
||||
if (KeyName) {
|
||||
if (KeyName->Buffer) {
|
||||
if (!IsBamRegistryPath(KeyName)) {
|
||||
ULONG FullSize = KeyName->Length + sizeof(WCHAR) +
|
||||
(Info->ValueName ? Info->ValueName->Length : 0) +
|
||||
sizeof(WCHAR);
|
||||
|
||||
if (FullSize <= 0xFFFF) {
|
||||
PWCHAR Buffer = (PWCHAR)PyasAllocate(FullSize);
|
||||
if (Buffer) {
|
||||
UNICODE_STRING FullPath;
|
||||
FullPath.Buffer = Buffer;
|
||||
FullPath.Length = 0;
|
||||
FullPath.MaximumLength = (USHORT)FullSize;
|
||||
|
||||
RtlCopyUnicodeString(&FullPath, KeyName);
|
||||
|
||||
if (Info->ValueName && Info->ValueName->Length > 0) {
|
||||
if (FullPath.Length > 0 && FullPath.Buffer[(FullPath.Length / sizeof(WCHAR)) - 1] != L'\\') {
|
||||
RtlAppendUnicodeToString(&FullPath, L"\\");
|
||||
}
|
||||
RtlAppendUnicodeStringToString(&FullPath, Info->ValueName);
|
||||
}
|
||||
|
||||
if (CheckRegistryRule(&FullPath)) {
|
||||
HANDLE Pid = PsGetCurrentProcessId();
|
||||
if (!IsProcessTrusted(Pid)) {
|
||||
SendMessageToUser(3001, (ULONG)(ULONG_PTR)Pid, FullPath.Buffer, FullPath.Length);
|
||||
status = STATUS_ACCESS_DENIED;
|
||||
}
|
||||
}
|
||||
PyasFree(Buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
CmCallbackReleaseKeyObjectIDEx(KeyName);
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
NTSTATUS InitializeRegistryProtection(PDRIVER_OBJECT DriverObject) {
|
||||
static UNICODE_STRING Altitude = RTL_CONSTANT_STRING(L"320000.PYAS.Cm");
|
||||
NTSTATUS status = CmRegisterCallbackEx(RegistryCallback, &Altitude, DriverObject, NULL, &Cookie, NULL);
|
||||
|
||||
if (!NT_SUCCESS(status)) {
|
||||
static UNICODE_STRING FallbackAltitude = RTL_CONSTANT_STRING(L"320000.PYAS.Cm.Fallback");
|
||||
status = CmRegisterCallbackEx(RegistryCallback, &FallbackAltitude, DriverObject, NULL, &Cookie, NULL);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
VOID UninitializeRegistryProtection() {
|
||||
if (Cookie.QuadPart != 0) {
|
||||
CmUnRegisterCallback(Cookie);
|
||||
Cookie.QuadPart = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,933 @@
|
||||
#include "DriverCommon.h"
|
||||
|
||||
constexpr auto MAX_TRACKERS = 64;
|
||||
constexpr auto RANSOM_TIME_WINDOW_MS = 3000;
|
||||
constexpr auto RANSOM_COUNT_THRESHOLD = 5;
|
||||
constexpr auto HIGH_ENTROPY_THRESHOLD = 10;
|
||||
|
||||
constexpr auto TRUST_CACHE_SIZE = 128;
|
||||
constexpr auto TRUST_CACHE_TTL_SEC = 300;
|
||||
|
||||
typedef struct _RANSOM_TRACKER {
|
||||
PEPROCESS Process;
|
||||
LARGE_INTEGER ProcessCreateTime;
|
||||
ULONG ActivityCount;
|
||||
LARGE_INTEGER LastActivityTime;
|
||||
} RANSOM_TRACKER, * PRANSOM_TRACKER;
|
||||
|
||||
typedef struct _TRUST_CACHE_ENTRY {
|
||||
PEPROCESS Process;
|
||||
LARGE_INTEGER ProcessCreateTime;
|
||||
BOOLEAN IsTrusted;
|
||||
LARGE_INTEGER CacheTime;
|
||||
} TRUST_CACHE_ENTRY, * PTRUST_CACHE_ENTRY;
|
||||
|
||||
static RANSOM_TRACKER RansomTrackers[MAX_TRACKERS];
|
||||
static TRUST_CACHE_ENTRY TrustCache[TRUST_CACHE_SIZE];
|
||||
static KSPIN_LOCK TrustCacheLock;
|
||||
static BOOLEAN g_CacheInitialized = FALSE;
|
||||
|
||||
static ERESOURCE RuleLock;
|
||||
static PRULE_NODE g_RegistryBlockList = NULL;
|
||||
static PRULE_NODE g_RegistryTrustedList = NULL;
|
||||
static PRULE_NODE g_ProcessTrustedPaths = NULL;
|
||||
static PRULE_NODE g_ProcessExploitable = NULL;
|
||||
static PRULE_NODE g_FileProtectedPaths = NULL;
|
||||
static PRULE_NODE g_FileExceptionPaths = NULL;
|
||||
static PRULE_NODE g_FileRansomExts = NULL;
|
||||
|
||||
const PCWSTR Helper_NaturallyCompressedExtensions[] = {
|
||||
L".zip", L".7z", L".rar", L".tar", L".gz",
|
||||
L".jpg", L".jpeg", L".png", L".webp", L".gif",
|
||||
L".mp3", L".wav", L".aac", L".ogg", L".flac",
|
||||
L".mp4", L".avi", L".mov", L".wmv", L".mkv",
|
||||
L".docx", L".xlsx", L".pptx", L".pdf", L".wps",
|
||||
L".apk", L".jar", L".class", L".db", L".sqlite"
|
||||
};
|
||||
|
||||
static VOID FreeList(PRULE_NODE* Head) {
|
||||
PRULE_NODE Current = *Head;
|
||||
while (Current) {
|
||||
PRULE_NODE Next = Current->Next;
|
||||
if (Current->Pattern.Buffer) PyasFree(Current->Pattern.Buffer);
|
||||
PyasFree(Current);
|
||||
Current = Next;
|
||||
}
|
||||
*Head = NULL;
|
||||
}
|
||||
|
||||
static VOID AddRule(PRULE_NODE* Head, PUNICODE_STRING RuleStr) {
|
||||
if (!RuleStr || !RuleStr->Buffer) return;
|
||||
|
||||
PRULE_NODE Check = *Head;
|
||||
while (Check) {
|
||||
if (RtlEqualUnicodeString(&Check->Pattern, RuleStr, TRUE)) {
|
||||
return;
|
||||
}
|
||||
Check = Check->Next;
|
||||
}
|
||||
|
||||
PRULE_NODE Node = (PRULE_NODE)PyasAllocate(sizeof(RULE_NODE));
|
||||
if (!Node) return;
|
||||
|
||||
SIZE_T Size = RuleStr->Length + sizeof(WCHAR);
|
||||
Node->Pattern.Buffer = (PWCHAR)PyasAllocate(Size);
|
||||
if (!Node->Pattern.Buffer) {
|
||||
PyasFree(Node);
|
||||
return;
|
||||
}
|
||||
|
||||
RtlCopyMemory(Node->Pattern.Buffer, RuleStr->Buffer, RuleStr->Length);
|
||||
Node->Pattern.Buffer[RuleStr->Length / sizeof(WCHAR)] = L'\0';
|
||||
Node->Pattern.Length = RuleStr->Length;
|
||||
Node->Pattern.MaximumLength = (USHORT)Size;
|
||||
|
||||
Node->Next = *Head;
|
||||
*Head = Node;
|
||||
}
|
||||
|
||||
static BOOLEAN HasSuffix(PCUNICODE_STRING String, PCWSTR Suffix) {
|
||||
if (!String || !String->Buffer || !Suffix) return FALSE;
|
||||
|
||||
SIZE_T StringLenChars = String->Length / sizeof(WCHAR);
|
||||
SIZE_T SuffixLenChars = 0;
|
||||
|
||||
while (Suffix[SuffixLenChars] != L'\0') {
|
||||
SuffixLenChars++;
|
||||
}
|
||||
|
||||
if (StringLenChars < SuffixLenChars) return FALSE;
|
||||
|
||||
PCWSTR Ptr = String->Buffer + (StringLenChars - SuffixLenChars);
|
||||
|
||||
for (SIZE_T i = 0; i < SuffixLenChars; i++) {
|
||||
if (RtlDowncaseUnicodeChar(Ptr[i]) != RtlDowncaseUnicodeChar(Suffix[i])) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOLEAN WildcardMatch(PCWSTR Pattern, PCWSTR String, USHORT StringLengthBytes) {
|
||||
if (Pattern == NULL || String == NULL) return FALSE;
|
||||
|
||||
USHORT StringLenChars = StringLengthBytes / sizeof(WCHAR);
|
||||
PCWSTR mp = NULL;
|
||||
PCWSTR cp = NULL;
|
||||
PCWSTR StringEnd = String + StringLenChars;
|
||||
|
||||
while (String < StringEnd) {
|
||||
if (*Pattern == L'*') {
|
||||
mp = ++Pattern;
|
||||
cp = String + 1;
|
||||
}
|
||||
else if (*Pattern == L'?' || (RtlDowncaseUnicodeChar(*Pattern) == RtlDowncaseUnicodeChar(*String))) {
|
||||
Pattern++;
|
||||
String++;
|
||||
}
|
||||
else if (mp != NULL) {
|
||||
Pattern = mp;
|
||||
String = cp++;
|
||||
}
|
||||
else {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
while (*Pattern == L'*') {
|
||||
Pattern++;
|
||||
}
|
||||
|
||||
return (*Pattern == L'\0') ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
static ULONG ProcessJsonUnescape(PWCHAR Buffer, ULONG LengthChars) {
|
||||
if (!Buffer || LengthChars == 0) return 0;
|
||||
|
||||
ULONG WriteIdx = 0;
|
||||
ULONG ReadIdx = 0;
|
||||
|
||||
while (ReadIdx < LengthChars) {
|
||||
if (Buffer[ReadIdx] == L'\\' && (ReadIdx + 1 < LengthChars)) {
|
||||
WCHAR NextChar = Buffer[ReadIdx + 1];
|
||||
if (NextChar == L'\\' || NextChar == L'"' || NextChar == L'/') {
|
||||
Buffer[WriteIdx++] = NextChar;
|
||||
ReadIdx += 2;
|
||||
}
|
||||
else if (NextChar == L'n') { Buffer[WriteIdx++] = L'\n'; ReadIdx += 2; }
|
||||
else if (NextChar == L'r') { Buffer[WriteIdx++] = L'\r'; ReadIdx += 2; }
|
||||
else if (NextChar == L't') { Buffer[WriteIdx++] = L'\t'; ReadIdx += 2; }
|
||||
else {
|
||||
Buffer[WriteIdx++] = Buffer[ReadIdx++];
|
||||
}
|
||||
}
|
||||
else {
|
||||
Buffer[WriteIdx++] = Buffer[ReadIdx++];
|
||||
}
|
||||
}
|
||||
|
||||
Buffer[WriteIdx] = L'\0';
|
||||
return WriteIdx * sizeof(WCHAR);
|
||||
}
|
||||
|
||||
static VOID SkipWhitespace(PCHAR* Ptr, PCHAR End) {
|
||||
while (*Ptr < End) {
|
||||
char c = **Ptr;
|
||||
if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
|
||||
(*Ptr)++;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static VOID ParseAndLoadRules(PCHAR JsonContent, ULONG ContentLength, PCSTR KeyName, PRULE_NODE* ListHead) {
|
||||
if (!JsonContent || !KeyName || !ListHead) return;
|
||||
|
||||
PCHAR Ptr = JsonContent;
|
||||
PCHAR End = JsonContent + ContentLength;
|
||||
SIZE_T KeyLen = 0;
|
||||
while (KeyName[KeyLen] != '\0') KeyLen++;
|
||||
|
||||
while (Ptr < End) {
|
||||
if (*Ptr == '"') {
|
||||
if ((SIZE_T)(End - Ptr) > KeyLen && RtlCompareMemory(Ptr + 1, KeyName, KeyLen) == KeyLen) {
|
||||
if (*(Ptr + 1 + KeyLen) == '"') {
|
||||
Ptr += 1 + KeyLen + 1;
|
||||
|
||||
SkipWhitespace(&Ptr, End);
|
||||
if (Ptr >= End || *Ptr != ':') continue;
|
||||
Ptr++;
|
||||
|
||||
SkipWhitespace(&Ptr, End);
|
||||
if (Ptr >= End || *Ptr != '[') continue;
|
||||
Ptr++;
|
||||
|
||||
while (Ptr < End) {
|
||||
SkipWhitespace(&Ptr, End);
|
||||
if (Ptr >= End || *Ptr == ']') {
|
||||
if (Ptr < End) Ptr++;
|
||||
return;
|
||||
}
|
||||
|
||||
if (*Ptr == '"') {
|
||||
PCHAR StartQuote = ++Ptr;
|
||||
BOOLEAN Escaped = FALSE;
|
||||
|
||||
while (Ptr < End) {
|
||||
if (*Ptr == '"' && Escaped == FALSE) break;
|
||||
if (*Ptr == '\\') {
|
||||
Escaped = (Escaped == FALSE) ? TRUE : FALSE;
|
||||
}
|
||||
else {
|
||||
Escaped = FALSE;
|
||||
}
|
||||
Ptr++;
|
||||
}
|
||||
|
||||
if (Ptr < End && *Ptr == '"') {
|
||||
ULONG UTF8Len = (ULONG)(Ptr - StartQuote);
|
||||
if (UTF8Len > 0) {
|
||||
ULONG WideSize = 0;
|
||||
RtlUTF8ToUnicodeN(NULL, 0, &WideSize, StartQuote, UTF8Len);
|
||||
|
||||
if (WideSize > 0) {
|
||||
PWCHAR WideBuffer = (PWCHAR)PyasAllocate(WideSize + sizeof(WCHAR));
|
||||
if (WideBuffer) {
|
||||
ULONG ResultSize = 0;
|
||||
RtlUTF8ToUnicodeN(WideBuffer, WideSize, &ResultSize, StartQuote, UTF8Len);
|
||||
ULONG FinalSize = ProcessJsonUnescape(WideBuffer, ResultSize / sizeof(WCHAR));
|
||||
|
||||
UNICODE_STRING Us;
|
||||
Us.Buffer = WideBuffer;
|
||||
Us.Length = (USHORT)FinalSize;
|
||||
Us.MaximumLength = (USHORT)(WideSize + sizeof(WCHAR));
|
||||
|
||||
AddRule(ListHead, &Us);
|
||||
PyasFree(WideBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ptr++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
Ptr++;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ptr++;
|
||||
}
|
||||
}
|
||||
|
||||
VOID InitializeRulesEngine() {
|
||||
ExInitializeResourceLite(&RuleLock);
|
||||
KeInitializeSpinLock(&TrustCacheLock);
|
||||
RtlZeroMemory(TrustCache, sizeof(TrustCache));
|
||||
g_CacheInitialized = TRUE;
|
||||
}
|
||||
|
||||
VOID UninitializeRulesEngine() {
|
||||
ExDeleteResourceLite(&RuleLock);
|
||||
g_CacheInitialized = FALSE;
|
||||
}
|
||||
|
||||
static VOID RemoveRule(PRULE_NODE* Head, PUNICODE_STRING RuleStr) {
|
||||
if (!RuleStr || !RuleStr->Buffer) return;
|
||||
PRULE_NODE Current = *Head;
|
||||
PRULE_NODE Previous = NULL;
|
||||
|
||||
while (Current) {
|
||||
if (RtlEqualUnicodeString(&Current->Pattern, RuleStr, TRUE)) {
|
||||
PRULE_NODE ToDelete = Current;
|
||||
|
||||
if (Previous) {
|
||||
Previous->Next = Current->Next;
|
||||
}
|
||||
else {
|
||||
*Head = Current->Next;
|
||||
}
|
||||
|
||||
Current = Current->Next;
|
||||
|
||||
if (ToDelete->Pattern.Buffer) PyasFree(ToDelete->Pattern.Buffer);
|
||||
PyasFree(ToDelete);
|
||||
}
|
||||
else {
|
||||
Previous = Current;
|
||||
Current = Current->Next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VOID AddDynamicWhitelist(PUNICODE_STRING RuleStr) {
|
||||
KeEnterCriticalRegion();
|
||||
ExAcquireResourceExclusiveLite(&RuleLock, TRUE);
|
||||
AddRule(&g_ProcessTrustedPaths, RuleStr);
|
||||
AddRule(&g_FileExceptionPaths, RuleStr);
|
||||
|
||||
KIRQL OldIrql;
|
||||
KeAcquireSpinLock(&TrustCacheLock, &OldIrql);
|
||||
RtlZeroMemory(TrustCache, sizeof(TrustCache));
|
||||
KeReleaseSpinLock(&TrustCacheLock, OldIrql);
|
||||
|
||||
ExReleaseResourceLite(&RuleLock);
|
||||
KeLeaveCriticalRegion();
|
||||
}
|
||||
|
||||
VOID RemoveDynamicWhitelist(PUNICODE_STRING RuleStr) {
|
||||
KeEnterCriticalRegion();
|
||||
ExAcquireResourceExclusiveLite(&RuleLock, TRUE);
|
||||
RemoveRule(&g_ProcessTrustedPaths, RuleStr);
|
||||
RemoveRule(&g_FileExceptionPaths, RuleStr);
|
||||
|
||||
KIRQL OldIrql;
|
||||
KeAcquireSpinLock(&TrustCacheLock, &OldIrql);
|
||||
RtlZeroMemory(TrustCache, sizeof(TrustCache));
|
||||
KeReleaseSpinLock(&TrustCacheLock, OldIrql);
|
||||
|
||||
ExReleaseResourceLite(&RuleLock);
|
||||
KeLeaveCriticalRegion();
|
||||
}
|
||||
|
||||
NTSTATUS LoadRulesFromDisk(PUNICODE_STRING RegistryPath) {
|
||||
NTSTATUS status = STATUS_SUCCESS;
|
||||
HANDLE FileHandle = NULL;
|
||||
IO_STATUS_BLOCK IoStatus = { 0 };
|
||||
OBJECT_ATTRIBUTES oa = { 0 };
|
||||
UNICODE_STRING ImagePathName;
|
||||
OBJECT_ATTRIBUTES RegOa = { 0 };
|
||||
HANDLE RegHandle = NULL;
|
||||
PKEY_VALUE_PARTIAL_INFORMATION Info = NULL;
|
||||
ULONG ResultLength = 0;
|
||||
PWCHAR PathBuffer = NULL;
|
||||
SIZE_T PathBufferSize = 0;
|
||||
UNICODE_STRING FinalPath = { 0 };
|
||||
|
||||
RtlInitUnicodeString(&ImagePathName, L"ImagePath");
|
||||
|
||||
InitializeObjectAttributes(&RegOa, RegistryPath, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
|
||||
status = ZwOpenKey(&RegHandle, KEY_READ, &RegOa);
|
||||
if (!NT_SUCCESS(status)) return status;
|
||||
|
||||
status = ZwQueryValueKey(RegHandle, &ImagePathName, KeyValuePartialInformation, NULL, 0, &ResultLength);
|
||||
if (status != STATUS_BUFFER_TOO_SMALL) {
|
||||
ZwClose(RegHandle);
|
||||
return status;
|
||||
}
|
||||
|
||||
Info = (PKEY_VALUE_PARTIAL_INFORMATION)PyasAllocate(ResultLength);
|
||||
if (!Info) {
|
||||
ZwClose(RegHandle);
|
||||
return STATUS_INSUFFICIENT_RESOURCES;
|
||||
}
|
||||
|
||||
status = ZwQueryValueKey(RegHandle, &ImagePathName, KeyValuePartialInformation, Info, ResultLength, &ResultLength);
|
||||
ZwClose(RegHandle);
|
||||
if (!NT_SUCCESS(status)) {
|
||||
PyasFree(Info);
|
||||
return status;
|
||||
}
|
||||
|
||||
if (Info->Type == REG_EXPAND_SZ || Info->Type == REG_SZ) {
|
||||
PathBufferSize = Info->DataLength + 1024;
|
||||
PathBuffer = (PWCHAR)PyasAllocate(PathBufferSize);
|
||||
|
||||
if (PathBuffer) {
|
||||
RtlZeroMemory(PathBuffer, PathBufferSize);
|
||||
if (Info->DataLength > 0) {
|
||||
RtlCopyMemory(PathBuffer, Info->Data, Info->DataLength);
|
||||
}
|
||||
|
||||
PWCHAR LastSlash = NULL;
|
||||
PWCHAR Current = PathBuffer;
|
||||
while (*Current) {
|
||||
if (*Current == L'\\') LastSlash = Current;
|
||||
Current++;
|
||||
}
|
||||
|
||||
if (LastSlash) {
|
||||
*LastSlash = L'\0';
|
||||
|
||||
SIZE_T CurrentPathLen = wcslen(PathBuffer);
|
||||
const WCHAR FilterSuffix[] = L"\\Filter";
|
||||
SIZE_T FilterLen = (sizeof(FilterSuffix) / sizeof(WCHAR)) - 1;
|
||||
|
||||
if (CurrentPathLen >= FilterLen) {
|
||||
PWCHAR SuffixStart = PathBuffer + CurrentPathLen - FilterLen;
|
||||
BOOLEAN Match = TRUE;
|
||||
for (SIZE_T i = 0; i < FilterLen; i++) {
|
||||
if (RtlDowncaseUnicodeChar(SuffixStart[i]) != RtlDowncaseUnicodeChar(FilterSuffix[i])) {
|
||||
Match = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (Match) {
|
||||
*SuffixStart = L'\0';
|
||||
}
|
||||
}
|
||||
|
||||
RtlStringCbCatW(PathBuffer, PathBufferSize, L"\\Rules\\Rules_Driver_P1.json");
|
||||
|
||||
if (wcsncmp(PathBuffer, L"\\??\\", 4) != 0 &&
|
||||
wcsncmp(PathBuffer, L"\\SystemRoot", 11) != 0 &&
|
||||
wcsncmp(PathBuffer, L"\\DosDevices\\", 12) != 0) {
|
||||
|
||||
PWCHAR TmpBuffer = (PWCHAR)PyasAllocate(PathBufferSize + 16);
|
||||
if (TmpBuffer) {
|
||||
RtlStringCbCopyW(TmpBuffer, PathBufferSize + 16, L"\\??\\");
|
||||
RtlStringCbCatW(TmpBuffer, PathBufferSize + 16, PathBuffer);
|
||||
PyasFree(PathBuffer);
|
||||
PathBuffer = TmpBuffer;
|
||||
}
|
||||
}
|
||||
RtlInitUnicodeString(&FinalPath, PathBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
PyasFree(Info);
|
||||
|
||||
if (!FinalPath.Buffer) {
|
||||
if (PathBuffer) PyasFree(PathBuffer);
|
||||
return STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
InitializeObjectAttributes(&oa, &FinalPath, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
|
||||
status = ZwCreateFile(&FileHandle, GENERIC_READ | SYNCHRONIZE, &oa, &IoStatus, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
|
||||
|
||||
if (NT_SUCCESS(status)) {
|
||||
FILE_STANDARD_INFORMATION FileInfo = { 0 };
|
||||
status = ZwQueryInformationFile(FileHandle, &IoStatus, &FileInfo, sizeof(FileInfo), FileStandardInformation);
|
||||
|
||||
if (NT_SUCCESS(status) && FileInfo.EndOfFile.LowPart > 0) {
|
||||
PVOID FileBuffer = PyasAllocate(FileInfo.EndOfFile.LowPart + 1);
|
||||
if (FileBuffer) {
|
||||
status = ZwReadFile(FileHandle, NULL, NULL, NULL, &IoStatus, FileBuffer, FileInfo.EndOfFile.LowPart, NULL, NULL);
|
||||
if (NT_SUCCESS(status)) {
|
||||
((PCHAR)FileBuffer)[FileInfo.EndOfFile.LowPart] = '\0';
|
||||
|
||||
KeEnterCriticalRegion();
|
||||
ExAcquireResourceExclusiveLite(&RuleLock, TRUE);
|
||||
ParseAndLoadRules((PCHAR)FileBuffer, FileInfo.EndOfFile.LowPart, "Rule_Registry_BlockList", &g_RegistryBlockList);
|
||||
ParseAndLoadRules((PCHAR)FileBuffer, FileInfo.EndOfFile.LowPart, "Rule_Registry_TrustedList", &g_RegistryTrustedList);
|
||||
ParseAndLoadRules((PCHAR)FileBuffer, FileInfo.EndOfFile.LowPart, "Rule_Process_TrustedPaths", &g_ProcessTrustedPaths);
|
||||
ParseAndLoadRules((PCHAR)FileBuffer, FileInfo.EndOfFile.LowPart, "Rule_Process_ExploitableBlacklist", &g_ProcessExploitable);
|
||||
ParseAndLoadRules((PCHAR)FileBuffer, FileInfo.EndOfFile.LowPart, "Rule_File_ProtectedPaths", &g_FileProtectedPaths);
|
||||
ParseAndLoadRules((PCHAR)FileBuffer, FileInfo.EndOfFile.LowPart, "Rule_File_ExceptionPaths", &g_FileExceptionPaths);
|
||||
ParseAndLoadRules((PCHAR)FileBuffer, FileInfo.EndOfFile.LowPart, "Rule_File_RansomwareExtensions", &g_FileRansomExts);
|
||||
ExReleaseResourceLite(&RuleLock);
|
||||
KeLeaveCriticalRegion();
|
||||
}
|
||||
PyasFree(FileBuffer);
|
||||
}
|
||||
}
|
||||
ZwClose(FileHandle);
|
||||
}
|
||||
|
||||
if (PathBuffer) PyasFree(PathBuffer);
|
||||
return status;
|
||||
}
|
||||
|
||||
VOID UnloadRules() {
|
||||
KeEnterCriticalRegion();
|
||||
ExAcquireResourceExclusiveLite(&RuleLock, TRUE);
|
||||
FreeList(&g_RegistryBlockList);
|
||||
FreeList(&g_RegistryTrustedList);
|
||||
FreeList(&g_ProcessTrustedPaths);
|
||||
FreeList(&g_ProcessExploitable);
|
||||
FreeList(&g_FileProtectedPaths);
|
||||
FreeList(&g_FileExceptionPaths);
|
||||
FreeList(&g_FileRansomExts);
|
||||
ExReleaseResourceLite(&RuleLock);
|
||||
KeLeaveCriticalRegion();
|
||||
}
|
||||
|
||||
NTSTATUS GetProcessImageName(HANDLE ProcessId, PUNICODE_STRING* ImageName) {
|
||||
if (KeGetCurrentIrql() != PASSIVE_LEVEL) {
|
||||
return STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
NTSTATUS status;
|
||||
PEPROCESS Process = NULL;
|
||||
|
||||
*ImageName = NULL;
|
||||
status = PsLookupProcessByProcessId(ProcessId, &Process);
|
||||
if (!NT_SUCCESS(status)) return status;
|
||||
|
||||
status = SeLocateProcessImageName(Process, ImageName);
|
||||
ObDereferenceObject(Process);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static BOOLEAN IsWindowsSystemApp(PCWSTR Buffer, USHORT Length) {
|
||||
if (WildcardMatch(L"*\\Windows\\SystemApps\\*", Buffer, Length)) return TRUE;
|
||||
if (WildcardMatch(L"*\\Windows\\ImmersiveControlPanel\\*", Buffer, Length)) return TRUE;
|
||||
if (WildcardMatch(L"*\\Windows\\explorer.exe", Buffer, Length)) return TRUE;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
BOOLEAN IsProcessTrusted(HANDLE ProcessId) {
|
||||
if ((ULONG)(ULONG_PTR)ProcessId == GlobalData.PyasPid) return TRUE;
|
||||
if (ProcessId == (HANDLE)4) return TRUE;
|
||||
|
||||
PEPROCESS Process = NULL;
|
||||
if (!NT_SUCCESS(PsLookupProcessByProcessId(ProcessId, &Process))) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
LARGE_INTEGER createTime;
|
||||
createTime.QuadPart = PsGetProcessCreateTimeQuadPart(Process);
|
||||
|
||||
if (g_CacheInitialized) {
|
||||
KIRQL OldIrql;
|
||||
KeAcquireSpinLock(&TrustCacheLock, &OldIrql);
|
||||
ULONG Hash = (((ULONG)(ULONG_PTR)ProcessId) >> 2) & (TRUST_CACHE_SIZE - 1);
|
||||
|
||||
if (TrustCache[Hash].Process == Process && TrustCache[Hash].ProcessCreateTime.QuadPart == createTime.QuadPart) {
|
||||
LARGE_INTEGER Now;
|
||||
KeQuerySystemTime(&Now);
|
||||
if ((Now.QuadPart - TrustCache[Hash].CacheTime.QuadPart) < (TRUST_CACHE_TTL_SEC * 10000000LL)) {
|
||||
BOOLEAN cachedResult = TrustCache[Hash].IsTrusted;
|
||||
KeReleaseSpinLock(&TrustCacheLock, OldIrql);
|
||||
ObDereferenceObject(Process);
|
||||
return cachedResult;
|
||||
}
|
||||
}
|
||||
KeReleaseSpinLock(&TrustCacheLock, OldIrql);
|
||||
}
|
||||
|
||||
if (KeGetCurrentIrql() != PASSIVE_LEVEL) {
|
||||
ObDereferenceObject(Process);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
PUNICODE_STRING imageFileName = NULL;
|
||||
NTSTATUS status = GetProcessImageName(ProcessId, &imageFileName);
|
||||
BOOLEAN isTrusted = FALSE;
|
||||
|
||||
KeEnterCriticalRegion();
|
||||
ExAcquireResourceSharedLite(&RuleLock, TRUE);
|
||||
|
||||
if (NT_SUCCESS(status) && imageFileName && imageFileName->Buffer) {
|
||||
PRULE_NODE Node = g_ProcessExploitable;
|
||||
while (Node) {
|
||||
if (WildcardMatch(Node->Pattern.Buffer, imageFileName->Buffer, imageFileName->Length)) {
|
||||
goto update_cache;
|
||||
}
|
||||
Node = Node->Next;
|
||||
}
|
||||
|
||||
if (IsWindowsSystemApp(imageFileName->Buffer, imageFileName->Length)) {
|
||||
isTrusted = TRUE;
|
||||
goto update_cache;
|
||||
}
|
||||
|
||||
Node = g_ProcessTrustedPaths;
|
||||
while (Node) {
|
||||
if (WildcardMatch(Node->Pattern.Buffer, imageFileName->Buffer, imageFileName->Length)) {
|
||||
isTrusted = TRUE;
|
||||
goto update_cache;
|
||||
}
|
||||
Node = Node->Next;
|
||||
}
|
||||
}
|
||||
|
||||
update_cache:
|
||||
if (g_CacheInitialized) {
|
||||
KIRQL OldIrql;
|
||||
KeAcquireSpinLock(&TrustCacheLock, &OldIrql);
|
||||
ULONG Hash = (((ULONG)(ULONG_PTR)ProcessId) >> 2) & (TRUST_CACHE_SIZE - 1);
|
||||
TrustCache[Hash].Process = Process;
|
||||
TrustCache[Hash].ProcessCreateTime = createTime;
|
||||
TrustCache[Hash].IsTrusted = isTrusted;
|
||||
KeQuerySystemTime(&TrustCache[Hash].CacheTime);
|
||||
KeReleaseSpinLock(&TrustCacheLock, OldIrql);
|
||||
}
|
||||
|
||||
ExReleaseResourceLite(&RuleLock);
|
||||
KeLeaveCriticalRegion();
|
||||
|
||||
if (imageFileName) ExFreePool(imageFileName);
|
||||
ObDereferenceObject(Process);
|
||||
return isTrusted;
|
||||
}
|
||||
|
||||
BOOLEAN IsTargetProtected(HANDLE ProcessId) {
|
||||
if ((ULONG)(ULONG_PTR)ProcessId == GlobalData.PyasPid) return TRUE;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
BOOLEAN CheckRegistryRule(PCUNICODE_STRING KeyName) {
|
||||
if (!KeyName || !KeyName->Buffer) return FALSE;
|
||||
if (KeyName->Length < 4 * sizeof(WCHAR)) return FALSE;
|
||||
|
||||
if (WildcardMatch(L"*{645FF040-5081-101B-9F08-00AA002F954E}\\DefaultIcon", KeyName->Buffer, KeyName->Length)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
KeEnterCriticalRegion();
|
||||
ExAcquireResourceSharedLite(&RuleLock, TRUE);
|
||||
|
||||
PRULE_NODE AllowNode = g_RegistryTrustedList;
|
||||
while (AllowNode) {
|
||||
if (WildcardMatch(AllowNode->Pattern.Buffer, KeyName->Buffer, KeyName->Length)) {
|
||||
ExReleaseResourceLite(&RuleLock);
|
||||
KeLeaveCriticalRegion();
|
||||
return FALSE;
|
||||
}
|
||||
AllowNode = AllowNode->Next;
|
||||
}
|
||||
|
||||
PRULE_NODE Node = g_RegistryBlockList;
|
||||
BOOLEAN Match = FALSE;
|
||||
while (Node) {
|
||||
if (WildcardMatch(Node->Pattern.Buffer, KeyName->Buffer, KeyName->Length)) {
|
||||
Match = TRUE;
|
||||
break;
|
||||
}
|
||||
Node = Node->Next;
|
||||
}
|
||||
ExReleaseResourceLite(&RuleLock);
|
||||
KeLeaveCriticalRegion();
|
||||
return Match;
|
||||
}
|
||||
|
||||
BOOLEAN CheckFileExtensionRule(PCUNICODE_STRING FileName) {
|
||||
if (!FileName || !FileName->Buffer) return FALSE;
|
||||
|
||||
KeEnterCriticalRegion();
|
||||
ExAcquireResourceSharedLite(&RuleLock, TRUE);
|
||||
|
||||
PRULE_NODE ExNode = g_FileExceptionPaths;
|
||||
while (ExNode) {
|
||||
if (WildcardMatch(ExNode->Pattern.Buffer, FileName->Buffer, FileName->Length)) {
|
||||
ExReleaseResourceLite(&RuleLock);
|
||||
KeLeaveCriticalRegion();
|
||||
return FALSE;
|
||||
}
|
||||
ExNode = ExNode->Next;
|
||||
}
|
||||
|
||||
PRULE_NODE Node = g_FileRansomExts;
|
||||
BOOLEAN Match = FALSE;
|
||||
while (Node) {
|
||||
if (HasSuffix(FileName, Node->Pattern.Buffer)) {
|
||||
Match = TRUE;
|
||||
break;
|
||||
}
|
||||
Node = Node->Next;
|
||||
}
|
||||
ExReleaseResourceLite(&RuleLock);
|
||||
KeLeaveCriticalRegion();
|
||||
return Match;
|
||||
}
|
||||
|
||||
BOOLEAN CheckProtectedPathRule(PCUNICODE_STRING FileName) {
|
||||
if (!FileName || !FileName->Buffer) return FALSE;
|
||||
|
||||
if (WildcardMatch(L"*\\Windows\\System32\\config\\systemprofile*", FileName->Buffer, FileName->Length)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
KeEnterCriticalRegion();
|
||||
ExAcquireResourceSharedLite(&RuleLock, TRUE);
|
||||
|
||||
PRULE_NODE ExNode = g_FileExceptionPaths;
|
||||
while (ExNode) {
|
||||
if (WildcardMatch(ExNode->Pattern.Buffer, FileName->Buffer, FileName->Length)) {
|
||||
ExReleaseResourceLite(&RuleLock);
|
||||
KeLeaveCriticalRegion();
|
||||
return FALSE;
|
||||
}
|
||||
ExNode = ExNode->Next;
|
||||
}
|
||||
|
||||
PRULE_NODE Node = g_FileProtectedPaths;
|
||||
BOOLEAN Match = FALSE;
|
||||
while (Node) {
|
||||
if (WildcardMatch(Node->Pattern.Buffer, FileName->Buffer, FileName->Length)) {
|
||||
Match = TRUE;
|
||||
break;
|
||||
}
|
||||
Node = Node->Next;
|
||||
}
|
||||
ExReleaseResourceLite(&RuleLock);
|
||||
KeLeaveCriticalRegion();
|
||||
return Match;
|
||||
}
|
||||
|
||||
static BOOLEAN IsHighEntropy(PVOID Buffer, ULONG Length) {
|
||||
if (!Buffer || Length < 256) return FALSE;
|
||||
|
||||
PUSHORT Histogram = (PUSHORT)PyasAllocate(256 * sizeof(USHORT));
|
||||
if (!Histogram) return FALSE;
|
||||
|
||||
PUCHAR Ptr = (PUCHAR)Buffer;
|
||||
ULONG ScanLen = (Length > 1024) ? 1024 : Length;
|
||||
|
||||
__try {
|
||||
for (ULONG i = 0; i < ScanLen; i++) {
|
||||
Histogram[Ptr[i]]++;
|
||||
}
|
||||
}
|
||||
__except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
PyasFree(Histogram);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
USHORT MaxFreq = 0;
|
||||
for (int i = 0; i < 256; i++) {
|
||||
if (Histogram[i] > MaxFreq) MaxFreq = Histogram[i];
|
||||
}
|
||||
|
||||
ULONG ExpectedAvg = ScanLen / 256;
|
||||
BOOLEAN Result = (MaxFreq < (ExpectedAvg + HIGH_ENTROPY_THRESHOLD));
|
||||
|
||||
PyasFree(Histogram);
|
||||
return Result;
|
||||
}
|
||||
|
||||
static BOOLEAN IsNaturallyCompressed(PCUNICODE_STRING FileName) {
|
||||
if (!FileName || !FileName->Buffer) return FALSE;
|
||||
for (int i = 0; i < sizeof(Helper_NaturallyCompressedExtensions) / sizeof(Helper_NaturallyCompressedExtensions[0]); i++) {
|
||||
if (HasSuffix(FileName, Helper_NaturallyCompressedExtensions[i])) return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static BOOLEAN IsNoisyRansomPath(PCUNICODE_STRING FileName) {
|
||||
if (!FileName || !FileName->Buffer) return FALSE;
|
||||
|
||||
if (WildcardMatch(L"*\\Windows\\Temp\\*", FileName->Buffer, FileName->Length) ||
|
||||
WildcardMatch(L"*\\Windows\\SystemTemp\\*", FileName->Buffer, FileName->Length) ||
|
||||
WildcardMatch(L"*\\Windows\\Prefetch\\*", FileName->Buffer, FileName->Length) ||
|
||||
WildcardMatch(L"*\\Windows\\Logs\\*", FileName->Buffer, FileName->Length) ||
|
||||
WildcardMatch(L"*\\Profiles\\*", FileName->Buffer, FileName->Length) ||
|
||||
WildcardMatch(L"*\\Program*\\*", FileName->Buffer, FileName->Length) ||
|
||||
WildcardMatch(L"*\\AppData\\Local\\*", FileName->Buffer, FileName->Length) ||
|
||||
WildcardMatch(L"*\\User Data\\*", FileName->Buffer, FileName->Length) ||
|
||||
WildcardMatch(L"*$Recycle.Bin*", FileName->Buffer, FileName->Length)) {
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static BOOLEAN IsUserDirectoryPath(PCUNICODE_STRING FileName) {
|
||||
if (!FileName || !FileName->Buffer) return FALSE;
|
||||
return WildcardMatch(L"*\\Users\\*", FileName->Buffer, FileName->Length);
|
||||
}
|
||||
|
||||
static BOOLEAN IsExplorerProcess(HANDLE ProcessId) {
|
||||
if (KeGetCurrentIrql() > APC_LEVEL) return FALSE;
|
||||
|
||||
PUNICODE_STRING imageFileName = NULL;
|
||||
NTSTATUS status = GetProcessImageName(ProcessId, &imageFileName);
|
||||
BOOLEAN isExplorer = FALSE;
|
||||
|
||||
if (NT_SUCCESS(status) && imageFileName) {
|
||||
if (imageFileName->Buffer && HasSuffix(imageFileName, L"\\explorer.exe")) {
|
||||
isExplorer = TRUE;
|
||||
}
|
||||
ExFreePool(imageFileName);
|
||||
}
|
||||
|
||||
return isExplorer;
|
||||
}
|
||||
|
||||
BOOLEAN CheckRansomActivity(HANDLE ProcessId, PUNICODE_STRING FileName, PVOID WriteBuffer, ULONG WriteLength, BOOLEAN IsWrite) {
|
||||
if (!IsUserDirectoryPath(FileName)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (IsNoisyRansomPath(FileName)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!IsWrite) {
|
||||
if (IsExplorerProcess(ProcessId)) return FALSE;
|
||||
}
|
||||
|
||||
BOOLEAN SuspiciousWrite = FALSE;
|
||||
if (IsWrite && WriteBuffer && WriteLength > 0) {
|
||||
if (!IsNaturallyCompressed(FileName) && IsHighEntropy(WriteBuffer, WriteLength)) {
|
||||
SuspiciousWrite = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
if (IsWrite && !SuspiciousWrite) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
PEPROCESS Process = NULL;
|
||||
if (!NT_SUCCESS(PsLookupProcessByProcessId(ProcessId, &Process))) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
LARGE_INTEGER Now = { 0 };
|
||||
KeQuerySystemTime(&Now);
|
||||
LARGE_INTEGER createTime;
|
||||
createTime.QuadPart = PsGetProcessCreateTimeQuadPart(Process);
|
||||
BOOLEAN Result = FALSE;
|
||||
|
||||
KIRQL OldIrql;
|
||||
KeAcquireSpinLock(&GlobalData.TrackerMutex, &OldIrql);
|
||||
|
||||
PRANSOM_TRACKER Tracker = NULL;
|
||||
PRANSOM_TRACKER CandidateSlot = NULL;
|
||||
PRANSOM_TRACKER LruSlot = &RansomTrackers[0];
|
||||
|
||||
for (int i = 0; i < MAX_TRACKERS; i++) {
|
||||
PRANSOM_TRACKER Current = &RansomTrackers[i];
|
||||
|
||||
if (Current->LastActivityTime.QuadPart < LruSlot->LastActivityTime.QuadPart) {
|
||||
LruSlot = Current;
|
||||
}
|
||||
|
||||
if (Current->Process == Process && Current->ProcessCreateTime.QuadPart == createTime.QuadPart) {
|
||||
Tracker = Current;
|
||||
break;
|
||||
}
|
||||
|
||||
BOOLEAN IsExpired = FALSE;
|
||||
if (Current->Process != NULL) {
|
||||
LARGE_INTEGER Diff;
|
||||
Diff.QuadPart = Now.QuadPart - Current->LastActivityTime.QuadPart;
|
||||
if (Diff.QuadPart > (RANSOM_TIME_WINDOW_MS * 10000LL)) {
|
||||
IsExpired = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
if ((Current->Process == NULL || IsExpired) && !CandidateSlot) {
|
||||
CandidateSlot = Current;
|
||||
}
|
||||
}
|
||||
|
||||
if (!Tracker) {
|
||||
if (CandidateSlot) {
|
||||
Tracker = CandidateSlot;
|
||||
}
|
||||
else {
|
||||
Tracker = LruSlot;
|
||||
}
|
||||
|
||||
Tracker->Process = Process;
|
||||
Tracker->ProcessCreateTime = createTime;
|
||||
Tracker->ActivityCount = 0;
|
||||
Tracker->LastActivityTime = Now;
|
||||
}
|
||||
else {
|
||||
LARGE_INTEGER Diff;
|
||||
Diff.QuadPart = Now.QuadPart - Tracker->LastActivityTime.QuadPart;
|
||||
|
||||
if (Diff.QuadPart > (RANSOM_TIME_WINDOW_MS * 10000LL)) {
|
||||
Tracker->ActivityCount = 0;
|
||||
}
|
||||
Tracker->LastActivityTime = Now;
|
||||
}
|
||||
|
||||
ULONG Weight = 2;
|
||||
Tracker->ActivityCount += Weight;
|
||||
|
||||
if (Tracker->ActivityCount >= RANSOM_COUNT_THRESHOLD) {
|
||||
Result = TRUE;
|
||||
}
|
||||
|
||||
KeReleaseSpinLock(&GlobalData.TrackerMutex, OldIrql);
|
||||
ObDereferenceObject(Process);
|
||||
return Result;
|
||||
}
|
||||
|
||||
NTSTATUS SendMessageToUser(ULONG Code, ULONG Pid, PWCHAR Path, USHORT PathSize) {
|
||||
if (KeGetCurrentIrql() > APC_LEVEL) return STATUS_UNSUCCESSFUL;
|
||||
|
||||
if (!ExAcquireRundownProtection(&GlobalData.PortRundown)) {
|
||||
return STATUS_PORT_DISCONNECTED;
|
||||
}
|
||||
|
||||
NTSTATUS status = STATUS_PORT_DISCONNECTED;
|
||||
|
||||
if (GlobalData.ClientPort) {
|
||||
PPYAS_MESSAGE msg = (PPYAS_MESSAGE)PyasAllocate(sizeof(PYAS_MESSAGE));
|
||||
if (msg) {
|
||||
msg->MessageCode = Code;
|
||||
msg->ProcessId = Pid;
|
||||
|
||||
if (Path && PathSize > 0) {
|
||||
size_t MaxSize = sizeof(msg->Path) - sizeof(WCHAR);
|
||||
size_t BytesToCopy = PathSize > MaxSize ? MaxSize : PathSize;
|
||||
|
||||
__try {
|
||||
RtlCopyMemory(msg->Path, Path, BytesToCopy);
|
||||
msg->Path[BytesToCopy / sizeof(WCHAR)] = L'\0';
|
||||
}
|
||||
__except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
PyasFree(msg);
|
||||
ExReleaseRundownProtection(&GlobalData.PortRundown);
|
||||
return STATUS_ACCESS_VIOLATION;
|
||||
}
|
||||
}
|
||||
|
||||
LARGE_INTEGER timeout;
|
||||
if (KeGetCurrentIrql() == PASSIVE_LEVEL) {
|
||||
timeout.QuadPart = -(5 * 10000);
|
||||
}
|
||||
else {
|
||||
timeout.QuadPart = 0;
|
||||
}
|
||||
|
||||
status = FltSendMessage(GlobalData.FilterHandle, &GlobalData.ClientPort, msg, sizeof(PYAS_MESSAGE), NULL, NULL, &timeout);
|
||||
|
||||
PyasFree(msg);
|
||||
}
|
||||
else {
|
||||
status = STATUS_INSUFFICIENT_RESOURCES;
|
||||
}
|
||||
}
|
||||
|
||||
ExReleaseRundownProtection(&GlobalData.PortRundown);
|
||||
return status;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#include <windows.h>
|
||||
#include <ntverp.h>
|
||||
|
||||
#define VER_FILETYPE VFT_DRV
|
||||
#define VER_FILESUBTYPE VFT2_DRV_SYSTEM
|
||||
#define VER_FILEDESCRIPTION_STR "PYAS Security Driver Protect"
|
||||
#define VER_INTERNALNAME_STR "PYAS"
|
||||
#define VER_LEGALCOPYRIGHT_STR "PYAS Security"
|
||||
#define VER_ORIGINALFILENAME_STR "PYAS_Driver.sys"
|
||||
#define VER_PRODUCTNAME_STR "PYAS"
|
||||
|
||||
#define VER_FILEVERSION 3,5,1,0
|
||||
#define VER_FILEVERSION_STR "3,5,1,0"
|
||||
|
||||
#define VER_PRODUCTVERSION 3,5,1,0
|
||||
#define VER_PRODUCTVERSION_STR "3,5,1,0"
|
||||
|
||||
#define VER_COMPANYNAME_STR "PYAS Security"
|
||||
|
||||
#include "common.ver"
|
||||
@@ -0,0 +1,233 @@
|
||||
{
|
||||
"Rule_Registry_BlockList": [
|
||||
"\\REGISTRY\\MACHINE\\BCD00000000",
|
||||
"\\REGISTRY\\MACHINE\\BCD00000000\\*",
|
||||
|
||||
"*\\Classes\\.exe",
|
||||
"*\\Classes\\.bat",
|
||||
"*\\Classes\\.cmd",
|
||||
"*\\Classes\\.com",
|
||||
"*\\Classes\\exefile\\*",
|
||||
"*\\Classes\\batfile\\*",
|
||||
"*\\Classes\\cmdfile\\*",
|
||||
"*\\Classes\\comfile\\*",
|
||||
|
||||
"*\\mouclass",
|
||||
"*\\kbdclass",
|
||||
"*\\DisableAntiSpyware",
|
||||
"*\\DisableWindowsUpdateAccess",
|
||||
"*\\EnableLUA",
|
||||
"*\\ConsentPromptBehaviorAdmin",
|
||||
"*\\NoControlPanel",
|
||||
"*\\NoDrives",
|
||||
"*\\NoFileMenu",
|
||||
"*\\NoFind",
|
||||
"*\\NoStartMenuPinnedList",
|
||||
"*\\NoSetFolders",
|
||||
"*\\NoSetFolderOptions",
|
||||
"*\\NoViewOnDrive",
|
||||
"*\\NoClose",
|
||||
"*\\NoDesktop",
|
||||
"*\\NoLogoff",
|
||||
"*\\NoFolderOptions",
|
||||
"*\\RestrictRun",
|
||||
"*\\NoViewContextMenu",
|
||||
"*\\HideClock",
|
||||
"*\\NoStartMenuMyGames",
|
||||
"*\\NoStartMenuMyMusic",
|
||||
"*\\DisableCMD",
|
||||
"*\\NoAddingComponents",
|
||||
"*\\NoWinKeys",
|
||||
"*\\NoStartMenuLogOff",
|
||||
"*\\NoSimpleNetIDList",
|
||||
"*\\NoLowDiskSpaceChecks",
|
||||
"*\\DisableLockWorkstation",
|
||||
"*\\Restrict_Run",
|
||||
"*\\DisableTaskMgr",
|
||||
"*\\DisableRegistryTools",
|
||||
"*\\DisableChangePassword",
|
||||
"*\\NoComponents",
|
||||
"*\\NoStartMenuMorePrograms",
|
||||
"*\\NoActiveDesktop",
|
||||
"*\\NoSetActiveDesktop",
|
||||
"*\\NoRecentDocsMenu",
|
||||
"*\\NoWindowsUpdate",
|
||||
"*\\NoChangeStartMenu",
|
||||
"*\\NoFavoritesMenu",
|
||||
"*\\NoRecentDocsHistory",
|
||||
"*\\NoSetTaskbar",
|
||||
"*\\NoSMHelp",
|
||||
"*\\NoTrayContextMenu",
|
||||
"*\\NoManageMyComputerVerb",
|
||||
"*\\NoRealMode",
|
||||
"*\\NoRun",
|
||||
"*\\ClearRecentDocsOnExit",
|
||||
"*\\NoActiveDesktopChanges",
|
||||
"*\\NoStartMenuNetworkPlaces",
|
||||
"*\\NoLockScreen",
|
||||
"*\\HideFastUserSwitching",
|
||||
"*\\ConsentPromptBehaviorUser",
|
||||
"*\\EnableSecureUIAPaths",
|
||||
"*\\NoSRM",
|
||||
"*\\NoInteractiveServices",
|
||||
"*\\ShutdownWithoutLogon",
|
||||
"*\\AutoAdminLogon",
|
||||
"*\\BootExecute",
|
||||
"*\\SubSystems",
|
||||
"*\\FirmwareBootDevice",
|
||||
"*\\BootDriverFlags",
|
||||
"*\\SystemStartOptions",
|
||||
"*\\DisableAutomaticRestartOnFailure",
|
||||
"*\\SystemBiosVersion",
|
||||
"*\\NoTheme",
|
||||
"*\\SystemSetupInProgress",
|
||||
"*\\SetupType",
|
||||
"*\\AutoReboot",
|
||||
"*\\Userinit",
|
||||
"*\\UIHost",
|
||||
"*\\Debugger",
|
||||
"*\\DefaultUserName",
|
||||
"*\\DefaultPassword",
|
||||
"*\\AltDefaultUserName",
|
||||
"*\\AltDefaultPassword",
|
||||
"*\\DisableCAD",
|
||||
"*\\LegalNoticeCaption",
|
||||
"*\\LegalNoticeText",
|
||||
],
|
||||
"Rule_Registry_TrustedList": [
|
||||
],
|
||||
"Rule_Process_TrustedPaths": [
|
||||
"\\Device\\HarddiskVolume*\\Windows\\*",
|
||||
"\\Device\\HarddiskVolume*\\Program Files\\*",
|
||||
"\\Device\\HarddiskVolume*\\Program Files (x86)\\*",
|
||||
|
||||
"\\Device\\HarddiskVolume*\\ProgramData\\Microsoft\\Windows Defender\\*",
|
||||
"\\Device\\HarddiskVolume*\\ProgramData\\Microsoft\\Windows Defender Advanced Threat Protection\\*",
|
||||
"*\\Sources\\SetupHost.exe",
|
||||
"*\\Work\\*\\DismHost.exe",
|
||||
|
||||
"*\\Google\\Chrome\\Application\\chrome.exe",
|
||||
"*\\Microsoft\\Edge\\Application\\msedge.exe",
|
||||
"*\\Mozilla Firefox\\firefox.exe",
|
||||
"*\\Thorium\\Application\\thorium.exe",
|
||||
|
||||
"*\\Steam\\steam.exe",
|
||||
"*\\steam\\*\\steam.exe",
|
||||
"*\\Battle.net\\*\\Battle.net.exe",
|
||||
"*\\Epic Games\\Launcher\\Portal\\Binaries\\*\\EpicGamesLauncher.exe",
|
||||
|
||||
"*\\resources\\bin\\SDK\\DownloadSDKServer.exe",
|
||||
"*\\ThunderEv.exe",
|
||||
"*\\ThunderStart.exe",
|
||||
"*\\KuGou.exe",
|
||||
"*\\XLGame.exe",
|
||||
"*\\XLLiveUD.exe",
|
||||
|
||||
"*\\hrkill-*.exe",
|
||||
"*\\bin\\HipsDaemon.exe",
|
||||
"*\\360Safe.exe",
|
||||
"*\\bilibili\\bilibili.exe",
|
||||
|
||||
"*\\Kingsoft\\WPS Office\\ksolaunch.exe",
|
||||
"*\\Kingsoft\\WPS Office\\*\\wps.exe",
|
||||
"*\\Kingsoft\\WPS Office\\*\\*.exe",
|
||||
|
||||
"*\\PC4399_WPFLauncher.exe",
|
||||
"*\\bin\\APlayer.exe",
|
||||
"*\\Neverness To Everness\\NTELauncher.exe",
|
||||
"*\\Application\\AndrowsLauncher.exe",
|
||||
"*\\bin\\*\\codex.exe",
|
||||
"*\\bin\\codex.exe",
|
||||
],
|
||||
"Rule_Process_ExploitableBlacklist": [
|
||||
"*\\cmd.exe",
|
||||
"*\\powershell.exe",
|
||||
"*\\pwsh.exe",
|
||||
"*\\wscript.exe",
|
||||
"*\\cscript.exe",
|
||||
"*\\mshta.exe",
|
||||
"*\\regsvr32.exe",
|
||||
"*\\certutil.exe",
|
||||
"*\\bitsadmin.exe",
|
||||
"*\\wmic.exe",
|
||||
"*\\hh.exe",
|
||||
"*\\cmstp.exe",
|
||||
"*\\installutil.exe",
|
||||
"*\\regasm.exe",
|
||||
"*\\regsvcs.exe",
|
||||
"*\\msbuild.exe",
|
||||
"*\\csc.exe",
|
||||
"*\\vbc.exe",
|
||||
"*\\jsc.exe",
|
||||
"*\\bash.exe",
|
||||
"*\\scrcons.exe",
|
||||
"*\\schtasks.exe",
|
||||
"*\\at.exe",
|
||||
"*\\reg.exe",
|
||||
"*\\sc.exe",
|
||||
"*\\net.exe",
|
||||
"*\\net1.exe",
|
||||
"*\\whoami.exe",
|
||||
"*\\ipconfig.exe",
|
||||
"*\\taskkill.exe",
|
||||
"*\\ProcessHacker.exe",
|
||||
"*\\procmon.exe",
|
||||
"*\\procexp.exe",
|
||||
],
|
||||
"Rule_File_ProtectedPaths": [
|
||||
"*\\PYAS.exe",
|
||||
"*\\Plugins\\Filter\\PYAS_Driver.sys",
|
||||
"*\\Plugins\\Rules\\Rules_Driver_P1.json",
|
||||
"*\\ProgramData\\PYAS\\*.json",
|
||||
"*\\Engine\\Pattern\\*.onnx",
|
||||
"*\\Engine\\Properties\\*.onnx",
|
||||
|
||||
"\\Device\\HarddiskVolume*\\bootmgr",
|
||||
"\\Device\\HarddiskVolume*\\boot.ini",
|
||||
"\\Device\\HarddiskVolume*\\BOOTNXT",
|
||||
"\\Device\\HarddiskVolume*\\EFI\\*",
|
||||
"\\Device\\HarddiskVolume*\\Boot\\*",
|
||||
"\\Device\\HarddiskVolume*\\Recovery\\*",
|
||||
"\\Device\\HarddiskVolume*\\System Volume Information\\*",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\config\\*",
|
||||
|
||||
"*\\CON",
|
||||
"*\\CON\\*",
|
||||
"*\\PRN",
|
||||
"*\\PRN\\*",
|
||||
"*\\AUX",
|
||||
"*\\AUX\\*",
|
||||
"*\\NUL",
|
||||
"*\\NUL\\*",
|
||||
"*\\COM?",
|
||||
"*\\COM?\\*",
|
||||
"*\\LPT?",
|
||||
"*\\LPT?\\*",
|
||||
"*\\OSDATA",
|
||||
"*\\OSDATA\\*",
|
||||
"*\\*<*",
|
||||
"*\\*>*",
|
||||
],
|
||||
"Rule_File_ExceptionPaths": [
|
||||
"*\\Users\\*\\steam\\*",
|
||||
"*\\AppData\\*",
|
||||
"*\\Temp\\*",
|
||||
"*\\Local\\Temp\\*",
|
||||
"*\\ventoy\\Boot\\*",
|
||||
"*\\AdminRegionConfig.*",
|
||||
"*\\nt_qq\\nt_*\\*",
|
||||
"*\\cache\\*",
|
||||
"*\\Documents\\My Games\\*",
|
||||
"*\\Documents\\Tencent Files\\*",
|
||||
"*\\Documents\\WeChat Files\\*",
|
||||
"*\\Documents\\WPS Cloud Files\\*"
|
||||
],
|
||||
"Rule_File_RansomwareExtensions": [
|
||||
".zip", ".7z", ".rar", ".tar", ".gz",
|
||||
".ppt", ".pptx", ".wps", ".txt", ".rtf",
|
||||
".pdf", ".xls", ".xlsx", ".doc", ".docx",
|
||||
".jpg", ".jpeg", ".png", ".webp", ".gif",
|
||||
".mp3", ".wav", ".aac", ".ogg", ".flac",
|
||||
".mp4", ".avi", ".mov", ".wmv", ".mkv",
|
||||
]
|
||||
}
|
||||
@@ -1,12 +1,164 @@
|
||||
PYAS ( Python 免費殺毒軟體 )
|
||||
# PYAS Security
|
||||
|
||||
PYAS 免費防毒軟體使用 Python 程式語言開發,包含超過 15 種免費功能
|
||||
且不會拖慢您的電腦速度,也不造成干擾,並採用 PYAE V1.2.3 掃毒引擎。
|
||||
Antivirus software written in Python and C++ that blocks threats through Machine Learning and behavioral monitoring!
|
||||
|
||||
網站:
|
||||
<img width="2440" height="1600" alt="PYAS_All" src="https://github.com/user-attachments/assets/1991aad3-64cc-4266-ac67-dab70c891ce7" />
|
||||
|
||||
https://xiaomi69ai.wixsite.com/pyas ,
|
||||
https://github.com/87owo/PYAS
|
||||
## Requirements
|
||||
|
||||
特別感謝:
|
||||
Wix, Avast, Github, Google, Python, Microsoft, VirusTotal, VirusShare, LenStevens
|
||||
Python 3.10 is recommended. Other Python versions may require different pip commands.
|
||||
|
||||
```
|
||||
pip install pystray
|
||||
pip install pefile
|
||||
pip install requests
|
||||
pip install pywebview
|
||||
pip install Pillow
|
||||
pip install yara-python
|
||||
pip install numpy
|
||||
pip install onnxruntime
|
||||
```
|
||||
|
||||
Non-essential requirements installation, only used for model training or other functions.
|
||||
|
||||
```
|
||||
pip install pandas
|
||||
pip install scikit-learn
|
||||
pip install lightgbm
|
||||
pip install onnxmltools
|
||||
pip install orjson
|
||||
```
|
||||
|
||||
## File Information
|
||||
|
||||
The following lists the storage locations of all relevant code and other related documents.
|
||||
|
||||
```
|
||||
PYAS/
|
||||
├── Engine/ # Complete code for Yara signatures and AI model training
|
||||
│ ├── Heuristic/
|
||||
│ │ └── ... # Yara Rules
|
||||
│ └── Properties/
|
||||
│ └── ... # LightGBM Model
|
||||
│
|
||||
├── Interface/ # Interface Interaction and Icons with WebView2
|
||||
│ ├── static/
|
||||
│ │ └── ... # style.css, main.js, icon.ico
|
||||
│ └── templates/
|
||||
│ └── ... # index.html
|
||||
│
|
||||
├── Plugins/ # Main filter driver protect and rules
|
||||
│ ├── Filter/
|
||||
│ │ └── ... # DriverCommon.h, DriverEntry.cpp, ProtectRegistry.cpp, ...
|
||||
│ └── Rules/
|
||||
│ └── ... # Rules_Driver_P1.json
|
||||
|
|
||||
├── PYAS.py # Main antivirus application entry point
|
||||
├── PYAS_Engine.py # Core scanning engine algorithm
|
||||
├── PYAS_Version.py # Version metadata for packaging and updates
|
||||
└── ...
|
||||
```
|
||||
|
||||
## Architecture diagram
|
||||
|
||||
PYAS Security antivirus software general architecture diagram.
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
%% Global Styles
|
||||
classDef userMode fill:#e3f2fd,stroke:#1565c0,stroke-width:2px,color:#0d47a1
|
||||
classDef kernelMode fill:#fff3e0,stroke:#e65100,stroke-width:2px,color:#e65100
|
||||
classDef storage fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px,color:#4a148c
|
||||
classDef interaction fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px,stroke-dasharray: 5 5,color:#1b5e20
|
||||
|
||||
subgraph UserMode ["User Mode (Python / Web)"]
|
||||
UI["Web UI (pywebview)"]:::userMode
|
||||
PYASCore["PYAS Core (PYAS.py)"]:::userMode
|
||||
|
||||
subgraph Engines ["Scanning Engines (PYAS_Engine.py)"]
|
||||
PEScanner["PE/ML Scanner (pe_scanner)"]:::userMode
|
||||
RuleScanner["YARA Scanner (rule_scanner)"]:::userMode
|
||||
CloudScanner["Cloud API (cloud_scanner)"]:::userMode
|
||||
SignScanner["Signature Verify (sign_scanner)"]:::userMode
|
||||
end
|
||||
|
||||
CommClient["Filter Communication Client"]:::interaction
|
||||
end
|
||||
|
||||
subgraph KernelMode ["Kernel Mode (C++ Minifilter Driver)"]
|
||||
CommServer["ALPC Port (\\PYAS_Output_Pipe)"]:::interaction
|
||||
DriverCore["Driver Entry (DriverEntry.cpp)"]:::kernelMode
|
||||
RuleEngine["Rules & Trust Cache (ProtectRules.cpp)"]:::kernelMode
|
||||
|
||||
subgraph Protections ["Protection Subsystems"]
|
||||
FileProtect["File Minifilter (ProtectFile.cpp)"]:::kernelMode
|
||||
ProcessProtect["ObRegisterCallbacks (ProtectProcess.cpp)"]:::kernelMode
|
||||
RegProtect["CmRegisterCallbackEx (ProtectRegistry.cpp)"]:::kernelMode
|
||||
BootProtect["IRP_MJ_DEVICE_CONTROL (ProtectBoot.cpp)"]:::kernelMode
|
||||
end
|
||||
end
|
||||
|
||||
subgraph OS ["Windows OS & Storage"]
|
||||
FS["File System (FltMgr)"]:::storage
|
||||
Reg["Registry (CmMgr)"]:::storage
|
||||
ProcMgr["Process/Thread Manager (ObMgr)"]:::storage
|
||||
Disk["Physical Disk / MBR"]:::storage
|
||||
end
|
||||
|
||||
CloudAPI["PYAS Cloud Server"]:::storage
|
||||
|
||||
%% User Mode Internal
|
||||
UI <-->|JS Evaluate / JSON| PYASCore
|
||||
PYASCore -->|Extract Features & Scan| Engines
|
||||
CloudScanner -.->|HTTPS REST API| CloudAPI
|
||||
PYASCore <-->|FilterSendMessage / FilterGetMessage| CommClient
|
||||
|
||||
%% User-Kernel Boundary
|
||||
CommClient <-->|PYAS_MESSAGE / PYAS_USER_MESSAGE| CommServer
|
||||
|
||||
%% Kernel Mode Internal
|
||||
CommServer <-->|Message Dispatch| DriverCore
|
||||
DriverCore --> RuleEngine
|
||||
RuleEngine --> Protections
|
||||
|
||||
%% Kernel-OS Boundary
|
||||
FileProtect -->|IRP_MJ_CREATE, IRP_MJ_WRITE...| FS
|
||||
ProcessProtect -->|PreOpenProcess, PreOpenThread| ProcMgr
|
||||
RegProtect -->|RegNtPreCreateKey, RegNtPreSetValueKey...| Reg
|
||||
BootProtect -->|IOCTL_DISK_FORMAT_TRACKS...| Disk
|
||||
```
|
||||
|
||||
## Machine Learning
|
||||
|
||||
The machine learning-based virus scanning engine training achieved extremely high accuracy.
|
||||
|
||||
<img width="895" height="1097" alt="train" src="https://github.com/user-attachments/assets/fa7cf0e2-01a1-4234-9358-718c2b8a2c9a" />
|
||||
|
||||
## Support System
|
||||
|
||||
Please ensure your computer supports Microsoft Visual C++ 2015-2022 Redistributable and Edge Webview2.
|
||||
|
||||
| Config | Permissions | System version | Processor | Memory | Storage |
|
||||
|-----------|---------------|----------------------|-----------|--------|---------|
|
||||
| Minimum | Administrator | >= Windows 10 (20H1) | 1 GHz | 300MB | 100MB |
|
||||
| Recommend | Administrator | >= Windows 10 (21H2) | 3 GHz | 500MB | 200MB |
|
||||
|
||||
## Official Website
|
||||
|
||||
If you are interested in this project, you can visit the website to see other related content.
|
||||
|
||||
Source Available : https://github.com/87owo/PYAS
|
||||
|
||||
Official Website : https://pyas-security.com/antivirus
|
||||
|
||||
Online Analyze : https://pyas-security.com/analyze
|
||||
|
||||
Packaged Download: https://github.com/87owo/PYAS/releases
|
||||
|
||||
## Project License
|
||||
|
||||
For any questions, needs, or bug feedback, please contact us through the following website.
|
||||
|
||||
Source Issues : https://github.com/87owo/PYAS/issues
|
||||
|
||||
Official Email : service@pyas-security.com
|
||||
Reference in new issue
Block a user