Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8dda40e7f | ||
|
|
5b4cd6e879 | ||
|
|
316b061d34 | ||
|
|
6ff27679e7 | ||
|
|
8f3adbc870 | ||
|
|
5798a35456 | ||
|
|
a8a2060978 | ||
|
|
7cf2763511 | ||
|
|
fc59619518 | ||
|
|
d01cfd2dd6 | ||
|
|
9563a4806a | ||
|
|
fa1be109e3 | ||
|
|
89a5a5928c | ||
|
|
7830378f23 | ||
|
|
043f616469 | ||
|
|
3bf631313a | ||
|
|
a416e2d4a8 | ||
|
|
53218cf1ce | ||
|
|
c5f0c8ed47 | ||
|
|
05e1c45adb | ||
|
|
e3787523c7 | ||
|
|
1c4e908b1b | ||
|
|
98b60b9833 | ||
|
|
71f8232ef0 | ||
|
|
0e4de407e3 | ||
|
|
802d732b3f | ||
|
|
373897f579 | ||
|
|
3ef72917c8 | ||
|
|
0d27125453 | ||
|
|
82c519b7d9 | ||
|
|
3c57a8a53c | ||
|
|
727ba6ccbf | ||
|
|
b32f1df9ec | ||
|
|
76b9afc6ec | ||
|
|
9fbf7baf76 | ||
|
|
9e69dac4e0 | ||
|
|
839599eed1 | ||
|
|
965b3d2863 | ||
|
|
64b206bc9e | ||
|
|
3b53e542da | ||
|
|
9974a0944a | ||
|
|
d40be1d740 | ||
|
|
14832d112e | ||
|
|
3b5f67f046 | ||
|
|
d1a074b2d3 | ||
|
|
03f3efb954 | ||
|
|
f82fa135c9 | ||
|
|
0e78b970ac | ||
|
|
cd2cf87b8f | ||
|
|
20d2f94146 | ||
|
|
06e0e6a01c | ||
|
|
901ee8e34a | ||
|
|
511a00cf84 | ||
|
|
30629a2363 | ||
|
|
7fc9aab048 | ||
|
|
3a468791c3 | ||
|
|
05a7103184 | ||
|
|
4ed8173c40 | ||
|
|
fb35bc372e | ||
|
|
11da9eb3bd | ||
|
|
5da8464bb4 | ||
|
|
f42524dcd5 | ||
|
|
3be437dc6e | ||
|
|
13291c36f5 | ||
|
|
948eb51ab8 | ||
|
|
0cef48771b | ||
|
|
a45b10b5c3 | ||
|
|
7627d4e5d7 | ||
|
|
5c88711094 | ||
|
|
a7fdd41530 | ||
|
|
c315b322cd | ||
|
|
181bfc5b44 | ||
|
|
d9ea2b522a | ||
|
|
dc498949c7 | ||
|
|
f804a5b7f0 | ||
|
|
c3ef1da373 | ||
|
|
b0a3482f25 | ||
|
|
018b497575 | ||
|
|
130b040de1 | ||
|
|
bc0a62224a | ||
|
|
77525fc7c5 | ||
|
|
a304a2f02d | ||
|
|
9456659544 | ||
|
|
3ec340e0da | ||
|
|
f1f35badcb | ||
|
|
045160c482 | ||
|
|
559c493997 | ||
|
|
936475f5bf | ||
|
|
e97f8eed54 | ||
|
|
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 |
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,876 @@
|
||||
import re, os, sys, math, time, zlib, mmap, 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,}')
|
||||
_STRING_FAST_PATH_BYTES = 4194304
|
||||
_STRING_BATCH_BYTES = 4194304
|
||||
_HISTOGRAM_BATCH_WINDOWS = 1024
|
||||
|
||||
_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))
|
||||
|
||||
_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'
|
||||
]
|
||||
|
||||
_SECTION_DEFAULTS = {
|
||||
'SectionCount': 0.0,
|
||||
'SectionMaxEntropy': 0.0,
|
||||
'SectionMinEntropy': 0.0,
|
||||
'SectionMeanEntropy': 0.0,
|
||||
'SectionMaxRawSize': 0.0,
|
||||
'SectionMinRawSize': 0.0,
|
||||
'SectionMeanRawSize': 0.0,
|
||||
'SectionMaxVSize': 0.0,
|
||||
'SectionMinVSize': 0.0,
|
||||
'SectionMeanVSize': 0.0,
|
||||
'ExecutableSections': 0.0,
|
||||
'WritableSections': 0.0,
|
||||
'ReadableSections': 0.0,
|
||||
'SectionException': 0.0
|
||||
}
|
||||
|
||||
_CONDITIONAL_DEFAULTS = {
|
||||
'IsDriver': 0.0,
|
||||
'HasTlsCallbacks': 0.0,
|
||||
'IsDebug': 0.0,
|
||||
'IsPreRelease': 0.0,
|
||||
'IsPatched': 0.0,
|
||||
'IsPrivateBuild': 0.0,
|
||||
'IsSpecialBuild': 0.0,
|
||||
'ImportCount': 0.0,
|
||||
'ImportFunctionCount': 0.0,
|
||||
'ExportCount': 0.0,
|
||||
'IconCount': 0.0,
|
||||
'DebugCount': 0.0
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
@classmethod
|
||||
def _base_defaults(cls):
|
||||
base = dict.fromkeys(cls._OPTIONAL_HEADER_FIELDS, 0.0)
|
||||
base.update(cls._SECTION_DEFAULTS)
|
||||
base.update(cls._CONDITIONAL_DEFAULTS)
|
||||
|
||||
for i in range(16):
|
||||
base[f'DataDirectory_{i}_Size'] = 0.0
|
||||
base[f'DataDirectory_{i}_VA'] = 0.0
|
||||
|
||||
return base
|
||||
|
||||
@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))
|
||||
|
||||
if len(file_bytes) <= cls._STRING_FAST_PATH_BYTES:
|
||||
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)
|
||||
global_counts = np.bincount(np.frombuffer(combined_data, dtype=np.uint8), minlength=256)
|
||||
else:
|
||||
count = 0
|
||||
total_len = 0
|
||||
global_counts = np.zeros(256, dtype=np.int64)
|
||||
mv = memoryview(file_bytes)
|
||||
pos = 0
|
||||
size = len(file_bytes)
|
||||
|
||||
while pos < size:
|
||||
chunk_end = min(pos + cls._STRING_BATCH_BYTES, size)
|
||||
chunk_arr = np.frombuffer(mv[pos:chunk_end], dtype=np.uint8)
|
||||
separators = (chunk_arr < 0x20) | (chunk_arr > 0x7E)
|
||||
|
||||
if separators.any():
|
||||
last_separator = len(separators) - 1 - int(np.argmax(separators[::-1]))
|
||||
segment_end = pos + last_separator + 1
|
||||
matches = cls._STRING_PATTERN.findall(mv[pos:segment_end])
|
||||
|
||||
if matches:
|
||||
combined_data = b''.join(matches)
|
||||
count += len(matches)
|
||||
total_len += len(combined_data)
|
||||
global_counts += np.bincount(np.frombuffer(combined_data, dtype=np.uint8), minlength=256)
|
||||
|
||||
pos = segment_end
|
||||
continue
|
||||
|
||||
pending_len = 0
|
||||
pending_counts = np.zeros(256, dtype=np.int64)
|
||||
|
||||
while pos < size:
|
||||
chunk_end = min(pos + cls._STRING_BATCH_BYTES, size)
|
||||
chunk_arr = np.frombuffer(mv[pos:chunk_end], dtype=np.uint8)
|
||||
separators = (chunk_arr < 0x20) | (chunk_arr > 0x7E)
|
||||
|
||||
if separators.any():
|
||||
first_separator = int(np.argmax(separators))
|
||||
if first_separator:
|
||||
pending_counts += np.bincount(chunk_arr[:first_separator], minlength=256)
|
||||
pending_len += first_separator
|
||||
pos += first_separator + 1
|
||||
break
|
||||
|
||||
pending_counts += np.bincount(chunk_arr, minlength=256)
|
||||
pending_len += len(chunk_arr)
|
||||
pos = chunk_end
|
||||
|
||||
if pending_len >= 5:
|
||||
count += 1
|
||||
total_len += pending_len
|
||||
global_counts += pending_counts
|
||||
|
||||
if count == 0 or total_len == 0:
|
||||
return res
|
||||
|
||||
res["StringCount"] = float(count)
|
||||
res["StringMeanLength"] = float(total_len) / count
|
||||
|
||||
counts_nonzero = global_counts[global_counts > 0]
|
||||
if len(counts_nonzero) > 0:
|
||||
res["StringEntropy"] = float(np.log2(total_len) - np.sum(counts_nonzero * np.log2(counts_nonzero)) / total_len)
|
||||
|
||||
valid_chars = global_counts[0x20:0x7F]
|
||||
res.update(dict(zip(cls._STRING_KEYS, valid_chars.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['FileEntropy'] = 0.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.zeros(256, dtype=np.int64)
|
||||
ent_hist = np.zeros(256, dtype=np.float64)
|
||||
window = 2048
|
||||
step = 1024
|
||||
|
||||
if sz < window:
|
||||
byte_counts += np.bincount(arr, minlength=256)
|
||||
p = byte_counts[byte_counts > 0] / float(sz)
|
||||
entropy = float(-np.sum(p * np.log2(p)))
|
||||
ent_bin = min(int(entropy * 2.0), 15)
|
||||
byte_bins = byte_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
|
||||
|
||||
for w_start in range(0, num_windows, cls._HISTOGRAM_BATCH_WINDOWS):
|
||||
w_end = min(w_start + cls._HISTOGRAM_BATCH_WINDOWS, num_windows)
|
||||
b_num = w_end - w_start
|
||||
|
||||
start_byte = w_start * step
|
||||
end_byte = start_byte + b_num * step + step
|
||||
encoded = arr[start_byte:end_byte].astype(np.int32).reshape(b_num + 1, step)
|
||||
encoded += np.arange(b_num + 1, dtype=np.int32)[:, None] * 256
|
||||
|
||||
chunk_counts = np.bincount(encoded.ravel(), minlength=(b_num + 1) * 256).reshape(b_num + 1, 256)
|
||||
del encoded
|
||||
|
||||
byte_counts += np.sum(chunk_counts[:-1], axis=0)
|
||||
if w_end == num_windows:
|
||||
byte_counts += chunk_counts[-1]
|
||||
|
||||
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())
|
||||
|
||||
covered_size = (num_windows + 1) * step
|
||||
if covered_size < sz:
|
||||
byte_counts += np.bincount(arr[covered_size:], minlength=256)
|
||||
|
||||
counts_nonzero = byte_counts[byte_counts > 0]
|
||||
res['FileEntropy'] = float(np.log2(sz) - np.sum(counts_nonzero * np.log2(counts_nonzero)) / sz)
|
||||
|
||||
byte_hist = byte_counts.astype(np.float64) / sz
|
||||
res.update(dict(zip(cls._BYTE_HIST_KEYS, byte_hist)))
|
||||
|
||||
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 = cls._base_defaults(), 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['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):
|
||||
mm = None
|
||||
f = None
|
||||
|
||||
try:
|
||||
fsize = os.path.getsize(file_path)
|
||||
if fsize == 0 or fsize > MAX_FILE_SIZE:
|
||||
return 'error', None
|
||||
|
||||
f = open(file_path, "rb")
|
||||
mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
|
||||
sha256 = hashlib.sha256(mm).hexdigest()
|
||||
|
||||
if sha256 in GLOBAL_HASHES:
|
||||
return 'skipped', None
|
||||
|
||||
res = FeatureExtractor.extract(mm, 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
|
||||
finally:
|
||||
if mm:
|
||||
mm.close()
|
||||
if f:
|
||||
f.close()
|
||||
|
||||
####################################################################################################
|
||||
|
||||
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,942 @@
|
||||
import os, re, sys, math, json, zlib, mmap, pefile, datetime
|
||||
import ctypes, ctypes.wintypes
|
||||
import numpy as np
|
||||
import onnxruntime as ort
|
||||
|
||||
####################################################################################################
|
||||
|
||||
MODEL_FILE = "Pefile_General_T1.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,}')
|
||||
_STRING_FAST_PATH_BYTES = 4194304
|
||||
_STRING_BATCH_BYTES = 4194304
|
||||
_HISTOGRAM_BATCH_WINDOWS = 1024
|
||||
|
||||
_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))
|
||||
|
||||
_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'
|
||||
]
|
||||
|
||||
_SECTION_DEFAULTS = {
|
||||
'SectionCount': 0.0,
|
||||
'SectionMaxEntropy': 0.0,
|
||||
'SectionMinEntropy': 0.0,
|
||||
'SectionMeanEntropy': 0.0,
|
||||
'SectionMaxRawSize': 0.0,
|
||||
'SectionMinRawSize': 0.0,
|
||||
'SectionMeanRawSize': 0.0,
|
||||
'SectionMaxVSize': 0.0,
|
||||
'SectionMinVSize': 0.0,
|
||||
'SectionMeanVSize': 0.0,
|
||||
'ExecutableSections': 0.0,
|
||||
'WritableSections': 0.0,
|
||||
'ReadableSections': 0.0,
|
||||
'SectionException': 0.0
|
||||
}
|
||||
|
||||
_CONDITIONAL_DEFAULTS = {
|
||||
'IsDriver': 0.0,
|
||||
'HasTlsCallbacks': 0.0,
|
||||
'IsDebug': 0.0,
|
||||
'IsPreRelease': 0.0,
|
||||
'IsPatched': 0.0,
|
||||
'IsPrivateBuild': 0.0,
|
||||
'IsSpecialBuild': 0.0,
|
||||
'ImportCount': 0.0,
|
||||
'ImportFunctionCount': 0.0,
|
||||
'ExportCount': 0.0,
|
||||
'IconCount': 0.0,
|
||||
'DebugCount': 0.0
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
@classmethod
|
||||
def _base_defaults(cls):
|
||||
base = dict.fromkeys(cls._OPTIONAL_HEADER_FIELDS, 0.0)
|
||||
base.update(cls._SECTION_DEFAULTS)
|
||||
base.update(cls._CONDITIONAL_DEFAULTS)
|
||||
|
||||
for i in range(16):
|
||||
base[f'DataDirectory_{i}_Size'] = 0.0
|
||||
base[f'DataDirectory_{i}_VA'] = 0.0
|
||||
|
||||
return base
|
||||
|
||||
@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))
|
||||
|
||||
if len(file_bytes) <= cls._STRING_FAST_PATH_BYTES:
|
||||
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)
|
||||
global_counts = np.bincount(np.frombuffer(combined_data, dtype=np.uint8), minlength=256)
|
||||
else:
|
||||
count = 0
|
||||
total_len = 0
|
||||
global_counts = np.zeros(256, dtype=np.int64)
|
||||
mv = memoryview(file_bytes)
|
||||
pos = 0
|
||||
size = len(file_bytes)
|
||||
|
||||
while pos < size:
|
||||
chunk_end = min(pos + cls._STRING_BATCH_BYTES, size)
|
||||
chunk_arr = np.frombuffer(mv[pos:chunk_end], dtype=np.uint8)
|
||||
separators = (chunk_arr < 0x20) | (chunk_arr > 0x7E)
|
||||
|
||||
if separators.any():
|
||||
last_separator = len(separators) - 1 - int(np.argmax(separators[::-1]))
|
||||
segment_end = pos + last_separator + 1
|
||||
matches = cls._STRING_PATTERN.findall(mv[pos:segment_end])
|
||||
|
||||
if matches:
|
||||
combined_data = b''.join(matches)
|
||||
count += len(matches)
|
||||
total_len += len(combined_data)
|
||||
global_counts += np.bincount(np.frombuffer(combined_data, dtype=np.uint8), minlength=256)
|
||||
|
||||
pos = segment_end
|
||||
continue
|
||||
|
||||
pending_len = 0
|
||||
pending_counts = np.zeros(256, dtype=np.int64)
|
||||
|
||||
while pos < size:
|
||||
chunk_end = min(pos + cls._STRING_BATCH_BYTES, size)
|
||||
chunk_arr = np.frombuffer(mv[pos:chunk_end], dtype=np.uint8)
|
||||
separators = (chunk_arr < 0x20) | (chunk_arr > 0x7E)
|
||||
|
||||
if separators.any():
|
||||
first_separator = int(np.argmax(separators))
|
||||
if first_separator:
|
||||
pending_counts += np.bincount(chunk_arr[:first_separator], minlength=256)
|
||||
pending_len += first_separator
|
||||
pos += first_separator + 1
|
||||
break
|
||||
|
||||
pending_counts += np.bincount(chunk_arr, minlength=256)
|
||||
pending_len += len(chunk_arr)
|
||||
pos = chunk_end
|
||||
|
||||
if pending_len >= 5:
|
||||
count += 1
|
||||
total_len += pending_len
|
||||
global_counts += pending_counts
|
||||
|
||||
if count == 0 or total_len == 0:
|
||||
return res
|
||||
|
||||
res["StringCount"] = float(count)
|
||||
res["StringMeanLength"] = float(total_len) / count
|
||||
|
||||
counts_nonzero = global_counts[global_counts > 0]
|
||||
if len(counts_nonzero) > 0:
|
||||
res["StringEntropy"] = float(np.log2(total_len) - np.sum(counts_nonzero * np.log2(counts_nonzero)) / total_len)
|
||||
|
||||
valid_chars = global_counts[0x20:0x7F]
|
||||
res.update(dict(zip(cls._STRING_KEYS, valid_chars.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['FileEntropy'] = 0.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.zeros(256, dtype=np.int64)
|
||||
ent_hist = np.zeros(256, dtype=np.float64)
|
||||
window = 2048
|
||||
step = 1024
|
||||
|
||||
if sz < window:
|
||||
byte_counts += np.bincount(arr, minlength=256)
|
||||
p = byte_counts[byte_counts > 0] / float(sz)
|
||||
entropy = float(-np.sum(p * np.log2(p)))
|
||||
ent_bin = min(int(entropy * 2.0), 15)
|
||||
byte_bins = byte_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
|
||||
|
||||
for w_start in range(0, num_windows, cls._HISTOGRAM_BATCH_WINDOWS):
|
||||
w_end = min(w_start + cls._HISTOGRAM_BATCH_WINDOWS, num_windows)
|
||||
b_num = w_end - w_start
|
||||
|
||||
start_byte = w_start * step
|
||||
end_byte = start_byte + b_num * step + step
|
||||
encoded = arr[start_byte:end_byte].astype(np.int32).reshape(b_num + 1, step)
|
||||
encoded += np.arange(b_num + 1, dtype=np.int32)[:, None] * 256
|
||||
|
||||
chunk_counts = np.bincount(encoded.ravel(), minlength=(b_num + 1) * 256).reshape(b_num + 1, 256)
|
||||
del encoded
|
||||
|
||||
byte_counts += np.sum(chunk_counts[:-1], axis=0)
|
||||
if w_end == num_windows:
|
||||
byte_counts += chunk_counts[-1]
|
||||
|
||||
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())
|
||||
|
||||
covered_size = (num_windows + 1) * step
|
||||
if covered_size < sz:
|
||||
byte_counts += np.bincount(arr[covered_size:], minlength=256)
|
||||
|
||||
counts_nonzero = byte_counts[byte_counts > 0]
|
||||
res['FileEntropy'] = float(np.log2(sz) - np.sum(counts_nonzero * np.log2(counts_nonzero)) / sz)
|
||||
|
||||
byte_hist = byte_counts.astype(np.float64) / sz
|
||||
res.update(dict(zip(cls._BYTE_HIST_KEYS, byte_hist)))
|
||||
|
||||
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
|
||||
mm = None
|
||||
source_file = None
|
||||
|
||||
try:
|
||||
fsize = os.path.getsize(file_path)
|
||||
if fsize == 0 or fsize > MAX_FILE_SIZE:
|
||||
return None
|
||||
|
||||
source_file = open(file_path, "rb")
|
||||
mm = mmap.mmap(source_file.fileno(), 0, access=mmap.ACCESS_READ)
|
||||
file_bytes = mm
|
||||
|
||||
base, dlls, apis = cls._base_defaults(), 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['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 field_name in fields:
|
||||
base[field_name] = getattr(op, field_name, 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 pefile.PEFormatError:
|
||||
return None
|
||||
finally:
|
||||
if pe is not None:
|
||||
try:
|
||||
pe.close()
|
||||
except Exception:
|
||||
pass
|
||||
if mm is not None:
|
||||
try:
|
||||
mm.close()
|
||||
except Exception:
|
||||
pass
|
||||
if source_file is not None:
|
||||
try:
|
||||
source_file.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
####################################################################################################
|
||||
|
||||
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 error: Model expects {expected_dim}, but feature file has {current_dim}")
|
||||
|
||||
self.feat_map = {feat: i for i, feat in enumerate(self.model_features)}
|
||||
|
||||
self.dll_hash_dim = 512
|
||||
self.api_hash_dim = 4096
|
||||
self.dll_hash_pad = 3
|
||||
self.api_hash_pad = 4
|
||||
self._parse_hash_dims()
|
||||
|
||||
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 _parse_hash_dims(self):
|
||||
max_dll = -1
|
||||
max_api = -1
|
||||
|
||||
for feat in self.model_features:
|
||||
if feat.startswith("DllHash_"):
|
||||
try:
|
||||
val_str = feat.split("_")[1]
|
||||
max_dll = max(max_dll, int(val_str))
|
||||
self.dll_hash_pad = len(val_str)
|
||||
except Exception:
|
||||
pass
|
||||
elif feat.startswith("ApiHash_"):
|
||||
try:
|
||||
val_str = feat.split("_")[1]
|
||||
max_api = max(max_api, int(val_str))
|
||||
self.api_hash_pad = len(val_str)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if max_dll >= 0:
|
||||
self.dll_hash_dim = max_dll + 1
|
||||
if max_api >= 0:
|
||||
self.api_hash_dim = max_api + 1
|
||||
|
||||
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')) % self.dll_hash_dim
|
||||
feat_name = f"DllHash_{h:0{self.dll_hash_pad}d}"
|
||||
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')) % self.api_hash_dim
|
||||
feat_name = f"ApiHash_{h:0{self.api_hash_pad}d}"
|
||||
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
|
||||
|
||||
log_buffer = []
|
||||
|
||||
def _log(message):
|
||||
print(message)
|
||||
log_buffer.append(message)
|
||||
|
||||
_log(f"[*] Scanning {len(files)} files...\n")
|
||||
_log(f"{'RESULT':<10} | {'PROB':<8} | {'FILE'}")
|
||||
_log("-" * 80)
|
||||
|
||||
safe_count = 0
|
||||
mal_count = 0
|
||||
unsupport_count = 0
|
||||
error_count = 0
|
||||
|
||||
try:
|
||||
for fpath in files:
|
||||
try:
|
||||
data = FeatureExtractor.extract(fpath)
|
||||
if not data:
|
||||
_log(f"{'UNSUPPORT':<10} | {'-':<8} | {fpath}")
|
||||
unsupport_count += 1
|
||||
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
|
||||
|
||||
_log(f"{label:<10} | {prob:.4f} | {fpath}")
|
||||
|
||||
except Exception as e:
|
||||
_log(f"{'FAIL':<10} | {'-':<8} | {fpath} ({str(e)})")
|
||||
error_count += 1
|
||||
|
||||
except KeyboardInterrupt:
|
||||
_log("\n[-] Scan aborted by user (Ctrl+C).\n")
|
||||
|
||||
_log("-" * 80)
|
||||
_log(f"\n[*] Summary: Safe={safe_count}, Malware={mal_count}, Unsupport={unsupport_count}, Error={error_count}, Total={len(files)}")
|
||||
|
||||
log_filename = datetime.datetime.now().strftime("%Y%m%d_%H%M%S.log")
|
||||
try:
|
||||
with open(log_filename, "w", encoding="utf-8") as f:
|
||||
f.write("\n".join(log_buffer) + "\n")
|
||||
print(f"[*] Log saved successfully: {log_filename}")
|
||||
except Exception as e:
|
||||
print(f"[-] Failed to save log: {e}")
|
||||
|
||||
####################################################################################################
|
||||
|
||||
if __name__ == "__main__":
|
||||
base_dir = os.path.dirname(sys.executable) if getattr(sys, 'frozen', False) else os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
custom_model = MODEL_FILE
|
||||
targets = []
|
||||
|
||||
args = sys.argv[1:]
|
||||
i = 0
|
||||
while i < len(args):
|
||||
if args[i] in ("-m", "-model", "--model") and i + 1 < len(args):
|
||||
custom_model = args[i+1]
|
||||
i += 2
|
||||
else:
|
||||
targets.append(args[i])
|
||||
i += 1
|
||||
|
||||
print("\n-------------------------- PE Malware Predictor v4.0 --------------------------\n")
|
||||
print(f"[*] © 2020-2026 87owo (PYAS Security)")
|
||||
print(f"[*] Official Website: https://github.com/87owo/PYAS")
|
||||
print(f"[*] Loading model: {custom_model}")
|
||||
|
||||
model_path = os.path.join(base_dir, custom_model) if not os.path.isabs(custom_model) else custom_model
|
||||
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 targets:
|
||||
for path in targets:
|
||||
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,309 @@
|
||||
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 = 512
|
||||
API_HASH_DIM = 4096
|
||||
|
||||
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
|
||||
|
||||
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,309 @@
|
||||
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 = 512
|
||||
API_HASH_DIM = 4096
|
||||
|
||||
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
|
||||
|
||||
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=350,
|
||||
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,870 @@
|
||||
* {
|
||||
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;
|
||||
}
|
||||
|
||||
#loading_overlay.startup-error {
|
||||
padding: 28px;
|
||||
background-color: #f5f5f7;
|
||||
color: #1d1d1f;
|
||||
}
|
||||
|
||||
.startup-error-box {
|
||||
max-width: 520px;
|
||||
padding: 24px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.12);
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.startup-error-box h1 {
|
||||
margin: 0 0 10px;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.startup-error-box p {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
color: #515154;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.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,594 @@
|
||||
<!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>
|
||||
<script>
|
||||
window.addEventListener('DOMContentLoaded', function () {
|
||||
window.setTimeout(function () {
|
||||
var overlay = document.getElementById('loading_overlay');
|
||||
if (overlay && !overlay.classList.contains('fade-out') && !overlay.classList.contains('startup-error')) {
|
||||
overlay.classList.add('startup-error');
|
||||
overlay.innerHTML = '<div class="startup-error-box"><h1>PYAS failed to open</h1><p>WebView2 initialization timed out. PYAS will retry automatically.</p></div>';
|
||||
}
|
||||
}, 20000);
|
||||
});
|
||||
</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">
|
||||
<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" 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"><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: 2026-07-18
|
||||
IMPORTANT NOTICE: THIS LICENSE IS "SOURCE AVAILABLE" AND IS NOT AN OPEN SOURCE LICENSE APPROVED BY THE OPEN SOURCE INITIATIVE (OSI). BY DOWNLOADING, ACCESSING, OR USING THE SOFTWARE, YOU AGREE TO BE BOUND BY THESE TERMS.
|
||||
|
||||
1. Definitions and Interpretation
|
||||
|
||||
- 1.1. Author: Refers to 87owo (PYAS Security).
|
||||
- 1.2. Software: Refers to the source code, binaries, project archives, assets, and documentation associated with this repository or distribution.
|
||||
- 1.3. Personal Local Use: Means viewing, downloading, executing, and modifying the Software strictly on local hardware owned or directly controlled by an individual for private, non-commercial, non-production, and non-distribution purposes.
|
||||
- 1.4. Enterprise Use: Includes enterprise internal deployment, multi-user institutional use, or utilization within any corporate environment, which is strictly excluded from Personal Local Use.
|
||||
|
||||
2. Intellectual Property and Ownership
|
||||
|
||||
- 2.1. Retention of Rights: The Author reserves all legal rights, titles, and interests in and to the Software, including but not limited to all copyrights, trademarks, patents, and trade secrets.
|
||||
- 2.2. No Transfer: No intellectual property rights or ownership stakes are transferred under this Agreement. All project components remain fully proprietary.
|
||||
- 2.3. Activation Keys: Access to or execution of certain premium features, modules, or binary distributions within the Software may require a valid, authorized activation key issued solely by the Author.
|
||||
|
||||
3. Grant of License and Permitted Use
|
||||
|
||||
Subject to your strict compliance with all terms of this Agreement, the Author hereby grants you a limited, non-exclusive, world-wide, royalty-free, revocable, and non-transferable license to:
|
||||
- 3.1. Code Inspection: View and download the source code from official authorized repositories.
|
||||
- 3.2. Local Modification: Modify the source code strictly for the purposes of Personal Local Use.
|
||||
- 3.3. Local Execution: Execute the Software locally for personal evaluation and private deployment.
|
||||
|
||||
4. Prohibitions and Restrictions
|
||||
|
||||
Except as explicitly granted in Section 3, you shall not, and shall not permit any third party to:
|
||||
- 4.1. Unauthorized Redistribution: Copy, redistribute, sublicense, republish, mirror, or publicly disclose any original or modified versions of the source code, binaries, or assets.
|
||||
- 4.2. Commercial and SaaS Deployment: Host, deploy, embed, or utilize the Software for commercial purposes, including but not limited to Software-as-a-Service (SaaS), commercial web hosting, paid remote execution, cloud infrastructure integration, or enterprise internal utility.
|
||||
- 4.3. Reverse Engineering: Reverse engineer, decompile, decrypt, or disassemble any binary components of the Software, except and only to the extent that such restriction is explicitly prohibited by applicable mandatory laws.
|
||||
- 4.4. Notice Preservation: Remove, obscure, deface, or alter any copyright notices, trademarks, hyperlinked terms, or license files included within the Software distribution.
|
||||
|
||||
5. Prior Authorization and Application Channels
|
||||
|
||||
- 5.1. Authorization Requirement: Any activity exceeding the scope of Personal Local Use (including but not limited to public redistribution, independent forks not targeting upstream contributions, commercial deployment, or enterprise use) requires prior explicit written consent from the Author.
|
||||
- 5.2. Official Application Channels: Users seeking such authorization must submit a formal request through one of the following official channels:
|
||||
- Channel A (GitHub Fork): Initiating a standard Fork on the official GitHub repository for collaborative and upstream contributions.
|
||||
- Channel B (GitHub Issues): Submitting a public issue utilizing the designated authorization request template.
|
||||
- Channel C (Official Email): Sending a detailed inquiry to the official organizational email address associated with the PYAS Security project.
|
||||
- 5.3. Request Content: Any application must explicitly state the specific use case, intended target audience, scope of deployment, and whether any commercial monetization is involved.
|
||||
|
||||
6. Updates, Amendments, and Scope of Terms
|
||||
|
||||
- 6.1. New Releases: The Author may deliver updates, critical security patches, feature enhancements, or user interface modifications. Continued use of the Software constitutes acceptance of the latest distributed version.
|
||||
- 6.2. Non-Retroactivity: The Author reserves the right to amend this License Agreement at any time. Amendments apply strictly to subsequent versions or new downloads. Copies lawfully acquired prior to such modification remain governed by the exact version of the License under which they were originally obtained.
|
||||
|
||||
7. Telemetry, Data, and Privacy
|
||||
|
||||
- 7.1. Operational Data Collection: The Software may collect anonymized technical, operational, and usage data strictly for security analysis, performance optimization, and bug reporting.
|
||||
- 7.2. Cloud Sample Submission: To enhance threat detection and malware analysis, the Software may automatically or manually transmit suspicious executable files and software samples to the Author's cloud infrastructure for deep analysis.
|
||||
- 7.3. Scope of Collected Extensions: Sample collection targets potential executable and system binary formats, which explicitly include, but are not limited to, files with the following extensions: `.exe`, `.dll`, `.sys`, `.ocx`, `.scr`, `.efi`, `.acm`, `.ax`, `.cpl`, `.drv`, `.com`, `.mui`, `.pyd`, `.wfx`, `.api`, `.awx`, `.rll`, and `.winmd`.
|
||||
- 7.4. Public Disclosure of Threat Intelligence: You explicitly acknowledge and agree that the metadata, behavioral reports, and threat intelligence data generated from the cloud analysis of submitted samples may be publicly disclosed, published, or shared with the cybersecurity community for collaborative defense purposes.
|
||||
- 7.5. Opt-Out Mechanism: Cloud-based telemetry and sample submission are fully optional. Users who do not wish to participate in sample collection or cloud-assisted analysis may disable the corresponding cloud services at any time via the toggle switches provided within the Software’s settings interface.
|
||||
- 7.6. Driver Signature: Certain kernel-mode drivers distributed with this software may be digitally signed using code signing methods that are not certified by Microsoft Windows Hardware Quality Labs (WHQL). By installing or using this software, you acknowledge this and decide for yourself whether to install and trust such drivers on your system.
|
||||
|
||||
8. Warranty Disclaimer and Limitation of Liability
|
||||
|
||||
- 8.1. Disclaimer of Warranty: TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THE SOFTWARE AND ALL RELATED ASSETS ARE PROVIDED ON AN "AS IS" AND "AS AVAILABLE" BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED.
|
||||
- 8.2. Limitation of Liability: TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL THE AUTHOR, COPYRIGHT HOLDERS, OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE. USERS ASSUME ALL RISKS REGARDING PERFORMANCE, DATA INTEGRITY, AND SYSTEM STABILITY.
|
||||
|
||||
© 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>
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
87owo Publicly Available Source Code License Agreement
|
||||
|
||||
Last Updated: 2026-07-18
|
||||
IMPORTANT NOTICE: THIS LICENSE IS "SOURCE AVAILABLE" AND IS NOT AN OPEN SOURCE LICENSE APPROVED BY THE OPEN SOURCE INITIATIVE (OSI). BY DOWNLOADING, ACCESSING, OR USING THE SOFTWARE, YOU AGREE TO BE BOUND BY THESE TERMS.
|
||||
|
||||
1. Definitions and Interpretation
|
||||
|
||||
- 1.1. Author: Refers to 87owo (PYAS Security).
|
||||
- 1.2. Software: Refers to the source code, binaries, project archives, assets, and documentation associated with this repository or distribution.
|
||||
- 1.3. Personal Local Use: Means viewing, downloading, executing, and modifying the Software strictly on local hardware owned or directly controlled by an individual for private, non-commercial, non-production, and non-distribution purposes.
|
||||
- 1.4. Enterprise Use: Includes enterprise internal deployment, multi-user institutional use, or utilization within any corporate environment, which is strictly excluded from Personal Local Use.
|
||||
|
||||
2. Intellectual Property and Ownership
|
||||
|
||||
- 2.1. Retention of Rights: The Author reserves all legal rights, titles, and interests in and to the Software, including but not limited to all copyrights, trademarks, patents, and trade secrets.
|
||||
- 2.2. No Transfer: No intellectual property rights or ownership stakes are transferred under this Agreement. All project components remain fully proprietary.
|
||||
- 2.3. Activation Keys: Access to or execution of certain premium features, modules, or binary distributions within the Software may require a valid, authorized activation key issued solely by the Author.
|
||||
|
||||
3. Grant of License and Permitted Use
|
||||
|
||||
Subject to your strict compliance with all terms of this Agreement, the Author hereby grants you a limited, non-exclusive, world-wide, royalty-free, revocable, and non-transferable license to:
|
||||
- 3.1. Code Inspection: View and download the source code from official authorized repositories.
|
||||
- 3.2. Local Modification: Modify the source code strictly for the purposes of Personal Local Use.
|
||||
- 3.3. Local Execution: Execute the Software locally for personal evaluation and private deployment.
|
||||
|
||||
4. Prohibitions and Restrictions
|
||||
|
||||
Except as explicitly granted in Section 3, you shall not, and shall not permit any third party to:
|
||||
- 4.1. Unauthorized Redistribution: Copy, redistribute, sublicense, republish, mirror, or publicly disclose any original or modified versions of the source code, binaries, or assets.
|
||||
- 4.2. Commercial and SaaS Deployment: Host, deploy, embed, or utilize the Software for commercial purposes, including but not limited to Software-as-a-Service (SaaS), commercial web hosting, paid remote execution, cloud infrastructure integration, or enterprise internal utility.
|
||||
- 4.3. Reverse Engineering: Reverse engineer, decompile, decrypt, or disassemble any binary components of the Software, except and only to the extent that such restriction is explicitly prohibited by applicable mandatory laws.
|
||||
- 4.4. Notice Preservation: Remove, obscure, deface, or alter any copyright notices, trademarks, hyperlinked terms, or license files included within the Software distribution.
|
||||
|
||||
5. Prior Authorization and Application Channels
|
||||
|
||||
- 5.1. Authorization Requirement: Any activity exceeding the scope of Personal Local Use (including but not limited to public redistribution, independent forks not targeting upstream contributions, commercial deployment, or enterprise use) requires prior explicit written consent from the Author.
|
||||
- 5.2. Official Application Channels: Users seeking such authorization must submit a formal request through one of the following official channels:
|
||||
- Channel A (GitHub Fork): Initiating a standard Fork on the official GitHub repository for collaborative and upstream contributions.
|
||||
- Channel B (GitHub Issues): Submitting a public issue utilizing the designated authorization request template.
|
||||
- Channel C (Official Email): Sending a detailed inquiry to the official organizational email address associated with the PYAS Security project.
|
||||
- 5.3. Request Content: Any application must explicitly state the specific use case, intended target audience, scope of deployment, and whether any commercial monetization is involved.
|
||||
|
||||
6. Updates, Amendments, and Scope of Terms
|
||||
|
||||
- 6.1. New Releases: The Author may deliver updates, critical security patches, feature enhancements, or user interface modifications. Continued use of the Software constitutes acceptance of the latest distributed version.
|
||||
- 6.2. Non-Retroactivity: The Author reserves the right to amend this License Agreement at any time. Amendments apply strictly to subsequent versions or new downloads. Copies lawfully acquired prior to such modification remain governed by the exact version of the License under which they were originally obtained.
|
||||
|
||||
7. Telemetry, Data, and Privacy
|
||||
|
||||
- 7.1. Operational Data Collection: The Software may collect anonymized technical, operational, and usage data strictly for security analysis, performance optimization, and bug reporting.
|
||||
- 7.2. Cloud Sample Submission: To enhance threat detection and malware analysis, the Software may automatically or manually transmit suspicious executable files and software samples to the Author's cloud infrastructure for deep analysis.
|
||||
- 7.3. Scope of Collected Extensions: Sample collection targets potential executable and system binary formats, which explicitly include, but are not limited to, files with the following extensions: `.exe`, `.dll`, `.sys`, `.ocx`, `.scr`, `.efi`, `.acm`, `.ax`, `.cpl`, `.drv`, `.com`, `.mui`, `.pyd`, `.wfx`, `.api`, `.awx`, `.rll`, and `.winmd`.
|
||||
- 7.4. Public Disclosure of Threat Intelligence: You explicitly acknowledge and agree that the metadata, behavioral reports, and threat intelligence data generated from the cloud analysis of submitted samples may be publicly disclosed, published, or shared with the cybersecurity community for collaborative defense purposes.
|
||||
- 7.5. Opt-Out Mechanism: Cloud-based telemetry and sample submission are fully optional. Users who do not wish to participate in sample collection or cloud-assisted analysis may disable the corresponding cloud services at any time via the toggle switches provided within the Software’s settings interface.
|
||||
- 7.6. Driver Signature: Certain kernel-mode drivers distributed with this software may be digitally signed using code signing methods that are not certified by Microsoft Windows Hardware Quality Labs (WHQL). By installing or using this software, you acknowledge this and decide for yourself whether to install and trust such drivers on your system.
|
||||
|
||||
8. Warranty Disclaimer and Limitation of Liability
|
||||
|
||||
- 8.1. Disclaimer of Warranty: TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THE SOFTWARE AND ALL RELATED ASSETS ARE PROVIDED ON AN "AS IS" AND "AS AVAILABLE" BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED.
|
||||
- 8.2. Limitation of Liability: TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL THE AUTHOR, COPYRIGHT HOLDERS, OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE. USERS ASSUME ALL RISKS REGARDING PERFORMANCE, DATA INTEGRITY, AND SYSTEM STABILITY.
|
||||
|
||||
© 2020-2026 87owo (PYAS Security). All rights reserved.
|
||||
+1086
File diff suppressed because it is too large.
Load diff
+339
@@ -0,0 +1,339 @@
|
||||
#define AppId "{{a7d7bac3-93b8-4630-8308-c7a56bf7fdf4}"
|
||||
#define AppName "PYAS"
|
||||
#define AppVersion "3.6.5.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}"
|
||||
|
||||
[Files]
|
||||
Source: "Redist\VC_redist.x64.exe"; DestDir: "{tmp}\PYAS_Redist"; Flags: deleteafterinstall ignoreversion; AfterInstall: EnsureVCRedist
|
||||
Source: "Redist\MicrosoftEdgeWebview2Setup.exe"; DestDir: "{tmp}\PYAS_Redist"; Flags: deleteafterinstall ignoreversion; AfterInstall: EnsureWebView2
|
||||
Source: "Payload\Engine\*"; DestDir: "{app}\Engine"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
Source: "Payload\Interface\*"; DestDir: "{app}\Interface"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
Source: "Payload\License\*"; DestDir: "{app}\License"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
Source: "Payload\PYAS.exe"; DestDir: "{app}"; Flags: ignoreversion; AfterInstall: FinalizeDriverRemoval
|
||||
Source: "Payload\Plugins\*"; DestDir: "{app}\Plugins"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
|
||||
[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: "{app}\{#AppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(AppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent runascurrentuser; Check: CanLaunchApp
|
||||
|
||||
[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.
|
||||
english.QuitFailed=PYAS or its driver could not be stopped safely. Restart Windows and run Setup again.
|
||||
english.DependencyInstallFailed=Required Microsoft runtime installation failed. Setup cannot continue.
|
||||
chinesesimplified.LegacyVersionDetected=检测到存在旧版 PYAS。请先手动卸载旧版后,再运行本安装程序。
|
||||
chinesetraditional.LegacyVersionDetected=檢測到存在舊版 PYAS。請先手動卸載舊版後,再執行本安裝程式。
|
||||
|
||||
[Code]
|
||||
var
|
||||
DependencyRestartRequired: Boolean;
|
||||
DependenciesReady: Boolean;
|
||||
|
||||
function FindWindow(lpClassName: LongWord; lpWindowName: string): HWND;
|
||||
external '[email protected] stdcall';
|
||||
|
||||
function GetTickCount: Cardinal;
|
||||
external '[email protected] stdcall';
|
||||
|
||||
function IsValidRuntimeVersion(Version: string): Boolean;
|
||||
begin
|
||||
Result := (Length(Trim(Version)) > 0) and (Trim(Version) <> '0.0.0.0');
|
||||
end;
|
||||
|
||||
function IsWebView2Installed: Boolean;
|
||||
var
|
||||
Version: string;
|
||||
begin
|
||||
Result := False;
|
||||
if RegQueryStringValue(HKLM, 'SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}', 'pv', Version) then
|
||||
Result := IsValidRuntimeVersion(Version);
|
||||
if not Result and RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}', 'pv', Version) then
|
||||
Result := IsValidRuntimeVersion(Version);
|
||||
if not Result and RegQueryStringValue(HKCU, 'SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}', 'pv', Version) then
|
||||
Result := IsValidRuntimeVersion(Version);
|
||||
end;
|
||||
|
||||
function IsVCRedistInstalled: Boolean;
|
||||
var
|
||||
Installed: Cardinal;
|
||||
Version: string;
|
||||
begin
|
||||
Result := False;
|
||||
if RegQueryDWordValue(HKLM64, 'SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64', 'Installed', Installed) and (Installed = 1) then
|
||||
begin
|
||||
if RegQueryStringValue(HKLM64, 'SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64', 'Version', Version) then
|
||||
Result := IsValidRuntimeVersion(Version)
|
||||
else
|
||||
Result := True;
|
||||
end;
|
||||
end;
|
||||
|
||||
function DependencyExitCodeSucceeded(ResultCode: Integer): Boolean;
|
||||
begin
|
||||
Result := (ResultCode = 0) or (ResultCode = 1641) or (ResultCode = 3010);
|
||||
if (ResultCode = 1641) or (ResultCode = 3010) then
|
||||
DependencyRestartRequired := True;
|
||||
end;
|
||||
|
||||
procedure EnsureVCRedist;
|
||||
var
|
||||
ResultCode: Integer;
|
||||
InstallerPath: string;
|
||||
begin
|
||||
if IsVCRedistInstalled then Exit;
|
||||
InstallerPath := ExpandConstant('{tmp}\PYAS_Redist\VC_redist.x64.exe');
|
||||
WizardForm.StatusLabel.Caption := CustomMessage('InstallingVCRuntime');
|
||||
if not Exec(InstallerPath, '/quiet /norestart', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) or
|
||||
not DependencyExitCodeSucceeded(ResultCode) or not IsVCRedistInstalled then
|
||||
begin
|
||||
DependenciesReady := False;
|
||||
RaiseException(CustomMessage('DependencyInstallFailed'));
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure EnsureWebView2;
|
||||
var
|
||||
ResultCode: Integer;
|
||||
InstallerPath: string;
|
||||
begin
|
||||
if IsWebView2Installed then Exit;
|
||||
InstallerPath := ExpandConstant('{tmp}\PYAS_Redist\MicrosoftEdgeWebview2Setup.exe');
|
||||
WizardForm.StatusLabel.Caption := CustomMessage('InstallingWebView2Runtime');
|
||||
if not Exec(InstallerPath, '/silent /install', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) or
|
||||
not DependencyExitCodeSucceeded(ResultCode) or not IsWebView2Installed then
|
||||
begin
|
||||
DependenciesReady := False;
|
||||
RaiseException(CustomMessage('DependencyInstallFailed'));
|
||||
end;
|
||||
end;
|
||||
|
||||
function WaitForMainWindowToClose(TimeoutMs: Cardinal): Boolean;
|
||||
var
|
||||
Deadline: Cardinal;
|
||||
begin
|
||||
Deadline := GetTickCount + TimeoutMs;
|
||||
repeat
|
||||
if FindWindow(0, 'PYAS Security') = 0 then
|
||||
begin
|
||||
Result := True;
|
||||
Exit;
|
||||
end;
|
||||
Sleep(100);
|
||||
until GetTickCount >= Deadline;
|
||||
Result := False;
|
||||
end;
|
||||
|
||||
function QuitOldInstance(InstallPath: string): Boolean;
|
||||
var
|
||||
ResultCode: Integer;
|
||||
ExePath: string;
|
||||
begin
|
||||
Result := True;
|
||||
ExePath := InstallPath + '\{#AppExeName}';
|
||||
if not FileExists(ExePath) then Exit;
|
||||
|
||||
if not Exec(ExePath, '-quit', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then
|
||||
begin
|
||||
Result := False;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
Result := (ResultCode = 0) and WaitForMainWindowToClose(30000);
|
||||
end;
|
||||
|
||||
procedure FinalizeDriverRemoval;
|
||||
var
|
||||
ResultCode: Integer;
|
||||
ExePath: string;
|
||||
begin
|
||||
ExePath := ExpandConstant('{app}\{#AppExeName}');
|
||||
if not Exec(ExePath, '-quit', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) or (ResultCode <> 0) then
|
||||
RaiseException(CustomMessage('QuitFailed'));
|
||||
end;
|
||||
|
||||
function InitializeSetup(): Boolean;
|
||||
var
|
||||
OldInstallPath: string;
|
||||
LegacyPath: string;
|
||||
begin
|
||||
DependencyRestartRequired := False;
|
||||
DependenciesReady := True;
|
||||
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
|
||||
if not QuitOldInstance(OldInstallPath) then
|
||||
begin
|
||||
MsgBox(CustomMessage('QuitFailed'), mbCriticalError, MB_OK);
|
||||
Result := False;
|
||||
Exit;
|
||||
end;
|
||||
end;
|
||||
|
||||
Result := True;
|
||||
end;
|
||||
|
||||
function InitializeUninstall(): Boolean;
|
||||
begin
|
||||
Result := QuitOldInstance(ExpandConstant('{app}'));
|
||||
if not Result then
|
||||
MsgBox(CustomMessage('QuitFailed'), mbCriticalError, MB_OK);
|
||||
end;
|
||||
|
||||
function CanLaunchApp: Boolean;
|
||||
begin
|
||||
Result := DependenciesReady and not DependencyRestartRequired;
|
||||
end;
|
||||
|
||||
function NeedRestart: Boolean;
|
||||
begin
|
||||
Result := DependencyRestartRequired;
|
||||
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;
|
||||
+1975
File diff suppressed because it is too large.
Load diff
+640
@@ -0,0 +1,640 @@
|
||||
import os, gc, json, time, stat, threading
|
||||
import ctypes, ctypes.wintypes
|
||||
|
||||
from PYAS_Tools import MEMORY_BASIC_INFORMATION
|
||||
|
||||
####################################################################################################
|
||||
|
||||
class ScannerMixin:
|
||||
def init_engine_thread(self):
|
||||
try:
|
||||
self.start_daemon_thread(self.backup_mbr)
|
||||
self.start_daemon_thread(self.relock_file)
|
||||
self.start_daemon_thread(self.init_whitelist)
|
||||
self.start_daemon_thread(self.popup_intercept_thread)
|
||||
|
||||
def log_callback(x):
|
||||
self.write_log("INFO", "Load Engine", source=os.path.basename(x))
|
||||
|
||||
self.heuristic.load_path(self.path_heuristic, callback=log_callback)
|
||||
self.properties.load_path(self.path_properties, callback=log_callback)
|
||||
self.write_log("INFO", "System", detail="Engine Initialization Complete")
|
||||
|
||||
if "-scan" in self.args_pyas:
|
||||
try:
|
||||
idx = self.args_pyas.index("-scan")
|
||||
self.trigger_context_scan(self.args_pyas[idx+1])
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
with self.lock_config:
|
||||
first_launch = self.pyas_config.get("first_launch", True)
|
||||
driver_enabled = self.pyas_config.get("driver_switch", False)
|
||||
context_enabled = self.pyas_config.get("context_switch", True)
|
||||
autostart_enabled = self.pyas_config.get("autostart_switch", True)
|
||||
|
||||
self.register_context_menu(context_enabled)
|
||||
self.manage_autostart(autostart_enabled)
|
||||
|
||||
if not first_launch:
|
||||
with self.lock_config:
|
||||
if self.pyas_config.get("process_switch"):
|
||||
self.start_daemon_thread(self.protect_proc_thread)
|
||||
if self.pyas_config.get("document_switch"):
|
||||
self.start_daemon_thread(self.protect_file_thread)
|
||||
if self.pyas_config.get("system_switch"):
|
||||
self.start_daemon_thread(self.protect_system_thread)
|
||||
if self.pyas_config.get("network_switch"):
|
||||
self.start_daemon_thread(self.protect_net_thread)
|
||||
|
||||
if driver_enabled:
|
||||
if self.install_system_driver() and self.start_driver_listener(wait_ready=True):
|
||||
pass
|
||||
else:
|
||||
self.stop_system_driver()
|
||||
with self.lock_config:
|
||||
self.pyas_config["driver_switch"] = False
|
||||
self.save_config()
|
||||
|
||||
self.write_log("WARN", "System", detail="Driver Protection Failed to Start", success=False)
|
||||
|
||||
except Exception as e:
|
||||
self.write_log("WARN", "init_engine_thread", detail=str(e), success=False)
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def yield_files(self, targets):
|
||||
if isinstance(targets, str):
|
||||
if os.path.isdir(targets):
|
||||
for root, dirs, files in os.walk(targets):
|
||||
dirs[:] = [d for d in dirs if not self._is_reparse_point(os.path.join(root, d))]
|
||||
for f in files:
|
||||
yield self.norm_path(os.path.join(root, f)), False
|
||||
|
||||
elif os.path.isfile(targets):
|
||||
yield self.norm_path(targets), True
|
||||
|
||||
elif isinstance(targets, (list, tuple, set)):
|
||||
for t in targets:
|
||||
yield from self.yield_files(t)
|
||||
|
||||
def _is_reparse_point(self, path):
|
||||
try:
|
||||
if os.path.islink(path):
|
||||
return True
|
||||
if hasattr(os.path, 'isjunction') and os.path.isjunction(path):
|
||||
return True
|
||||
|
||||
st = os.lstat(path)
|
||||
return bool(getattr(st, 'st_file_attributes', 0) & 0x400)
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def scan_engine(self, file_path):
|
||||
with self.lock_config:
|
||||
ext_switch = self.pyas_config.get("extension_switch", False)
|
||||
sen_switch = self.pyas_config.get("sensitive_switch", False)
|
||||
|
||||
try:
|
||||
pe_label, _ = self.properties.pe_scan(file_path, enhanced_mode=sen_switch)
|
||||
if pe_label:
|
||||
return pe_label
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
if ext_switch:
|
||||
yara_label, _ = self.heuristic.yara_scan(file_path)
|
||||
if yara_label:
|
||||
return yara_label
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
def safe_scan_engine(self, file_path):
|
||||
norm_path = self.norm_path(file_path)
|
||||
if not norm_path:
|
||||
return False
|
||||
|
||||
file_hash = self.calc_file_hash(norm_path)
|
||||
cache_key = file_hash if file_hash else os.path.normcase(norm_path)
|
||||
|
||||
with self.lock_file_ops:
|
||||
if cache_key in self.hash_cache:
|
||||
return self.hash_cache[cache_key]
|
||||
|
||||
if cache_key in self.scan_events:
|
||||
event = self.scan_events[cache_key]
|
||||
needs_scan = False
|
||||
else:
|
||||
event = threading.Event()
|
||||
self.scan_events[cache_key] = event
|
||||
needs_scan = True
|
||||
|
||||
if not needs_scan:
|
||||
event.wait(timeout=60)
|
||||
with self.lock_file_ops:
|
||||
return self.hash_cache.get(cache_key, False)
|
||||
|
||||
try:
|
||||
result = self.scan_engine(norm_path)
|
||||
with self.lock_file_ops:
|
||||
if len(self.hash_cache) > 10000:
|
||||
for k in list(self.hash_cache.keys())[:1000]:
|
||||
del self.hash_cache[k]
|
||||
|
||||
self.hash_cache[cache_key] = result
|
||||
return result
|
||||
|
||||
finally:
|
||||
with self.lock_file_ops:
|
||||
if cache_key in self.scan_events:
|
||||
self.scan_events[cache_key].set()
|
||||
del self.scan_events[cache_key]
|
||||
|
||||
gc.collect()
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def _scan_result_messages(self, count=None, scanned=None, elapsed=None):
|
||||
if count is None or scanned is None or elapsed is None:
|
||||
with self.lock_virus:
|
||||
count = len(getattr(self, 'virus_results', [])) if count is None else count
|
||||
scanned = getattr(self, 'scan_count', 0) if scanned is None else scanned
|
||||
scan_start = getattr(self, 'scan_start', time.time())
|
||||
elapsed = int(time.time() - scan_start) if elapsed is None else elapsed
|
||||
|
||||
return {
|
||||
"traditional_switch": f"發現 {count} 個病毒,掃描 {scanned} 個檔案,耗時 {elapsed} 秒",
|
||||
"simplified_switch": f"发现 {count} 个病毒,扫描 {scanned} 个文件,耗时 {elapsed} 秒",
|
||||
"english_switch": f"Found {count} viruses, scanned {scanned} files, time {elapsed}s",
|
||||
"japanese_switch": f"{count} 個のウイルスを発見し、{scanned} 個のファイルをスキャンしました(所要時間 {elapsed} 秒)",
|
||||
"korean_switch": f"바이러스 {count}개 발견, 파일 {scanned}개 검사, 소요 시간 {elapsed}초",
|
||||
"french_switch": f"{count} virus trouvés, {scanned} fichiers analysés, temps {elapsed}s",
|
||||
"spanish_switch": f"Se encontraron {count} virus, {scanned} archivos escaneados, tiempo {elapsed}s",
|
||||
"hindi_switch": f"{count} वायरस मिले, {scanned} फ़ाइलें स्कैन की गईं, समय {elapsed}s",
|
||||
"arabic_switch": f"تم العثور على {count} فيروسات، تم فحص {scanned} ملفات، الوقت {elapsed} ثانية",
|
||||
"russian_switch": f"Найдено {count} вирусов, проверено {scanned} файлов, время {elapsed}с",
|
||||
"slovenian_switch": f"Najdenih {count} virusov, skeniranih {scanned} datotek, čas {elapsed}s"
|
||||
}
|
||||
|
||||
def _is_scan_cancel_requested(self):
|
||||
with self.lock_virus:
|
||||
return getattr(self, 'scan_stop_requested', False)
|
||||
|
||||
def _finish_scan_cancelled(self, messages=None):
|
||||
with self.lock_virus:
|
||||
count = len(getattr(self, 'virus_results', []))
|
||||
scanned = getattr(self, 'scan_count', 0)
|
||||
scan_start = getattr(self, 'scan_start', time.time())
|
||||
elapsed = int(time.time() - scan_start)
|
||||
self.scan_running = False
|
||||
self.scan_preparing = False
|
||||
self.scan_finished = True
|
||||
self.scan_stop_requested = False
|
||||
|
||||
messages = messages or self._scan_result_messages(count, scanned, elapsed)
|
||||
|
||||
if self._window:
|
||||
js_cmd = f"if(window.finishScan) window.finishScan({json.dumps(messages)}, {count});"
|
||||
self.ui_queue.put(js_cmd)
|
||||
|
||||
return False
|
||||
|
||||
def _begin_scan_preparation(self):
|
||||
with self.lock_virus:
|
||||
if getattr(self, 'scan_running', False) or getattr(self, 'scan_preparing', False):
|
||||
return False
|
||||
|
||||
self.scan_preparing = True
|
||||
self.scan_stop_requested = False
|
||||
self.scan_finished = False
|
||||
self.virus_results = []
|
||||
self.scan_count = 0
|
||||
self.scan_start = time.time()
|
||||
return True
|
||||
|
||||
def start_scan(self, targets, from_preparing=False):
|
||||
with self.lock_virus:
|
||||
if getattr(self, 'scan_running', False) or (getattr(self, 'scan_preparing', False) and not from_preparing):
|
||||
return False
|
||||
|
||||
if getattr(self, 'scan_stop_requested', False):
|
||||
self.scan_running = False
|
||||
self.scan_preparing = False
|
||||
self.scan_finished = True
|
||||
return False
|
||||
|
||||
self.scan_preparing = False
|
||||
self.scan_running = True
|
||||
self.scan_finished = False
|
||||
self.scan_stop_requested = False
|
||||
self.virus_results = []
|
||||
self.scan_count = 0
|
||||
self.scan_start = time.time()
|
||||
|
||||
self.scan_pool.submit(self.scan_worker, targets)
|
||||
return True
|
||||
|
||||
def scan_worker(self, targets):
|
||||
last_update = 0.0
|
||||
try:
|
||||
for file_path, is_explicit in self.yield_files(targets):
|
||||
with self.lock_virus:
|
||||
if not self.scan_running or getattr(self, 'scan_stop_requested', False):
|
||||
break
|
||||
|
||||
norm_path = self.norm_path(file_path)
|
||||
if not norm_path:
|
||||
continue
|
||||
|
||||
current_time = time.time()
|
||||
if current_time - last_update >= 0.05:
|
||||
if self._window:
|
||||
js_cmd = f"if(window.updateScanProgress) window.updateScanProgress({json.dumps(norm_path.replace(os.sep, '/'))});"
|
||||
self.ui_queue.put(js_cmd)
|
||||
|
||||
last_update = current_time
|
||||
|
||||
was_locked = False
|
||||
try:
|
||||
with self.lock_file_ops:
|
||||
if norm_path in self.virus_lock:
|
||||
self.lock_file(norm_path, False)
|
||||
was_locked = True
|
||||
|
||||
if self.is_in_whitelist(norm_path):
|
||||
continue
|
||||
|
||||
with self.lock_virus:
|
||||
if not self.scan_running or getattr(self, 'scan_stop_requested', False):
|
||||
break
|
||||
self.scan_count += 1
|
||||
|
||||
with self.lock_config:
|
||||
ext_filter = self.pyas_config.get("suffix_switch", True)
|
||||
suffix = self.pyas_config.get("suffix", [])
|
||||
|
||||
if not is_explicit and ext_filter and os.path.splitext(norm_path)[-1].lower() not in suffix:
|
||||
continue
|
||||
|
||||
result = self.safe_scan_engine(norm_path)
|
||||
with self.lock_virus:
|
||||
if not self.scan_running or getattr(self, 'scan_stop_requested', False):
|
||||
break
|
||||
|
||||
if result:
|
||||
with self.lock_virus:
|
||||
self.virus_results.append(norm_path)
|
||||
|
||||
if self._window:
|
||||
js_cmd = f"if(window.addVirusResult) window.addVirusResult({json.dumps(result)}, {json.dumps(norm_path.replace(os.sep, '/'))});"
|
||||
self.ui_queue.put(js_cmd)
|
||||
|
||||
self.write_log("SCAN", "Virus Detected", source=norm_path, file_hash=self.calc_file_hash(norm_path))
|
||||
|
||||
self.cloud_check(norm_path)
|
||||
except Exception as e:
|
||||
self.write_log("WARN", "Scan Engine", source=norm_path, detail=str(e), success=False)
|
||||
|
||||
finally:
|
||||
if was_locked:
|
||||
self.lock_file(norm_path, True)
|
||||
|
||||
finally:
|
||||
with self.lock_virus:
|
||||
cancelled = getattr(self, 'scan_stop_requested', False)
|
||||
self.scan_running = False
|
||||
self.scan_preparing = False
|
||||
self.scan_finished = True
|
||||
self.scan_stop_requested = False
|
||||
count, scanned, elapsed = len(self.virus_results), self.scan_count, int(time.time() - self.scan_start)
|
||||
|
||||
messages = self._scan_result_messages(count, scanned, elapsed)
|
||||
log_detail = f"Found {count} viruses, scanned {scanned} files, time {elapsed}s"
|
||||
|
||||
if self._window:
|
||||
js_cmd = f"if(window.finishScan) window.finishScan({json.dumps(messages)}, {count});"
|
||||
self.ui_queue.put(js_cmd)
|
||||
|
||||
self.write_log("INFO", "Scan Completed", detail=log_detail)
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def stop_scan(self):
|
||||
with self.lock_virus:
|
||||
active = getattr(self, 'scan_running', False) or getattr(self, 'scan_preparing', False)
|
||||
if active:
|
||||
self.scan_stop_requested = True
|
||||
self.scan_running = False
|
||||
return active
|
||||
|
||||
def trigger_scan(self, method):
|
||||
targets = []
|
||||
|
||||
if not self._begin_scan_preparation():
|
||||
return False
|
||||
|
||||
try:
|
||||
if method == "smart":
|
||||
for folder in ["Desktop", "Downloads", "AppData"]:
|
||||
if self._is_scan_cancel_requested():
|
||||
return self._finish_scan_cancelled()
|
||||
|
||||
fp = os.path.join(self.path_user, folder)
|
||||
if os.path.exists(fp):
|
||||
targets.append(fp)
|
||||
|
||||
if self._is_scan_cancel_requested():
|
||||
return self._finish_scan_cancelled()
|
||||
|
||||
if os.path.exists(self.path_config):
|
||||
targets.append(self.path_config)
|
||||
|
||||
buf = ctypes.create_unicode_buffer(1024)
|
||||
max_address = 0x7FFFFFFFFFFF if ctypes.sizeof(ctypes.c_void_p) == 8 else 0x7FFFFFFF
|
||||
system_dir = self.path_system.lower()
|
||||
|
||||
for proc in self.get_process_list():
|
||||
if self._is_scan_cancel_requested():
|
||||
return self._finish_scan_cancelled()
|
||||
|
||||
if proc["path"] and proc["path"] != "None":
|
||||
targets.append(proc["path"])
|
||||
|
||||
pid = proc["pid"]
|
||||
if pid <= 4:
|
||||
continue
|
||||
|
||||
h_process = self.kernel32.OpenProcess(0x1000, False, pid)
|
||||
if h_process:
|
||||
try:
|
||||
address = 0
|
||||
mbi = MEMORY_BASIC_INFORMATION()
|
||||
while address < max_address and not self._is_scan_cancel_requested() and self.kernel32.VirtualQueryEx(h_process, ctypes.c_void_p(address), ctypes.byref(mbi), ctypes.sizeof(mbi)):
|
||||
if mbi.State == 0x1000 and mbi.Type == 0x1000000:
|
||||
if self.psapi.GetMappedFileNameW(h_process, ctypes.c_void_p(address), buf, 1024):
|
||||
raw_path = buf.value
|
||||
if raw_path.startswith("\\"):
|
||||
raw_path = self.device_path_to_drive(raw_path)
|
||||
|
||||
file_path = self.norm_path(raw_path)
|
||||
if file_path and not file_path.lower().startswith(system_dir):
|
||||
targets.append(file_path)
|
||||
|
||||
if mbi.RegionSize == 0:
|
||||
break
|
||||
address += mbi.RegionSize
|
||||
finally:
|
||||
self.kernel32.CloseHandle(h_process)
|
||||
|
||||
if self._is_scan_cancel_requested():
|
||||
return self._finish_scan_cancelled()
|
||||
|
||||
if not self.start_scan(list(set(targets)), from_preparing=True):
|
||||
return self._finish_scan_cancelled()
|
||||
|
||||
return True
|
||||
|
||||
elif method == "file":
|
||||
targets = self.select_files()
|
||||
if self._is_scan_cancel_requested():
|
||||
return self._finish_scan_cancelled()
|
||||
|
||||
if targets:
|
||||
if not self.start_scan(targets, from_preparing=True):
|
||||
return self._finish_scan_cancelled()
|
||||
return True
|
||||
|
||||
return self._finish_scan_cancelled()
|
||||
|
||||
elif method == "path":
|
||||
targets = self.select_folder()
|
||||
if self._is_scan_cancel_requested():
|
||||
return self._finish_scan_cancelled()
|
||||
|
||||
if targets:
|
||||
if not self.start_scan(targets, from_preparing=True):
|
||||
return self._finish_scan_cancelled()
|
||||
return True
|
||||
|
||||
return self._finish_scan_cancelled()
|
||||
|
||||
elif method == "full":
|
||||
targets = [f"{chr(d)}:/" for d in range(65, 91) if os.path.exists(f"{chr(d)}:/")]
|
||||
if self._is_scan_cancel_requested() or not self.start_scan(targets, from_preparing=True):
|
||||
return self._finish_scan_cancelled()
|
||||
return True
|
||||
|
||||
return self._finish_scan_cancelled()
|
||||
|
||||
except Exception as e:
|
||||
self.write_log("WARN", "trigger_scan", detail=str(e), success=False)
|
||||
return self._finish_scan_cancelled()
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def _queue_virus_delete(self, file_path):
|
||||
self._schedule_file_task("delete", file_path, self._retry_virus_delete, 0.5)
|
||||
|
||||
def _retry_virus_delete(self, file_path):
|
||||
file_hash = self.calc_file_hash(file_path)
|
||||
try:
|
||||
with self.lock_file_ops:
|
||||
if file_path in self.virus_lock:
|
||||
self.lock_file(file_path, False)
|
||||
|
||||
target_key = os.path.normcase(self.norm_path(file_path, must_exist=False) or file_path)
|
||||
for proc in self.get_process_list():
|
||||
proc_path = proc.get("path")
|
||||
if not proc_path or proc_path == "None":
|
||||
continue
|
||||
proc_key = os.path.normcase(self.norm_path(proc_path, must_exist=False) or proc_path)
|
||||
if proc_key == target_key:
|
||||
self.kill_process(proc["pid"])
|
||||
|
||||
try:
|
||||
os.chmod(file_path, stat.S_IWRITE)
|
||||
except Exception:
|
||||
pass
|
||||
os.remove(file_path)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.lock_file(file_path, True, quiet=True)
|
||||
self._queue_virus_delete(file_path)
|
||||
self.write_log("INFO", "Virus Delete Deferred", source=file_path, detail=str(e))
|
||||
return
|
||||
|
||||
self.remove_list_items("quarantine", [file_path])
|
||||
with self.lock_virus:
|
||||
target_key = os.path.normcase(self.norm_path(file_path, must_exist=False) or file_path)
|
||||
self.virus_results = [
|
||||
result for result in self.virus_results
|
||||
if os.path.normcase(self.norm_path(result, must_exist=False) or result) != target_key
|
||||
]
|
||||
self.write_log("INFO", "Virus Delete", source=file_path, file_hash=file_hash, operate=True, success=True)
|
||||
|
||||
def solve_scan(self, file_paths):
|
||||
deleted_paths = []
|
||||
proc_map = {}
|
||||
for proc in self.get_process_list():
|
||||
if proc["path"] and proc["path"] != "None":
|
||||
|
||||
norm_p = self.norm_path(proc["path"], must_exist=False)
|
||||
if norm_p:
|
||||
proc_map.setdefault(os.path.normcase(norm_p), []).append(proc["pid"])
|
||||
|
||||
last_update = 0.0
|
||||
with self.lock_virus:
|
||||
deleted_set = set()
|
||||
for raw_path in file_paths:
|
||||
|
||||
path = self.norm_path(raw_path, must_exist=False)
|
||||
if not path:
|
||||
continue
|
||||
|
||||
current_time = time.time()
|
||||
if current_time - last_update >= 0.05:
|
||||
if self._window:
|
||||
self._window.evaluate_js(f"if(window.updateDeleteProgress) window.updateDeleteProgress({json.dumps(path.replace(os.sep, '/'))});")
|
||||
|
||||
last_update = current_time
|
||||
|
||||
try:
|
||||
if path in self.virus_lock:
|
||||
self.lock_file(path, False)
|
||||
|
||||
path_key = os.path.normcase(path)
|
||||
if path_key in proc_map:
|
||||
for pid in proc_map[path_key]:
|
||||
self.kill_process(pid)
|
||||
|
||||
try:
|
||||
os.chmod(path, stat.S_IWRITE)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
file_hash = self.calc_file_hash(path)
|
||||
try:
|
||||
os.remove(path)
|
||||
except Exception as e:
|
||||
self.lock_file(path, True, quiet=True)
|
||||
self._queue_virus_delete(path)
|
||||
self.write_log("INFO", "Virus Delete Deferred", source=path, detail=str(e))
|
||||
continue
|
||||
|
||||
self.remove_list_items("quarantine", [path])
|
||||
deleted_paths.append(raw_path)
|
||||
deleted_set.add(path)
|
||||
self.write_log("INFO", "Virus Delete", source=path, file_hash=file_hash, operate=True, success=True)
|
||||
|
||||
except Exception as e:
|
||||
self.lock_file(path, True)
|
||||
self.write_log("SCAN", "Virus Delete", source=path, file_hash=self.calc_file_hash(path), detail=str(e), operate=True, success=False)
|
||||
|
||||
if deleted_set:
|
||||
self.virus_results = [p for p in self.virus_results if p not in deleted_set]
|
||||
|
||||
remaining = len(self.virus_results)
|
||||
|
||||
messages = {
|
||||
"traditional_switch": f"剩餘 {remaining} 個病毒,已刪除 {len(deleted_paths)} 個檔案。",
|
||||
"simplified_switch": f"剩余 {remaining} 个病毒,已删除 {len(deleted_paths)} 个文件。",
|
||||
"english_switch": f"Remaining {remaining} viruses, deleted {len(deleted_paths)} files.",
|
||||
"japanese_switch": f"残りのウイルス {remaining} 個、削除されたファイル {len(deleted_paths)} 個。",
|
||||
"korean_switch": f"남은 바이러스 {remaining}개, 삭제된 파일 {len(deleted_paths)}개.",
|
||||
"french_switch": f"Virus restants : {remaining}, fichiers supprimés : {len(deleted_paths)}.",
|
||||
"spanish_switch": f"Virus restantes: {remaining}, archivos eliminados: {len(deleted_paths)}.",
|
||||
"hindi_switch": f"शेष वायरस {remaining}, हटाए गए फ़ाइलें {len(deleted_paths)}.",
|
||||
"arabic_switch": f"الفيروسات المتبقية {remaining}، تم حذف {len(deleted_paths)} ملفات.",
|
||||
"russian_switch": f"Осталось вирусов: {remaining}, удалено файлов: {len(deleted_paths)}.",
|
||||
"slovenian_switch": f"Preostalih virusov: {remaining}, izbrisanih datotek: {len(deleted_paths)}."
|
||||
}
|
||||
if self._window:
|
||||
self._window.evaluate_js(f"if(window.finishScan) window.finishScan({json.dumps(messages)}, {remaining});")
|
||||
|
||||
return deleted_paths
|
||||
|
||||
def remove_virus_result(self, paths):
|
||||
with self.lock_virus:
|
||||
norm_paths = set(self.norm_path(p, must_exist=False) for p in paths)
|
||||
self.virus_results = [p for p in self.virus_results if p not in norm_paths]
|
||||
return len(self.virus_results)
|
||||
|
||||
####################################################################################################
|
||||
|
||||
def cloud_check(self, file_path):
|
||||
norm_path = self.norm_path(file_path)
|
||||
if not norm_path:
|
||||
return
|
||||
|
||||
cache_key = os.path.normcase(norm_path)
|
||||
with self.lock_file_ops:
|
||||
if cache_key in self.cloud_pending:
|
||||
return
|
||||
|
||||
self.cloud_pending.add(cache_key)
|
||||
|
||||
self.cloud_queue.put(norm_path)
|
||||
|
||||
def cloud_worker(self):
|
||||
while True:
|
||||
try:
|
||||
file_path = self.cloud_queue.get()
|
||||
cache_key = os.path.normcase(file_path)
|
||||
|
||||
try:
|
||||
self.perform_cloud_scan(file_path)
|
||||
finally:
|
||||
with self.lock_file_ops:
|
||||
self.cloud_pending.discard(cache_key)
|
||||
|
||||
self.cloud_queue.task_done()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def perform_cloud_scan(self, file_path):
|
||||
was_locked = False
|
||||
try:
|
||||
with self.lock_config:
|
||||
if not self.pyas_config.get("cloud_switch", True):
|
||||
return False
|
||||
|
||||
api_host, api_key, max_size = self.pyas_config.get("api_host"), self.pyas_config.get("api_key"), self.pyas_config.get("size", 256 * 1024 * 1024)
|
||||
|
||||
if not os.path.exists(file_path) or not os.path.isfile(file_path):
|
||||
return False
|
||||
|
||||
with self.lock_file_ops:
|
||||
if file_path in self.virus_lock:
|
||||
self.lock_file(file_path, False)
|
||||
was_locked = True
|
||||
|
||||
if os.path.getsize(file_path) > max_size:
|
||||
if was_locked:
|
||||
self.lock_file(file_path, True)
|
||||
return False
|
||||
|
||||
file_hash = self.calc_file_hash(file_path)
|
||||
success, sha256 = self.cloud.upload_file(file_path, api_host, api_key, file_hash=file_hash)
|
||||
if was_locked:
|
||||
self.lock_file(file_path, True)
|
||||
was_locked = False
|
||||
|
||||
if not success:
|
||||
self.write_log("WARN", "Cloud API", source=file_path, detail="Failed", success=False)
|
||||
|
||||
except Exception as e:
|
||||
self.write_log("WARN", "perform_cloud_scan", detail=str(e), success=False)
|
||||
|
||||
finally:
|
||||
if was_locked:
|
||||
try:
|
||||
self.lock_file(file_path, True)
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
+1111
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,11 @@
|
||||
import pyinstaller_versionfile
|
||||
|
||||
pyinstaller_versionfile.create_versionfile(
|
||||
output_file="versionfile.txt",
|
||||
version='3.6.5.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,360 @@
|
||||
#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';
|
||||
constexpr auto PYAS_OB_ALTITUDE = L"320000.4101";
|
||||
constexpr auto PYAS_CONNECTION_MAGIC = 0x53415950;
|
||||
constexpr auto PYAS_CONNECTION_VERSION = 1;
|
||||
|
||||
#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 THREAD_IMPERSONATE
|
||||
#define THREAD_IMPERSONATE (0x0100)
|
||||
#endif
|
||||
#ifndef THREAD_DIRECT_IMPERSONATION
|
||||
#define THREAD_DIRECT_IMPERSONATION (0x0200)
|
||||
#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 enum _PYAS_DRIVER_STATE {
|
||||
PyasDriverStateCold = 0,
|
||||
PyasDriverStateStarting = 1,
|
||||
PyasDriverStateRunning = 2,
|
||||
PyasDriverStateStopping = 3,
|
||||
PyasDriverStateStopRetry = 4,
|
||||
PyasDriverStateStopped = 5
|
||||
} PYAS_DRIVER_STATE;
|
||||
|
||||
typedef struct _PYAS_MESSAGE {
|
||||
ULONG MessageCode;
|
||||
ULONG ProcessId;
|
||||
WCHAR Path[MAX_PATH_LEN];
|
||||
} PYAS_MESSAGE, * PPYAS_MESSAGE;
|
||||
|
||||
typedef enum _PYAS_COMMAND {
|
||||
PyasCommandAddWhitelist = 1,
|
||||
PyasCommandRemoveWhitelist = 2,
|
||||
PyasCommandLoadRuleFile = 3,
|
||||
PyasCommandClearRules = 4,
|
||||
PyasCommandAuthorizeUnload = 5,
|
||||
PyasCommandRevokeUnload = 6,
|
||||
PyasCommandQueryState = 7
|
||||
} PYAS_COMMAND;
|
||||
|
||||
typedef struct _PYAS_USER_MESSAGE {
|
||||
ULONG Command;
|
||||
WCHAR Path[MAX_PATH_LEN];
|
||||
} PYAS_USER_MESSAGE, * PPYAS_USER_MESSAGE;
|
||||
|
||||
typedef struct _PYAS_CONNECTION_CONTEXT {
|
||||
ULONG Size;
|
||||
ULONG Version;
|
||||
ULONG Magic;
|
||||
ULONG ProcessId;
|
||||
} PYAS_CONNECTION_CONTEXT, * PPYAS_CONNECTION_CONTEXT;
|
||||
|
||||
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;
|
||||
KSPIN_LOCK PidLock;
|
||||
BOOLEAN Initialized;
|
||||
volatile LONG DriverState;
|
||||
volatile LONG UnloadAuthorized;
|
||||
volatile LONG PortStopping;
|
||||
volatile LONG ClientCloseActive;
|
||||
EX_RUNDOWN_REF PortRundown;
|
||||
KEVENT CleanupCompleteEvent;
|
||||
PVOID ObRegistrationHandle;
|
||||
UNICODE_STRING ClientImagePath;
|
||||
WCHAR ClientImagePathBuffer[MAX_PATH_LEN];
|
||||
} DRIVER_DATA, * PDRIVER_DATA;
|
||||
|
||||
typedef struct _RULE_NODE {
|
||||
struct _RULE_NODE* Next;
|
||||
UNICODE_STRING Pattern;
|
||||
} RULE_NODE, * PRULE_NODE;
|
||||
|
||||
typedef enum _RULE_CATEGORY {
|
||||
RuleCategoryProcess,
|
||||
RuleCategoryFile,
|
||||
RuleCategoryRegistry,
|
||||
RuleCategoryDevice,
|
||||
RuleCategoryMemory,
|
||||
RuleCategoryThread,
|
||||
RuleCategoryUnknown
|
||||
} RULE_CATEGORY;
|
||||
|
||||
#define OP_WRITE 0x00000001
|
||||
#define OP_DELETE 0x00000002
|
||||
#define OP_CREATE 0x00000004
|
||||
#define OP_EXECUTE 0x00000008
|
||||
#define OP_RENAME 0x00000010
|
||||
#define OP_IOCTL 0x00000020
|
||||
#define OP_VM_READ 0x00000040
|
||||
#define OP_VM_WRITE 0x00000080
|
||||
#define OP_TERMINATE 0x00000100
|
||||
#define OP_SUSPEND_RESUME 0x00000200
|
||||
#define OP_DUP_HANDLE 0x00000400
|
||||
#define OP_SET_INFORMATION 0x00000800
|
||||
#define OP_VM_OPERATION 0x00001000
|
||||
#define OP_CREATE_THREAD 0x00002000
|
||||
#define OP_THREAD_SET_CONTEXT 0x00004000
|
||||
#define OP_THREAD_SET_TOKEN 0x00008000
|
||||
#define OP_CREATE_PROCESS 0x00010000
|
||||
#define OP_IMAGE_LOAD 0x00020000
|
||||
#define OP_IMPERSONATE 0x00040000
|
||||
|
||||
#define PYAS_HANDLE_CREATE 0x00000001
|
||||
#define PYAS_HANDLE_DUPLICATE 0x00000002
|
||||
|
||||
#define PYAS_OBJECT_PROCESS 0x00000001
|
||||
#define PYAS_OBJECT_THREAD 0x00000002
|
||||
|
||||
#define PYAS_MEMORY_PRIVATE 0x00000001
|
||||
#define PYAS_MEMORY_MAPPED 0x00000002
|
||||
#define PYAS_MEMORY_IMAGE 0x00000004
|
||||
|
||||
#define PYAS_PROTECT_EXECUTE 0x00000001
|
||||
#define PYAS_PROTECT_EXECUTE_WRITE 0x00000002
|
||||
|
||||
typedef enum _RULE_TRI_STATE {
|
||||
RuleTriAny = 0,
|
||||
RuleTriTrue = 1,
|
||||
RuleTriFalse = 2
|
||||
} RULE_TRI_STATE;
|
||||
|
||||
typedef enum _RULE_OPERATION_MATCH {
|
||||
RuleOperationAny = 0,
|
||||
RuleOperationAll = 1
|
||||
} RULE_OPERATION_MATCH;
|
||||
|
||||
typedef struct _DYNAMIC_RULE {
|
||||
ULONG Code;
|
||||
BOOLEAN Kill;
|
||||
ULONG Priority;
|
||||
RULE_CATEGORY Category;
|
||||
ULONG Operations;
|
||||
RULE_OPERATION_MATCH OperationMatch;
|
||||
BOOLEAN Invalid;
|
||||
|
||||
PRULE_NODE Initiator;
|
||||
PRULE_NODE InitiatorExclude;
|
||||
PRULE_NODE InitiatorParent;
|
||||
PRULE_NODE InitiatorParentExclude;
|
||||
PRULE_NODE InitiatorProcessTree;
|
||||
PRULE_NODE InitiatorProcessTreeExclude;
|
||||
PRULE_NODE Target;
|
||||
PRULE_NODE TargetExclude;
|
||||
PRULE_NODE TargetProcessTree;
|
||||
PRULE_NODE TargetProcessTreeExclude;
|
||||
PRULE_NODE Creator;
|
||||
PRULE_NODE CreatorExclude;
|
||||
PRULE_NODE Parent;
|
||||
PRULE_NODE ParentExclude;
|
||||
PRULE_NODE CommandLine;
|
||||
PRULE_NODE CommandLineExclude;
|
||||
PRULE_NODE Extensions;
|
||||
|
||||
ULONG HandleTypes;
|
||||
ULONG ObjectTypes;
|
||||
ULONG MinimumRiskScore;
|
||||
ULONG MaximumRiskScore;
|
||||
ULONG ThreadMemoryTypes;
|
||||
ULONG ThreadMemoryProtections;
|
||||
ULONGLONG MinimumRegionSize;
|
||||
ULONGLONG MaximumRegionSize;
|
||||
RULE_TRI_STATE ParentMismatch;
|
||||
RULE_TRI_STATE FileOpenNameAvailable;
|
||||
RULE_TRI_STATE SubsystemProcess;
|
||||
|
||||
ULONG Threshold;
|
||||
ULONG TimeWindow;
|
||||
|
||||
struct _DYNAMIC_RULE* Next;
|
||||
} DYNAMIC_RULE, * PDYNAMIC_RULE;
|
||||
|
||||
BOOLEAN EvaluateDeviceRule(HANDLE ProcessId, PULONG OutCode, PBOOLEAN OutKill);
|
||||
|
||||
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 InitializeRegistryProtection(PDRIVER_OBJECT DriverObject);
|
||||
NTSTATUS UninitializeRegistryProtection();
|
||||
|
||||
NTSTATUS InitializeProcessProtection();
|
||||
NTSTATUS UninitializeProcessProtection();
|
||||
BOOLEAN GetProcessRelation(HANDLE ProcessId, PHANDLE ParentProcessId, PHANDLE CreatorProcessId);
|
||||
|
||||
NTSTATUS InitializeMemoryProtection(PDRIVER_OBJECT DriverObject);
|
||||
NTSTATUS UninitializeMemoryProtection(BOOLEAN WaitWithoutTimeout);
|
||||
|
||||
NTSTATUS LoadRulesFromDisk(PUNICODE_STRING RegistryPath);
|
||||
VOID UnloadRules();
|
||||
|
||||
NTSTATUS InitializeRulesEngine();
|
||||
VOID UninitializeRulesEngine();
|
||||
|
||||
VOID AddDynamicWhitelist(PUNICODE_STRING RuleStr);
|
||||
VOID RemoveDynamicWhitelist(PUNICODE_STRING RuleStr);
|
||||
|
||||
NTSTATUS LoadRuleFile(PCUNICODE_STRING FilePath);
|
||||
VOID ClearDynamicRules();
|
||||
|
||||
VOID ProcessNotifyCallbackEx(PEPROCESS Process, HANDLE ProcessId, PPS_CREATE_NOTIFY_INFO CreateInfo);
|
||||
NTSTATUS GetProcessImageName(HANDLE ProcessId, PUNICODE_STRING* ImageName);
|
||||
|
||||
ULONG SafeGetPyasPid();
|
||||
VOID SafeSetPyasPid(ULONG Pid);
|
||||
|
||||
BOOLEAN IsProcessTrusted(HANDLE ProcessId);
|
||||
BOOLEAN WildcardMatch(PCWSTR Pattern, PCWSTR String, USHORT StringLengthBytes);
|
||||
|
||||
BOOLEAN EvaluateProcessCreateRule(
|
||||
HANDLE CreatorPid,
|
||||
HANDLE ParentPid,
|
||||
HANDLE ProcessId,
|
||||
PCUNICODE_STRING TargetPath,
|
||||
PCUNICODE_STRING CommandLine,
|
||||
BOOLEAN FileOpenNameAvailable,
|
||||
BOOLEAN IsSubsystemProcess,
|
||||
PULONG OutCode,
|
||||
PBOOLEAN OutKill
|
||||
);
|
||||
BOOLEAN EvaluateProcessAccessRule(
|
||||
HANDLE SourcePid,
|
||||
HANDLE TargetPid,
|
||||
ULONG RequestedOperations,
|
||||
ULONG HandleType,
|
||||
ULONG ObjectType,
|
||||
PULONG DeniedOperations,
|
||||
PULONG OutCode,
|
||||
PBOOLEAN OutKill
|
||||
);
|
||||
BOOLEAN EvaluateThreadRule(
|
||||
HANDLE SourcePid,
|
||||
HANDLE TargetPid,
|
||||
PVOID StartAddress,
|
||||
ULONG MemoryType,
|
||||
ULONG MemoryProtection,
|
||||
SIZE_T RegionSize,
|
||||
PULONG OutCode,
|
||||
PBOOLEAN OutKill
|
||||
);
|
||||
BOOLEAN EvaluateImageLoadRule(HANDLE ProcessId, PCUNICODE_STRING ImagePath, PIMAGE_INFO ImageInfo, PULONG OutCode, PBOOLEAN OutKill);
|
||||
BOOLEAN EvaluateProcessRule(HANDLE ProcessId, PCUNICODE_STRING TargetPath, PCUNICODE_STRING CommandLine, PULONG OutCode, PBOOLEAN OutKill);
|
||||
BOOLEAN EvaluateFileRule(HANDLE ProcessId, PCUNICODE_STRING TargetPath, ULONG Operation, PVOID WriteBuffer, ULONG WriteLength, PULONG OutCode, PBOOLEAN OutKill);
|
||||
BOOLEAN EvaluateRegistryRule(HANDLE ProcessId, PCUNICODE_STRING KeyName, ULONG Operation, PULONG OutCode, PBOOLEAN OutKill);
|
||||
BOOLEAN EvaluateMemoryRule(HANDLE SourcePid, HANDLE TargetPid, ULONG Operation, PULONG OutCode, PBOOLEAN OutKill);
|
||||
BOOLEAN EvaluateProcessHandleRule(HANDLE SourcePid, HANDLE TargetPid, ULONG Operation, PULONG OutCode, PBOOLEAN OutKill);
|
||||
|
||||
VOID QueueRuleProcessTermination(HANDLE ProcessId, BOOLEAN Kill);
|
||||
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,728 @@
|
||||
#include "DriverCommon.h"
|
||||
|
||||
DRIVER_DATA GlobalData;
|
||||
static BOOLEAN g_ProcessNotifyRegistered = FALSE;
|
||||
static BOOLEAN g_ProcessProtectionInitialized = FALSE;
|
||||
static BOOLEAN g_RegistryProtectionInitialized = FALSE;
|
||||
static BOOLEAN g_RulesEngineInitialized = 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 SelectFailure(NTSTATUS CurrentStatus, NTSTATUS CandidateStatus) {
|
||||
if (!NT_SUCCESS(CurrentStatus)) return CurrentStatus;
|
||||
return CandidateStatus;
|
||||
}
|
||||
|
||||
static NTSTATUS LoadClientIdentityFromRegistry(PUNICODE_STRING RegistryPath) {
|
||||
if (!RegistryPath || !RegistryPath->Buffer || !GlobalData.ClientImagePath.Buffer) return STATUS_INVALID_PARAMETER;
|
||||
|
||||
UNICODE_STRING ValueName = RTL_CONSTANT_STRING(L"ClientImagePath");
|
||||
OBJECT_ATTRIBUTES ObjectAttributes;
|
||||
InitializeObjectAttributes(&ObjectAttributes, RegistryPath, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
|
||||
|
||||
HANDLE KeyHandle = NULL;
|
||||
NTSTATUS Status = ZwOpenKey(&KeyHandle, KEY_READ, &ObjectAttributes);
|
||||
if (!NT_SUCCESS(Status)) return Status;
|
||||
|
||||
ULONG ResultLength = 0;
|
||||
Status = ZwQueryValueKey(KeyHandle, &ValueName, KeyValuePartialInformation, NULL, 0, &ResultLength);
|
||||
if (Status != STATUS_BUFFER_TOO_SMALL && Status != STATUS_BUFFER_OVERFLOW) {
|
||||
ZwClose(KeyHandle);
|
||||
return Status;
|
||||
}
|
||||
|
||||
if (ResultLength == 0 || ResultLength > FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION, Data) + GlobalData.ClientImagePath.MaximumLength) {
|
||||
ZwClose(KeyHandle);
|
||||
return STATUS_NAME_TOO_LONG;
|
||||
}
|
||||
|
||||
PKEY_VALUE_PARTIAL_INFORMATION Information = (PKEY_VALUE_PARTIAL_INFORMATION)PyasAllocate(ResultLength);
|
||||
if (!Information) {
|
||||
ZwClose(KeyHandle);
|
||||
return STATUS_INSUFFICIENT_RESOURCES;
|
||||
}
|
||||
|
||||
Status = ZwQueryValueKey(KeyHandle, &ValueName, KeyValuePartialInformation, Information, ResultLength, &ResultLength);
|
||||
ZwClose(KeyHandle);
|
||||
|
||||
if (NT_SUCCESS(Status)) {
|
||||
if ((Information->Type != REG_SZ && Information->Type != REG_EXPAND_SZ) ||
|
||||
Information->DataLength < sizeof(WCHAR) ||
|
||||
(Information->DataLength % sizeof(WCHAR)) != 0 ||
|
||||
Information->DataLength > GlobalData.ClientImagePath.MaximumLength - sizeof(WCHAR)) {
|
||||
Status = STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
else {
|
||||
RtlZeroMemory(GlobalData.ClientImagePath.Buffer, GlobalData.ClientImagePath.MaximumLength);
|
||||
RtlCopyMemory(GlobalData.ClientImagePath.Buffer, Information->Data, Information->DataLength);
|
||||
|
||||
USHORT CharacterCount = (USHORT)(Information->DataLength / sizeof(WCHAR));
|
||||
while (CharacterCount > 0 && GlobalData.ClientImagePath.Buffer[CharacterCount - 1] == L'\0') {
|
||||
CharacterCount--;
|
||||
}
|
||||
|
||||
if (CharacterCount == 0) {
|
||||
Status = STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
else {
|
||||
GlobalData.ClientImagePath.Buffer[CharacterCount] = L'\0';
|
||||
GlobalData.ClientImagePath.Length = CharacterCount * sizeof(WCHAR);
|
||||
Status = STATUS_SUCCESS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PyasFree(Information);
|
||||
return Status;
|
||||
}
|
||||
|
||||
static BOOLEAN IsAuthorizedClientConnection(PVOID ConnectionContext, ULONG SizeOfContext) {
|
||||
if (!ConnectionContext || SizeOfContext < sizeof(PYAS_CONNECTION_CONTEXT)) return FALSE;
|
||||
if (!GlobalData.ClientImagePath.Buffer || GlobalData.ClientImagePath.Length == 0) return FALSE;
|
||||
if (KeGetCurrentIrql() != PASSIVE_LEVEL) return FALSE;
|
||||
|
||||
PYAS_CONNECTION_CONTEXT Context = { 0 };
|
||||
__try {
|
||||
RtlCopyMemory(&Context, ConnectionContext, sizeof(Context));
|
||||
}
|
||||
__except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (Context.Size < sizeof(PYAS_CONNECTION_CONTEXT)) return FALSE;
|
||||
if (Context.Version != PYAS_CONNECTION_VERSION) return FALSE;
|
||||
if (Context.Magic != PYAS_CONNECTION_MAGIC) return FALSE;
|
||||
if (Context.ProcessId != (ULONG)(ULONG_PTR)PsGetCurrentProcessId()) return FALSE;
|
||||
|
||||
PUNICODE_STRING ImagePath = NULL;
|
||||
NTSTATUS Status = SeLocateProcessImageName(PsGetCurrentProcess(), &ImagePath);
|
||||
if (!NT_SUCCESS(Status) || !ImagePath || !ImagePath->Buffer) {
|
||||
if (ImagePath) ExFreePool(ImagePath);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
BOOLEAN Authorized = RtlEqualUnicodeString(ImagePath, &GlobalData.ClientImagePath, TRUE);
|
||||
ExFreePool(ImagePath);
|
||||
return Authorized;
|
||||
}
|
||||
|
||||
static NTSTATUS UnregisterRuntimeCallbacksPass(BOOLEAN WaitWithoutTimeout) {
|
||||
NTSTATUS Status = STATUS_SUCCESS;
|
||||
|
||||
NTSTATUS MemoryStatus = UninitializeMemoryProtection(WaitWithoutTimeout);
|
||||
if (!NT_SUCCESS(MemoryStatus)) {
|
||||
Status = SelectFailure(Status, MemoryStatus);
|
||||
}
|
||||
|
||||
if (g_ProcessNotifyRegistered) {
|
||||
NTSTATUS ProcessNotifyStatus = PsSetCreateProcessNotifyRoutineEx(
|
||||
ProcessNotifyCallbackEx,
|
||||
TRUE
|
||||
);
|
||||
|
||||
if (NT_SUCCESS(ProcessNotifyStatus) || ProcessNotifyStatus == STATUS_INVALID_PARAMETER) {
|
||||
g_ProcessNotifyRegistered = FALSE;
|
||||
}
|
||||
else {
|
||||
Status = SelectFailure(Status, ProcessNotifyStatus);
|
||||
}
|
||||
}
|
||||
|
||||
if (!g_ProcessNotifyRegistered && g_ProcessProtectionInitialized) {
|
||||
NTSTATUS ProcessStatus = UninitializeProcessProtection();
|
||||
if (NT_SUCCESS(ProcessStatus)) {
|
||||
g_ProcessProtectionInitialized = FALSE;
|
||||
}
|
||||
else {
|
||||
Status = SelectFailure(Status, ProcessStatus);
|
||||
}
|
||||
}
|
||||
|
||||
if (g_RegistryProtectionInitialized) {
|
||||
NTSTATUS RegistryStatus = UninitializeRegistryProtection();
|
||||
if (NT_SUCCESS(RegistryStatus)) {
|
||||
g_RegistryProtectionInitialized = FALSE;
|
||||
}
|
||||
else {
|
||||
Status = SelectFailure(Status, RegistryStatus);
|
||||
}
|
||||
}
|
||||
|
||||
return Status;
|
||||
}
|
||||
|
||||
static NTSTATUS UnregisterRuntimeCallbacks(BOOLEAN WaitWithoutTimeout) {
|
||||
for (;;) {
|
||||
NTSTATUS Status = UnregisterRuntimeCallbacksPass(WaitWithoutTimeout);
|
||||
if (NT_SUCCESS(Status) || !WaitWithoutTimeout) return Status;
|
||||
|
||||
LARGE_INTEGER Delay;
|
||||
Delay.QuadPart = -(10LL * 10 * 1000);
|
||||
KeDelayExecutionThread(KernelMode, FALSE, &Delay);
|
||||
}
|
||||
}
|
||||
|
||||
static PEPROCESS DetachConnectedProcessReference(PEPROCESS ExpectedProcess) {
|
||||
PEPROCESS Process = NULL;
|
||||
|
||||
KIRQL OldIrql;
|
||||
KeAcquireSpinLock(&GlobalData.PortMutex, &OldIrql);
|
||||
|
||||
if (!ExpectedProcess || GlobalData.PyasProcess == ExpectedProcess) {
|
||||
Process = GlobalData.PyasProcess;
|
||||
GlobalData.PyasProcess = NULL;
|
||||
}
|
||||
|
||||
KeReleaseSpinLock(&GlobalData.PortMutex, OldIrql);
|
||||
return Process;
|
||||
}
|
||||
|
||||
static VOID ReleaseConnectedProcessReference() {
|
||||
PEPROCESS Process = DetachConnectedProcessReference(NULL);
|
||||
|
||||
SafeSetPyasPid(0);
|
||||
InterlockedExchange(&GlobalData.UnloadAuthorized, 0);
|
||||
|
||||
if (Process) {
|
||||
ObDereferenceObject(Process);
|
||||
}
|
||||
}
|
||||
|
||||
static VOID CloseCurrentClientPort() {
|
||||
if (InterlockedCompareExchange(&GlobalData.ClientCloseActive, 1, 0) != 0) return;
|
||||
|
||||
PFLT_FILTER FilterHandle = NULL;
|
||||
PFLT_PORT ClientPort = NULL;
|
||||
|
||||
KIRQL OldIrql;
|
||||
KeAcquireSpinLock(&GlobalData.PortMutex, &OldIrql);
|
||||
FilterHandle = GlobalData.FilterHandle;
|
||||
ClientPort = GlobalData.ClientPort;
|
||||
GlobalData.ClientPort = NULL;
|
||||
KeReleaseSpinLock(&GlobalData.PortMutex, OldIrql);
|
||||
|
||||
if (FilterHandle && ClientPort) {
|
||||
FltCloseClientPort(FilterHandle, &ClientPort);
|
||||
}
|
||||
|
||||
InterlockedExchange(&GlobalData.ClientCloseActive, 0);
|
||||
}
|
||||
|
||||
static VOID WaitForClientPortClose() {
|
||||
LARGE_INTEGER Delay;
|
||||
Delay.QuadPart = -(10LL * 1000);
|
||||
|
||||
while (InterlockedCompareExchange(&GlobalData.ClientCloseActive, 0, 0) != 0) {
|
||||
KeDelayExecutionThread(KernelMode, FALSE, &Delay);
|
||||
}
|
||||
}
|
||||
|
||||
static VOID ReleaseFilterResources() {
|
||||
InterlockedExchange(&GlobalData.PortStopping, 1);
|
||||
|
||||
if (GlobalData.ServerPort) {
|
||||
PFLT_PORT ServerPort = GlobalData.ServerPort;
|
||||
GlobalData.ServerPort = NULL;
|
||||
FltCloseCommunicationPort(ServerPort);
|
||||
}
|
||||
|
||||
CloseCurrentClientPort();
|
||||
WaitForClientPortClose();
|
||||
ExWaitForRundownProtectionRelease(&GlobalData.PortRundown);
|
||||
|
||||
if (GlobalData.FilterHandle) {
|
||||
PFLT_FILTER FilterHandle = GlobalData.FilterHandle;
|
||||
FltUnregisterFilter(FilterHandle);
|
||||
GlobalData.FilterHandle = NULL;
|
||||
}
|
||||
|
||||
ReleaseConnectedProcessReference();
|
||||
}
|
||||
|
||||
static NTSTATUS CleanupDriverState(BOOLEAN WaitWithoutTimeout) {
|
||||
NTSTATUS Status = UnregisterRuntimeCallbacks(WaitWithoutTimeout);
|
||||
if (!NT_SUCCESS(Status)) return Status;
|
||||
|
||||
GlobalData.Initialized = FALSE;
|
||||
ReleaseFilterResources();
|
||||
|
||||
if (g_RulesEngineInitialized) {
|
||||
UnloadRules();
|
||||
UninitializeRulesEngine();
|
||||
g_RulesEngineInitialized = FALSE;
|
||||
}
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static NTSTATUS BeginUnload(BOOLEAN MandatoryUnload) {
|
||||
for (;;) {
|
||||
LONG State = InterlockedCompareExchange(
|
||||
&GlobalData.DriverState,
|
||||
PyasDriverStateCold,
|
||||
PyasDriverStateCold
|
||||
);
|
||||
|
||||
if (State == PyasDriverStateStopped) return STATUS_SUCCESS;
|
||||
|
||||
if (State == PyasDriverStateStopping) {
|
||||
LARGE_INTEGER Timeout;
|
||||
PLARGE_INTEGER TimeoutPointer = NULL;
|
||||
|
||||
if (!MandatoryUnload) {
|
||||
Timeout.QuadPart = -(2LL * 10 * 1000 * 1000);
|
||||
TimeoutPointer = &Timeout;
|
||||
}
|
||||
|
||||
NTSTATUS WaitStatus = KeWaitForSingleObject(
|
||||
&GlobalData.CleanupCompleteEvent,
|
||||
Executive,
|
||||
KernelMode,
|
||||
FALSE,
|
||||
TimeoutPointer
|
||||
);
|
||||
|
||||
if (!MandatoryUnload && WaitStatus == STATUS_TIMEOUT) {
|
||||
return STATUS_DEVICE_BUSY;
|
||||
}
|
||||
|
||||
if (!NT_SUCCESS(WaitStatus)) return WaitStatus;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (State != PyasDriverStateRunning && State != PyasDriverStateStopRetry) {
|
||||
return STATUS_DEVICE_NOT_READY;
|
||||
}
|
||||
|
||||
if (!MandatoryUnload && InterlockedCompareExchange(&GlobalData.UnloadAuthorized, 0, 1) != 1) {
|
||||
return STATUS_ACCESS_DENIED;
|
||||
}
|
||||
|
||||
if (MandatoryUnload) {
|
||||
InterlockedExchange(&GlobalData.UnloadAuthorized, 0);
|
||||
}
|
||||
|
||||
if (InterlockedCompareExchange(
|
||||
&GlobalData.DriverState,
|
||||
PyasDriverStateStopping,
|
||||
State
|
||||
) != State) {
|
||||
if (!MandatoryUnload) return STATUS_DEVICE_BUSY;
|
||||
continue;
|
||||
}
|
||||
|
||||
KeClearEvent(&GlobalData.CleanupCompleteEvent);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
static NTSTATUS DriverUnload(FLT_FILTER_UNLOAD_FLAGS Flags) {
|
||||
BOOLEAN MandatoryUnload = FlagOn(Flags, FLTFL_FILTER_UNLOAD_MANDATORY) ? TRUE : FALSE;
|
||||
|
||||
NTSTATUS Status = BeginUnload(MandatoryUnload);
|
||||
if (!NT_SUCCESS(Status)) return Status;
|
||||
|
||||
LONG State = InterlockedCompareExchange(
|
||||
&GlobalData.DriverState,
|
||||
PyasDriverStateCold,
|
||||
PyasDriverStateCold
|
||||
);
|
||||
|
||||
if (State == PyasDriverStateStopped) return STATUS_SUCCESS;
|
||||
|
||||
Status = CleanupDriverState(MandatoryUnload);
|
||||
|
||||
InterlockedExchange(
|
||||
&GlobalData.DriverState,
|
||||
NT_SUCCESS(Status) ? PyasDriverStateStopped : PyasDriverStateStopRetry
|
||||
);
|
||||
|
||||
KeSetEvent(&GlobalData.CleanupCompleteEvent, IO_NO_INCREMENT, FALSE);
|
||||
return Status;
|
||||
}
|
||||
|
||||
static NTSTATUS PortMessage(
|
||||
PVOID PortCookie,
|
||||
PVOID InputBuffer,
|
||||
ULONG InputBufferLength,
|
||||
PVOID OutputBuffer,
|
||||
ULONG OutputBufferLength,
|
||||
PULONG ReturnOutputBufferLength
|
||||
) {
|
||||
if (ReturnOutputBufferLength) *ReturnOutputBufferLength = 0;
|
||||
|
||||
if (InterlockedCompareExchange(&GlobalData.PortStopping, 0, 0) != 0) {
|
||||
return STATUS_DELETE_PENDING;
|
||||
}
|
||||
|
||||
if (!ExAcquireRundownProtection(&GlobalData.PortRundown)) {
|
||||
return STATUS_DELETE_PENDING;
|
||||
}
|
||||
|
||||
NTSTATUS Status = STATUS_SUCCESS;
|
||||
PYAS_USER_MESSAGE Message = { 0 };
|
||||
UNICODE_STRING Path = { 0 };
|
||||
LONG DriverState = PyasDriverStateCold;
|
||||
ULONG StateValue = PyasDriverStateCold;
|
||||
|
||||
if (!PortCookie || PortCookie != GlobalData.PyasProcess) {
|
||||
Status = STATUS_ACCESS_DENIED;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
if (!InputBuffer || InputBufferLength < sizeof(PYAS_USER_MESSAGE)) {
|
||||
Status = STATUS_INVALID_PARAMETER;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
__try {
|
||||
RtlCopyMemory(&Message, InputBuffer, sizeof(Message));
|
||||
}
|
||||
__except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
Status = GetExceptionCode();
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
Message.Path[MAX_PATH_LEN - 1] = L'\0';
|
||||
|
||||
if (Message.Command == PyasCommandQueryState) {
|
||||
if (!OutputBuffer || OutputBufferLength < sizeof(ULONG) || !ReturnOutputBufferLength) {
|
||||
Status = STATUS_BUFFER_TOO_SMALL;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
StateValue = (ULONG)InterlockedCompareExchange(
|
||||
&GlobalData.DriverState,
|
||||
PyasDriverStateCold,
|
||||
PyasDriverStateCold
|
||||
);
|
||||
|
||||
__try {
|
||||
*(PULONG)OutputBuffer = StateValue;
|
||||
*ReturnOutputBufferLength = sizeof(ULONG);
|
||||
}
|
||||
__except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
Status = GetExceptionCode();
|
||||
}
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
if (Message.Command == PyasCommandAuthorizeUnload) {
|
||||
DriverState = InterlockedCompareExchange(
|
||||
&GlobalData.DriverState,
|
||||
PyasDriverStateCold,
|
||||
PyasDriverStateCold
|
||||
);
|
||||
|
||||
if (DriverState != PyasDriverStateRunning && DriverState != PyasDriverStateStopRetry) {
|
||||
Status = STATUS_DEVICE_NOT_READY;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
InterlockedExchange(&GlobalData.UnloadAuthorized, 1);
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
if (Message.Command == PyasCommandRevokeUnload) {
|
||||
InterlockedExchange(&GlobalData.UnloadAuthorized, 0);
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
if (Message.Command < PyasCommandAddWhitelist || Message.Command > PyasCommandClearRules) {
|
||||
Status = STATUS_INVALID_DEVICE_REQUEST;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
DriverState = InterlockedCompareExchange(
|
||||
&GlobalData.DriverState,
|
||||
PyasDriverStateCold,
|
||||
PyasDriverStateCold
|
||||
);
|
||||
|
||||
if (DriverState != PyasDriverStateRunning) {
|
||||
Status = STATUS_DEVICE_NOT_READY;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
RtlInitUnicodeString(&Path, Message.Path);
|
||||
|
||||
if (Message.Command == PyasCommandAddWhitelist) {
|
||||
if (Path.Length == 0) {
|
||||
Status = STATUS_INVALID_PARAMETER;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
AddDynamicWhitelist(&Path);
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
if (Message.Command == PyasCommandRemoveWhitelist) {
|
||||
if (Path.Length == 0) {
|
||||
Status = STATUS_INVALID_PARAMETER;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
RemoveDynamicWhitelist(&Path);
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
if (Message.Command == PyasCommandLoadRuleFile) {
|
||||
if (Path.Length == 0) {
|
||||
Status = STATUS_INVALID_PARAMETER;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
Status = LoadRuleFile(&Path);
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
ClearDynamicRules();
|
||||
|
||||
Exit:
|
||||
ExReleaseRundownProtection(&GlobalData.PortRundown);
|
||||
return Status;
|
||||
}
|
||||
|
||||
static NTSTATUS PortConnect(
|
||||
PFLT_PORT ClientPort,
|
||||
PVOID ServerPortCookie,
|
||||
PVOID ConnectionContext,
|
||||
ULONG SizeOfContext,
|
||||
PVOID* ConnectionPortCookie
|
||||
) {
|
||||
UNREFERENCED_PARAMETER(ServerPortCookie);
|
||||
|
||||
if (!ConnectionPortCookie) return STATUS_INVALID_PARAMETER;
|
||||
if (!IsAuthorizedClientConnection(ConnectionContext, SizeOfContext)) return STATUS_ACCESS_DENIED;
|
||||
*ConnectionPortCookie = NULL;
|
||||
|
||||
if (InterlockedCompareExchange(&GlobalData.PortStopping, 0, 0) != 0) {
|
||||
return STATUS_DELETE_PENDING;
|
||||
}
|
||||
|
||||
LONG DriverState = InterlockedCompareExchange(
|
||||
&GlobalData.DriverState,
|
||||
PyasDriverStateCold,
|
||||
PyasDriverStateCold
|
||||
);
|
||||
|
||||
if (DriverState != PyasDriverStateRunning && DriverState != PyasDriverStateStopRetry) {
|
||||
return STATUS_DEVICE_NOT_READY;
|
||||
}
|
||||
|
||||
if (!ExAcquireRundownProtection(&GlobalData.PortRundown)) {
|
||||
return STATUS_DELETE_PENDING;
|
||||
}
|
||||
|
||||
PEPROCESS ConnectingProcess = PsGetCurrentProcess();
|
||||
ObReferenceObject(ConnectingProcess);
|
||||
|
||||
NTSTATUS Status = STATUS_SUCCESS;
|
||||
KIRQL OldIrql;
|
||||
KeAcquireSpinLock(&GlobalData.PortMutex, &OldIrql);
|
||||
|
||||
DriverState = InterlockedCompareExchange(
|
||||
&GlobalData.DriverState,
|
||||
PyasDriverStateCold,
|
||||
PyasDriverStateCold
|
||||
);
|
||||
|
||||
if (InterlockedCompareExchange(&GlobalData.PortStopping, 0, 0) != 0) {
|
||||
Status = STATUS_DELETE_PENDING;
|
||||
}
|
||||
else if (DriverState != PyasDriverStateRunning && DriverState != PyasDriverStateStopRetry) {
|
||||
Status = STATUS_DEVICE_NOT_READY;
|
||||
}
|
||||
else if (GlobalData.ClientPort || GlobalData.PyasProcess) {
|
||||
Status = STATUS_DEVICE_BUSY;
|
||||
}
|
||||
else {
|
||||
GlobalData.ClientPort = ClientPort;
|
||||
GlobalData.PyasProcess = ConnectingProcess;
|
||||
*ConnectionPortCookie = ConnectingProcess;
|
||||
}
|
||||
|
||||
KeReleaseSpinLock(&GlobalData.PortMutex, OldIrql);
|
||||
|
||||
if (!NT_SUCCESS(Status)) {
|
||||
ObDereferenceObject(ConnectingProcess);
|
||||
ExReleaseRundownProtection(&GlobalData.PortRundown);
|
||||
return Status;
|
||||
}
|
||||
|
||||
SafeSetPyasPid((ULONG)(ULONG_PTR)PsGetCurrentProcessId());
|
||||
InterlockedExchange(&GlobalData.UnloadAuthorized, 0);
|
||||
ExReleaseRundownProtection(&GlobalData.PortRundown);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static VOID PortDisconnect(PVOID ConnectionCookie) {
|
||||
if (!ConnectionCookie) return;
|
||||
|
||||
PEPROCESS Process = DetachConnectedProcessReference((PEPROCESS)ConnectionCookie);
|
||||
if (!Process) return;
|
||||
|
||||
CloseCurrentClientPort();
|
||||
InterlockedExchange(&GlobalData.UnloadAuthorized, 0);
|
||||
SafeSetPyasPid(0);
|
||||
ObDereferenceObject(Process);
|
||||
}
|
||||
|
||||
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
|
||||
) {
|
||||
RtlZeroMemory(&GlobalData, sizeof(GlobalData));
|
||||
GlobalData.DriverObject = DriverObject;
|
||||
GlobalData.ClientImagePath.Buffer = GlobalData.ClientImagePathBuffer;
|
||||
GlobalData.ClientImagePath.MaximumLength = sizeof(GlobalData.ClientImagePathBuffer);
|
||||
KeInitializeSpinLock(&GlobalData.PortMutex);
|
||||
KeInitializeSpinLock(&GlobalData.TrackerMutex);
|
||||
KeInitializeSpinLock(&GlobalData.PidLock);
|
||||
ExInitializeRundownProtection(&GlobalData.PortRundown);
|
||||
KeInitializeEvent(&GlobalData.CleanupCompleteEvent, NotificationEvent, FALSE);
|
||||
InterlockedExchange(&GlobalData.PortStopping, 0);
|
||||
InterlockedExchange(&GlobalData.ClientCloseActive, 0);
|
||||
InterlockedExchange(&GlobalData.DriverState, PyasDriverStateStarting);
|
||||
|
||||
NTSTATUS Status = STATUS_SUCCESS;
|
||||
PSECURITY_DESCRIPTOR SecurityDescriptor = NULL;
|
||||
|
||||
Status = InitializeRulesEngine();
|
||||
if (!NT_SUCCESS(Status)) {
|
||||
InterlockedExchange(&GlobalData.DriverState, PyasDriverStateStopped);
|
||||
KeSetEvent(&GlobalData.CleanupCompleteEvent, IO_NO_INCREMENT, FALSE);
|
||||
return Status;
|
||||
}
|
||||
g_RulesEngineInitialized = TRUE;
|
||||
|
||||
Status = LoadRulesFromDisk(RegistryPath);
|
||||
if (!NT_SUCCESS(Status)) goto Failure;
|
||||
|
||||
Status = LoadClientIdentityFromRegistry(RegistryPath);
|
||||
if (!NT_SUCCESS(Status)) goto Failure;
|
||||
|
||||
Status = FltRegisterFilter(DriverObject, &FilterRegistration, &GlobalData.FilterHandle);
|
||||
if (!NT_SUCCESS(Status)) goto Failure;
|
||||
|
||||
Status = FltBuildDefaultSecurityDescriptor(&SecurityDescriptor, FLT_PORT_ALL_ACCESS);
|
||||
if (!NT_SUCCESS(Status)) goto Failure;
|
||||
|
||||
{
|
||||
UNICODE_STRING PortName;
|
||||
RtlInitUnicodeString(&PortName, PYAS_PORT_NAME);
|
||||
|
||||
OBJECT_ATTRIBUTES ObjectAttributes;
|
||||
InitializeObjectAttributes(
|
||||
&ObjectAttributes,
|
||||
&PortName,
|
||||
OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE,
|
||||
NULL,
|
||||
SecurityDescriptor
|
||||
);
|
||||
|
||||
Status = FltCreateCommunicationPort(
|
||||
GlobalData.FilterHandle,
|
||||
&GlobalData.ServerPort,
|
||||
&ObjectAttributes,
|
||||
NULL,
|
||||
PortConnect,
|
||||
PortDisconnect,
|
||||
PortMessage,
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
FltFreeSecurityDescriptor(SecurityDescriptor);
|
||||
if (!NT_SUCCESS(Status)) goto Failure;
|
||||
|
||||
Status = InitializeRegistryProtection(DriverObject);
|
||||
if (!NT_SUCCESS(Status)) goto Failure;
|
||||
g_RegistryProtectionInitialized = TRUE;
|
||||
|
||||
Status = InitializeProcessProtection();
|
||||
if (!NT_SUCCESS(Status)) goto Failure;
|
||||
g_ProcessProtectionInitialized = TRUE;
|
||||
|
||||
Status = PsSetCreateProcessNotifyRoutineEx(ProcessNotifyCallbackEx, FALSE);
|
||||
if (!NT_SUCCESS(Status)) goto Failure;
|
||||
g_ProcessNotifyRegistered = TRUE;
|
||||
|
||||
Status = InitializeMemoryProtection(DriverObject);
|
||||
if (!NT_SUCCESS(Status)) goto Failure;
|
||||
|
||||
Status = FltStartFiltering(GlobalData.FilterHandle);
|
||||
if (!NT_SUCCESS(Status)) goto Failure;
|
||||
|
||||
GlobalData.Initialized = TRUE;
|
||||
InterlockedExchange(&GlobalData.DriverState, PyasDriverStateRunning);
|
||||
return STATUS_SUCCESS;
|
||||
|
||||
Failure:
|
||||
CleanupDriverState(TRUE);
|
||||
InterlockedExchange(&GlobalData.DriverState, PyasDriverStateStopped);
|
||||
KeSetEvent(&GlobalData.CleanupCompleteEvent, IO_NO_INCREMENT, FALSE);
|
||||
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,131 @@
|
||||
<?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>
|
||||
<AdditionalOptions>/INTEGRITYCHECK %(AdditionalOptions)</AdditionalOptions>
|
||||
</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>
|
||||
<AdditionalOptions>/INTEGRITYCHECK %(AdditionalOptions)</AdditionalOptions>
|
||||
</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="ProtectMemory.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,54 @@
|
||||
<?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>
|
||||
<ClCompile Include="ProtectMemory.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,37 @@
|
||||
#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();
|
||||
ULONG RuleCode = 0;
|
||||
BOOLEAN Kill = FALSE;
|
||||
|
||||
if (EvaluateDeviceRule(Pid, &RuleCode, &Kill)) {
|
||||
UNICODE_STRING MsgStr = RTL_CONSTANT_STRING(L"Disk_Wiper_Attempt");
|
||||
SendMessageToUser(RuleCode, (ULONG)(ULONG_PTR)Pid, MsgStr.Buffer, MsgStr.Length);
|
||||
QueueRuleProcessTermination(Pid, Kill);
|
||||
Data->IoStatus.Status = STATUS_ACCESS_DENIED;
|
||||
return FLT_PREOP_COMPLETE;
|
||||
}
|
||||
}
|
||||
return FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
#include "DriverCommon.h"
|
||||
|
||||
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;
|
||||
NTSTATUS 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) || !nameInfo->Name.Buffer || nameInfo->Name.Length == 0) {
|
||||
FltReleaseFileNameInformation(nameInfo);
|
||||
return FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
}
|
||||
|
||||
HANDLE Pid = PsGetCurrentProcessId();
|
||||
FLT_PREOP_CALLBACK_STATUS callbackStatus = FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
|
||||
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);
|
||||
ULONG DesiredAccess = Data->Iopb->Parameters.Create.SecurityContext->DesiredAccess;
|
||||
|
||||
ULONG Operation = 0;
|
||||
if (IsCreateAction) Operation |= OP_CREATE;
|
||||
if (DesiredAccess & (FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_WRITE_ATTRIBUTES | WRITE_DAC | WRITE_OWNER | GENERIC_WRITE)) Operation |= OP_WRITE;
|
||||
if (DesiredAccess & (DELETE | FILE_DELETE_CHILD)) Operation |= OP_DELETE;
|
||||
|
||||
if (Operation != 0) {
|
||||
ULONG RuleCode = 0;
|
||||
BOOLEAN Kill = FALSE;
|
||||
if (EvaluateFileRule(Pid, &nameInfo->Name, Operation, NULL, 0, &RuleCode, &Kill)) {
|
||||
SendMessageToUser(RuleCode, (ULONG)(ULONG_PTR)Pid, nameInfo->Name.Buffer, nameInfo->Name.Length);
|
||||
QueueRuleProcessTermination(Pid, Kill);
|
||||
Data->IoStatus.Status = STATUS_ACCESS_DENIED;
|
||||
callbackStatus = FLT_PREOP_COMPLETE;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
if (!NT_SUCCESS(FltGetFileNameInformation(Data, FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT, &nameInfo)) || !nameInfo) return FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
if (!NT_SUCCESS(FltParseFileNameInformation(nameInfo)) || !nameInfo->Name.Buffer) {
|
||||
FltReleaseFileNameInformation(nameInfo);
|
||||
return FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
}
|
||||
|
||||
HANDLE Pid = PsGetCurrentProcessId();
|
||||
FLT_PREOP_CALLBACK_STATUS callbackStatus = FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
ULONG Operation = 0;
|
||||
|
||||
if (infoClass == FileDispositionInformation) {
|
||||
Operation = OP_DELETE;
|
||||
}
|
||||
else {
|
||||
Operation = OP_RENAME;
|
||||
}
|
||||
|
||||
ULONG RuleCode = 0;
|
||||
BOOLEAN Kill = FALSE;
|
||||
if (EvaluateFileRule(Pid, &nameInfo->Name, Operation, NULL, 0, &RuleCode, &Kill)) {
|
||||
SendMessageToUser(RuleCode, (ULONG)(ULONG_PTR)Pid, nameInfo->Name.Buffer, nameInfo->Name.Length);
|
||||
QueueRuleProcessTermination(Pid, Kill);
|
||||
Data->IoStatus.Status = STATUS_ACCESS_DENIED;
|
||||
callbackStatus = FLT_PREOP_COMPLETE;
|
||||
}
|
||||
|
||||
FltReleaseFileNameInformation(nameInfo);
|
||||
return callbackStatus;
|
||||
}
|
||||
|
||||
FLT_PREOP_CALLBACK_STATUS ProtectFile_PreWrite(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;
|
||||
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;
|
||||
if (!NT_SUCCESS(FltGetFileNameInformation(Data, FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT, &nameInfo)) || !nameInfo) return FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
if (!NT_SUCCESS(FltParseFileNameInformation(nameInfo)) || !nameInfo->Name.Buffer) {
|
||||
FltReleaseFileNameInformation(nameInfo);
|
||||
return FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
}
|
||||
|
||||
HANDLE Pid = PsGetCurrentProcessId();
|
||||
FLT_PREOP_CALLBACK_STATUS callbackStatus = FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
|
||||
PVOID SystemBuffer = NULL;
|
||||
PMDL Mdl = Data->Iopb->Parameters.Write.MdlAddress;
|
||||
if (!Mdl && 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);
|
||||
|
||||
ULONG RuleCode = 0;
|
||||
BOOLEAN Kill = FALSE;
|
||||
if (EvaluateFileRule(Pid, &nameInfo->Name, OP_WRITE, SystemBuffer, WriteLength, &RuleCode, &Kill)) {
|
||||
SendMessageToUser(RuleCode, (ULONG)(ULONG_PTR)Pid, nameInfo->Name.Buffer, nameInfo->Name.Length);
|
||||
QueueRuleProcessTermination(Pid, Kill);
|
||||
Data->IoStatus.Status = STATUS_ACCESS_DENIED;
|
||||
callbackStatus = FLT_PREOP_COMPLETE;
|
||||
}
|
||||
|
||||
FltReleaseFileNameInformation(nameInfo);
|
||||
return callbackStatus;
|
||||
}
|
||||
|
||||
FLT_PREOP_CALLBACK_STATUS ProtectFile_SetSecurity(PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID* CompletionContext) {
|
||||
UNREFERENCED_PARAMETER(Data);
|
||||
UNREFERENCED_PARAMETER(FltObjects);
|
||||
UNREFERENCED_PARAMETER(CompletionContext);
|
||||
return FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
}
|
||||
|
||||
FLT_PREOP_CALLBACK_STATUS ProtectFile_FileSystemControl(PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID* CompletionContext) {
|
||||
UNREFERENCED_PARAMETER(Data);
|
||||
UNREFERENCED_PARAMETER(FltObjects);
|
||||
UNREFERENCED_PARAMETER(CompletionContext);
|
||||
return FLT_PREOP_SUCCESS_NO_CALLBACK;
|
||||
}
|
||||
@@ -0,0 +1,818 @@
|
||||
#include "DriverCommon.h"
|
||||
|
||||
extern "C" {
|
||||
NTSTATUS ZwOpenThread(
|
||||
PHANDLE ThreadHandle,
|
||||
ACCESS_MASK DesiredAccess,
|
||||
POBJECT_ATTRIBUTES ObjectAttributes,
|
||||
PCLIENT_ID ClientId
|
||||
);
|
||||
|
||||
NTSTATUS ZwQueryInformationThread(
|
||||
HANDLE ThreadHandle,
|
||||
THREADINFOCLASS ThreadInformationClass,
|
||||
PVOID ThreadInformation,
|
||||
ULONG ThreadInformationLength,
|
||||
PULONG ReturnLength
|
||||
);
|
||||
}
|
||||
|
||||
#ifndef THREAD_QUERY_INFORMATION
|
||||
#define THREAD_QUERY_INFORMATION 0x0040
|
||||
#endif
|
||||
|
||||
#define THREAD_QUERY_SET_WIN32_START_ADDRESS 9
|
||||
|
||||
constexpr ULONG PYAS_MEM_IMAGE_TYPE = 0x01000000UL;
|
||||
|
||||
typedef enum _MEMORY_WORK_TYPE {
|
||||
MemoryWorkThreadAnalysis = 1,
|
||||
MemoryWorkViolationReport = 2
|
||||
} MEMORY_WORK_TYPE;
|
||||
|
||||
typedef struct _MEMORY_WORK_ITEM {
|
||||
LIST_ENTRY Entry;
|
||||
MEMORY_WORK_TYPE Type;
|
||||
PEPROCESS SourceProcess;
|
||||
PEPROCESS TargetProcess;
|
||||
HANDLE SourcePid;
|
||||
HANDLE TargetPid;
|
||||
HANDLE ThreadId;
|
||||
ULONG RuleCode;
|
||||
BOOLEAN Kill;
|
||||
WCHAR FallbackMessage[64];
|
||||
} MEMORY_WORK_ITEM, * PMEMORY_WORK_ITEM;
|
||||
|
||||
typedef struct _THREAD_MEMORY_REGION {
|
||||
PVOID StartAddress;
|
||||
ULONG MemoryType;
|
||||
ULONG MemoryProtection;
|
||||
SIZE_T RegionSize;
|
||||
} THREAD_MEMORY_REGION, * PTHREAD_MEMORY_REGION;
|
||||
|
||||
static BOOLEAN g_ThreadNotifyRegistered = FALSE;
|
||||
static BOOLEAN g_LoadImageNotifyRegistered = FALSE;
|
||||
static BOOLEAN g_MemoryProtectionInitialized = FALSE;
|
||||
static BOOLEAN g_MemoryRundownCompleted = FALSE;
|
||||
static EX_RUNDOWN_REF g_MemoryRundown;
|
||||
static volatile LONG g_MemoryStopping = 1;
|
||||
static volatile LONG g_PendingMemoryWork = 0;
|
||||
static LIST_ENTRY g_MemoryWorkQueue;
|
||||
static KSPIN_LOCK g_MemoryWorkLock;
|
||||
static KSEMAPHORE g_MemoryWorkSemaphore;
|
||||
static HANDLE g_MemoryWorkerHandle = NULL;
|
||||
constexpr auto MAX_PENDING_MEMORY_WORK = 256;
|
||||
constexpr auto MEMORY_WORKER_STOP_TIMEOUT_MS = 2000;
|
||||
|
||||
static PACCESS_MASK GetDesiredAccessPointer(POB_PRE_OPERATION_INFORMATION OperationInformation) {
|
||||
if (OperationInformation->Operation == OB_OPERATION_HANDLE_CREATE) {
|
||||
return &OperationInformation->Parameters->CreateHandleInformation.DesiredAccess;
|
||||
}
|
||||
|
||||
if (OperationInformation->Operation == OB_OPERATION_HANDLE_DUPLICATE) {
|
||||
return &OperationInformation->Parameters->DuplicateHandleInformation.DesiredAccess;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static ULONG GetHandleType(POB_PRE_OPERATION_INFORMATION OperationInformation) {
|
||||
if (OperationInformation->Operation == OB_OPERATION_HANDLE_CREATE) {
|
||||
return PYAS_HANDLE_CREATE;
|
||||
}
|
||||
|
||||
if (OperationInformation->Operation == OB_OPERATION_HANDLE_DUPLICATE) {
|
||||
return PYAS_HANDLE_DUPLICATE;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ULONG GetProcessOperations(ACCESS_MASK Access) {
|
||||
ULONG Operations = 0;
|
||||
|
||||
if (Access & PROCESS_VM_READ) Operations |= OP_VM_READ;
|
||||
if (Access & PROCESS_VM_WRITE) Operations |= OP_VM_WRITE;
|
||||
if (Access & PROCESS_VM_OPERATION) Operations |= OP_VM_OPERATION;
|
||||
if (Access & PROCESS_CREATE_THREAD) Operations |= OP_CREATE_THREAD;
|
||||
if (Access & PROCESS_TERMINATE) Operations |= OP_TERMINATE;
|
||||
if (Access & PROCESS_SUSPEND_RESUME) Operations |= OP_SUSPEND_RESUME;
|
||||
if (Access & PROCESS_DUP_HANDLE) Operations |= OP_DUP_HANDLE;
|
||||
if (Access & (PROCESS_SET_INFORMATION | PROCESS_SET_QUOTA)) Operations |= OP_SET_INFORMATION;
|
||||
if (Access & PROCESS_CREATE_PROCESS) Operations |= OP_CREATE_PROCESS;
|
||||
|
||||
return Operations;
|
||||
}
|
||||
|
||||
static ULONG GetThreadOperations(ACCESS_MASK Access) {
|
||||
ULONG Operations = 0;
|
||||
|
||||
if (Access & THREAD_SET_CONTEXT) Operations |= OP_THREAD_SET_CONTEXT;
|
||||
if (Access & THREAD_TERMINATE) Operations |= OP_TERMINATE;
|
||||
if (Access & THREAD_SUSPEND_RESUME) Operations |= OP_SUSPEND_RESUME;
|
||||
if (Access & THREAD_SET_INFORMATION) Operations |= OP_SET_INFORMATION;
|
||||
if (Access & THREAD_SET_THREAD_TOKEN) Operations |= OP_THREAD_SET_TOKEN;
|
||||
if (Access & (THREAD_IMPERSONATE | THREAD_DIRECT_IMPERSONATION)) Operations |= OP_IMPERSONATE;
|
||||
|
||||
return Operations;
|
||||
}
|
||||
|
||||
static ACCESS_MASK GetProcessDeniedAccess(ULONG Operations) {
|
||||
ACCESS_MASK DeniedAccess = 0;
|
||||
|
||||
if (Operations & OP_VM_READ) DeniedAccess |= PROCESS_VM_READ;
|
||||
if (Operations & OP_VM_WRITE) DeniedAccess |= PROCESS_VM_WRITE;
|
||||
if (Operations & OP_VM_OPERATION) DeniedAccess |= PROCESS_VM_OPERATION;
|
||||
if (Operations & OP_CREATE_THREAD) DeniedAccess |= PROCESS_CREATE_THREAD;
|
||||
if (Operations & OP_TERMINATE) DeniedAccess |= PROCESS_TERMINATE;
|
||||
if (Operations & OP_SUSPEND_RESUME) DeniedAccess |= PROCESS_SUSPEND_RESUME;
|
||||
if (Operations & OP_DUP_HANDLE) DeniedAccess |= PROCESS_DUP_HANDLE;
|
||||
if (Operations & OP_SET_INFORMATION) DeniedAccess |= PROCESS_SET_INFORMATION | PROCESS_SET_QUOTA;
|
||||
if (Operations & OP_CREATE_PROCESS) DeniedAccess |= PROCESS_CREATE_PROCESS;
|
||||
|
||||
return DeniedAccess;
|
||||
}
|
||||
|
||||
static ACCESS_MASK GetThreadDeniedAccess(ULONG Operations) {
|
||||
ACCESS_MASK DeniedAccess = 0;
|
||||
|
||||
if (Operations & OP_THREAD_SET_CONTEXT) DeniedAccess |= THREAD_SET_CONTEXT;
|
||||
if (Operations & OP_TERMINATE) DeniedAccess |= THREAD_TERMINATE;
|
||||
if (Operations & OP_SUSPEND_RESUME) DeniedAccess |= THREAD_SUSPEND_RESUME;
|
||||
if (Operations & OP_SET_INFORMATION) DeniedAccess |= THREAD_SET_INFORMATION;
|
||||
if (Operations & OP_THREAD_SET_TOKEN) DeniedAccess |= THREAD_SET_THREAD_TOKEN;
|
||||
if (Operations & OP_IMPERSONATE) DeniedAccess |= THREAD_IMPERSONATE | THREAD_DIRECT_IMPERSONATION;
|
||||
|
||||
return DeniedAccess;
|
||||
}
|
||||
|
||||
static BOOLEAN IsProcessIdentityCurrent(HANDLE ProcessId, PEPROCESS ExpectedProcess);
|
||||
|
||||
static VOID ReleaseMemoryWork(PMEMORY_WORK_ITEM Work) {
|
||||
if (!Work) return;
|
||||
|
||||
if (Work->TargetProcess) ObDereferenceObject(Work->TargetProcess);
|
||||
if (Work->SourceProcess) ObDereferenceObject(Work->SourceProcess);
|
||||
InterlockedDecrement(&g_PendingMemoryWork);
|
||||
PyasFree(Work);
|
||||
}
|
||||
|
||||
static PMEMORY_WORK_ITEM PopMemoryWork() {
|
||||
PMEMORY_WORK_ITEM Work = NULL;
|
||||
KIRQL OldIrql;
|
||||
KeAcquireSpinLock(&g_MemoryWorkLock, &OldIrql);
|
||||
|
||||
if (!IsListEmpty(&g_MemoryWorkQueue)) {
|
||||
PLIST_ENTRY Entry = RemoveHeadList(&g_MemoryWorkQueue);
|
||||
Work = CONTAINING_RECORD(Entry, MEMORY_WORK_ITEM, Entry);
|
||||
}
|
||||
|
||||
KeReleaseSpinLock(&g_MemoryWorkLock, OldIrql);
|
||||
return Work;
|
||||
}
|
||||
|
||||
static VOID CancelQueuedMemoryWork() {
|
||||
for (;;) {
|
||||
PMEMORY_WORK_ITEM Work = PopMemoryWork();
|
||||
if (!Work) return;
|
||||
ReleaseMemoryWork(Work);
|
||||
}
|
||||
}
|
||||
|
||||
static BOOLEAN QueueMemoryWork(PMEMORY_WORK_ITEM Work) {
|
||||
if (!Work) return FALSE;
|
||||
|
||||
LONG Pending = InterlockedIncrement(&g_PendingMemoryWork);
|
||||
if (Pending > MAX_PENDING_MEMORY_WORK) {
|
||||
InterlockedDecrement(&g_PendingMemoryWork);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
KIRQL OldIrql;
|
||||
KeAcquireSpinLock(&g_MemoryWorkLock, &OldIrql);
|
||||
|
||||
if (InterlockedCompareExchange(&g_MemoryStopping, 0, 0) != 0) {
|
||||
KeReleaseSpinLock(&g_MemoryWorkLock, OldIrql);
|
||||
InterlockedDecrement(&g_PendingMemoryWork);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
InsertTailList(&g_MemoryWorkQueue, &Work->Entry);
|
||||
KeReleaseSpinLock(&g_MemoryWorkLock, OldIrql);
|
||||
KeReleaseSemaphore(&g_MemoryWorkSemaphore, IO_NO_INCREMENT, 1, FALSE);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static VOID DeliverViolation(PMEMORY_WORK_ITEM Work) {
|
||||
if (!Work) return;
|
||||
if (InterlockedCompareExchange(&g_MemoryStopping, 0, 0) != 0) return;
|
||||
|
||||
BOOLEAN SourceMatches = IsProcessIdentityCurrent(Work->SourcePid, Work->SourceProcess);
|
||||
BOOLEAN TargetMatches = IsProcessIdentityCurrent(Work->TargetPid, Work->TargetProcess);
|
||||
if (!SourceMatches || !TargetMatches) return;
|
||||
|
||||
PUNICODE_STRING TargetPath = NULL;
|
||||
|
||||
if (NT_SUCCESS(GetProcessImageName(Work->TargetPid, &TargetPath)) && TargetPath && TargetPath->Buffer) {
|
||||
SendMessageToUser(
|
||||
Work->RuleCode,
|
||||
(ULONG)(ULONG_PTR)Work->SourcePid,
|
||||
TargetPath->Buffer,
|
||||
TargetPath->Length
|
||||
);
|
||||
}
|
||||
else {
|
||||
UNICODE_STRING MessageString;
|
||||
RtlInitUnicodeString(&MessageString, Work->FallbackMessage);
|
||||
SendMessageToUser(
|
||||
Work->RuleCode,
|
||||
(ULONG)(ULONG_PTR)Work->SourcePid,
|
||||
MessageString.Buffer,
|
||||
MessageString.Length
|
||||
);
|
||||
}
|
||||
|
||||
if (TargetPath) ExFreePool(TargetPath);
|
||||
QueueRuleProcessTermination(Work->SourcePid, Work->Kill);
|
||||
}
|
||||
|
||||
static VOID ReportViolation(
|
||||
ULONG RuleCode,
|
||||
BOOLEAN Kill,
|
||||
HANDLE SourcePid,
|
||||
HANDLE TargetPid,
|
||||
PCWSTR FallbackMessage
|
||||
) {
|
||||
if (!RuleCode || !SourcePid || !TargetPid || !FallbackMessage) return;
|
||||
if (InterlockedCompareExchange(&g_MemoryStopping, 0, 0) != 0) return;
|
||||
|
||||
PEPROCESS SourceProcess = NULL;
|
||||
PEPROCESS TargetProcess = NULL;
|
||||
|
||||
NTSTATUS Status = PsLookupProcessByProcessId(SourcePid, &SourceProcess);
|
||||
if (NT_SUCCESS(Status)) {
|
||||
Status = PsLookupProcessByProcessId(TargetPid, &TargetProcess);
|
||||
}
|
||||
|
||||
if (!NT_SUCCESS(Status)) {
|
||||
if (SourceProcess) ObDereferenceObject(SourceProcess);
|
||||
return;
|
||||
}
|
||||
|
||||
PMEMORY_WORK_ITEM Work = (PMEMORY_WORK_ITEM)PyasAllocate(sizeof(MEMORY_WORK_ITEM));
|
||||
if (!Work) {
|
||||
ObDereferenceObject(TargetProcess);
|
||||
ObDereferenceObject(SourceProcess);
|
||||
return;
|
||||
}
|
||||
|
||||
RtlZeroMemory(Work, sizeof(*Work));
|
||||
Work->Type = MemoryWorkViolationReport;
|
||||
Work->SourceProcess = SourceProcess;
|
||||
Work->TargetProcess = TargetProcess;
|
||||
Work->SourcePid = SourcePid;
|
||||
Work->TargetPid = TargetPid;
|
||||
Work->RuleCode = RuleCode;
|
||||
Work->Kill = Kill;
|
||||
RtlStringCchCopyW(Work->FallbackMessage, RTL_NUMBER_OF(Work->FallbackMessage), FallbackMessage);
|
||||
|
||||
if (!QueueMemoryWork(Work)) {
|
||||
ObDereferenceObject(TargetProcess);
|
||||
ObDereferenceObject(SourceProcess);
|
||||
PyasFree(Work);
|
||||
}
|
||||
}
|
||||
|
||||
static BOOLEAN IsExecuteProtection(ULONG Protect) {
|
||||
ULONG BaseProtect = Protect & 0xFF;
|
||||
|
||||
return BaseProtect == PAGE_EXECUTE ||
|
||||
BaseProtect == PAGE_EXECUTE_READ ||
|
||||
BaseProtect == PAGE_EXECUTE_READWRITE ||
|
||||
BaseProtect == PAGE_EXECUTE_WRITECOPY;
|
||||
}
|
||||
|
||||
static BOOLEAN IsExecuteWriteProtection(ULONG Protect) {
|
||||
ULONG BaseProtect = Protect & 0xFF;
|
||||
|
||||
return BaseProtect == PAGE_EXECUTE_READWRITE ||
|
||||
BaseProtect == PAGE_EXECUTE_WRITECOPY;
|
||||
}
|
||||
|
||||
static BOOLEAN QueryThreadMemoryRegionForProcess(
|
||||
PEPROCESS Process,
|
||||
PVOID Address,
|
||||
PTHREAD_MEMORY_REGION Region
|
||||
) {
|
||||
if (!Process || !Region || !Address || KeGetCurrentIrql() != PASSIVE_LEVEL) return FALSE;
|
||||
|
||||
RtlZeroMemory(Region, sizeof(*Region));
|
||||
|
||||
KAPC_STATE ApcState;
|
||||
KeStackAttachProcess(Process, &ApcState);
|
||||
|
||||
MEMORY_BASIC_INFORMATION MemoryInformation = { 0 };
|
||||
SIZE_T ReturnLength = 0;
|
||||
NTSTATUS Status = ZwQueryVirtualMemory(
|
||||
ZwCurrentProcess(),
|
||||
Address,
|
||||
MemoryBasicInformation,
|
||||
&MemoryInformation,
|
||||
sizeof(MemoryInformation),
|
||||
&ReturnLength
|
||||
);
|
||||
|
||||
KeUnstackDetachProcess(&ApcState);
|
||||
|
||||
if (!NT_SUCCESS(Status)) return FALSE;
|
||||
if (MemoryInformation.State != MEM_COMMIT) return FALSE;
|
||||
if (!IsExecuteProtection(MemoryInformation.Protect)) return FALSE;
|
||||
|
||||
Region->StartAddress = Address;
|
||||
Region->RegionSize = MemoryInformation.RegionSize;
|
||||
|
||||
if (MemoryInformation.Type == MEM_PRIVATE) Region->MemoryType = PYAS_MEMORY_PRIVATE;
|
||||
else if (MemoryInformation.Type == MEM_MAPPED) Region->MemoryType = PYAS_MEMORY_MAPPED;
|
||||
else if (MemoryInformation.Type == PYAS_MEM_IMAGE_TYPE) Region->MemoryType = PYAS_MEMORY_IMAGE;
|
||||
else return FALSE;
|
||||
|
||||
Region->MemoryProtection = PYAS_PROTECT_EXECUTE;
|
||||
if (IsExecuteWriteProtection(MemoryInformation.Protect)) {
|
||||
Region->MemoryProtection |= PYAS_PROTECT_EXECUTE_WRITE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static BOOLEAN QueryThreadMemoryRegion(
|
||||
HANDLE ProcessId,
|
||||
PVOID Address,
|
||||
PTHREAD_MEMORY_REGION Region
|
||||
) {
|
||||
if (!ProcessId) return FALSE;
|
||||
|
||||
PEPROCESS Process = NULL;
|
||||
NTSTATUS Status = PsLookupProcessByProcessId(ProcessId, &Process);
|
||||
if (!NT_SUCCESS(Status)) return FALSE;
|
||||
|
||||
BOOLEAN Result = QueryThreadMemoryRegionForProcess(Process, Address, Region);
|
||||
ObDereferenceObject(Process);
|
||||
return Result;
|
||||
}
|
||||
|
||||
BOOLEAN IsAddressInUnmappedMemory(HANDLE ProcessId, PVOID Address) {
|
||||
THREAD_MEMORY_REGION Region = { 0 };
|
||||
if (!QueryThreadMemoryRegion(ProcessId, Address, &Region)) return FALSE;
|
||||
return Region.MemoryType == PYAS_MEMORY_PRIVATE;
|
||||
}
|
||||
|
||||
static BOOLEAN IsProcessIdentityCurrent(HANDLE ProcessId, PEPROCESS ExpectedProcess) {
|
||||
if (!ProcessId || !ExpectedProcess) return FALSE;
|
||||
|
||||
PEPROCESS CurrentProcess = NULL;
|
||||
NTSTATUS Status = PsLookupProcessByProcessId(ProcessId, &CurrentProcess);
|
||||
if (!NT_SUCCESS(Status)) return FALSE;
|
||||
|
||||
BOOLEAN Matches = CurrentProcess == ExpectedProcess;
|
||||
ObDereferenceObject(CurrentProcess);
|
||||
return Matches;
|
||||
}
|
||||
|
||||
static OB_PREOP_CALLBACK_STATUS ObjectPreCallbackCore(
|
||||
POB_PRE_OPERATION_INFORMATION OperationInformation
|
||||
) {
|
||||
if (!OperationInformation || OperationInformation->KernelHandle) {
|
||||
return OB_PREOP_SUCCESS;
|
||||
}
|
||||
|
||||
PACCESS_MASK DesiredAccess = GetDesiredAccessPointer(OperationInformation);
|
||||
ULONG HandleType = GetHandleType(OperationInformation);
|
||||
|
||||
if (!DesiredAccess || *DesiredAccess == 0 || HandleType == 0) {
|
||||
return OB_PREOP_SUCCESS;
|
||||
}
|
||||
|
||||
HANDLE SourcePid = PsGetCurrentProcessId();
|
||||
if (!SourcePid || IsProcessTrusted(SourcePid)) {
|
||||
return OB_PREOP_SUCCESS;
|
||||
}
|
||||
|
||||
HANDLE TargetPid = NULL;
|
||||
ULONG ObjectType = 0;
|
||||
ULONG RequestedOperations = 0;
|
||||
ACCESS_MASK OriginalAccess = *DesiredAccess;
|
||||
|
||||
if (OperationInformation->ObjectType == *PsProcessType) {
|
||||
TargetPid = PsGetProcessId((PEPROCESS)OperationInformation->Object);
|
||||
ObjectType = PYAS_OBJECT_PROCESS;
|
||||
RequestedOperations = GetProcessOperations(OriginalAccess);
|
||||
}
|
||||
else if (OperationInformation->ObjectType == *PsThreadType) {
|
||||
TargetPid = PsGetThreadProcessId((PETHREAD)OperationInformation->Object);
|
||||
ObjectType = PYAS_OBJECT_THREAD;
|
||||
RequestedOperations = GetThreadOperations(OriginalAccess);
|
||||
}
|
||||
else {
|
||||
return OB_PREOP_SUCCESS;
|
||||
}
|
||||
|
||||
if (!TargetPid || SourcePid == TargetPid || RequestedOperations == 0) {
|
||||
return OB_PREOP_SUCCESS;
|
||||
}
|
||||
|
||||
ULONG DeniedOperations = 0;
|
||||
ULONG RuleCode = 0;
|
||||
BOOLEAN Kill = FALSE;
|
||||
|
||||
if (!EvaluateProcessAccessRule(
|
||||
SourcePid,
|
||||
TargetPid,
|
||||
RequestedOperations,
|
||||
HandleType,
|
||||
ObjectType,
|
||||
&DeniedOperations,
|
||||
&RuleCode,
|
||||
&Kill
|
||||
)) {
|
||||
return OB_PREOP_SUCCESS;
|
||||
}
|
||||
|
||||
ACCESS_MASK DeniedAccess = ObjectType == PYAS_OBJECT_PROCESS
|
||||
? GetProcessDeniedAccess(DeniedOperations)
|
||||
: GetThreadDeniedAccess(DeniedOperations);
|
||||
|
||||
ACCESS_MASK EffectiveDeniedAccess = OriginalAccess & DeniedAccess;
|
||||
if (EffectiveDeniedAccess == 0) return OB_PREOP_SUCCESS;
|
||||
|
||||
*DesiredAccess = OriginalAccess & ~EffectiveDeniedAccess;
|
||||
|
||||
ReportViolation(
|
||||
RuleCode,
|
||||
Kill,
|
||||
SourcePid,
|
||||
TargetPid,
|
||||
ObjectType == PYAS_OBJECT_PROCESS
|
||||
? L"Process_Handle_Access_Denied"
|
||||
: L"Thread_Handle_Access_Denied"
|
||||
);
|
||||
|
||||
return OB_PREOP_SUCCESS;
|
||||
}
|
||||
|
||||
static OB_PREOP_CALLBACK_STATUS ObjectPreCallback(
|
||||
PVOID RegistrationContext,
|
||||
POB_PRE_OPERATION_INFORMATION OperationInformation
|
||||
) {
|
||||
UNREFERENCED_PARAMETER(RegistrationContext);
|
||||
|
||||
if (!g_MemoryProtectionInitialized) return OB_PREOP_SUCCESS;
|
||||
if (InterlockedCompareExchange(&g_MemoryStopping, 0, 0) != 0) return OB_PREOP_SUCCESS;
|
||||
if (!ExAcquireRundownProtection(&g_MemoryRundown)) return OB_PREOP_SUCCESS;
|
||||
|
||||
OB_PREOP_CALLBACK_STATUS Result = ObjectPreCallbackCore(OperationInformation);
|
||||
ExReleaseRundownProtection(&g_MemoryRundown);
|
||||
return Result;
|
||||
}
|
||||
|
||||
static VOID ProcessThreadAnalysis(PMEMORY_WORK_ITEM Work) {
|
||||
if (!Work) return;
|
||||
if (InterlockedCompareExchange(&g_MemoryStopping, 0, 0) != 0) return;
|
||||
|
||||
PVOID StartAddress = NULL;
|
||||
HANDLE ThreadHandle = NULL;
|
||||
BOOLEAN IdentityValid =
|
||||
IsProcessIdentityCurrent(Work->SourcePid, Work->SourceProcess) &&
|
||||
IsProcessIdentityCurrent(Work->TargetPid, Work->TargetProcess);
|
||||
CLIENT_ID ClientId = { Work->TargetPid, Work->ThreadId };
|
||||
OBJECT_ATTRIBUTES ObjectAttributes;
|
||||
InitializeObjectAttributes(&ObjectAttributes, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);
|
||||
|
||||
NTSTATUS Status = STATUS_PROCESS_IS_TERMINATING;
|
||||
if (IdentityValid) {
|
||||
Status = ZwOpenThread(
|
||||
&ThreadHandle,
|
||||
THREAD_QUERY_INFORMATION,
|
||||
&ObjectAttributes,
|
||||
&ClientId
|
||||
);
|
||||
}
|
||||
|
||||
if (NT_SUCCESS(Status)) {
|
||||
Status = ZwQueryInformationThread(
|
||||
ThreadHandle,
|
||||
(THREADINFOCLASS)THREAD_QUERY_SET_WIN32_START_ADDRESS,
|
||||
&StartAddress,
|
||||
sizeof(StartAddress),
|
||||
NULL
|
||||
);
|
||||
ZwClose(ThreadHandle);
|
||||
}
|
||||
|
||||
if (!NT_SUCCESS(Status) || !StartAddress) return;
|
||||
if (InterlockedCompareExchange(&g_MemoryStopping, 0, 0) != 0) return;
|
||||
|
||||
THREAD_MEMORY_REGION Region = { 0 };
|
||||
if (!QueryThreadMemoryRegionForProcess(Work->TargetProcess, StartAddress, &Region)) return;
|
||||
|
||||
ULONG RuleCode = 0;
|
||||
BOOLEAN Kill = FALSE;
|
||||
if (!EvaluateThreadRule(
|
||||
Work->SourcePid,
|
||||
Work->TargetPid,
|
||||
StartAddress,
|
||||
Region.MemoryType,
|
||||
Region.MemoryProtection,
|
||||
Region.RegionSize,
|
||||
&RuleCode,
|
||||
&Kill
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Work->RuleCode = RuleCode;
|
||||
Work->Kill = Kill;
|
||||
RtlStringCchCopyW(
|
||||
Work->FallbackMessage,
|
||||
RTL_NUMBER_OF(Work->FallbackMessage),
|
||||
L"Remote_Executable_Thread_Detected"
|
||||
);
|
||||
DeliverViolation(Work);
|
||||
}
|
||||
|
||||
static VOID MemoryWorkerThread(PVOID Parameter) {
|
||||
UNREFERENCED_PARAMETER(Parameter);
|
||||
|
||||
for (;;) {
|
||||
KeWaitForSingleObject(
|
||||
&g_MemoryWorkSemaphore,
|
||||
Executive,
|
||||
KernelMode,
|
||||
FALSE,
|
||||
NULL
|
||||
);
|
||||
|
||||
if (InterlockedCompareExchange(&g_MemoryStopping, 0, 0) != 0) {
|
||||
CancelQueuedMemoryWork();
|
||||
break;
|
||||
}
|
||||
|
||||
PMEMORY_WORK_ITEM Work = PopMemoryWork();
|
||||
if (!Work) continue;
|
||||
|
||||
if (Work->Type == MemoryWorkThreadAnalysis) {
|
||||
ProcessThreadAnalysis(Work);
|
||||
}
|
||||
else if (Work->Type == MemoryWorkViolationReport) {
|
||||
DeliverViolation(Work);
|
||||
}
|
||||
|
||||
ReleaseMemoryWork(Work);
|
||||
}
|
||||
|
||||
PsTerminateSystemThread(STATUS_SUCCESS);
|
||||
}
|
||||
|
||||
static VOID ThreadNotifyCallback(
|
||||
HANDLE ProcessId,
|
||||
HANDLE ThreadId,
|
||||
BOOLEAN IsCreated
|
||||
) {
|
||||
if (!IsCreated || !g_MemoryProtectionInitialized) return;
|
||||
if (InterlockedCompareExchange(&g_MemoryStopping, 0, 0) != 0) return;
|
||||
|
||||
HANDLE SourcePid = PsGetCurrentProcessId();
|
||||
if (!SourcePid || SourcePid == ProcessId || IsProcessTrusted(SourcePid)) return;
|
||||
|
||||
PEPROCESS SourceProcess = PsGetCurrentProcess();
|
||||
PEPROCESS TargetProcess = NULL;
|
||||
ObReferenceObject(SourceProcess);
|
||||
|
||||
NTSTATUS Status = PsLookupProcessByProcessId(ProcessId, &TargetProcess);
|
||||
if (!NT_SUCCESS(Status)) {
|
||||
ObDereferenceObject(SourceProcess);
|
||||
return;
|
||||
}
|
||||
|
||||
PMEMORY_WORK_ITEM Work = (PMEMORY_WORK_ITEM)PyasAllocate(sizeof(MEMORY_WORK_ITEM));
|
||||
if (!Work) {
|
||||
ObDereferenceObject(TargetProcess);
|
||||
ObDereferenceObject(SourceProcess);
|
||||
return;
|
||||
}
|
||||
|
||||
RtlZeroMemory(Work, sizeof(*Work));
|
||||
Work->Type = MemoryWorkThreadAnalysis;
|
||||
Work->SourceProcess = SourceProcess;
|
||||
Work->TargetProcess = TargetProcess;
|
||||
Work->SourcePid = SourcePid;
|
||||
Work->TargetPid = ProcessId;
|
||||
Work->ThreadId = ThreadId;
|
||||
|
||||
if (!QueueMemoryWork(Work)) {
|
||||
ObDereferenceObject(TargetProcess);
|
||||
ObDereferenceObject(SourceProcess);
|
||||
PyasFree(Work);
|
||||
}
|
||||
}
|
||||
|
||||
static VOID LoadImageNotifyCallback(
|
||||
PUNICODE_STRING FullImageName,
|
||||
HANDLE ProcessId,
|
||||
PIMAGE_INFO ImageInfo
|
||||
) {
|
||||
if (!g_MemoryProtectionInitialized) return;
|
||||
if (InterlockedCompareExchange(&g_MemoryStopping, 0, 0) != 0) return;
|
||||
if (!FullImageName || !FullImageName->Buffer || !ImageInfo || ImageInfo->SystemModeImage) return;
|
||||
if (!ProcessId || !ExAcquireRundownProtection(&g_MemoryRundown)) return;
|
||||
|
||||
ULONG RuleCode = 0;
|
||||
BOOLEAN Kill = FALSE;
|
||||
if (EvaluateImageLoadRule(ProcessId, FullImageName, ImageInfo, &RuleCode, &Kill)) {
|
||||
SendMessageToUser(
|
||||
RuleCode,
|
||||
(ULONG)(ULONG_PTR)ProcessId,
|
||||
FullImageName->Buffer,
|
||||
FullImageName->Length
|
||||
);
|
||||
QueueRuleProcessTermination(ProcessId, Kill);
|
||||
}
|
||||
|
||||
ExReleaseRundownProtection(&g_MemoryRundown);
|
||||
}
|
||||
|
||||
static NTSTATUS RemoveMemoryCallbacks() {
|
||||
NTSTATUS FirstFailure = STATUS_SUCCESS;
|
||||
|
||||
if (GlobalData.ObRegistrationHandle) {
|
||||
ObUnRegisterCallbacks(GlobalData.ObRegistrationHandle);
|
||||
GlobalData.ObRegistrationHandle = NULL;
|
||||
}
|
||||
|
||||
if (g_ThreadNotifyRegistered) {
|
||||
NTSTATUS Status = PsRemoveCreateThreadNotifyRoutine(ThreadNotifyCallback);
|
||||
if (NT_SUCCESS(Status) || Status == STATUS_PROCEDURE_NOT_FOUND) {
|
||||
g_ThreadNotifyRegistered = FALSE;
|
||||
}
|
||||
else if (NT_SUCCESS(FirstFailure)) {
|
||||
FirstFailure = Status;
|
||||
}
|
||||
}
|
||||
|
||||
if (g_LoadImageNotifyRegistered) {
|
||||
NTSTATUS Status = PsRemoveLoadImageNotifyRoutine(LoadImageNotifyCallback);
|
||||
if (NT_SUCCESS(Status) || Status == STATUS_PROCEDURE_NOT_FOUND) {
|
||||
g_LoadImageNotifyRegistered = FALSE;
|
||||
}
|
||||
else if (NT_SUCCESS(FirstFailure)) {
|
||||
FirstFailure = Status;
|
||||
}
|
||||
}
|
||||
|
||||
return FirstFailure;
|
||||
}
|
||||
|
||||
static NTSTATUS StartMemoryWorker() {
|
||||
if (g_MemoryWorkerHandle) return STATUS_SUCCESS;
|
||||
|
||||
OBJECT_ATTRIBUTES ObjectAttributes;
|
||||
InitializeObjectAttributes(
|
||||
&ObjectAttributes,
|
||||
NULL,
|
||||
OBJ_KERNEL_HANDLE,
|
||||
NULL,
|
||||
NULL
|
||||
);
|
||||
|
||||
return PsCreateSystemThread(
|
||||
&g_MemoryWorkerHandle,
|
||||
SYNCHRONIZE,
|
||||
&ObjectAttributes,
|
||||
NULL,
|
||||
NULL,
|
||||
MemoryWorkerThread,
|
||||
NULL
|
||||
);
|
||||
}
|
||||
|
||||
static NTSTATUS StopMemoryWorker(BOOLEAN WaitWithoutTimeout) {
|
||||
CancelQueuedMemoryWork();
|
||||
|
||||
if (!g_MemoryWorkerHandle) return STATUS_SUCCESS;
|
||||
|
||||
KeReleaseSemaphore(&g_MemoryWorkSemaphore, IO_NO_INCREMENT, 1, FALSE);
|
||||
|
||||
LARGE_INTEGER Timeout;
|
||||
PLARGE_INTEGER TimeoutPointer = NULL;
|
||||
|
||||
if (!WaitWithoutTimeout) {
|
||||
Timeout.QuadPart = -((LONGLONG)MEMORY_WORKER_STOP_TIMEOUT_MS * 10 * 1000);
|
||||
TimeoutPointer = &Timeout;
|
||||
}
|
||||
|
||||
NTSTATUS Status = ZwWaitForSingleObject(
|
||||
g_MemoryWorkerHandle,
|
||||
FALSE,
|
||||
TimeoutPointer
|
||||
);
|
||||
|
||||
if (Status == STATUS_TIMEOUT) return STATUS_DEVICE_BUSY;
|
||||
if (!NT_SUCCESS(Status)) return Status;
|
||||
|
||||
ZwClose(g_MemoryWorkerHandle);
|
||||
g_MemoryWorkerHandle = NULL;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
NTSTATUS InitializeMemoryProtection(PDRIVER_OBJECT DriverObject) {
|
||||
UNREFERENCED_PARAMETER(DriverObject);
|
||||
|
||||
if (g_MemoryProtectionInitialized) return STATUS_SUCCESS;
|
||||
|
||||
InitializeListHead(&g_MemoryWorkQueue);
|
||||
KeInitializeSpinLock(&g_MemoryWorkLock);
|
||||
KeInitializeSemaphore(&g_MemoryWorkSemaphore, 0, MAXLONG);
|
||||
ExInitializeRundownProtection(&g_MemoryRundown);
|
||||
g_MemoryRundownCompleted = FALSE;
|
||||
InterlockedExchange(&g_PendingMemoryWork, 0);
|
||||
InterlockedExchange(&g_MemoryStopping, 0);
|
||||
g_MemoryProtectionInitialized = TRUE;
|
||||
|
||||
NTSTATUS Status = StartMemoryWorker();
|
||||
if (!NT_SUCCESS(Status)) {
|
||||
InterlockedExchange(&g_MemoryStopping, 1);
|
||||
g_MemoryProtectionInitialized = FALSE;
|
||||
return Status;
|
||||
}
|
||||
|
||||
Status = PsSetCreateThreadNotifyRoutine(ThreadNotifyCallback);
|
||||
if (!NT_SUCCESS(Status)) {
|
||||
InterlockedExchange(&g_MemoryStopping, 1);
|
||||
StopMemoryWorker(TRUE);
|
||||
g_MemoryProtectionInitialized = FALSE;
|
||||
return Status;
|
||||
}
|
||||
g_ThreadNotifyRegistered = TRUE;
|
||||
|
||||
Status = PsSetLoadImageNotifyRoutine(LoadImageNotifyCallback);
|
||||
if (!NT_SUCCESS(Status)) {
|
||||
InterlockedExchange(&g_MemoryStopping, 1);
|
||||
RemoveMemoryCallbacks();
|
||||
ExWaitForRundownProtectionRelease(&g_MemoryRundown);
|
||||
g_MemoryRundownCompleted = TRUE;
|
||||
StopMemoryWorker(TRUE);
|
||||
g_MemoryProtectionInitialized = FALSE;
|
||||
return Status;
|
||||
}
|
||||
g_LoadImageNotifyRegistered = TRUE;
|
||||
|
||||
OB_OPERATION_REGISTRATION OperationRegistration[2] = { 0 };
|
||||
OperationRegistration[0].ObjectType = PsProcessType;
|
||||
OperationRegistration[0].Operations = OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE;
|
||||
OperationRegistration[0].PreOperation = ObjectPreCallback;
|
||||
|
||||
OperationRegistration[1].ObjectType = PsThreadType;
|
||||
OperationRegistration[1].Operations = OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE;
|
||||
OperationRegistration[1].PreOperation = ObjectPreCallback;
|
||||
|
||||
OB_CALLBACK_REGISTRATION CallbackRegistration = { 0 };
|
||||
CallbackRegistration.Version = OB_FLT_REGISTRATION_VERSION;
|
||||
CallbackRegistration.OperationRegistrationCount = RTL_NUMBER_OF(OperationRegistration);
|
||||
CallbackRegistration.OperationRegistration = OperationRegistration;
|
||||
RtlInitUnicodeString(&CallbackRegistration.Altitude, PYAS_OB_ALTITUDE);
|
||||
|
||||
Status = ObRegisterCallbacks(&CallbackRegistration, &GlobalData.ObRegistrationHandle);
|
||||
if (NT_SUCCESS(Status)) return STATUS_SUCCESS;
|
||||
|
||||
InterlockedExchange(&g_MemoryStopping, 1);
|
||||
RemoveMemoryCallbacks();
|
||||
ExWaitForRundownProtectionRelease(&g_MemoryRundown);
|
||||
g_MemoryRundownCompleted = TRUE;
|
||||
StopMemoryWorker(TRUE);
|
||||
g_MemoryProtectionInitialized = FALSE;
|
||||
return Status;
|
||||
}
|
||||
|
||||
NTSTATUS UninitializeMemoryProtection(BOOLEAN WaitWithoutTimeout) {
|
||||
if (!g_MemoryProtectionInitialized) return STATUS_SUCCESS;
|
||||
|
||||
InterlockedExchange(&g_MemoryStopping, 1);
|
||||
|
||||
NTSTATUS CallbackStatus = RemoveMemoryCallbacks();
|
||||
|
||||
if (!g_MemoryRundownCompleted) {
|
||||
ExWaitForRundownProtectionRelease(&g_MemoryRundown);
|
||||
g_MemoryRundownCompleted = TRUE;
|
||||
}
|
||||
|
||||
NTSTATUS WorkerStatus = StopMemoryWorker(WaitWithoutTimeout);
|
||||
if (!NT_SUCCESS(CallbackStatus)) return CallbackStatus;
|
||||
if (!NT_SUCCESS(WorkerStatus)) return WorkerStatus;
|
||||
|
||||
if (g_ThreadNotifyRegistered || g_LoadImageNotifyRegistered || GlobalData.ObRegistrationHandle) {
|
||||
return STATUS_DEVICE_BUSY;
|
||||
}
|
||||
|
||||
g_MemoryProtectionInitialized = FALSE;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
#include "DriverCommon.h"
|
||||
|
||||
constexpr auto PROCESS_RELATION_BUCKET_COUNT = 1024;
|
||||
constexpr auto PROCESS_RELATION_BUCKET_WAYS = 4;
|
||||
|
||||
typedef HANDLE(NTAPI* PPS_GET_PROCESS_INHERITED_FROM_UNIQUE_PROCESS_ID)(PEPROCESS Process);
|
||||
|
||||
typedef struct _PROCESS_RELATION_ENTRY {
|
||||
HANDLE ProcessId;
|
||||
HANDLE ParentProcessId;
|
||||
HANDLE CreatorProcessId;
|
||||
LARGE_INTEGER CreateTime;
|
||||
LARGE_INTEGER ParentCreateTime;
|
||||
ULONGLONG Sequence;
|
||||
} PROCESS_RELATION_ENTRY, * PPROCESS_RELATION_ENTRY;
|
||||
|
||||
static PROCESS_RELATION_ENTRY g_ProcessRelations[PROCESS_RELATION_BUCKET_COUNT][PROCESS_RELATION_BUCKET_WAYS];
|
||||
static KSPIN_LOCK g_ProcessRelationLock;
|
||||
static EX_RUNDOWN_REF g_ProcessRundown;
|
||||
static volatile LONG g_ProcessStopping = 1;
|
||||
static volatile LONG64 g_ProcessRelationSequence = 0;
|
||||
static BOOLEAN g_ProcessProtectionInitialized = FALSE;
|
||||
static PPS_GET_PROCESS_INHERITED_FROM_UNIQUE_PROCESS_ID g_GetInheritedProcessId = NULL;
|
||||
|
||||
static ULONG GetProcessRelationBucket(HANDLE ProcessId) {
|
||||
return ((ULONG)(ULONG_PTR)ProcessId >> 2) & (PROCESS_RELATION_BUCKET_COUNT - 1);
|
||||
}
|
||||
|
||||
static BOOLEAN QueryProcessCreateTime(HANDLE ProcessId, PLARGE_INTEGER CreateTime) {
|
||||
if (CreateTime) CreateTime->QuadPart = 0;
|
||||
if (!ProcessId || !CreateTime || KeGetCurrentIrql() > APC_LEVEL) return FALSE;
|
||||
|
||||
PEPROCESS Process = NULL;
|
||||
NTSTATUS Status = PsLookupProcessByProcessId(ProcessId, &Process);
|
||||
if (!NT_SUCCESS(Status)) return FALSE;
|
||||
|
||||
CreateTime->QuadPart = PsGetProcessCreateTimeQuadPart(Process);
|
||||
ObDereferenceObject(Process);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static VOID StoreProcessRelation(
|
||||
PEPROCESS Process,
|
||||
HANDLE ProcessId,
|
||||
HANDLE ParentProcessId,
|
||||
HANDLE CreatorProcessId,
|
||||
PLARGE_INTEGER KnownParentCreateTime
|
||||
) {
|
||||
if (!Process || !ProcessId) return;
|
||||
|
||||
PROCESS_RELATION_ENTRY NewEntry = { 0 };
|
||||
NewEntry.ProcessId = ProcessId;
|
||||
NewEntry.ParentProcessId = ParentProcessId;
|
||||
NewEntry.CreatorProcessId = CreatorProcessId;
|
||||
NewEntry.CreateTime.QuadPart = PsGetProcessCreateTimeQuadPart(Process);
|
||||
|
||||
if (KnownParentCreateTime) {
|
||||
NewEntry.ParentCreateTime = *KnownParentCreateTime;
|
||||
}
|
||||
else if (ParentProcessId) {
|
||||
QueryProcessCreateTime(ParentProcessId, &NewEntry.ParentCreateTime);
|
||||
}
|
||||
|
||||
NewEntry.Sequence = (ULONGLONG)InterlockedIncrement64(&g_ProcessRelationSequence);
|
||||
|
||||
ULONG Bucket = GetProcessRelationBucket(ProcessId);
|
||||
ULONG Selected = 0;
|
||||
|
||||
KIRQL OldIrql;
|
||||
KeAcquireSpinLock(&g_ProcessRelationLock, &OldIrql);
|
||||
|
||||
for (ULONG Index = 0; Index < PROCESS_RELATION_BUCKET_WAYS; Index++) {
|
||||
PPROCESS_RELATION_ENTRY Entry = &g_ProcessRelations[Bucket][Index];
|
||||
|
||||
if (Entry->ProcessId == ProcessId || Entry->ProcessId == NULL) {
|
||||
Selected = Index;
|
||||
break;
|
||||
}
|
||||
|
||||
if (Entry->Sequence < g_ProcessRelations[Bucket][Selected].Sequence) {
|
||||
Selected = Index;
|
||||
}
|
||||
}
|
||||
|
||||
g_ProcessRelations[Bucket][Selected] = NewEntry;
|
||||
KeReleaseSpinLock(&g_ProcessRelationLock, OldIrql);
|
||||
}
|
||||
|
||||
static VOID RemoveProcessRelation(HANDLE ProcessId) {
|
||||
if (!ProcessId) return;
|
||||
|
||||
ULONG Bucket = GetProcessRelationBucket(ProcessId);
|
||||
|
||||
KIRQL OldIrql;
|
||||
KeAcquireSpinLock(&g_ProcessRelationLock, &OldIrql);
|
||||
|
||||
for (ULONG Index = 0; Index < PROCESS_RELATION_BUCKET_WAYS; Index++) {
|
||||
PPROCESS_RELATION_ENTRY Entry = &g_ProcessRelations[Bucket][Index];
|
||||
if (Entry->ProcessId == ProcessId) {
|
||||
RtlZeroMemory(Entry, sizeof(*Entry));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
KeReleaseSpinLock(&g_ProcessRelationLock, OldIrql);
|
||||
}
|
||||
|
||||
BOOLEAN GetProcessRelation(
|
||||
HANDLE ProcessId,
|
||||
PHANDLE ParentProcessId,
|
||||
PHANDLE CreatorProcessId
|
||||
) {
|
||||
if (ParentProcessId) *ParentProcessId = NULL;
|
||||
if (CreatorProcessId) *CreatorProcessId = NULL;
|
||||
if (!ProcessId || !g_ProcessProtectionInitialized || KeGetCurrentIrql() > APC_LEVEL) return FALSE;
|
||||
|
||||
PEPROCESS Process = NULL;
|
||||
NTSTATUS Status = PsLookupProcessByProcessId(ProcessId, &Process);
|
||||
if (!NT_SUCCESS(Status)) return FALSE;
|
||||
|
||||
LARGE_INTEGER CurrentCreateTime = { 0 };
|
||||
CurrentCreateTime.QuadPart = PsGetProcessCreateTimeQuadPart(Process);
|
||||
|
||||
PROCESS_RELATION_ENTRY Snapshot = { 0 };
|
||||
ULONG Bucket = GetProcessRelationBucket(ProcessId);
|
||||
|
||||
KIRQL OldIrql;
|
||||
KeAcquireSpinLock(&g_ProcessRelationLock, &OldIrql);
|
||||
|
||||
for (ULONG Index = 0; Index < PROCESS_RELATION_BUCKET_WAYS; Index++) {
|
||||
PPROCESS_RELATION_ENTRY Entry = &g_ProcessRelations[Bucket][Index];
|
||||
if (Entry->ProcessId == ProcessId) {
|
||||
Snapshot = *Entry;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
KeReleaseSpinLock(&g_ProcessRelationLock, OldIrql);
|
||||
|
||||
BOOLEAN CacheValid = Snapshot.ProcessId == ProcessId &&
|
||||
Snapshot.CreateTime.QuadPart == CurrentCreateTime.QuadPart;
|
||||
|
||||
if (CacheValid && Snapshot.ParentProcessId && Snapshot.ParentCreateTime.QuadPart != 0) {
|
||||
LARGE_INTEGER LiveParentCreateTime = { 0 };
|
||||
if (QueryProcessCreateTime(Snapshot.ParentProcessId, &LiveParentCreateTime) &&
|
||||
LiveParentCreateTime.QuadPart != Snapshot.ParentCreateTime.QuadPart) {
|
||||
CacheValid = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
if (CacheValid) {
|
||||
if (ParentProcessId) *ParentProcessId = Snapshot.ParentProcessId;
|
||||
if (CreatorProcessId) *CreatorProcessId = Snapshot.CreatorProcessId;
|
||||
ObDereferenceObject(Process);
|
||||
return Snapshot.ParentProcessId != NULL || Snapshot.CreatorProcessId != NULL;
|
||||
}
|
||||
|
||||
if (Snapshot.ProcessId == ProcessId) {
|
||||
RemoveProcessRelation(ProcessId);
|
||||
}
|
||||
|
||||
HANDLE LiveParentProcessId = NULL;
|
||||
LARGE_INTEGER LiveParentCreateTime = { 0 };
|
||||
|
||||
if (g_GetInheritedProcessId) {
|
||||
LiveParentProcessId = g_GetInheritedProcessId(Process);
|
||||
}
|
||||
|
||||
if (!LiveParentProcessId || LiveParentProcessId == ProcessId) {
|
||||
ObDereferenceObject(Process);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!QueryProcessCreateTime(LiveParentProcessId, &LiveParentCreateTime) ||
|
||||
LiveParentCreateTime.QuadPart > CurrentCreateTime.QuadPart) {
|
||||
ObDereferenceObject(Process);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
StoreProcessRelation(
|
||||
Process,
|
||||
ProcessId,
|
||||
LiveParentProcessId,
|
||||
NULL,
|
||||
&LiveParentCreateTime
|
||||
);
|
||||
|
||||
if (ParentProcessId) *ParentProcessId = LiveParentProcessId;
|
||||
if (CreatorProcessId) *CreatorProcessId = NULL;
|
||||
|
||||
ObDereferenceObject(Process);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
NTSTATUS InitializeProcessProtection() {
|
||||
if (g_ProcessProtectionInitialized) return STATUS_SUCCESS;
|
||||
|
||||
KeInitializeSpinLock(&g_ProcessRelationLock);
|
||||
RtlZeroMemory(g_ProcessRelations, sizeof(g_ProcessRelations));
|
||||
InterlockedExchange64(&g_ProcessRelationSequence, 0);
|
||||
ExInitializeRundownProtection(&g_ProcessRundown);
|
||||
|
||||
UNICODE_STRING RoutineName = RTL_CONSTANT_STRING(L"PsGetProcessInheritedFromUniqueProcessId");
|
||||
g_GetInheritedProcessId = reinterpret_cast<PPS_GET_PROCESS_INHERITED_FROM_UNIQUE_PROCESS_ID>(
|
||||
MmGetSystemRoutineAddress(&RoutineName)
|
||||
);
|
||||
|
||||
InterlockedExchange(&g_ProcessStopping, 0);
|
||||
g_ProcessProtectionInitialized = TRUE;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
NTSTATUS UninitializeProcessProtection() {
|
||||
if (!g_ProcessProtectionInitialized) return STATUS_SUCCESS;
|
||||
|
||||
InterlockedExchange(&g_ProcessStopping, 1);
|
||||
ExWaitForRundownProtectionRelease(&g_ProcessRundown);
|
||||
|
||||
KIRQL OldIrql;
|
||||
KeAcquireSpinLock(&g_ProcessRelationLock, &OldIrql);
|
||||
RtlZeroMemory(g_ProcessRelations, sizeof(g_ProcessRelations));
|
||||
KeReleaseSpinLock(&g_ProcessRelationLock, OldIrql);
|
||||
|
||||
g_GetInheritedProcessId = NULL;
|
||||
g_ProcessProtectionInitialized = FALSE;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
VOID ProcessNotifyCallbackEx(
|
||||
PEPROCESS Process,
|
||||
HANDLE ProcessId,
|
||||
PPS_CREATE_NOTIFY_INFO CreateInfo
|
||||
) {
|
||||
if (!g_ProcessProtectionInitialized) return;
|
||||
if (InterlockedCompareExchange(&g_ProcessStopping, 0, 0) != 0) return;
|
||||
if (!ExAcquireRundownProtection(&g_ProcessRundown)) return;
|
||||
|
||||
if (!CreateInfo) {
|
||||
RemoveProcessRelation(ProcessId);
|
||||
ExReleaseRundownProtection(&g_ProcessRundown);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NT_SUCCESS(CreateInfo->CreationStatus)) {
|
||||
ExReleaseRundownProtection(&g_ProcessRundown);
|
||||
return;
|
||||
}
|
||||
|
||||
HANDLE CreatorPid = CreateInfo->CreatingThreadId.UniqueProcess;
|
||||
HANDLE ParentPid = CreateInfo->ParentProcessId;
|
||||
ULONG RuleCode = 0;
|
||||
BOOLEAN Kill = FALSE;
|
||||
|
||||
BOOLEAN Blocked = EvaluateProcessCreateRule(
|
||||
CreatorPid,
|
||||
ParentPid,
|
||||
ProcessId,
|
||||
CreateInfo->ImageFileName,
|
||||
CreateInfo->CommandLine,
|
||||
CreateInfo->FileOpenNameAvailable ? TRUE : FALSE,
|
||||
CreateInfo->IsSubsystemProcess ? TRUE : FALSE,
|
||||
&RuleCode,
|
||||
&Kill
|
||||
);
|
||||
|
||||
if (Blocked) {
|
||||
CreateInfo->CreationStatus = STATUS_ACCESS_DENIED;
|
||||
|
||||
if (CreateInfo->ImageFileName && CreateInfo->ImageFileName->Buffer) {
|
||||
SendMessageToUser(
|
||||
RuleCode,
|
||||
(ULONG)(ULONG_PTR)CreatorPid,
|
||||
CreateInfo->ImageFileName->Buffer,
|
||||
CreateInfo->ImageFileName->Length
|
||||
);
|
||||
}
|
||||
|
||||
QueueRuleProcessTermination(CreatorPid, Kill);
|
||||
}
|
||||
else {
|
||||
StoreProcessRelation(Process, ProcessId, ParentPid, CreatorPid, NULL);
|
||||
}
|
||||
|
||||
ExReleaseRundownProtection(&g_ProcessRundown);
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
#include "DriverCommon.h"
|
||||
|
||||
static LARGE_INTEGER Cookie;
|
||||
|
||||
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);
|
||||
|
||||
HANDLE Pid = PsGetCurrentProcessId();
|
||||
ULONG RuleCode = 0;
|
||||
BOOLEAN Kill = FALSE;
|
||||
if (EvaluateRegistryRule(Pid, &FullPath, OP_CREATE | OP_WRITE, &RuleCode, &Kill)) {
|
||||
SendMessageToUser(RuleCode, (ULONG)(ULONG_PTR)Pid, FullPath.Buffer, FullPath.Length);
|
||||
QueueRuleProcessTermination(Pid, Kill);
|
||||
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) {
|
||||
HANDLE Pid = PsGetCurrentProcessId();
|
||||
ULONG RuleCode = 0;
|
||||
BOOLEAN Kill = FALSE;
|
||||
if (EvaluateRegistryRule(Pid, KeyName, OP_DELETE, &RuleCode, &Kill)) {
|
||||
SendMessageToUser(RuleCode, (ULONG)(ULONG_PTR)Pid, KeyName->Buffer, KeyName->Length);
|
||||
QueueRuleProcessTermination(Pid, Kill);
|
||||
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 && KeyName->Buffer) {
|
||||
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);
|
||||
}
|
||||
|
||||
HANDLE Pid = PsGetCurrentProcessId();
|
||||
ULONG RuleCode = 0;
|
||||
BOOLEAN Kill = FALSE;
|
||||
if (EvaluateRegistryRule(Pid, &FullPath, OP_WRITE, &RuleCode, &Kill)) {
|
||||
SendMessageToUser(RuleCode, (ULONG)(ULONG_PTR)Pid, FullPath.Buffer, FullPath.Length);
|
||||
QueueRuleProcessTermination(Pid, Kill);
|
||||
status = STATUS_ACCESS_DENIED;
|
||||
}
|
||||
PyasFree(Buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (KeyName) 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 && KeyName->Buffer) {
|
||||
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);
|
||||
}
|
||||
|
||||
HANDLE Pid = PsGetCurrentProcessId();
|
||||
ULONG RuleCode = 0;
|
||||
BOOLEAN Kill = FALSE;
|
||||
if (EvaluateRegistryRule(Pid, &FullPath, OP_DELETE, &RuleCode, &Kill)) {
|
||||
SendMessageToUser(RuleCode, (ULONG)(ULONG_PTR)Pid, FullPath.Buffer, FullPath.Length);
|
||||
QueueRuleProcessTermination(Pid, Kill);
|
||||
status = STATUS_ACCESS_DENIED;
|
||||
}
|
||||
PyasFree(Buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (KeyName) CmCallbackReleaseKeyObjectIDEx(KeyName);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
NTSTATUS InitializeRegistryProtection(PDRIVER_OBJECT DriverObject) {
|
||||
if (Cookie.QuadPart != 0) return STATUS_SUCCESS;
|
||||
|
||||
UNICODE_STRING Altitude;
|
||||
RtlInitUnicodeString(&Altitude, L"320000.4201");
|
||||
return CmRegisterCallbackEx(RegistryCallback, &Altitude, DriverObject, NULL, &Cookie, NULL);
|
||||
}
|
||||
|
||||
NTSTATUS UninitializeRegistryProtection() {
|
||||
if (Cookie.QuadPart == 0) return STATUS_SUCCESS;
|
||||
|
||||
NTSTATUS Status = CmUnRegisterCallback(Cookie);
|
||||
if (NT_SUCCESS(Status)) {
|
||||
Cookie.QuadPart = 0;
|
||||
}
|
||||
|
||||
return Status;
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -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,706 @@
|
||||
{
|
||||
"DynamicRules": [
|
||||
{
|
||||
"Name": "Self_Antivirus_Protect_Command",
|
||||
"Code": 1001,
|
||||
"Kill": false,
|
||||
"Category": "Process",
|
||||
"Initiator": [
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\cmd.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\SysWOW64\\cmd.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\WindowsPowerShell\\*\\powershell.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\SysWOW64\\WindowsPowerShell\\*\\powershell.exe",
|
||||
"\\Device\\HarddiskVolume*\\Program Files\\PowerShell\\*\\pwsh.exe",
|
||||
"\\Device\\HarddiskVolume*\\Program Files (x86)\\PowerShell\\*\\pwsh.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\wscript.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\SysWOW64\\wscript.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\cscript.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\SysWOW64\\cscript.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\mshta.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\SysWOW64\\mshta.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\wsl.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\bash.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\rundll32.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\SysWOW64\\rundll32.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\regsvr32.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\SysWOW64\\regsvr32.exe",
|
||||
],
|
||||
"InitiatorExclude": [
|
||||
"*\\PYAS.exe",
|
||||
],
|
||||
"CommandLine": [
|
||||
"*taskkill*PYAS.exe*",
|
||||
"*taskkill*msedgewebview2.exe*",
|
||||
"*Stop-Process*PYAS*",
|
||||
"*Stop-Process*msedgewebview2*",
|
||||
"*Get-Process*PYAS*Stop-Process*",
|
||||
"*Get-Process*msedgewebview2*Stop-Process*",
|
||||
"*wmic*PYAS.exe*call terminate*",
|
||||
"*wmic*PYAS.exe*delete*",
|
||||
"*wmic*msedgewebview2.exe*call terminate*",
|
||||
"*wmic*msedgewebview2.exe*delete*",
|
||||
],
|
||||
"Operations": [
|
||||
"Execute",
|
||||
],
|
||||
"Target": [
|
||||
"*\\taskkill.exe",
|
||||
"*\\cmd.exe",
|
||||
"*\\powershell.exe",
|
||||
"*\\pwsh.exe",
|
||||
"*\\wmic.exe",
|
||||
],
|
||||
"Priority": 110,
|
||||
"FileOpenNameAvailable": true,
|
||||
"SubsystemProcess": false,
|
||||
},
|
||||
{
|
||||
"Name": "Self_Antivirus_Protect_Handle",
|
||||
"Code": 1002,
|
||||
"Kill": false,
|
||||
"Category": "Process",
|
||||
"InitiatorExclude": [
|
||||
"*\\PYAS.exe",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\AppData\\Local\\Temp\\*\\PYAS_Setup.tmp",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\AppData\\Local\\Temp\\*\\_unins.tmp",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\explorer.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\csrss.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\lsass.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\services.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\svchost.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\msiexec.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\SystemApps\\*\\SearchHost.exe",
|
||||
"\\Device\\HarddiskVolume*\\Program Files\\*",
|
||||
"\\Device\\HarddiskVolume*\\Program Files (x86)\\*",
|
||||
"*\\games\\Arknights Game\\Arknights.exe",
|
||||
"*\\games\\EndField Game\\EndField.exe",
|
||||
],
|
||||
"Target": [
|
||||
"*\\PYAS.exe",
|
||||
"*\\msedgewebview2.exe",
|
||||
],
|
||||
"TargetProcessTree": [
|
||||
"*\\PYAS.exe",
|
||||
],
|
||||
"Operations": [
|
||||
"Terminate",
|
||||
"SuspendResume",
|
||||
"DuplicateHandle",
|
||||
"CreateProcess",
|
||||
"SetThreadToken",
|
||||
"Impersonate",
|
||||
],
|
||||
"Priority": 130,
|
||||
"InitiatorProcessTreeExclude": [
|
||||
"*\\PYAS.exe",
|
||||
],
|
||||
"HandleTypes": [
|
||||
"Create",
|
||||
"Duplicate",
|
||||
],
|
||||
"OperationMatch": "Any",
|
||||
"MinimumRiskScore": 20,
|
||||
"ObjectTypes": [
|
||||
"Process",
|
||||
"Thread",
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "Attack_Command_Block_EFI_Mount",
|
||||
"Code": 1003,
|
||||
"Kill": false,
|
||||
"Category": "Process",
|
||||
"Target": [
|
||||
"*\\mountvol.exe",
|
||||
],
|
||||
"CommandLine": [
|
||||
"*mountvol* /S*",
|
||||
],
|
||||
"Operations": [
|
||||
"Execute",
|
||||
],
|
||||
"Priority": 170,
|
||||
"FileOpenNameAvailable": true,
|
||||
},
|
||||
{
|
||||
"Name": "Attack_Command_Block_Destructive_Cmd_Delete",
|
||||
"Code": 1004,
|
||||
"Kill": true,
|
||||
"Category": "Process",
|
||||
"Target": [
|
||||
"*\\cmd.exe",
|
||||
],
|
||||
"CommandLine": [
|
||||
"*rd /s /q C:\\*",
|
||||
"*rmdir /s /q C:\\*",
|
||||
"*del X:\\* /s /f /q*",
|
||||
"*rd /s /q X:\\EFI*",
|
||||
],
|
||||
"Operations": [
|
||||
"Execute",
|
||||
],
|
||||
"Priority": 190,
|
||||
"FileOpenNameAvailable": true,
|
||||
},
|
||||
{
|
||||
"Name": "Attack_Command_Block_Forced_OOBE_Sysprep",
|
||||
"Code": 1005,
|
||||
"Kill": false,
|
||||
"Category": "Process",
|
||||
"Target": [
|
||||
"*\\sysprep.exe",
|
||||
],
|
||||
"CommandLine": [
|
||||
"*sysprep* /oobe*",
|
||||
"*sysprep*/oobe*",
|
||||
"* /oobe* /reboot*",
|
||||
"* /generalize* /oobe*",
|
||||
],
|
||||
"Operations": [
|
||||
"Execute",
|
||||
],
|
||||
"Priority": 185,
|
||||
"FileOpenNameAvailable": true,
|
||||
},
|
||||
{
|
||||
"Name": "Process_Protect_UserWritable_SystemName_Dll_Load",
|
||||
"Code": 1006,
|
||||
"Kill": false,
|
||||
"Category": "Process",
|
||||
"Initiator": [
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\AppData\\*\\svchost.exe",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\AppData\\*\\rundll32.exe",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\AppData\\*\\regsvr32.exe",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\AppData\\*\\dllhost.exe",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\AppData\\*\\msiexec.exe",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\AppData\\*\\taskhostw.exe",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\AppData\\*\\spoolsv.exe",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\Downloads\\*\\svchost.exe",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\Downloads\\*\\rundll32.exe",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\Downloads\\*\\regsvr32.exe",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\Desktop\\*\\svchost.exe",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\Desktop\\*\\rundll32.exe",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\Desktop\\*\\regsvr32.exe",
|
||||
"\\Device\\HarddiskVolume*\\ProgramData\\*\\svchost.exe",
|
||||
"\\Device\\HarddiskVolume*\\ProgramData\\*\\rundll32.exe",
|
||||
"\\Device\\HarddiskVolume*\\ProgramData\\*\\regsvr32.exe",
|
||||
],
|
||||
"Target": [
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\AppData\\*\\*.dll",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\Downloads\\*\\*.dll",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\Desktop\\*\\*.dll",
|
||||
"\\Device\\HarddiskVolume*\\ProgramData\\*\\*.dll",
|
||||
],
|
||||
"Operations": [
|
||||
"ImageLoad",
|
||||
],
|
||||
"Priority": 160,
|
||||
},
|
||||
{
|
||||
"Name": "Self_Antivirus_Protect_File",
|
||||
"Code": 2001,
|
||||
"Kill": false,
|
||||
"Category": "File",
|
||||
"InitiatorExclude": [
|
||||
"*\\PYAS.exe",
|
||||
"*\\notepad*.exe",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\AppData\\Local\\Temp\\*\\PYAS_Setup.tmp",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\AppData\\Local\\Temp\\*\\_unins.tmp",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\explorer.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\cleanmgr.exe",
|
||||
],
|
||||
"Target": [
|
||||
"*\\PYAS.exe",
|
||||
"*\\ProgramData\\PYAS\\*.json",
|
||||
"*\\Engine\\Heuristic\\*.yar",
|
||||
"*\\Engine\\Heuristic\\*.yara",
|
||||
"*\\Engine\\Heuristic\\*.yc",
|
||||
"*\\Engine\\Heuristic\\*.yrc",
|
||||
"*\\Engine\\Heuristic\\*.ip",
|
||||
"*\\Engine\\Heuristic\\*.txt",
|
||||
"*\\Engine\\Pattern\\*.onnx",
|
||||
"*\\Engine\\Properties\\*.json",
|
||||
"*\\Engine\\Properties\\*.onnx",
|
||||
"*\\Interface\\static\\css\\*.css",
|
||||
"*\\Interface\\static\\img\\*.ico",
|
||||
"*\\Interface\\static\\js\\*.js",
|
||||
"*\\Interface\\templates\\*.html",
|
||||
"*\\Plugins\\Filter\\*.sys",
|
||||
"*\\Plugins\\Rules\\*.json",
|
||||
],
|
||||
"Operations": [
|
||||
"Write",
|
||||
"Delete",
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "System_Boot_Protect_File",
|
||||
"Code": 2002,
|
||||
"Kill": true,
|
||||
"Category": "File",
|
||||
"InitiatorExclude": [
|
||||
"*\\PYAS.exe",
|
||||
"*\\Huorong\\Sysdiag\\bin\\sysclean.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\SearchIndexer.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\lsass.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\svchost.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\services.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\taskhostw.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\TrustedInstaller.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\WinSxS\\*\\TiWorker.exe",
|
||||
"\\Device\\HarddiskVolume*\\$WinREAgent\\Scratch\\*\\DismHost.exe",
|
||||
],
|
||||
"Target": [
|
||||
"\\Device\\HarddiskVolume*\\bootmgr",
|
||||
"\\Device\\HarddiskVolume*\\boot.ini",
|
||||
"\\Device\\HarddiskVolume*\\BOOTNXT",
|
||||
"\\Device\\HarddiskVolume*\\EFI\\*",
|
||||
"\\Device\\HarddiskVolume*\\Boot\\*",
|
||||
"\\Device\\HarddiskVolume*\\Recovery\\*",
|
||||
],
|
||||
"Operations": [
|
||||
"Write",
|
||||
"Delete",
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "Illegal_Characters_Protect_File",
|
||||
"Code": 2003,
|
||||
"Kill": false,
|
||||
"Category": "File",
|
||||
"InitiatorExclude": [
|
||||
"*\\PYAS.exe",
|
||||
],
|
||||
"Target": [
|
||||
"*\\CON",
|
||||
"*\\CON\\*",
|
||||
"*\\PRN",
|
||||
"*\\PRN\\*",
|
||||
"*\\AUX",
|
||||
"*\\AUX\\*",
|
||||
"*\\NUL",
|
||||
"*\\NUL\\*",
|
||||
"*\\COM?",
|
||||
"*\\COM?\\*",
|
||||
"*\\LPT?",
|
||||
"*\\LPT?\\*",
|
||||
"*\\OSDATA",
|
||||
"*\\OSDATA\\*",
|
||||
"*\\*<*",
|
||||
"*\\*>*",
|
||||
],
|
||||
"Operations": [
|
||||
"Create",
|
||||
"Write",
|
||||
"Rename",
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "System_Ransom_Protect_File",
|
||||
"Code": 2004,
|
||||
"Kill": true,
|
||||
"Category": "File",
|
||||
"Initiator": [
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\cmd.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\SysWOW64\\cmd.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\WindowsPowerShell\\*\\powershell.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\SysWOW64\\WindowsPowerShell\\*\\powershell.exe",
|
||||
"\\Device\\HarddiskVolume*\\Program Files\\PowerShell\\*\\pwsh.exe",
|
||||
"\\Device\\HarddiskVolume*\\Program Files (x86)\\PowerShell\\*\\pwsh.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\wscript.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\SysWOW64\\wscript.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\cscript.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\SysWOW64\\cscript.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\mshta.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\SysWOW64\\mshta.exe",
|
||||
],
|
||||
"Target": [
|
||||
"\\Device\\HarddiskVolume*\\Windows\\*",
|
||||
],
|
||||
"TargetExclude": [
|
||||
"\\Device\\HarddiskVolume*\\Windows\\Temp\\*",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\SystemTemp\\*",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\Prefetch\\*",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\Logs\\*",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\WinSxS\\*",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\servicing\\*",
|
||||
],
|
||||
"Operations": [
|
||||
"Delete",
|
||||
"Rename",
|
||||
],
|
||||
"Threshold": 50,
|
||||
"TimeWindow": 3000,
|
||||
},
|
||||
{
|
||||
"Name": "User_Ransom_Protect_File",
|
||||
"Code": 2005,
|
||||
"Kill": true,
|
||||
"Category": "File",
|
||||
"InitiatorExclude": [
|
||||
"\\Device\\HarddiskVolume*\\Windows\\*",
|
||||
"\\Device\\HarddiskVolume*\\Program Files\\*",
|
||||
"\\Device\\HarddiskVolume*\\Program Files (x86)\\*",
|
||||
"*\\Google\\Chrome\\Application\\chrome.exe",
|
||||
"*\\Microsoft\\Edge\\Application\\msedge.exe",
|
||||
"*\\Microsoft\\Edge*\\Application\\*\\msedgewebview2.exe",
|
||||
"*\\Microsoft\\Edge*\\Install\\*\\setup.exe",
|
||||
"*\\Microsoft\\Edge*\\Application\\*\\Installer\\setup.exe",
|
||||
"*\\Mozilla Firefox\\firefox.exe",
|
||||
"*\\Thorium\\Application\\thorium.exe",
|
||||
"*\\Steam\\steam.exe",
|
||||
"*\\steam\\*\\steam.exe",
|
||||
"*\\Steam\\steamapps\\common\\*",
|
||||
"*\\Battle.net\\*\\Battle.net.exe",
|
||||
"*\\Epic Games\\Launcher\\Portal\\Binaries\\*\\EpicGamesLauncher.exe",
|
||||
"*\\PC4399_WPFLauncher.exe",
|
||||
"*\\Neverness To Everness\\NTELauncher.exe",
|
||||
"*\\ThunderEv.exe",
|
||||
"*\\ThunderStart.exe",
|
||||
"*\\bin\\APlayer.exe",
|
||||
"*\\bilibili\\bilibili.exe",
|
||||
"*\\KuGou.exe",
|
||||
"*\\XLGame.exe",
|
||||
"*\\XLLiveUD.exe",
|
||||
"*\\hrkill-*.exe",
|
||||
"*\\Huorong\\Sysdiag\\bin\\HipsDaemon.exe",
|
||||
"*\\360Safe.exe",
|
||||
"*\\PYAS.exe",
|
||||
"*\\WPS Office\\ksolaunch.exe",
|
||||
"*\\WPS Office\\*\\wps.exe",
|
||||
"*\\Application\\AndrowsLauncher.exe",
|
||||
"*\\resources\\bin\\SDK\\DownloadSDKServer.exe",
|
||||
"*\\bin\\*\\codex.exe",
|
||||
"*\\bin\\codex.exe",
|
||||
"*\\Weixin\\Weixin.exe",
|
||||
],
|
||||
"Target": [
|
||||
"\\Device\\HarddiskVolume*\\Users\\*",
|
||||
],
|
||||
"TargetExclude": [
|
||||
"*\\Documents\\My Games\\*",
|
||||
"*\\Documents\\Tencent Files\\*",
|
||||
"*\\Documents\\WeChat Files\\*",
|
||||
"*\\Documents\\WPS Cloud Files\\*",
|
||||
"*\\Users\\*\\steam\\*",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\User Data\\*",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\AppData\\*",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\AppData\\Local\\*",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\Temp\\*",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\Local\\Temp\\*",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\cache\\*",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\.cache\\*",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\.aws\\*",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\.codex\\*",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\.designer\\*",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\.idlerc\\*",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\.keras\\*",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\.matplotlib\\*",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\.ssh\\*",
|
||||
"*\\ventoy\\Boot\\*",
|
||||
"*\\AdminRegionConfig.*",
|
||||
"*\\nt_qq\\nt_*\\*",
|
||||
"\\Device\\HarddiskVolume*\\Users\\*\\Documents\\Tencent Files\\*\\nt_qq\\nt_*\\*",
|
||||
],
|
||||
"Extensions": [
|
||||
".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",
|
||||
],
|
||||
"Operations": [
|
||||
"Write",
|
||||
"Delete",
|
||||
"Rename",
|
||||
],
|
||||
"Threshold": 10,
|
||||
"TimeWindow": 3000,
|
||||
},
|
||||
{
|
||||
"Name": "System_Boot_Protect_Disk_Wiper",
|
||||
"Code": 2006,
|
||||
"Kill": false,
|
||||
"Category": "Device",
|
||||
"InitiatorExclude": [
|
||||
"*\\PYAS.exe",
|
||||
"*\\rufus-*.exe",
|
||||
"*\\DiskInfo*.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\diskpart.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\diskmgmt.msc",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\taskhostw.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\vds.exe",
|
||||
"\\Device\\HarddiskVolume*\\Windows\\System32\\format.com",
|
||||
],
|
||||
"Operations": [
|
||||
"Ioctl",
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "Registry_Protect_System_Suspicious",
|
||||
"Code": 3001,
|
||||
"Kill": false,
|
||||
"Category": "Registry",
|
||||
"InitiatorExclude": [
|
||||
"\\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\\*",
|
||||
"*\\hrkill-*.exe",
|
||||
"*\\Huorong\\Sysdiag\\bin\\HipsDaemon.exe",
|
||||
"*\\360Safe.exe",
|
||||
"*\\PYAS.exe",
|
||||
],
|
||||
"Target": [
|
||||
"*\\mouclass",
|
||||
"*\\kbdclass",
|
||||
"*\\DisableAntiSpyware",
|
||||
"*\\DisableWindowsUpdateAccess",
|
||||
"*\\NoWindowsUpdate",
|
||||
"*\\AutoAdminLogon",
|
||||
"*\\DefaultUserName",
|
||||
"*\\DefaultPassword",
|
||||
"*\\AltDefaultUserName",
|
||||
"*\\AltDefaultPassword",
|
||||
"*\\LegalNoticeCaption",
|
||||
"*\\LegalNoticeText",
|
||||
"*\\AutoReboot",
|
||||
"*\\DisableAutomaticRestartOnFailure",
|
||||
"*\\SystemBiosVersion",
|
||||
"*\\ClearRecentDocsOnExit",
|
||||
"*\\HideClock",
|
||||
"*\\HideFastUserSwitching",
|
||||
"*\\ShutdownWithoutLogon",
|
||||
"*\\DisableCAD",
|
||||
],
|
||||
"Operations": [
|
||||
"Write",
|
||||
"Delete",
|
||||
"Create",
|
||||
],
|
||||
"Priority": 130,
|
||||
},
|
||||
{
|
||||
"Name": "Registry_Protect_Open_With_Suspicious",
|
||||
"Code": 3002,
|
||||
"Kill": false,
|
||||
"Category": "Registry",
|
||||
"InitiatorExclude": [
|
||||
"\\Device\\HarddiskVolume*\\Windows\\*",
|
||||
"\\Device\\HarddiskVolume*\\Program Files\\*",
|
||||
"\\Device\\HarddiskVolume*\\Program Files (x86)\\*",
|
||||
"*\\PYAS.exe",
|
||||
],
|
||||
"Target": [
|
||||
"*\\Classes\\.exe",
|
||||
"*\\Classes\\.com",
|
||||
"*\\Classes\\.bat",
|
||||
"*\\Classes\\.cmd",
|
||||
"*\\Classes\\exefile\\*",
|
||||
"*\\Classes\\comfile\\*",
|
||||
"*\\Classes\\batfile\\*",
|
||||
"*\\Classes\\cmdfile\\*",
|
||||
],
|
||||
"Operations": [
|
||||
"Write",
|
||||
"Delete",
|
||||
"Create",
|
||||
],
|
||||
"Priority": 150,
|
||||
},
|
||||
{
|
||||
"Name": "Registry_Protect_BCD_Suspicious",
|
||||
"Code": 3003,
|
||||
"Kill": false,
|
||||
"Category": "Registry",
|
||||
"InitiatorExclude": [
|
||||
"\\Device\\HarddiskVolume*\\Windows\\*",
|
||||
"\\Device\\HarddiskVolume*\\ProgramData\\Microsoft\\Windows Defender\\*",
|
||||
"\\Device\\HarddiskVolume*\\ProgramData\\Microsoft\\Windows Defender Advanced Threat Protection\\*",
|
||||
"*\\PYAS.exe",
|
||||
],
|
||||
"Target": [
|
||||
"\\REGISTRY\\MACHINE\\BCD00000000",
|
||||
"\\REGISTRY\\MACHINE\\BCD00000000\\*",
|
||||
],
|
||||
"Operations": [
|
||||
"Write",
|
||||
"Delete",
|
||||
"Create",
|
||||
],
|
||||
"Priority": 140,
|
||||
},
|
||||
{
|
||||
"Name": "Registry_Protect_System_Critical",
|
||||
"Code": 3004,
|
||||
"Kill": true,
|
||||
"Category": "Registry",
|
||||
"InitiatorExclude": [
|
||||
"\\Device\\HarddiskVolume*\\Windows\\*",
|
||||
"*\\PYAS.exe",
|
||||
],
|
||||
"Target": [
|
||||
"*\\EnableLUA",
|
||||
"*\\ConsentPromptBehaviorAdmin",
|
||||
"*\\ConsentPromptBehaviorUser",
|
||||
"*\\EnableSecureUIAPaths",
|
||||
"*\\BootExecute",
|
||||
"*\\SubSystems",
|
||||
"*\\FirmwareBootDevice",
|
||||
"*\\BootDriverFlags",
|
||||
"*\\SystemStartOptions",
|
||||
"*\\Userinit",
|
||||
"*\\UIHost",
|
||||
"*\\NoControlPanel",
|
||||
"*\\NoDrives",
|
||||
"*\\NoFileMenu",
|
||||
"*\\NoFind",
|
||||
"*\\NoStartMenuPinnedList",
|
||||
"*\\NoSetFolders",
|
||||
"*\\NoSetFolderOptions",
|
||||
"*\\NoViewOnDrive",
|
||||
"*\\NoClose",
|
||||
"*\\NoDesktop",
|
||||
"*\\NoLogoff",
|
||||
"*\\NoFolderOptions",
|
||||
"*\\RestrictRun",
|
||||
"*\\NoViewContextMenu",
|
||||
"*\\NoStartMenuMyGames",
|
||||
"*\\NoStartMenuMyMusic",
|
||||
"*\\DisableCMD",
|
||||
"*\\NoAddingComponents",
|
||||
"*\\NoWinKeys",
|
||||
"*\\NoStartMenuLogOff",
|
||||
"*\\NoSimpleNetIDList",
|
||||
"*\\NoLowDiskSpaceChecks",
|
||||
"*\\DisableLockWorkstation",
|
||||
"*\\Restrict_Run",
|
||||
"*\\DisableTaskMgr",
|
||||
"*\\DisableRegistryTools",
|
||||
"*\\DisableChangePassword",
|
||||
"*\\NoComponents",
|
||||
"*\\NoStartMenuMorePrograms",
|
||||
"*\\NoActiveDesktop",
|
||||
"*\\NoSetActiveDesktop",
|
||||
"*\\NoRecentDocsMenu",
|
||||
"*\\NoChangeStartMenu",
|
||||
"*\\NoFavoritesMenu",
|
||||
"*\\NoRecentDocsHistory",
|
||||
"*\\NoSetTaskbar",
|
||||
"*\\NoSMHelp",
|
||||
"*\\NoTrayContextMenu",
|
||||
"*\\NoManageMyComputerVerb",
|
||||
"*\\NoRealMode",
|
||||
"*\\NoRun",
|
||||
"*\\NoActiveDesktopChanges",
|
||||
"*\\NoStartMenuNetworkPlaces",
|
||||
"*\\NoLockScreen",
|
||||
"*\\NoSRM",
|
||||
"*\\NoInteractiveServices",
|
||||
"*\\NoTheme",
|
||||
],
|
||||
"Operations": [
|
||||
"Write",
|
||||
"Delete",
|
||||
"Create",
|
||||
],
|
||||
"Priority": 180,
|
||||
},
|
||||
{
|
||||
"Name": "Registry_Protect_SafeMode_Autostart",
|
||||
"Code": 3005,
|
||||
"Kill": true,
|
||||
"Category": "Registry",
|
||||
"InitiatorExclude": [
|
||||
"\\Device\\HarddiskVolume*\\Windows\\*",
|
||||
"\\Device\\HarddiskVolume*\\ProgramData\\Microsoft\\Windows Defender\\*",
|
||||
"\\Device\\HarddiskVolume*\\ProgramData\\Microsoft\\Windows Defender Advanced Threat Protection\\*",
|
||||
"*\\Sources\\SetupHost.exe",
|
||||
"*\\Work\\*\\DismHost.exe",
|
||||
"*\\Huorong\\Sysdiag\\bin\\HipsDaemon.exe",
|
||||
"*\\360Safe.exe",
|
||||
"*\\PYAS.exe",
|
||||
],
|
||||
"Target": [
|
||||
"\\REGISTRY\\MACHINE\\SYSTEM\\*ControlSet*\\Control\\SafeBoot\\Minimal\\*",
|
||||
"\\REGISTRY\\MACHINE\\SYSTEM\\*ControlSet*\\Control\\SafeBoot\\Network\\*",
|
||||
"\\REGISTRY\\MACHINE\\SYSTEM\\*ControlSet*\\Control\\SafeBoot\\AlternateShell",
|
||||
"\\REGISTRY\\MACHINE\\SYSTEM\\*ControlSet*\\Control\\SafeBoot\\AlternateShell\\*",
|
||||
"\\REGISTRY\\MACHINE\\SYSTEM\\*ControlSet*\\Control\\SafeBoot\\Option",
|
||||
"\\REGISTRY\\MACHINE\\SYSTEM\\*ControlSet*\\Control\\SafeBoot\\Option\\*",
|
||||
],
|
||||
"Operations": [
|
||||
"Write",
|
||||
"Delete",
|
||||
"Create",
|
||||
],
|
||||
"Priority": 170,
|
||||
},
|
||||
{
|
||||
"Name": "Registry_Protect_OOBE_Reset",
|
||||
"Code": 3006,
|
||||
"Kill": false,
|
||||
"Category": "Registry",
|
||||
"InitiatorExclude": [
|
||||
"*\\Sources\\SetupHost.exe",
|
||||
"*\\Work\\*\\DismHost.exe",
|
||||
"*\\Windows\\UUS\\amd64\\MoUsoCoreWorker.exe",
|
||||
"*\\Windows\\System32\\MoUsoCoreWorker.exe",
|
||||
"*\\Windows\\System32\\Sysprep\\sysprep.exe",
|
||||
"*\\Windows\\System32\\oobe\\*",
|
||||
"*\\Windows\\System32\\svchost.exe",
|
||||
"*\\Windows\\explorer.exe",
|
||||
"*\\PYAS.exe",
|
||||
],
|
||||
"Target": [
|
||||
"\\REGISTRY\\MACHINE\\SYSTEM\\Setup\\OOBEInProgress",
|
||||
"\\REGISTRY\\MACHINE\\SYSTEM\\Setup\\SystemSetupInProgress",
|
||||
"\\REGISTRY\\MACHINE\\SYSTEM\\Setup\\SetupType",
|
||||
"\\REGISTRY\\MACHINE\\SYSTEM\\Setup\\CmdLine",
|
||||
"\\REGISTRY\\MACHINE\\SYSTEM\\Setup\\AuditInProgress",
|
||||
"\\REGISTRY\\MACHINE\\SYSTEM\\Setup\\MiniSetupInProgress",
|
||||
"\\REGISTRY\\MACHINE\\SYSTEM\\Setup\\SetupPhase",
|
||||
"\\REGISTRY\\MACHINE\\SYSTEM\\Setup\\Status\\SysprepStatus\\*",
|
||||
"\\REGISTRY\\MACHINE\\SYSTEM\\Setup\\Status\\ChildCompletion\\*",
|
||||
"\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Setup\\State\\ImageState",
|
||||
"\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Setup\\OOBE\\*",
|
||||
"\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\OOBE\\*",
|
||||
],
|
||||
"TargetExclude": [
|
||||
"\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\OOBE\\TelemetryCorrelation",
|
||||
],
|
||||
"Operations": [
|
||||
"Write",
|
||||
"Delete",
|
||||
"Create",
|
||||
],
|
||||
"Priority": 175,
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -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